253 lines · 5.8 KB · C
RawDownload
// 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);}