]> Gentwo Git Trees - linux/.git/commitdiff
Bluetooth: HCI: Add support for LL Extended Feature Set
authorLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Fri, 14 Nov 2025 14:29:28 +0000 (09:29 -0500)
committerLuiz Augusto von Dentz <luiz.von.dentz@intel.com>
Mon, 1 Dec 2025 21:21:16 +0000 (16:21 -0500)
This adds support for emulating LL Extended Feature Set introduced in 6.0
that adds the following:

Commands:

 - HCI_LE_Read_All_Local_Supported_­Features(0x2087)(Feature:47,1)
 - HCI_LE_Read_All_Remote_Features(0x2088)(Feature:47,2)

Events:

 - HCI_LE_Read_All_Remote_Features_Complete(0x2b)(Mask bit:42)

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

index d883ad233ebcdb1320b81c737db9123e163d9fab..a27cd3626b87262b6664acc21929492c8b70bb1a 100644 (file)
@@ -653,6 +653,7 @@ enum {
 #define HCI_LE_CIS_PERIPHERAL          0x20
 #define HCI_LE_ISO_BROADCASTER         0x40
 #define HCI_LE_ISO_SYNC_RECEIVER       0x80
+#define HCI_LE_LL_EXT_FEATURE          0x80
 
 /* Connection modes */
 #define HCI_CM_ACTIVE  0x0000
@@ -2255,6 +2256,19 @@ struct hci_cp_le_set_host_feature {
        __u8     bit_value;
 } __packed;
 
+#define HCI_OP_LE_READ_ALL_LOCAL_FEATURES      0x2087
+struct hci_rp_le_read_all_local_features {
+       __u8    status;
+       __u8    page;
+       __u8    features[248];
+} __packed;
+
+#define HCI_OP_LE_READ_ALL_REMOTE_FEATURES     0x2088
+struct hci_cp_le_read_all_remote_features {
+       __le16   handle;
+       __u8     pages;
+} __packed;
+
 /* ---- HCI Events ---- */
 struct hci_ev_status {
        __u8    status;
@@ -2937,6 +2951,15 @@ struct hci_evt_le_big_info_adv_report {
        __u8    encryption;
 } __packed;
 
+#define HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE 0x2b
+struct hci_evt_le_read_all_remote_features_complete {
+       __u8    status;
+       __le16  handle;
+       __u8    max_pages;
+       __u8    valid_pages;
+       __u8    features[248];
+} __packed;
+
 #define HCI_EV_VENDOR                  0xff
 
 /* Internal events generated by Bluetooth stack */
index 858b58206e8022a0a41c63a71723a6ad3b3ccbfc..4263e71a23efb297bb26e991554e5af137f12d06 100644 (file)
@@ -378,7 +378,7 @@ struct hci_dev {
        __u8            minor_class;
        __u8            max_page;
        __u8            features[HCI_MAX_PAGES][8];
-       __u8            le_features[8];
+       __u8            le_features[248];
        __u8            le_accept_list_size;
        __u8            le_resolv_list_size;
        __u8            le_num_of_adv_sets;
@@ -702,6 +702,7 @@ struct hci_conn {
        __u8            attempt;
        __u8            dev_class[3];
        __u8            features[HCI_MAX_PAGES][8];
+       __u8            le_features[248];
        __u16           pkt_type;
        __u16           link_policy;
        __u8            key_type;
@@ -2067,6 +2068,8 @@ void hci_conn_del_sysfs(struct hci_conn *conn);
        (le_enabled(dev) && past_receiver_capable(dev))
 #define past_enabled(dev) \
        (past_sender_enabled(dev) || past_receiver_enabled(dev))
+#define ll_ext_feature_capable(dev) \
+       ((dev)->le_features[7] & HCI_LE_LL_EXT_FEATURE)
 
 #define mws_transport_config_capable(dev) (((dev)->commands[30] & 0x08) && \
        (!hci_test_quirk((dev), HCI_QUIRK_BROKEN_MWS_TRANSPORT_CONFIG)))
index 3133f40fa9f9d58d079615024dde660e9de592d1..56076bbc981d92dbe05633ab81ae2c917ec4257e 100644 (file)
@@ -189,3 +189,5 @@ int hci_le_conn_update_sync(struct hci_dev *hdev, struct hci_conn *conn,
 int hci_connect_pa_sync(struct hci_dev *hdev, struct hci_conn *conn);
 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);
index 7c4ca14f13e52c2880f074557019fd0ee68de9da..a9868f17ef40ff381285e0c04e200e7930753132 100644 (file)
@@ -2886,12 +2886,8 @@ static void hci_cs_le_read_remote_features(struct hci_dev *hdev, u8 status)
        hci_dev_lock(hdev);
 
        conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
-       if (conn) {
-               if (conn->state == BT_CONFIG) {
-                       hci_connect_cfm(conn, status);
-                       hci_conn_drop(conn);
-               }
-       }
+       if (conn && conn->state == BT_CONFIG)
+               hci_connect_cfm(conn, status);
 
        hci_dev_unlock(hdev);
 }
@@ -3915,11 +3911,49 @@ static u8 hci_cc_le_setup_iso_path(struct hci_dev *hdev, void *data,
        return rp->status;
 }
 
+static u8 hci_cc_le_read_all_local_features(struct hci_dev *hdev, void *data,
+                                           struct sk_buff *skb)
+{
+       struct hci_rp_le_read_all_local_features *rp = data;
+
+       bt_dev_dbg(hdev, "status 0x%2.2x", rp->status);
+
+       if (rp->status)
+               return rp->status;
+
+       memcpy(hdev->le_features, rp->features, 248);
+
+       return rp->status;
+}
+
 static void hci_cs_le_create_big(struct hci_dev *hdev, u8 status)
 {
        bt_dev_dbg(hdev, "status 0x%2.2x", status);
 }
 
+static void hci_cs_le_read_all_remote_features(struct hci_dev *hdev, u8 status)
+{
+       struct hci_cp_le_read_remote_features *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_READ_ALL_REMOTE_FEATURES);
+       if (!cp)
+               return;
+
+       hci_dev_lock(hdev);
+
+       conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(cp->handle));
+       if (conn && conn->state == BT_CONFIG)
+               hci_connect_cfm(conn, status);
+
+       hci_dev_unlock(hdev);
+}
+
 static u8 hci_cc_set_per_adv_param(struct hci_dev *hdev, void *data,
                                   struct sk_buff *skb)
 {
@@ -4171,6 +4205,9 @@ static const struct hci_cc {
                  sizeof(struct hci_rp_le_set_cig_params), HCI_MAX_EVENT_SIZE),
        HCI_CC(HCI_OP_LE_SETUP_ISO_PATH, hci_cc_le_setup_iso_path,
               sizeof(struct hci_rp_le_setup_iso_path)),
+       HCI_CC(HCI_OP_LE_READ_ALL_LOCAL_FEATURES,
+              hci_cc_le_read_all_local_features,
+              sizeof(struct hci_rp_le_read_all_local_features)),
 };
 
 static u8 hci_cc_func(struct hci_dev *hdev, const struct hci_cc *cc,
@@ -4325,6 +4362,8 @@ static const struct hci_cs {
        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),
+       HCI_CS(HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+              hci_cs_le_read_all_remote_features),
 };
 
 static void hci_cmd_status_evt(struct hci_dev *hdev, void *data,
@@ -5645,6 +5684,7 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
        struct hci_conn *conn;
        struct smp_irk *irk;
        u8 addr_type;
+       int err;
 
        hci_dev_lock(hdev);
 
@@ -5775,26 +5815,8 @@ static void le_conn_complete_evt(struct hci_dev *hdev, u8 status,
        hci_debugfs_create_conn(conn);
        hci_conn_add_sysfs(conn);
 
-       /* The remote features procedure is defined for central
-        * role only. So only in case of an initiated connection
-        * request the remote features.
-        *
-        * If the local controller supports peripheral-initiated features
-        * exchange, then requesting the remote features in peripheral
-        * role is possible. Otherwise just transition into the
-        * connected state without requesting the remote features.
-        */
-       if (conn->out ||
-           (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES)) {
-               struct hci_cp_le_read_remote_features cp;
-
-               cp.handle = __cpu_to_le16(conn->handle);
-
-               hci_send_cmd(hdev, HCI_OP_LE_READ_REMOTE_FEATURES,
-                            sizeof(cp), &cp);
-
-               hci_conn_hold(conn);
-       } else {
+       err = hci_le_read_remote_features(conn);
+       if (err) {
                conn->state = BT_CONNECTED;
                hci_connect_cfm(conn, status);
        }
@@ -6608,7 +6630,6 @@ static void hci_le_remote_feat_complete_evt(struct hci_dev *hdev, void *data,
 
                        conn->state = BT_CONNECTED;
                        hci_connect_cfm(conn, status);
-                       hci_conn_drop(conn);
                }
        }
 
@@ -7186,6 +7207,50 @@ static void hci_le_big_info_adv_report_evt(struct hci_dev *hdev, void *data,
        hci_dev_unlock(hdev);
 }
 
+static void hci_le_read_all_remote_features_evt(struct hci_dev *hdev,
+                                               void *data, struct sk_buff *skb)
+{
+       struct hci_evt_le_read_all_remote_features_complete *ev = data;
+       struct hci_conn *conn;
+
+       bt_dev_dbg(hdev, "status 0x%2.2x", ev->status);
+
+       hci_dev_lock(hdev);
+
+       conn = hci_conn_hash_lookup_handle(hdev, __le16_to_cpu(ev->handle));
+       if (!conn)
+               goto unlock;
+
+       if (!ev->status)
+               memcpy(conn->le_features, ev->features, 248);
+
+       if (conn->state == BT_CONFIG) {
+               __u8 status;
+
+               /* If the local controller supports peripheral-initiated
+                * features exchange, but the remote controller does
+                * not, then it is possible that the error code 0x1a
+                * for unsupported remote feature gets returned.
+                *
+                * In this specific case, allow the connection to
+                * transition into connected state and mark it as
+                * successful.
+                */
+               if (!conn->out &&
+                   ev->status == HCI_ERROR_UNSUPPORTED_REMOTE_FEATURE &&
+                   (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES))
+                       status = 0x00;
+               else
+                       status = ev->status;
+
+               conn->state = BT_CONNECTED;
+               hci_connect_cfm(conn, status);
+       }
+
+unlock:
+       hci_dev_unlock(hdev);
+}
+
 #define HCI_LE_EV_VL(_op, _func, _min_len, _max_len) \
 [_op] = { \
        .func = _func, \
@@ -7291,6 +7356,12 @@ static const struct hci_le_ev {
                     hci_le_big_info_adv_report_evt,
                     sizeof(struct hci_evt_le_big_info_adv_report),
                     HCI_MAX_EVENT_SIZE),
+       /* [0x2b = HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE] */
+       HCI_LE_EV_VL(HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE,
+                    hci_le_read_all_remote_features_evt,
+                    sizeof(struct
+                           hci_evt_le_read_all_remote_features_complete),
+                    HCI_MAX_EVENT_SIZE),
 };
 
 static void hci_le_meta_evt(struct hci_dev *hdev, void *data,
index a36d2414a3cafaac84dbd63251b4d2b1dcc85815..a9f5b1a68356ea9ecefc4232c83a1366caf8d6be 100644 (file)
@@ -4011,8 +4011,19 @@ static int hci_le_read_buffer_size_sync(struct hci_dev *hdev)
 /* Read LE Local Supported Features */
 static int hci_le_read_local_features_sync(struct hci_dev *hdev)
 {
-       return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_LOCAL_FEATURES,
-                                    0, NULL, HCI_CMD_TIMEOUT);
+       int err;
+
+       err = __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_LOCAL_FEATURES,
+                                   0, NULL, HCI_CMD_TIMEOUT);
+       if (err)
+               return err;
+
+       if (ll_ext_feature_capable(hdev) && hdev->commands[47] & BIT(2))
+               return __hci_cmd_sync_status(hdev,
+                                            HCI_OP_LE_READ_ALL_LOCAL_FEATURES,
+                                            0, NULL, HCI_CMD_TIMEOUT);
+
+       return err;
 }
 
 /* Read LE Supported States */
@@ -7320,3 +7331,90 @@ int hci_past_sync(struct hci_conn *conn, struct hci_conn *le)
 
        return err;
 }
