root/net/bluetooth/hci_codec.c
// 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 < sizeof(caps->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);
}