]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
Bluetooth: L2CAP: Add support for setting BT_PHY
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Wed, 17 Dec 2025 15:50:51 +0000 (10:50 -0500)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Thu, 29 Jan 2026 18:25:34 +0000 (13:25 -0500)
This enables client to use setsockopt(BT_PHY) to set the connection
packet type/PHY:

Example setting BT_PHY_BR_1M_1SLOT:

< HCI Command: Change Conne.. (0x01|0x000f) plen 4
        Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
        Packet type: 0x331e
          2-DH1 may not be used
          3-DH1 may not be used
          DM1 may be used
          DH1 may be used
          2-DH3 may not be used
          3-DH3 may not be used
          2-DH5 may not be used
          3-DH5 may not be used
> HCI Event: Command Status (0x0f) plen 4
      Change Connection Packet Type (0x01|0x000f) ncmd 1
        Status: Success (0x00)
> HCI Event: Connection Packet Typ.. (0x1d) plen 5
        Status: Success (0x00)
        Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
        Packet type: 0x331e
          2-DH1 may not be used
          3-DH1 may not be used
          DM1 may be used
          DH1 may be used
          2-DH3 may not be used
          3-DH3 may not be used
          2-DH5 may not be used

Example setting BT_PHY_LE_1M_TX and BT_PHY_LE_1M_RX:

< HCI Command: LE Set PHY (0x08|0x0032) plen 7
        Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
        All PHYs preference: 0x00
        TX PHYs preference: 0x01
          LE 1M
        RX PHYs preference: 0x01
          LE 1M
        PHY options preference: Reserved (0x0000)
> HCI Event: Command Status (0x0f) plen 4
      LE Set PHY (0x08|0x0032) ncmd 1
        Status: Success (0x00)
> HCI Event: LE Meta Event (0x3e) plen 6
      LE PHY Update Complete (0x0c)
        Status: Success (0x00)
        Handle: 1 Address: 00:AA:01:01:00:00 (Intel Corporation)
        TX PHY: LE 1M (0x01)
        RX PHY: LE 1M (0x01)

Signed-off-by: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
include/net/bluetooth/bluetooth.h
include/net/bluetooth/hci.h
include/net/bluetooth/hci_core.h
include/net/bluetooth/hci_sync.h
net/bluetooth/hci_conn.c
net/bluetooth/hci_event.c
net/bluetooth/hci_sync.c
net/bluetooth/l2cap_sock.c

index d46ed9011ee5d0decba05f324b94609ce6c8a805..89a60919050b00e585fd1c31f7ecc3c1fcf93573 100644 (file)
@@ -130,21 +130,30 @@ struct bt_voice {
 #define BT_RCVMTU              13
 #define BT_PHY                 14
 
-#define BT_PHY_BR_1M_1SLOT     0x00000001
-#define BT_PHY_BR_1M_3SLOT     0x00000002
-#define BT_PHY_BR_1M_5SLOT     0x00000004
-#define BT_PHY_EDR_2M_1SLOT    0x00000008
-#define BT_PHY_EDR_2M_3SLOT    0x00000010
-#define BT_PHY_EDR_2M_5SLOT    0x00000020
-#define BT_PHY_EDR_3M_1SLOT    0x00000040
-#define BT_PHY_EDR_3M_3SLOT    0x00000080
-#define BT_PHY_EDR_3M_5SLOT    0x00000100
-#define BT_PHY_LE_1M_TX                0x00000200
-#define BT_PHY_LE_1M_RX                0x00000400
-#define BT_PHY_LE_2M_TX                0x00000800
-#define BT_PHY_LE_2M_RX                0x00001000
-#define BT_PHY_LE_CODED_TX     0x00002000
-#define BT_PHY_LE_CODED_RX     0x00004000
+#define BT_PHY_BR_1M_1SLOT     BIT(0)
+#define BT_PHY_BR_1M_3SLOT     BIT(1)
+#define BT_PHY_BR_1M_5SLOT     BIT(2)
+#define BT_PHY_EDR_2M_1SLOT    BIT(3)
+#define BT_PHY_EDR_2M_3SLOT    BIT(4)
+#define BT_PHY_EDR_2M_5SLOT    BIT(5)
+#define BT_PHY_EDR_3M_1SLOT    BIT(6)
+#define BT_PHY_EDR_3M_3SLOT    BIT(7)
+#define BT_PHY_EDR_3M_5SLOT    BIT(8)
+#define BT_PHY_LE_1M_TX                BIT(9)
+#define BT_PHY_LE_1M_RX                BIT(10)
+#define BT_PHY_LE_2M_TX                BIT(11)
+#define BT_PHY_LE_2M_RX                BIT(12)
+#define BT_PHY_LE_CODED_TX     BIT(13)
+#define BT_PHY_LE_CODED_RX     BIT(14)
+
+#define BT_PHY_BREDR_MASK      (BT_PHY_BR_1M_1SLOT | BT_PHY_BR_1M_3SLOT | \
+                                BT_PHY_BR_1M_5SLOT | BT_PHY_EDR_2M_1SLOT | \
+                                BT_PHY_EDR_2M_3SLOT | BT_PHY_EDR_2M_5SLOT | \
+                                BT_PHY_EDR_3M_1SLOT | BT_PHY_EDR_3M_3SLOT | \
+                                BT_PHY_EDR_3M_5SLOT)
+#define BT_PHY_LE_MASK         (BT_PHY_LE_1M_TX | BT_PHY_LE_1M_RX | \
+                                BT_PHY_LE_2M_TX | BT_PHY_LE_2M_RX | \
+                                BT_PHY_LE_CODED_TX | BT_PHY_LE_CODED_RX)
 
 #define BT_MODE                        15
 