+
+static void le_read_features_complete(struct hci_dev *hdev, void *data, int err)
+{
+       struct hci_conn *conn = data;
+
+       bt_dev_dbg(hdev, "err %d", err);
+
+       if (err == -ECANCELED)
+               return;
+
+       hci_conn_drop(conn);
+}
+
+static int hci_le_read_all_remote_features_sync(struct hci_dev *hdev,
+                                               void *data)
+{
+       struct hci_conn *conn = data;
+       struct hci_cp_le_read_all_remote_features cp;
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = cpu_to_le16(conn->handle);
+       cp.pages = 10; /* Attempt to read all pages */
+
+       /* Wait for HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE event otherwise
+        * hci_conn_drop may run prematurely causing a disconnection.
+        */
+       return __hci_cmd_sync_status_sk(hdev,
+                                       HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+                                       sizeof(cp), &cp,
+                                       HCI_EVT_LE_ALL_REMOTE_FEATURES_COMPLETE,
+                                       HCI_CMD_TIMEOUT, NULL);
+
+       return __hci_cmd_sync_status(hdev, HCI_OP_LE_READ_ALL_REMOTE_FEATURES,
+                                    sizeof(cp), &cp, HCI_CMD_TIMEOUT);
+}
+
+static int hci_le_read_remote_features_sync(struct hci_dev *hdev, void *data)
+{
+       struct hci_conn *conn = data;
+       struct hci_cp_le_read_remote_features cp;
+
+       if (!hci_conn_valid(hdev, conn))
+               return -ECANCELED;
+
+       /* Check if LL Extended Feature Set is supported and
+        * HCI_OP_LE_READ_ALL_REMOTE_FEATURES is supported then use that to read
+        * all features.
+        */
+       if (ll_ext_feature_capable(hdev) && hdev->commands[47] & BIT(3))
+               return hci_le_read_all_remote_features_sync(hdev, data);
+
+       memset(&cp, 0, sizeof(cp));
+       cp.handle = cpu_to_le16(conn->handle);
+
+       /* Wait for HCI_EV_LE_REMOTE_FEAT_COMPLETE event otherwise
+        * hci_conn_drop may run prematurely causing a disconnection.
+        */
+       return __hci_cmd_sync_status_sk(hdev, HCI_OP_LE_READ_REMOTE_FEATURES,
+                                       sizeof(cp), &cp,
+                                       HCI_EV_LE_REMOTE_FEAT_COMPLETE,
+                                       HCI_CMD_TIMEOUT, NULL);
+}
+
+int hci_le_read_remote_features(struct hci_conn *conn)
+{
+       struct hci_dev *hdev = conn->hdev;
+       int err;
+
+       /* The remote features procedure is defined for central
+        * role only. So only in case of an initiated connection
+        * request the remote features.
+        *
+        * If the local controller supports peripheral-initiated features
+        * exchange, then requesting the remote features in peripheral
+        * role is possible. Otherwise just transition into the
+        * connected state without requesting the remote features.
+        */
+       if (conn->out || (hdev->le_features[0] & HCI_LE_PERIPHERAL_FEATURES))
+               err = hci_cmd_sync_queue_once(hdev,
+                                             hci_le_read_remote_features_sync,
+                                             hci_conn_hold(conn),
+                                             le_read_features_complete);
+       else
+               err = -EOPNOTSUPP;
+
+       return err;
+}