@@ -1314,6 +1314,24 @@ struct hci_rp_read_local_pairing_opts {
__u8 max_key_size;
} __packed;
+#define HCI_OP_READ_LOCAL_CODEC_CAPS 0x100e
+struct hci_op_read_local_codec_caps {
+ __u8 codec_id[5];
+ __u8 transport;
+ __u8 direction;
+} __packed;
+
+struct hci_codec_caps {
+ __u8 len;
+ __u8 caps[];
+} __packed;
+
+struct hci_rp_read_local_codec_caps {
+ __u8 status;
+ __u8 num_caps;
+ struct hci_codec_caps caps[];
+} __packed;
+
#define HCI_OP_READ_PAGE_SCAN_ACTIVITY 0x0c1b
struct hci_rp_read_page_scan_activity {
__u8 status;
@@ -131,6 +131,14 @@ struct bdaddr_list {
u8 bdaddr_type;
};
+struct codec_list {
+ struct list_head list;
+ u8 transport;
+ u8 codec_id[5];
+ u8 num_caps;
+ struct hci_codec_caps caps[];
+};
+
struct bdaddr_list_with_irk {
struct list_head list;
bdaddr_t bdaddr;
@@ -534,6 +542,7 @@ struct hci_dev {
struct list_head pend_le_conns;
struct list_head pend_le_reports;
struct list_head blocked_keys;
+ struct list_head local_codecs;
struct hci_dev_stats stat;
@@ -1843,9 +1852,23 @@ void hci_le_start_enc(struct hci_conn *conn, __le16 ediv, __le64 rand,
void hci_copy_identity_address(struct hci_dev *hdev, bdaddr_t *bdaddr,
u8 *bdaddr_type);
+int hci_codec_list_add(struct list_head *list, struct hci_rp_read_local_codec_caps *rp,
+ __u32 len,
+ struct hci_op_read_local_codec_caps *sent);
+void hci_codec_list_clear(struct list_head *codec_list);
#define SCO_AIRMODE_MASK 0x0003
#define SCO_AIRMODE_CVSD 0x0000
#define SCO_AIRMODE_TRANSP 0x0003
+#define LOCAL_CODEC_ACL_MASK BIT(0)
+#define LOCAL_CODEC_SCO_MASK BIT(1)
+#define LOCAL_CODEC_CIS_MASK BIT(2)
+#define LOCAL_CODEC_BIS_MASK BIT(3)
+
+#define LOCAL_CODEC_ACL 0x00
+#define LOCAL_CODEC_SCO 0x01
+#define LOCAL_CODEC_CIS 0x02
+#define LOCAL_CODEC_BIS 0x03
+
#endif /* __HCI_CORE_H */
@@ -838,10 +838,6 @@ static int hci_init4_req(struct hci_request *req, unsigned long opt)
if (hdev->commands[22] & 0x04)
hci_set_event_mask_page_2(req);
- /* Read local codec list if the HCI command is supported */
- if (hdev->commands[29] & 0x20)
- hci_req_add(req, HCI_OP_READ_LOCAL_CODECS, 0, NULL);
-
/* Read local pairing options if the HCI command is supported */
if (hdev->commands[41] & 0x08)
hci_req_add(req, HCI_OP_READ_LOCAL_PAIRING_OPTS, 0, NULL);
@@ -907,6 +903,113 @@ static int hci_init4_req(struct hci_request *req, unsigned long opt)
return 0;
}
+static void hci_read_codec_capabilities(struct hci_dev *hdev, __u8 *codec_id,
+ __u8 transport, bool is_vendor_codec)
+{
+ struct hci_op_read_local_codec_caps caps;
+ struct hci_rp_read_local_codec_caps *rp;
+ struct sk_buff *skb;
+
+ memset(&caps, 0, sizeof(caps));
+
+ if (is_vendor_codec) {
+ caps.codec_id[0] = 0xFF;
+ memcpy(&caps.codec_id[1], codec_id, 4);
+ } else {
+ caps.codec_id[0] = codec_id[0];
+ }
+
+ caps.direction = 0x00;
+ caps.transport = transport;
+
+ skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_CODEC_CAPS, sizeof(caps),
+ &caps, HCI_CMD_TIMEOUT);
+ if (IS_ERR(skb)) {
+ bt_dev_err(hdev, "Failed to read local supported codecs v2 (%ld)",
+ PTR_ERR(skb));
+ return;
+ }
+
+ if (skb->len < sizeof(*rp))
+ goto error;
+
+ rp = (void *)skb->data;
+
+ if (rp->status)
+ goto error;
+
+ hci_dev_lock(hdev);
+ hci_codec_list_add(&hdev->local_codecs, rp, skb->len - 2, &caps);
+ hci_dev_unlock(hdev);
+
+error:
+ kfree_skb(skb);
+}
+
+static void hci_read_supported_codecs(struct hci_dev *hdev)
+{
+ struct sk_buff *skb;
+ __u8 num_codecs;
+
+ skb = __hci_cmd_sync(hdev, HCI_OP_READ_LOCAL_CODECS, 0, NULL,
+ HCI_CMD_TIMEOUT);
+
+ if (IS_ERR(skb)) {
+ bt_dev_err(hdev, "Failed to read local supported codecs (%ld)",
+ PTR_ERR(skb));
+ return;
+ }
+
+ if (skb->len < 1 || skb->data[0])
+ goto error;
+
+ skb_pull(skb, 1);
+
+ if (skb->len < 1)
+ goto error;
+
+ /* enumerate standard codecs */
+ num_codecs = skb->data[0];
+
+ skb_pull(skb, 1);
+
+ if (num_codecs && skb->len < num_codecs)
+ goto error;
+
+ while (num_codecs--) {
+ hci_read_codec_capabilities(hdev, skb->data, LOCAL_CODEC_ACL,
+ false);
+ skb_pull(skb, 1);
+ }
+
+ /* enumerate vendor specific codecs */
+ if (skb->len < 1)
+ goto error;
+
+ num_codecs = skb->data[0];
+
+ skb_pull(skb, 1);
+
+ if (num_codecs && skb->len < (num_codecs * 4))
+ goto error;
+
+ while (num_codecs--) {
+ hci_read_codec_capabilities(hdev, skb->data, LOCAL_CODEC_ACL,
+ true);
+ skb_pull(skb, 4);
+ }
+
+error:
+ kfree_skb(skb);
+}
+
+static void hci_init5_req(struct hci_dev *hdev)
+{
+ /* Read local codec list if the HCI command is supported */
+ if (hdev->commands[29] & 0x20)
+ hci_read_supported_codecs(hdev);
+}
+
static int __hci_init(struct hci_dev *hdev)
{
int err;
@@ -937,6 +1040,8 @@ static int __hci_init(struct hci_dev *hdev)
if (err < 0)
return err;
+ hci_init5_req(hdev);
+
/* This function is only called when the controller is actually in
* configured state. When the controller is marked as unconfigured,
* this initialization procedure is not run.
@@ -3559,6 +3664,35 @@ void hci_conn_params_clear_disabled(struct hci_dev *hdev)
BT_DBG("All LE disabled connection parameters were removed");
}
+int hci_codec_list_add(struct list_head *list, struct hci_rp_read_local_codec_caps *rp,
+ __u32 len,
+ struct hci_op_read_local_codec_caps *sent)
+{
+ struct codec_list *entry;
+
+ entry = kzalloc(sizeof(*entry) + len, GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+
+ memcpy(entry->codec_id, sent->codec_id, 5);
+ entry->transport = sent->transport;
+ entry->num_caps = rp->num_caps;
+ if (rp->num_caps)
+ memcpy(entry->caps, rp->caps, len);
+ list_add(&entry->list, list);
+
+ return 0;
+}
+
+void hci_codec_list_clear(struct list_head *codec_list)
+{
+ struct codec_list *c, *n;
+
+ list_for_each_entry_safe(c, n, codec_list, list) {
+ list_del(&c->list);
+ kfree(c);
+ }
+}
/* This function requires the caller holds hdev->lock */
static void hci_conn_params_clear_all(struct hci_dev *hdev)
{
@@ -3818,6 +3952,7 @@ struct hci_dev *hci_alloc_dev(void)
INIT_LIST_HEAD(&hdev->conn_hash.list);
INIT_LIST_HEAD(&hdev->adv_instances);
INIT_LIST_HEAD(&hdev->blocked_keys);
+ INIT_LIST_HEAD(&hdev->local_codecs);
INIT_WORK(&hdev->rx_work, hci_rx_work);
INIT_WORK(&hdev->cmd_work, hci_cmd_work);
@@ -4036,6 +4171,7 @@ void hci_unregister_dev(struct hci_dev *hdev)
hci_conn_params_clear_all(hdev);
hci_discovery_filter_clear(hdev);
hci_blocked_keys_clear(hdev);
+ hci_codec_list_clear(&hdev->local_codecs);
hci_dev_unlock(hdev);
hci_dev_put(hdev);