index 4a731e1bec53b4957bf3d71b41b6d54409a90170..db76c2d1eeaa1120f4717dbe5c1e399efae83693 100644 (file)
@@ -1885,6 +1885,15 @@ struct hci_cp_le_set_default_phy {
 #define HCI_LE_SET_PHY_2M              0x02
 #define HCI_LE_SET_PHY_CODED           0x04
 
+#define HCI_OP_LE_SET_PHY              0x2032
+struct hci_cp_le_set_phy {
+       __le16  handle;
+       __u8    all_phys;
+       __u8    tx_phys;
+       __u8    rx_phys;
+       __le16   phy_opts;
+} __packed;
+
 #define HCI_OP_LE_SET_EXT_SCAN_PARAMS   0x2041
 struct hci_cp_le_set_ext_scan_params {
        __u8    own_addr_type;
index a7d5beb01b695cf8e835fa6beadb8415f494e48b..a7bffb908c1ec99634c257084c36144897b6e4ff 100644 (file)
@@ -2342,6 +2342,7 @@ void *hci_sent_cmd_data(struct hci_dev *hdev, __u16 opcode);
 void *hci_recv_event_data(struct hci_dev *hdev, __u8 event);
 
 u32 hci_conn_get_phy(struct hci_conn *conn);
+int hci_conn_set_phy(struct hci_conn *conn, u32 phys);
 
 /* ----- HCI Sockets ----- */
 void hci_send_to_sock(struct hci_dev *hdev, struct sk_buff *skb);
index 56076bbc981d92dbe05633ab81ae2c917ec4257e..73e494b2591de3ea72423df6111c197beebc06d1 100644 (file)
@@ -191,3 +191,6 @@ int hci_connect_big_sync(struct hci_dev *hdev, struct hci_conn *conn);
 int hci_past_sync(struct hci_conn *conn, struct hci_conn *le);
 
 int hci_le_read_remote_features(struct hci_conn *conn);
+
+int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type);
+int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys);
index dc085856f5e91293c3dd4ef406d5840088d7e32f..1a4b6badf2b35650bbeec799fc60773fd6f0334e 100644 (file)
@@ -2958,6 +2958,111 @@ u32 hci_conn_get_phy(struct hci_conn *conn)
        return phys;
 }
 
