|  | // SPDX-License-Identifier: GPL-2.0 | 
|  |  | 
|  | /* Copyright (C) 2021 Intel Corporation */ | 
|  |  | 
|  | #include <net/bluetooth/bluetooth.h> | 
|  | #include <net/bluetooth/hci_core.h> | 
|  | #include "hci_codec.h" | 
|  |  | 
|  | static int hci_codec_list_add(struct list_head *list, | 
|  | struct hci_op_read_local_codec_caps *sent, | 
|  | struct hci_rp_read_local_codec_caps *rp, | 
|  | void *caps, | 
|  | __u32 len) | 
|  | { | 
|  | struct codec_list *entry; | 
|  |  | 
|  | entry = kzalloc(sizeof(*entry) + len, GFP_KERNEL); | 
|  | if (!entry) | 
|  | return -ENOMEM; | 
|  |  | 
|  | entry->id = sent->id; | 
|  | if (sent->id == 0xFF) { | 
|  | entry->cid = __le16_to_cpu(sent->cid); | 
|  | entry->vid = __le16_to_cpu(sent->vid); | 
|  | } | 
|  | entry->transport = sent->transport; | 
|  | entry->len = len; | 
|  | entry->num_caps = 0; | 
|  | if (rp) { | 
|  | entry->num_caps = rp->num_caps; | 
|  | memcpy(entry->caps, 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); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void hci_read_codec_capabilities(struct hci_dev *hdev, __u8 transport, | 
|  | struct hci_op_read_local_codec_caps | 
|  | *cmd) | 
|  | { | 
|  | __u8 i; | 
|  |  | 
|  | for (i = 0; i < TRANSPORT_TYPE_MAX; i++) { | 
|  | if (transport & BIT(i)) { | 
|  | struct hci_rp_read_local_codec_caps *rp; | 
|  | struct hci_codec_caps *caps; | 
|  | struct sk_buff *skb; | 
|  | __u8 j; | 
|  | __u32 len; | 
|  |  | 
|  | cmd->transport = i; | 
|  |  | 
|  | /* If Read_Codec_Capabilities command is not supported | 
|  | * then just add codec to the list without caps | 
|  | */ | 
|  | if (!(hdev->commands[45] & 0x08)) { | 
|  | hci_dev_lock(hdev); | 
|  | hci_codec_list_add(&hdev->local_codecs, cmd, | 
|  | NULL, NULL, 0); | 
|  | hci_dev_unlock(hdev); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | skb = __hci_cmd_sync_sk(hdev, HCI_OP_READ_LOCAL_CODEC_CAPS, | 
|  | sizeof(*cmd), cmd, 0, HCI_CMD_TIMEOUT, NULL); | 
|  | if (IS_ERR(skb)) { | 
|  | bt_dev_err(hdev, "Failed to read codec capabilities (%ld)", | 
|  | PTR_ERR(skb)); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (skb->len < sizeof(*rp)) | 
|  | goto error; | 
|  |  | 
|  | rp = (void *)skb->data; | 
|  |  | 
|  | if (rp->status) | 
|  | goto error; | 
|  |  | 
|  | if (!rp->num_caps) { | 
|  | len = 0; | 
|  | /* this codec doesn't have capabilities */ | 
|  | goto skip_caps_parse; | 
|  | } | 
|  |  | 
|  | skb_pull(skb, sizeof(*rp)); | 
|  |  | 
|  | for (j = 0, len = 0; j < rp->num_caps; j++) { | 
|  | caps = (void *)skb->data; | 
|  | if (skb->len < sizeof(*caps)) | 
|  | goto error; | 
|  | if (skb->len < caps->len) | 
|  | goto error; | 
|  | len += sizeof(caps->len) + caps->len; | 
|  | skb_pull(skb,  sizeof(caps->len) + caps->len); | 
|  | } | 
|  |  | 
|  | skip_caps_parse: | 
|  | hci_dev_lock(hdev); | 
|  | hci_codec_list_add(&hdev->local_codecs, cmd, rp, | 
|  | (__u8 *)rp + sizeof(*rp), len); | 
|  | hci_dev_unlock(hdev); | 
|  | error: | 
|  | kfree_skb(skb); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void hci_read_supported_codecs(struct hci_dev *hdev) | 
|  | { | 
|  | struct sk_buff *skb; | 
|  | struct hci_rp_read_local_supported_codecs *rp; | 
|  | struct hci_std_codecs *std_codecs; | 
|  | struct hci_vnd_codecs *vnd_codecs; | 
|  | struct hci_op_read_local_codec_caps caps; | 
|  | __u8 i; | 
|  |  | 
|  | skb = __hci_cmd_sync_sk(hdev, HCI_OP_READ_LOCAL_CODECS, 0, NULL, | 
|  | 0, HCI_CMD_TIMEOUT, NULL); | 
|  |  | 
|  | if (IS_ERR(skb)) { | 
|  | bt_dev_err(hdev, "Failed to read local supported codecs (%ld)", | 
|  | PTR_ERR(skb)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (skb->len < sizeof(*rp)) | 
|  | goto error; | 
|  |  | 
|  | rp = (void *)skb->data; | 
|  |  | 
|  | if (rp->status) | 
|  | goto error; | 
|  |  | 
|  | skb_pull(skb, sizeof(rp->status)); | 
|  |  | 
|  | std_codecs = (void *)skb->data; | 
|  |  | 
|  | /* validate codecs length before accessing */ | 
|  | if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) | 
|  | + sizeof(std_codecs->num)) | 
|  | goto error; | 
|  |  | 
|  | /* enumerate codec capabilities of standard codecs */ | 
|  | memset(&caps, 0, sizeof(caps)); | 
|  | for (i = 0; i < std_codecs->num; i++) { | 
|  | caps.id = std_codecs->codec[i]; | 
|  | caps.direction = 0x00; | 
|  | hci_read_codec_capabilities(hdev, | 
|  | LOCAL_CODEC_ACL_MASK | LOCAL_CODEC_SCO_MASK, &caps); | 
|  | } | 
|  |  | 
|  | skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) | 
|  | + sizeof(std_codecs->num)); | 
|  |  | 
|  | vnd_codecs = (void *)skb->data; | 
|  |  | 
|  | /* validate vendor codecs length before accessing */ | 
|  | if (skb->len < | 
|  | flex_array_size(vnd_codecs, codec, vnd_codecs->num) | 
|  | + sizeof(vnd_codecs->num)) | 
|  | goto error; | 
|  |  | 
|  | /* enumerate vendor codec capabilities */ | 
|  | for (i = 0; i < vnd_codecs->num; i++) { | 
|  | caps.id = 0xFF; | 
|  | caps.cid = vnd_codecs->codec[i].cid; | 
|  | caps.vid = vnd_codecs->codec[i].vid; | 
|  | caps.direction = 0x00; | 
|  | hci_read_codec_capabilities(hdev, | 
|  | LOCAL_CODEC_ACL_MASK | LOCAL_CODEC_SCO_MASK, &caps); | 
|  | } | 
|  |  | 
|  | error: | 
|  | kfree_skb(skb); | 
|  | } | 
|  |  | 
|  | void hci_read_supported_codecs_v2(struct hci_dev *hdev) | 
|  | { | 
|  | struct sk_buff *skb; | 
|  | struct hci_rp_read_local_supported_codecs_v2 *rp; | 
|  | struct hci_std_codecs_v2 *std_codecs; | 
|  | struct hci_vnd_codecs_v2 *vnd_codecs; | 
|  | struct hci_op_read_local_codec_caps caps; | 
|  | __u8 i; | 
|  |  | 
|  | skb = __hci_cmd_sync_sk(hdev, HCI_OP_READ_LOCAL_CODECS_V2, 0, NULL, | 
|  | 0, HCI_CMD_TIMEOUT, NULL); | 
|  |  | 
|  | if (IS_ERR(skb)) { | 
|  | bt_dev_err(hdev, "Failed to read local supported codecs (%ld)", | 
|  | PTR_ERR(skb)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (skb->len < sizeof(*rp)) | 
|  | goto error; | 
|  |  | 
|  | rp = (void *)skb->data; | 
|  |  | 
|  | if (rp->status) | 
|  | goto error; | 
|  |  | 
|  | skb_pull(skb, sizeof(rp->status)); | 
|  |  | 
|  | std_codecs = (void *)skb->data; | 
|  |  | 
|  | /* check for payload data length before accessing */ | 
|  | if (skb->len < flex_array_size(std_codecs, codec, std_codecs->num) | 
|  | + sizeof(std_codecs->num)) | 
|  | goto error; | 
|  |  | 
|  | memset(&caps, 0, sizeof(caps)); | 
|  |  | 
|  | for (i = 0; i < std_codecs->num; i++) { | 
|  | caps.id = std_codecs->codec[i].id; | 
|  | hci_read_codec_capabilities(hdev, std_codecs->codec[i].transport, | 
|  | &caps); | 
|  | } | 
|  |  | 
|  | skb_pull(skb, flex_array_size(std_codecs, codec, std_codecs->num) | 
|  | + sizeof(std_codecs->num)); | 
|  |  | 
|  | vnd_codecs = (void *)skb->data; | 
|  |  | 
|  | /* check for payload data length before accessing */ | 
|  | if (skb->len < | 
|  | flex_array_size(vnd_codecs, codec, vnd_codecs->num) | 
|  | + sizeof(vnd_codecs->num)) | 
|  | goto error; | 
|  |  | 
|  | for (i = 0; i < vnd_codecs->num; i++) { | 
|  | caps.id = 0xFF; | 
|  | caps.cid = vnd_codecs->codec[i].cid; | 
|  | caps.vid = vnd_codecs->codec[i].vid; | 
|  | hci_read_codec_capabilities(hdev, vnd_codecs->codec[i].transport, | 
|  | &caps); | 
|  | } | 
|  |  | 
|  | error: | 
|  | kfree_skb(skb); | 
|  | } |