+static u16 bt_phy_pkt_type(struct hci_conn *conn, u32 phys)
+{
+       u16 pkt_type = conn->pkt_type;
+
+       if (phys & BT_PHY_BR_1M_3SLOT)
+               pkt_type |= HCI_DM3 | HCI_DH3;
+       else
+               pkt_type &= ~(HCI_DM3 | HCI_DH3);
+
+       if (phys & BT_PHY_BR_1M_5SLOT)
+               pkt_type |= HCI_DM5 | HCI_DH5;
+       else
+               pkt_type &= ~(HCI_DM5 | HCI_DH5);
+
+       if (phys & BT_PHY_EDR_2M_1SLOT)
+               pkt_type &= ~HCI_2DH1;
+       else
+               pkt_type |= HCI_2DH1;
+
+       if (phys & BT_PHY_EDR_2M_3SLOT)
+               pkt_type &= ~HCI_2DH3;
+       else
+               pkt_type |= HCI_2DH3;
+
+       if (phys & BT_PHY_EDR_2M_5SLOT)
+               pkt_type &= ~HCI_2DH5;
+       else
+               pkt_type |= HCI_2DH5;
+
+       if (phys & BT_PHY_EDR_3M_1SLOT)
+               pkt_type &= ~HCI_3DH1;
+       else
+               pkt_type |= HCI_3DH1;
+
+       if (phys & BT_PHY_EDR_3M_3SLOT)
+               pkt_type &= ~HCI_3DH3;
+       else
+               pkt_type |= HCI_3DH3;
+
+       if (phys & BT_PHY_EDR_3M_5SLOT)
+               pkt_type &= ~HCI_3DH5;
+       else
+               pkt_type |= HCI_3DH5;
+
+       return pkt_type;
+}
+
+static int bt_phy_le_phy(u32 phys, u8 *tx_phys, u8 *rx_phys)
+{
+       if (!tx_phys || !rx_phys)
+               return -EINVAL;
+
+       *tx_phys = 0;
+       *rx_phys = 0;
+
+       if (phys & BT_PHY_LE_1M_TX)
+               *tx_phys |= HCI_LE_SET_PHY_1M;
+
+       if (phys & BT_PHY_LE_1M_RX)
+               *rx_phys |= HCI_LE_SET_PHY_1M;
+
+       if (phys & BT_PHY_LE_2M_TX)
+               *tx_phys |= HCI_LE_SET_PHY_2M;
+
+       if (phys & BT_PHY_LE_2M_RX)
+               *rx_phys |= HCI_LE_SET_PHY_2M;
+
+       if (phys & BT_PHY_LE_CODED_TX)
+               *tx_phys |= HCI_LE_SET_PHY_CODED;
+
+       if (phys & BT_PHY_LE_CODED_RX)
+               *rx_phys |= HCI_LE_SET_PHY_CODED;
+
+       return 0;
+}
+
+int hci_conn_set_phy(struct hci_conn *conn, u32 phys)
+{
+       u8 tx_phys, rx_phys;
+
+       switch (conn->type) {
+       case SCO_LINK:
+       case ESCO_LINK:
+               return -EINVAL;
+       case ACL_LINK:
+               /* Only allow setting BR/EDR PHYs if link type is ACL */
+               if (phys & ~BT_PHY_BREDR_MASK)
+                       return -EINVAL;
+
+               return hci_acl_change_pkt_type(conn,
+                                              bt_phy_pkt_type(conn, phys));
+       case LE_LINK:
+               /* Only allow setting LE PHYs if link type is LE */
+               if (phys & ~BT_PHY_LE_MASK)
+                       return -EINVAL;
+
+               if (bt_phy_le_phy(phys, &tx_phys, &rx_phys))
+                       return -EINVAL;
+
+               return hci_le_set_phy(conn, tx_phys, rx_phys);
+       default:
+               return -EINVAL;
+       }
+}
+
 static int abort_conn_sync(struct hci_dev *hdev, void *data)
 {
        struct hci_conn *conn = data;
index 58075bf720554045e440c7cda17193994f181b8d..467710a42d4537689bff13ed5e8a8a0b58c5277d 100644 (file)
@@ -2869,6 +2869,31 @@ static void hci_cs_le_ext_create_conn(struct hci_dev *hdev, u8 status)
        hci_dev_unlock(hdev);
 }
 
+static void hci_cs_le_set_phy(struct hci_dev *hdev, u8 status)
+{
+       struct hci_cp_le_set_phy *cp;
+       struct hci_conn *conn;
+
+       bt_dev_dbg(hdev, "status 0x%2.2x", status);
+
+       if (status)
+               return;
+
+       cp = hci_sent_cmd_data(hdev, HCI_OP_LE_SET_PHY);
+       if (!cp)
+               return;
+
+       hci_dev_lock(hdev);
+
+       conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
+       if (conn) {
+               conn->le_tx_def_phys = cp->tx_phys;
+               conn->le_rx_def_phys = cp->rx_phys;
+       }
+
+       hci_dev_unlock(hdev);
+}
+
 static void hci_cs_le_read_remote_features(struct hci_dev *hdev, u8 status)
 {
        struct hci_cp_le_read_remote_features *cp;
@@ -4359,6 +4384,7 @@ static const struct hci_cs {
        HCI_CS(HCI_OP_LE_CREATE_CONN, hci_cs_le_create_conn),
        HCI_CS(HCI_OP_LE_READ_REMOTE_FEATURES, hci_cs_le_read_remote_features),
        HCI_CS(HCI_OP_LE_START_ENC, hci_cs_le_start_enc),
+       HCI_CS(HCI_OP_LE_SET_PHY, hci_cs_le_set_phy),
        HCI_CS(HCI_OP_LE_EXT_CREATE_CONN, hci_cs_le_ext_create_conn),
        HCI_CS(HCI_OP_LE_CREATE_CIS, hci_cs_le_create_cis),
        HCI_CS(HCI_OP_LE_CREATE_BIG, hci_cs_le_create_big),
index 4163d9b16e1a4d6ea79e618387e1508b2d155482..0e6660ddc00a7c200ee44c5343e24a7dfb9b3a3f 100644 (file)
@@ -7448,3 +7448,75 @@ int hci_le_read_remote_features(struct hci_conn *conn)
 
        return err;
 }
+
+static void pkt_type_changed(struct hci_dev *hdev, void *data, int err)
+{
+       struct hci_cp_change_conn_ptype *cp = data;
+
+       bt_dev_dbg(hdev, "err %d", err);
+
+       kfree(cp);
+}
+
+static int hci_change_conn_ptype_sync(struct hci_dev *hdev, void *data)
+{
+       struct hci_cp_change_conn_ptype *cp = data;
+
+       return __hci_cmd_sync_status_sk(hdev, HCI_OP_CHANGE_CONN_PTYPE,
+                                       sizeof(*cp), cp,
+                                       HCI_EV_PKT_TYPE_CHANGE,
+                                       HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_acl_change_pkt_type(struct hci_conn *conn, u16 pkt_type)
+{
+       struct hci_dev *hdev = conn->hdev;
+       struct hci_cp_change_conn_ptype *cp;
+
+       cp = kmalloc(sizeof(*cp), GFP_KERNEL);
+       if (!cp)
+               return -ENOMEM;
+
+       cp->handle = cpu_to_le16(conn->handle);
+       cp->pkt_type = cpu_to_le16(pkt_type);
+
+       return hci_cmd_sync_queue_once(hdev, hci_change_conn_ptype_sync, cp,
+                                      pkt_type_changed);
+}
+
+static void le_phy_update_complete(struct hci_dev *hdev, void *data, int err)
+{
+       struct hci_cp_le_set_phy *cp = data;
+
+       bt_dev_dbg(hdev, "err %d", err);
+
+       kfree(cp);
+}
+
+static int hci_le_set_phy_sync(struct hci_dev *hdev, void *data)
+{
+       struct hci_cp_le_set_phy *cp = data;
+
+       return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_SET_PHY,
+                                       sizeof(*cp), cp,
+                                       HCI_EV_LE_PHY_UPDATE_COMPLETE,
+                                       HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_le_set_phy(struct hci_conn *conn, u8 tx_phys, u8 rx_phys)
+{
+       struct hci_dev *hdev = conn->hdev;
+       struct hci_cp_le_set_phy *cp;
+
+       cp = kmalloc(sizeof(*cp), GFP_KERNEL);
+       if (!cp)
+               return -ENOMEM;
+
+       memset(cp, 0, sizeof(*cp));
+       cp->handle = cpu_to_le16(conn->handle);
+       cp->tx_phys = tx_phys;
+       cp->rx_phys = rx_phys;
+
+       return hci_cmd_sync_queue_once(hdev, hci_le_set_phy_sync, cp,
+                                      le_phy_update_complete);
+}
index 9ee189c815d4989c9c909d3856dd46ff27cdff00..3ba3ce7eaa98a57786b6ef80a12cf1be1d78eab8 100644 (file)
@@ -885,7 +885,7 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
        struct bt_power pwr;
        struct l2cap_conn *conn;
        int err = 0;
-       u32 opt;
+       u32 opt, phys;
        u16 mtu;
        u8 mode;
 
@@ -1059,6 +1059,24 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname,
 
                break;
 
+       case BT_PHY:
+               if (sk->sk_state != BT_CONNECTED) {
+                       err = -ENOTCONN;
+                       break;
+               }
+
+               err = copy_safe_from_sockptr(&phys, sizeof(phys), optval,
+                                            optlen);
+               if (err)
+                       break;
+
+               if (!chan->conn)
+                       break;
+
+               conn = chan->conn;
+               err = hci_conn_set_phy(conn->hcon, phys);
+               break;
+
        case BT_MODE:
                if (!enable_ecred) {
                        err = -ENOPROTOOPT;