From 144732c6d101334fbbedaedf10dd5338d3746a75 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:24 +0000 Subject: [PATCH 1/9] Bluetooth: btintel: configure offload on operational boot When an Intel Bluetooth controller boots or reloads, offload callback registration depends on the firmware state encountered in btintel_bootloader_setup_tlv(): 1. On a cold boot (ver->img_type != BTINTEL_IMG_OP), execution downloads operational firmware, loads DDC (btintel_load_ddc_config()), and calls btintel_configure_offload(hdev) to set hdev->get_data_path_id and hdev->get_codec_config_data. 2. On a warm reboot or PCIe/USB driver reload where the controller is already running operational firmware (ver->img_type == BTINTEL_IMG_OP), btintel_bootloader_setup_tlv() jumps straight to the finish: label, bypassing btintel_configure_offload(hdev). 3. Because hdev->get_data_path_id and hdev->get_codec_config_data remain NULL, subsequent BT_CODEC offload queries and configuration fail with -EOPNOTSUPP. Move btintel_configure_offload(hdev) under the finish: label so offload callbacks are registered on both cold boot and operational-firmware boot paths. Signed-off-by: Liam Girdwood --- drivers/bluetooth/btintel.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 0709fb3212f969..ca51255803d547 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -3650,9 +3650,6 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev, */ btintel_load_ddc_config(hdev, ddcname); - /* Read supported use cases and set callbacks to fetch datapath id */ - btintel_configure_offload(hdev); - hci_dev_clear_flag(hdev, HCI_QUALITY_REPORT); /* Send sar values to controller */ @@ -3672,6 +3669,12 @@ int btintel_bootloader_setup_tlv(struct hci_dev *hdev, *ver = new_ver; finish: + /* Read supported use cases and set callbacks to fetch datapath id. + * Run this in finish: so warm reboots (where ver->img_type is already + * BTINTEL_IMG_OP) also register offload callbacks. + */ + btintel_configure_offload(hdev); + /* Set the event mask for Intel specific vendor events. This enables * a few extra events that are useful during general operation. It * does not enable any debugging related events. From e9f1da254e1621ca83fe67ab7cf569c5ceb0846d Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 2/9] Bluetooth: btintel: enable offload codecs flag on configuration Set HCI_OFFLOAD_CODECS_ENABLED in btintel_configure_offload() when 0xfc86 (HCI_Intel_Read_Offload_Use_Cases) reports supported offload use cases (preset[0] & 0x03), so hci_dev_open_sync() queries the controller's local offload codecs (0x100d / 0x100e) during adapter initialization. Signed-off-by: Liam Girdwood --- drivers/bluetooth/btintel.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index ca51255803d547..42dabfe9bea2d5 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2651,6 +2651,7 @@ static int btintel_configure_offload(struct hci_dev *hdev) if (use_cases->preset[0] & 0x03) { hdev->get_data_path_id = btintel_get_data_path_id; hdev->get_codec_config_data = btintel_get_codec_config_data; + hci_dev_set_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED); } error: kfree_skb(skb); From 5ee248913fa954c2a92a8b43695abe9b10fccf20 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 3/9] Bluetooth: sco: support Enhanced Accept Synchronous Connection Add HCI_OP_ENHANCED_ACCEPT_SYNC_CONN (0x043e) and hci_accept_sync() so incoming eSCO/SCO connections on sockets configured with BT_CODEC (data_path != 0) configure the controller data path (0x0c83) and accept the synchronous connection over the offloaded PCM/SSP transport instead of falling back to legacy HCI_OP_ACCEPT_SYNC_CONN_REQ (0x0429). Allow setting BT_CODEC on listening SCO sockets, propagate the listening socket's codec settings onto conn in hci_conn_request_evt() for non-deferred accepts as well as child sockets during sco_sock_init(), and invoke hci_accept_sync() whenever an offloaded data path is configured. Signed-off-by: Liam Girdwood --- include/net/bluetooth/hci.h | 28 +++++++ include/net/bluetooth/hci_core.h | 7 ++ net/bluetooth/hci_conn.c | 130 +++++++++++++++++++++++++++++++ net/bluetooth/hci_event.c | 31 +++++--- net/bluetooth/sco.c | 24 +++++- 5 files changed, 208 insertions(+), 12 deletions(-) diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 9dc714d9145eea..4c4257660486ca 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -1023,6 +1023,34 @@ struct hci_cp_enhanced_setup_sync_conn { __u8 retrans_effort; } __packed; +#define HCI_OP_ENHANCED_ACCEPT_SYNC_CONN 0x043e +struct hci_cp_enhanced_accept_sync_conn { + bdaddr_t bdaddr; + __le32 tx_bandwidth; + __le32 rx_bandwidth; + struct hci_coding_format tx_coding_format; + struct hci_coding_format rx_coding_format; + __le16 tx_codec_frame_size; + __le16 rx_codec_frame_size; + __le32 in_bandwidth; + __le32 out_bandwidth; + struct hci_coding_format in_coding_format; + struct hci_coding_format out_coding_format; + __le16 in_coded_data_size; + __le16 out_coded_data_size; + __u8 in_pcm_data_format; + __u8 out_pcm_data_format; + __u8 in_pcm_sample_payload_msb_pos; + __u8 out_pcm_sample_payload_msb_pos; + __u8 in_data_path; + __u8 out_data_path; + __u8 in_transport_unit_size; + __u8 out_transport_unit_size; + __le16 max_latency; + __le16 pkt_type; + __u8 retrans_effort; +} __packed; + struct hci_rp_logical_link_cancel { __u8 status; __u8 phy_handle; diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index aa04dec4515341..455f8bf77ac1d8 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -884,6 +884,7 @@ int l2cap_recv_acldata(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, #if IS_ENABLED(CONFIG_BT_BREDR) int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags); +void sco_conn_set_codec(struct hci_dev *hdev, struct hci_conn *conn); int sco_recv_scodata(struct hci_dev *hdev, u16 handle, struct sk_buff *skb); #else static inline int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, @@ -892,6 +893,11 @@ static inline int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, return 0; } +static inline void sco_conn_set_codec(struct hci_dev *hdev, + struct hci_conn *conn) +{ +} + static inline int sco_recv_scodata(struct hci_dev *hdev, u16 handle, struct sk_buff *skb) { @@ -1632,6 +1638,7 @@ static inline bool hci_is_le_conn_scanning(struct hci_dev *hdev) int hci_disconnect(struct hci_conn *conn, __u8 reason); bool hci_setup_sync(struct hci_conn *conn, __u16 handle); +bool hci_accept_sync(struct hci_conn *conn); void hci_sco_setup(struct hci_conn *conn, __u8 status); bool hci_iso_setup_path(struct hci_conn *conn); int hci_le_create_cis_pending(struct hci_dev *hdev); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 29da3fe2b95384..f613be8aa6e058 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -495,6 +495,136 @@ bool hci_setup_sync(struct hci_conn *conn, __u16 handle) return hci_setup_sync_conn(conn, handle); } +static int hci_enhanced_accept_sync(struct hci_dev *hdev, void *data) +{ + struct hci_conn *conn = data; + struct hci_cp_enhanced_accept_sync_conn cp; + + if (!hci_conn_valid(hdev, conn)) + return -ECANCELED; + + bt_dev_dbg(hdev, "hcon %p", conn); + + configure_datapath_sync(hdev, &conn->codec); + + memset(&cp, 0x00, sizeof(cp)); + + bacpy(&cp.bdaddr, &conn->dst); + cp.pkt_type = cpu_to_le16(conn->pkt_type); + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + + switch (conn->codec.id) { + case BT_CODEC_MSBC: + cp.tx_coding_format.id = 0x05; + cp.rx_coding_format.id = 0x05; + cp.tx_codec_frame_size = __cpu_to_le16(60); + cp.rx_codec_frame_size = __cpu_to_le16(60); + cp.in_bandwidth = __cpu_to_le32(32000); + cp.out_bandwidth = __cpu_to_le32(32000); + cp.in_coding_format.id = 0x04; + cp.out_coding_format.id = 0x04; + cp.in_coded_data_size = __cpu_to_le16(16); + cp.out_coded_data_size = __cpu_to_le16(16); + cp.in_pcm_data_format = 2; + cp.out_pcm_data_format = 2; + cp.in_pcm_sample_payload_msb_pos = 0; + cp.out_pcm_sample_payload_msb_pos = 0; + cp.in_data_path = conn->codec.data_path; + cp.out_data_path = conn->codec.data_path; + cp.in_transport_unit_size = 1; + cp.out_transport_unit_size = 1; + if (conn->pkt_type & ESCO_2EV3) + cp.max_latency = cpu_to_le16(0x0008); + else + cp.max_latency = cpu_to_le16(0x000D); + cp.retrans_effort = 0x02; + break; + + case BT_CODEC_TRANSPARENT: + cp.tx_coding_format.id = 0x03; + cp.rx_coding_format.id = 0x03; + cp.tx_codec_frame_size = __cpu_to_le16(60); + cp.rx_codec_frame_size = __cpu_to_le16(60); + cp.in_bandwidth = __cpu_to_le32(0x1f40); + cp.out_bandwidth = __cpu_to_le32(0x1f40); + cp.in_coding_format.id = 0x03; + cp.out_coding_format.id = 0x03; + cp.in_coded_data_size = __cpu_to_le16(16); + cp.out_coded_data_size = __cpu_to_le16(16); + cp.in_pcm_data_format = 2; + cp.out_pcm_data_format = 2; + cp.in_pcm_sample_payload_msb_pos = 0; + cp.out_pcm_sample_payload_msb_pos = 0; + cp.in_data_path = conn->codec.data_path; + cp.out_data_path = conn->codec.data_path; + cp.in_transport_unit_size = 1; + cp.out_transport_unit_size = 1; + if (conn->pkt_type & ESCO_2EV3) + cp.max_latency = cpu_to_le16(0x0008); + else + cp.max_latency = cpu_to_le16(0x000D); + cp.retrans_effort = 0x02; + break; + + case BT_CODEC_CVSD: + default: + cp.tx_coding_format.id = 2; + cp.rx_coding_format.id = 2; + cp.tx_codec_frame_size = __cpu_to_le16(60); + cp.rx_codec_frame_size = __cpu_to_le16(60); + cp.in_bandwidth = __cpu_to_le32(16000); + cp.out_bandwidth = __cpu_to_le32(16000); + cp.in_coding_format.id = 4; + cp.out_coding_format.id = 4; + cp.in_coded_data_size = __cpu_to_le16(16); + cp.out_coded_data_size = __cpu_to_le16(16); + cp.in_pcm_data_format = 2; + cp.out_pcm_data_format = 2; + cp.in_pcm_sample_payload_msb_pos = 0; + cp.out_pcm_sample_payload_msb_pos = 0; + cp.in_data_path = conn->codec.data_path; + cp.out_data_path = conn->codec.data_path; + cp.in_transport_unit_size = 16; + cp.out_transport_unit_size = 16; + cp.max_latency = cpu_to_le16(0xffff); + cp.retrans_effort = 0xff; + break; + } + + if (hci_send_cmd(hdev, HCI_OP_ENHANCED_ACCEPT_SYNC_CONN, + sizeof(cp), &cp) < 0) + return -EIO; + + return 0; +} + +static void hci_enhanced_accept_sync_destroy(struct hci_dev *hdev, void *data, + int err) +{ + struct hci_conn *conn = data; + + hci_conn_put(conn); +} + +bool hci_accept_sync(struct hci_conn *conn) +{ + int result; + + if (!enhanced_sync_conn_capable(conn->hdev)) + return false; + + hci_conn_get(conn); + result = hci_cmd_sync_queue(conn->hdev, hci_enhanced_accept_sync, + conn, hci_enhanced_accept_sync_destroy); + if (result < 0) { + hci_conn_put(conn); + return false; + } + + return true; +} + struct le_conn_update_data { struct hci_conn *conn; u16 min; diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 1f80f2cf6b2c81..3536ef416fa837 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -3459,6 +3459,9 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data, memcpy(conn->dev_class, ev->dev_class, 3); + if (ev->link_type == SCO_LINK || ev->link_type == ESCO_LINK) + sco_conn_set_codec(hdev, conn); + if (ev->link_type == ACL_LINK || (!(flags & HCI_PROTO_DEFER) && !lmp_esco_capable(hdev))) { struct hci_cp_accept_conn_req cp; @@ -3473,20 +3476,26 @@ static void hci_conn_request_evt(struct hci_dev *hdev, void *data, hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, sizeof(cp), &cp); } else if (!(flags & HCI_PROTO_DEFER)) { - struct hci_cp_accept_sync_conn_req cp; - conn->state = BT_CONNECT; + if (enhanced_sync_conn_capable(hdev) && conn->codec.data_path) { + conn->state = BT_CONNECT; + hci_accept_sync(conn); + } else { + struct hci_cp_accept_sync_conn_req cp; - bacpy(&cp.bdaddr, &ev->bdaddr); - cp.pkt_type = cpu_to_le16(conn->pkt_type); + conn->state = BT_CONNECT; - cp.tx_bandwidth = cpu_to_le32(0x00001f40); - cp.rx_bandwidth = cpu_to_le32(0x00001f40); - cp.max_latency = cpu_to_le16(0xffff); - cp.content_format = cpu_to_le16(hdev->voice_setting); - cp.retrans_effort = 0xff; + bacpy(&cp.bdaddr, &ev->bdaddr); + cp.pkt_type = cpu_to_le16(conn->pkt_type); + + cp.tx_bandwidth = cpu_to_le32(0x00001f40); + cp.rx_bandwidth = cpu_to_le32(0x00001f40); + cp.max_latency = cpu_to_le16(0xffff); + cp.content_format = cpu_to_le16(hdev->voice_setting); + cp.retrans_effort = 0xff; - hci_send_cmd(hdev, HCI_OP_ACCEPT_SYNC_CONN_REQ, sizeof(cp), - &cp); + hci_send_cmd(hdev, HCI_OP_ACCEPT_SYNC_CONN_REQ, + sizeof(cp), &cp); + } } else { conn->state = BT_CONNECT2; hci_connect_cfm(conn, 0); diff --git a/net/bluetooth/sco.c b/net/bluetooth/sco.c index 8be48ca9f4c5b6..3371c9cd7f1a5e 100644 --- a/net/bluetooth/sco.c +++ b/net/bluetooth/sco.c @@ -645,6 +645,8 @@ static void sco_sock_init(struct sock *sk, struct sock *parent) if (parent) { sk->sk_type = parent->sk_type; bt_sk(sk)->flags = bt_sk(parent)->flags; + sco_pi(sk)->setting = sco_pi(parent)->setting; + sco_pi(sk)->codec = sco_pi(parent)->codec; security_sk_clone(parent, sk); } } @@ -950,6 +952,8 @@ static void sco_conn_defer_accept(struct hci_conn *conn, u16 setting) cp.role = 0x00; /* Ignored */ hci_send_cmd(hdev, HCI_OP_ACCEPT_CONN_REQ, sizeof(cp), &cp); + } else if (enhanced_sync_conn_capable(hdev) && conn->codec.data_path) { + hci_accept_sync(conn); } else { struct hci_cp_accept_sync_conn_req cp; @@ -998,6 +1002,7 @@ static int sco_sock_recvmsg(struct socket *sock, struct msghdr *msg, if (sk->sk_state == BT_CONNECT2 && test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags)) { + pi->conn->hcon->codec = pi->codec; sco_conn_defer_accept(pi->conn->hcon, pi->setting); sk->sk_state = BT_CONFIG; @@ -1082,7 +1087,7 @@ static int sco_sock_setsockopt(struct socket *sock, int level, int optname, case BT_CODEC: if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND && - sk->sk_state != BT_CONNECT2) { + sk->sk_state != BT_LISTEN && sk->sk_state != BT_CONNECT2) { err = -EINVAL; break; } @@ -1513,6 +1518,23 @@ static void sco_conn_ready(struct sco_conn *conn) } /* ----- SCO interface with lower layer (HCI) ----- */ +void sco_conn_set_codec(struct hci_dev *hdev, struct hci_conn *conn) +{ + struct sock *parent; + + parent = sco_get_sock_listen(&hdev->bdaddr); + if (!parent) + return; + + lock_sock(parent); + if (parent->sk_state == BT_LISTEN && !sock_flag(parent, SOCK_ZAPPED)) { + conn->codec = sco_pi(parent)->codec; + conn->setting = sco_pi(parent)->setting; + } + release_sock(parent); + sock_put(parent); +} + int sco_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) { struct sock *sk; From 7dc68b7c2a9cd9458fb10eea01ecdce40f448319 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 4/9] Bluetooth: btintel: enable DDC offload codecs and ACL/ISO paths Enable DDC 0x0130 (0x3f: LC3, SCO, A2DP SBC/AAC, LE Audio vendor codec, and controller delay reporting) and DDC 0x0120 (0x0b: SCO, LE ISO, and A2DP offload use cases) via HCI_Intel_Write_DDC (0xfc8b) during btintel_configure_offload() so HCI_Read_Local_Supported_Codecs_v2 (0x100d) and HCI_Read_Local_Supported_Codec_Capabilities (0x100e) report all hardware offload codecs supported by operational firmware. Extend btintel_get_codec_config_data() to configure 16 kHz SCO presets (0x01) for non-CVSD SCO codecs and 48 kHz 16-bit stereo SSP offload (preset 0x22) for ACL and LE CIS/BIS links. Signed-off-by: Liam Girdwood --- drivers/bluetooth/btintel.c | 54 +++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 42dabfe9bea2d5..290c9653d8f581 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2579,33 +2579,48 @@ static int btintel_get_codec_config_data(struct hci_dev *hdev, *ven_len = 0; *ven_data = NULL; - if (link != ESCO_LINK) { - bt_dev_err(hdev, "Invalid link type(%u)", link); - return -EINVAL; - } - *ven_data = kmalloc(sizeof(__u8), GFP_KERNEL); if (!*ven_data) { err = -ENOMEM; goto error; } - /* supports only CVSD and mSBC offload codecs */ - switch (codec->id) { - case 0x02: - **ven_data = 0x00; + switch (link) { + case ESCO_LINK: + case SCO_LINK: + switch (codec->id) { + case 0x02: + **ven_data = 0x00; + break; + case 0x00: + case 0x01: + case 0x03: + case 0x04: + case 0x05: + **ven_data = 0x01; + break; + default: + err = -EINVAL; + bt_dev_err(hdev, "Invalid SCO codec id(%u)", codec->id); + goto error; + } break; - case 0x05: - **ven_data = 0x01; + case ACL_LINK: + case CIS_LINK: + case BIS_LINK: + /* Preset 0x22 configures 48 kHz 16-bit stereo SSP offload */ + **ven_data = 0x22; break; default: err = -EINVAL; - bt_dev_err(hdev, "Invalid codec id(%u)", codec->id); + bt_dev_err(hdev, "Invalid link type(%u)", link); goto error; } + /* codec and its capabilities are pre-defined to ids * preset id = 0x00 represents CVSD codec with sampling rate 8K - * preset id = 0x01 represents mSBC codec with sampling rate 16K + * preset id = 0x01 represents mSBC/16K mono SCO codec + * preset id = 0x22 represents 48K stereo A2DP / LE Audio offload */ *ven_len = sizeof(__u8); return err; @@ -2625,10 +2640,21 @@ static int btintel_get_data_path_id(struct hci_dev *hdev, __u8 *data_path_id) static int btintel_configure_offload(struct hci_dev *hdev) { + static const u8 ddc_offload_enable[] = { + /* DDC 0x0130: enable LC3, SCO, A2DP SBC/AAC, LE Audio, and delay reporting */ + 0x03, 0x30, 0x01, 0x3f, + /* DDC 0x0120: enable SCO (BIT(0)), LE ISO (BIT(1)), and A2DP (BIT(3)) */ + 0x0a, 0x20, 0x01, 0x0b, 0x00, 0x81, 0xf1, 0x01, 0x00, 0x00, 0x00, + }; struct sk_buff *skb; int err = 0; struct intel_offload_use_cases *use_cases; + skb = __hci_cmd_sync(hdev, 0xfc8b, sizeof(ddc_offload_enable), + ddc_offload_enable, HCI_INIT_TIMEOUT); + if (!IS_ERR(skb)) + kfree_skb(skb); + skb = __hci_cmd_sync(hdev, 0xfc86, 0, NULL, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { bt_dev_err(hdev, "Reading offload use cases failed (%ld)", @@ -2648,7 +2674,7 @@ static int btintel_configure_offload(struct hci_dev *hdev) goto error; } - if (use_cases->preset[0] & 0x03) { + if (use_cases->preset[0] & 0x0b) { hdev->get_data_path_id = btintel_get_data_path_id; hdev->get_codec_config_data = btintel_get_codec_config_data; hci_dev_set_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED); From 9acf91c0d7b6c1b5d72f25cbc9f2931b8e58734e Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 5/9] Bluetooth: iso, l2cap: support BT_CODEC getsockopt and setsockopt Add BT_CODEC getsockopt and setsockopt support to BTPROTO_ISO (for HCI_TRANSPORT_LE_CIS and HCI_TRANSPORT_LE_BIS offload codecs such as LC3 and vendor LE Audio codecs) and BTPROTO_L2CAP (for HCI_TRANSPORT_ACL A2DP offload codecs such as Intel SBC 0x0002:0x0000 and AAC 0x0002:0x0002), and export hci_configure_datapath() for use by L2CAP and ISO data path setup. Signed-off-by: Liam Girdwood --- include/net/bluetooth/bluetooth.h | 2 + include/net/bluetooth/hci.h | 3 + include/net/bluetooth/hci_core.h | 2 + net/bluetooth/hci_conn.c | 53 +++++++++- net/bluetooth/iso.c | 157 ++++++++++++++++++++++++++++++ net/bluetooth/l2cap_sock.c | 138 ++++++++++++++++++++++++++ 6 files changed, 351 insertions(+), 4 deletions(-) diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index cd918924456668..bbf19d492fbe89 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -247,6 +247,8 @@ struct bt_codecs { #define BT_CODEC_CVSD 0x02 #define BT_CODEC_TRANSPARENT 0x03 #define BT_CODEC_MSBC 0x05 +#define BT_CODEC_LC3 0x06 +#define BT_CODEC_VENDOR 0xff #define BT_ISO_BASE 20 diff --git a/include/net/bluetooth/hci.h b/include/net/bluetooth/hci.h index 4c4257660486ca..766ddc8a7540e2 100644 --- a/include/net/bluetooth/hci.h +++ b/include/net/bluetooth/hci.h @@ -3512,7 +3512,10 @@ static inline __u16 hci_acl_dlen(const struct sk_buff *skb) #define hci_iso_data_flags(h) ((h) >> 14) /* codec transport types */ +#define HCI_TRANSPORT_ACL 0x00 #define HCI_TRANSPORT_SCO_ESCO 0x01 +#define HCI_TRANSPORT_LE_CIS 0x02 +#define HCI_TRANSPORT_LE_BIS 0x03 /* le24 support */ static inline void hci_cpu_to_le24(__u32 val, __u8 dst[3]) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 455f8bf77ac1d8..02f1a8a8a0ec00 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -1637,6 +1637,8 @@ static inline bool hci_is_le_conn_scanning(struct hci_dev *hdev) } int hci_disconnect(struct hci_conn *conn, __u8 reason); +int hci_configure_datapath(struct hci_dev *hdev, __u8 link, + struct bt_codec *codec); bool hci_setup_sync(struct hci_conn *conn, __u16 handle); bool hci_accept_sync(struct hci_conn *conn); void hci_sco_setup(struct hci_conn *conn, __u8 status); diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index f613be8aa6e058..229cf2801567a2 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -229,7 +229,52 @@ static bool find_next_esco_param(struct hci_conn *conn, return conn->attempt <= size; } -static int configure_datapath_sync(struct hci_dev *hdev, struct bt_codec *codec) +int hci_configure_datapath(struct hci_dev *hdev, __u8 link, + struct bt_codec *codec) +{ + int err; + __u8 vnd_len, *vnd_data = NULL; + struct hci_op_configure_data_path *cmd = NULL; + + if (!codec->data_path || !hdev->get_codec_config_data) + return 0; + + err = hdev->get_codec_config_data(hdev, link, codec, &vnd_len, + &vnd_data); + if (err < 0) + goto error; + + cmd = kzalloc(sizeof(*cmd) + vnd_len, GFP_ATOMIC); + if (!cmd) { + err = -ENOMEM; + goto error; + } + + err = hdev->get_data_path_id(hdev, &cmd->data_path_id); + if (err < 0) + goto error; + + cmd->vnd_len = vnd_len; + memcpy(cmd->vnd_data, vnd_data, vnd_len); + + cmd->direction = 0x00; + err = hci_send_cmd(hdev, HCI_CONFIGURE_DATA_PATH, + sizeof(*cmd) + vnd_len, cmd); + if (err < 0) + goto error; + + cmd->direction = 0x01; + err = hci_send_cmd(hdev, HCI_CONFIGURE_DATA_PATH, + sizeof(*cmd) + vnd_len, cmd); + +error: + kfree(cmd); + kfree(vnd_data); + return err; +} + +static int configure_datapath_sync(struct hci_dev *hdev, __u8 link, + struct bt_codec *codec) { int err; __u8 vnd_len, *vnd_data = NULL; @@ -242,7 +287,7 @@ static int configure_datapath_sync(struct hci_dev *hdev, struct bt_codec *codec) if (!codec->data_path || !hdev->get_codec_config_data) return 0; - err = hdev->get_codec_config_data(hdev, ESCO_LINK, codec, &vnd_len, + err = hdev->get_codec_config_data(hdev, link, codec, &vnd_len, &vnd_data); if (err < 0) goto error; @@ -288,7 +333,7 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) bt_dev_dbg(hdev, "hcon %p", conn); - configure_datapath_sync(hdev, &conn->codec); + configure_datapath_sync(hdev, ESCO_LINK, &conn->codec); conn->state = BT_CONNECT; conn->out = true; @@ -505,7 +550,7 @@ static int hci_enhanced_accept_sync(struct hci_dev *hdev, void *data) bt_dev_dbg(hdev, "hcon %p", conn); - configure_datapath_sync(hdev, &conn->codec); + configure_datapath_sync(hdev, ESCO_LINK, &conn->codec); memset(&cp, 0x00, sizeof(cp)); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index c1fdd9846eeb01..58b0719847b8b3 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -79,6 +79,7 @@ struct iso_pinfo { bool qos_user_set; __u8 base_len; __u8 base[BASE_MAX_LENGTH]; + struct bt_codec codec; struct iso_conn *conn; struct delayed_work timeout_work; }; @@ -468,6 +469,8 @@ static int iso_connect_bis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + hcon->codec = iso_pi(sk)->codec; + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -581,6 +584,8 @@ static int iso_connect_cis(struct sock *sk) goto unlock; } + hcon->codec = iso_pi(sk)->codec; + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -990,6 +995,7 @@ static void iso_sock_init(struct sock *sk, struct sock *parent) if (parent) { sk->sk_type = parent->sk_type; bt_sk(sk)->flags = bt_sk(parent)->flags; + iso_pi(sk)->codec = iso_pi(parent)->codec; security_sk_clone(parent, sk); } } @@ -1046,6 +1052,8 @@ static struct sock *iso_sock_alloc(struct net *net, struct socket *sock, iso_pi(sk)->qos = default_qos; iso_pi(sk)->sync_handle = -1; + iso_pi(sk)->codec.id = BT_CODEC_TRANSPARENT; + iso_pi(sk)->codec.data_path = 0x00; INIT_DELAYED_WORK(&iso_pi(sk)->timeout_work, iso_sock_timeout); @@ -1392,6 +1400,8 @@ static int iso_listen_bis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); + hcon->codec = iso_pi(sk)->codec; + conn = iso_conn_add(hcon); if (!conn) { hci_conn_drop(hcon); @@ -1960,6 +1970,9 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, struct sock *sk = sock->sk; int err = 0; struct bt_iso_qos qos = default_qos; + struct bt_codecs *codecs; + struct hci_dev *hdev; + __u8 buffer[255]; u32 opt; BT_DBG("sk %p", sk); @@ -2035,6 +2048,56 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; + case BT_CODEC: + if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND && + sk->sk_state != BT_LISTEN && sk->sk_state != BT_CONNECT2) { + err = -EINVAL; + break; + } + + hdev = hci_get_route(&iso_pi(sk)->dst, &iso_pi(sk)->src, + iso_pi(sk)->src_type); + if (!hdev) { + err = -EBADFD; + break; + } + + if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || + !hdev->get_data_path_id) { + hci_dev_put(hdev); + err = -EOPNOTSUPP; + break; + } + + if (optlen < sizeof(struct bt_codecs) || + optlen > sizeof(buffer)) { + hci_dev_put(hdev); + err = -EINVAL; + break; + } + + err = copy_struct_from_sockptr(buffer, sizeof(buffer), optval, + optlen); + if (err) { + hci_dev_put(hdev); + break; + } + + codecs = (void *)buffer; + + if (codecs->num_codecs != 1 || + optlen < struct_size(codecs, codecs, codecs->num_codecs)) { + hci_dev_put(hdev); + err = -EINVAL; + break; + } + + iso_pi(sk)->codec = codecs->codecs[0]; + if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon) + iso_pi(sk)->conn->hcon->codec = iso_pi(sk)->codec; + hci_dev_put(hdev); + break; + default: err = -ENOPROTOOPT; break; @@ -2050,6 +2113,12 @@ static int iso_sock_getsockopt(struct socket *sock, int level, int optname, struct sock *sk = sock->sk; struct bt_iso_qos *qos; int len, val, err = 0; + int buf_len; + struct codec_list *c; + u8 num_codecs, i; + struct hci_dev *hdev; + struct hci_codec_caps *caps; + struct bt_codec codec; u8 base_len; u8 *base; @@ -2106,6 +2175,94 @@ static int iso_sock_getsockopt(struct socket *sock, int level, int optname, break; + case BT_CODEC: + num_codecs = 0; + buf_len = 0; + + hdev = hci_get_route(&iso_pi(sk)->dst, &iso_pi(sk)->src, + iso_pi(sk)->src_type); + if (!hdev) { + err = -EBADFD; + break; + } + + if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || + !hdev->get_data_path_id) { + hci_dev_put(hdev); + err = -EOPNOTSUPP; + break; + } + + release_sock(sk); + + hci_dev_lock(hdev); + list_for_each_entry(c, &hdev->local_codecs, list) { + if (c->transport != HCI_TRANSPORT_LE_CIS && + c->transport != HCI_TRANSPORT_LE_BIS) + continue; + num_codecs++; + for (i = 0, caps = c->caps; i < c->num_caps; i++) { + buf_len += 1 + caps->len; + caps = (void *)&caps->data[caps->len]; + } + buf_len += sizeof(struct bt_codec); + } + hci_dev_unlock(hdev); + + buf_len += sizeof(struct bt_codecs); + if (buf_len > len) { + hci_dev_put(hdev); + return -ENOBUFS; + } + + if (copy_to_iter(&num_codecs, sizeof(num_codecs), + &opt->iter_out) != sizeof(num_codecs)) { + hci_dev_put(hdev); + return -EFAULT; + } + + hci_dev_lock(hdev); + list_for_each_entry(c, &hdev->local_codecs, list) { + if (c->transport != HCI_TRANSPORT_LE_CIS && + c->transport != HCI_TRANSPORT_LE_BIS) + continue; + + codec.id = c->id; + codec.cid = c->cid; + codec.vid = c->vid; + err = hdev->get_data_path_id(hdev, &codec.data_path); + if (err < 0) + break; + codec.num_caps = c->num_caps; + if (copy_to_iter(&codec, sizeof(codec), &opt->iter_out) + != sizeof(codec)) { + err = -EFAULT; + break; + } + + len = 0; + for (i = 0, caps = c->caps; i < c->num_caps; i++) { + len += 1 + caps->len; + caps = (void *)&caps->data[caps->len]; + } + + if (len && + copy_to_iter(c->caps, len, &opt->iter_out) != len) { + err = -EFAULT; + break; + } + } + + hci_dev_unlock(hdev); + hci_dev_put(hdev); + + lock_sock(sk); + + if (!err) + opt->optlen = buf_len; + + break; + default: err = -ENOPROTOOPT; break; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index 7a2551ef7f50b8..cc619a2c86ab15 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -591,6 +591,12 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, struct bt_security sec; struct bt_power pwr; int len, mode, err = 0; + int buf_len; + struct codec_list *c; + u8 num_codecs, i; + struct hci_dev *hdev; + struct hci_codec_caps *caps; + struct bt_codec codec; u32 opt; u16 mtu; u8 mval; @@ -745,6 +751,93 @@ static int l2cap_sock_getsockopt(struct socket *sock, int level, int optname, err = -EFAULT; break; + case BT_CODEC: + num_codecs = 0; + buf_len = 0; + + hdev = hci_get_route(&chan->dst, &chan->src, chan->src_type); + if (!hdev) { + err = -EBADFD; + break; + } + + if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || + !hdev->get_data_path_id) { + hci_dev_put(hdev); + err = -EOPNOTSUPP; + break; + } + + release_sock(sk); + l2cap_chan_unlock(chan); + + hci_dev_lock(hdev); + list_for_each_entry(c, &hdev->local_codecs, list) { + if (c->transport != HCI_TRANSPORT_ACL) + continue; + num_codecs++; + for (i = 0, caps = c->caps; i < c->num_caps; i++) { + buf_len += 1 + caps->len; + caps = (void *)&caps->data[caps->len]; + } + buf_len += sizeof(struct bt_codec); + } + hci_dev_unlock(hdev); + + buf_len += sizeof(struct bt_codecs); + if (buf_len > len) { + hci_dev_put(hdev); + return -ENOBUFS; + } + + if (copy_to_iter(&num_codecs, sizeof(num_codecs), + &sopt->iter_out) != sizeof(num_codecs)) { + hci_dev_put(hdev); + return -EFAULT; + } + + hci_dev_lock(hdev); + list_for_each_entry(c, &hdev->local_codecs, list) { + if (c->transport != HCI_TRANSPORT_ACL) + continue; + + codec.id = c->id; + codec.cid = c->cid; + codec.vid = c->vid; + err = hdev->get_data_path_id(hdev, &codec.data_path); + if (err < 0) + break; + codec.num_caps = c->num_caps; + if (copy_to_iter(&codec, sizeof(codec), &sopt->iter_out) + != sizeof(codec)) { + err = -EFAULT; + break; + } + + len = 0; + for (i = 0, caps = c->caps; i < c->num_caps; i++) { + len += 1 + caps->len; + caps = (void *)&caps->data[caps->len]; + } + + if (len && + copy_to_iter(c->caps, len, &sopt->iter_out) != len) { + err = -EFAULT; + break; + } + } + + hci_dev_unlock(hdev); + hci_dev_put(hdev); + + l2cap_chan_lock(chan); + lock_sock(sk); + + if (!err) + sopt->optlen = buf_len; + + break; + default: err = -ENOPROTOOPT; break; @@ -935,6 +1028,9 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, struct bt_security sec; struct bt_power pwr; struct l2cap_conn *conn; + struct bt_codecs *codecs; + struct hci_dev *hdev; + __u8 buffer[255]; int err = 0; u32 opt, phys; u16 mtu; @@ -1160,6 +1256,48 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, break; + case BT_CODEC: + hdev = hci_get_route(&chan->dst, &chan->src, chan->src_type); + if (!hdev) { + err = -EBADFD; + break; + } + + if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || + !hdev->get_data_path_id) { + hci_dev_put(hdev); + err = -EOPNOTSUPP; + break; + } + + if (optlen < sizeof(struct bt_codecs) || + optlen > sizeof(buffer)) { + hci_dev_put(hdev); + err = -EINVAL; + break; + } + + err = copy_struct_from_sockptr(buffer, sizeof(buffer), optval, + optlen); + if (err) { + hci_dev_put(hdev); + break; + } + + codecs = (void *)buffer; + + if (codecs->num_codecs != 1 || + optlen < struct_size(codecs, codecs, codecs->num_codecs)) { + hci_dev_put(hdev); + err = -EINVAL; + break; + } + + err = hci_configure_datapath(hdev, ACL_LINK, + &codecs->codecs[0]); + hci_dev_put(hdev); + break; + default: err = -ENOPROTOOPT; break; From c607dcf22954f659ebd9e5bc5dd6050a8c69939b Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 6/9] Bluetooth: hci_conn: configure offloaded ISO data path Update hci_iso_setup_path() to configure the controller data path (0x0c83) via hci_configure_datapath() and pass the requested data_path, codec ID, and vendor CID/VID to HCI_LE_Setup_ISO_Data_Path (0x206e) when offload is enabled. Signed-off-by: Liam Girdwood --- net/bluetooth/hci_conn.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 229cf2801567a2..aa1bb522d2a78d 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2342,14 +2342,23 @@ bool hci_iso_setup_path(struct hci_conn *conn) { struct hci_dev *hdev = conn->hdev; struct hci_cp_le_setup_iso_path cmd; + __u8 path = conn->codec.data_path; + __u8 codec = path ? conn->codec.id : 0x03; /* Transparent Data */ + __le16 cid = path ? cpu_to_le16(conn->codec.cid) : 0; + __le16 vid = path ? cpu_to_le16(conn->codec.vid) : 0; + + if (path) + hci_configure_datapath(hdev, conn->type, &conn->codec); memset(&cmd, 0, sizeof(cmd)); if (conn->iso_qos.ucast.out.sdu) { cmd.handle = cpu_to_le16(conn->handle); cmd.direction = 0x00; /* Input (Host to Controller) */ - cmd.path = 0x00; /* HCI path if enabled */ - cmd.codec = 0x03; /* Transparent Data */ + cmd.path = path; + cmd.codec = codec; + cmd.codec_cid = cid; + cmd.codec_vid = vid; if (hci_send_cmd(hdev, HCI_OP_LE_SETUP_ISO_PATH, sizeof(cmd), &cmd) < 0) @@ -2359,8 +2368,10 @@ bool hci_iso_setup_path(struct hci_conn *conn) if (conn->iso_qos.ucast.in.sdu) { cmd.handle = cpu_to_le16(conn->handle); cmd.direction = 0x01; /* Output (Controller to Host) */ - cmd.path = 0x00; /* HCI path if enabled */ - cmd.codec = 0x03; /* Transparent Data */ + cmd.path = path; + cmd.codec = codec; + cmd.codec_cid = cid; + cmd.codec_vid = vid; if (hci_send_cmd(hdev, HCI_OP_LE_SETUP_ISO_PATH, sizeof(cmd), &cmd) < 0) From 70840e12cca933442235a1891e112bff6b52f30e Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:25 +0000 Subject: [PATCH 7/9] Bluetooth: msft: support AVDTP A2DP hardware offload over SSP Implement Microsoft HCI vendor extension (0xfc1e) subcommands 0x07..0x0b (HCI_VS_MSFT_Avdtp_Capabilities_Configuration, Avdtp_Open, Avdtp_Start, Avdtp_Suspend, and Avdtp_Close) when MSFT_FEATURE_MASK_AVDTP (BIT(10)) is advertised by the controller. During msft_do_open(), query subcommand 0x07 and register the controller's offloaded AVDTP Media Codec Service Capabilities (SBC 0x00 and MPEG-2,4 AAC 0x02) into hdev->local_codecs under HCI_TRANSPORT_ACL. When setsockopt(SOL_BLUETOOTH, BT_CODEC) is called on a connected BR/EDR ACL BTPROTO_L2CAP channel, serialize stream transitions under chan->avdtp_lock, validate supplied SBC/AAC capability TLVs and AAC bitrates (>= 128 kbps), invoke msft_avdtp_open() to open and start hardware A2DP offload on the controller's SSP TDM interface, and automatically close any active stream on disable or L2CAP release. Signed-off-by: Liam Girdwood --- include/net/bluetooth/l2cap.h | 2 + net/bluetooth/l2cap_core.c | 1 + net/bluetooth/l2cap_sock.c | 94 +++++-- net/bluetooth/msft.c | 474 ++++++++++++++++++++++++++++++++++ net/bluetooth/msft.h | 24 ++ 5 files changed, 575 insertions(+), 20 deletions(-) diff --git a/include/net/bluetooth/l2cap.h b/include/net/bluetooth/l2cap.h index 54eddcdd341f98..fd36d6e4d5cdcd 100644 --- a/include/net/bluetooth/l2cap.h +++ b/include/net/bluetooth/l2cap.h @@ -531,6 +531,8 @@ struct l2cap_chan { __u8 mode; __u8 chan_type; __u8 chan_policy; + __u16 avdtp_handle; + struct mutex avdtp_lock; __u8 sec_level; diff --git a/net/bluetooth/l2cap_core.c b/net/bluetooth/l2cap_core.c index 4dd56e0074fdaf..78976b8aa39526 100644 --- a/net/bluetooth/l2cap_core.c +++ b/net/bluetooth/l2cap_core.c @@ -479,6 +479,7 @@ struct l2cap_chan *l2cap_chan_create(void) skb_queue_head_init(&chan->tx_q); skb_queue_head_init(&chan->srej_q); mutex_init(&chan->lock); + mutex_init(&chan->avdtp_lock); /* Set default lock nesting level */ atomic_set(&chan->nesting, L2CAP_NESTING_NORMAL); diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index cc619a2c86ab15..e9a8c1dc3e5c04 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -35,6 +35,7 @@ #include #include "smp.h" +#include "msft.h" static struct bt_sock_list l2cap_sk_list = { .lock = __RW_LOCK_UNLOCKED(l2cap_sk_list.lock) @@ -1257,44 +1258,78 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, break; case BT_CODEC: - hdev = hci_get_route(&chan->dst, &chan->src, chan->src_type); - if (!hdev) { - err = -EBADFD; - break; - } - - if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || - !hdev->get_data_path_id) { - hci_dev_put(hdev); - err = -EOPNOTSUPP; - break; - } - if (optlen < sizeof(struct bt_codecs) || optlen > sizeof(buffer)) { - hci_dev_put(hdev); err = -EINVAL; break; } err = copy_struct_from_sockptr(buffer, sizeof(buffer), optval, optlen); - if (err) { - hci_dev_put(hdev); + if (err) break; - } codecs = (void *)buffer; if (codecs->num_codecs != 1 || optlen < struct_size(codecs, codecs, codecs->num_codecs)) { - hci_dev_put(hdev); err = -EINVAL; break; } - err = hci_configure_datapath(hdev, ACL_LINK, - &codecs->codecs[0]); + if (codecs->codecs[0].data_path) { + if (sk->sk_state != BT_CONNECTED || + !chan->conn || !chan->conn->hcon || + HCI_CONN_HANDLE_UNSET(chan->conn->hcon->handle) || + !chan->dcid) { + err = -ENOTCONN; + break; + } + if (chan->conn->hcon->type != ACL_LINK) { + err = -EINVAL; + break; + } + hdev = hci_dev_hold(chan->conn->hcon->hdev); + } else { + hdev = (chan->conn && chan->conn->hcon) ? + hci_dev_hold(chan->conn->hcon->hdev) : + hci_get_route(&chan->dst, &chan->src, + chan->src_type); + } + + if (!hdev) { + err = -EBADFD; + break; + } + + if (!hci_dev_test_flag(hdev, HCI_OFFLOAD_CODECS_ENABLED) || + !hdev->get_data_path_id) { + hci_dev_put(hdev); + err = -EOPNOTSUPP; + break; + } + + /* Check if the controller supports MSFT AVDTP hardware offload */ + if (msft_avdtp_supported(hdev)) { + size_t caps_len = optlen - + struct_size(codecs, codecs, 1); + + l2cap_chan_hold(chan); + release_sock(sk); + l2cap_chan_unlock(chan); + + mutex_lock(&chan->avdtp_lock); + err = msft_avdtp_open(hdev, chan, &codecs->codecs[0], + caps_len); + mutex_unlock(&chan->avdtp_lock); + + l2cap_chan_lock(chan); + lock_sock(sk); + l2cap_chan_put(chan); + } else { + err = hci_configure_datapath(hdev, ACL_LINK, + &codecs->codecs[0]); + } hci_dev_put(hdev); break; @@ -1668,6 +1703,25 @@ static int l2cap_sock_release(struct socket *sock) err = l2cap_sock_shutdown(sock, SHUT_RDWR); chan = l2cap_pi(sk)->chan; + /* Check if an active MSFT AVDTP offload stream needs to be closed */ + if (chan) { + l2cap_chan_hold(chan); + mutex_lock(&chan->avdtp_lock); + if (chan->avdtp_handle) { + struct hci_dev *hdev; + + hdev = hci_get_route(&chan->dst, &chan->src, + chan->src_type); + if (hdev) { + msft_avdtp_close(hdev, chan->avdtp_handle); + hci_dev_put(hdev); + } + chan->avdtp_handle = 0; + } + mutex_unlock(&chan->avdtp_lock); + l2cap_chan_put(chan); + } + l2cap_chan_hold(chan); l2cap_chan_lock(chan); diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c index 2559c5b1bef020..e0d4c0620999ae 100644 --- a/net/bluetooth/msft.c +++ b/net/bluetooth/msft.c @@ -5,6 +5,7 @@ #include #include +#include #include #include "mgmt_util.h" @@ -79,6 +80,88 @@ struct msft_rp_le_set_advertisement_filter_enable { __u8 sub_opcode; } __packed; +/* Microsoft Bluetooth HCI Vendor-Specific Extension for AVDTP hardware offload + * (feature bit 10 in HCI_VS_MSFT_Read_Supported_Features). Command and event + * structures are wire-protocol HCI packets mapped onto skb->data with __le16 + * fields at unaligned byte offsets, so __packed is required without extra + * struct alignment padding. Capability payloads use standard Bluetooth SIG + * AVDTP v1.3 (Section 8.21.5) Media Codec Service Capability elements and + * A2DP v1.3 (Sections 4.3.2 & 4.5.2) SBC / MPEG-2,4 AAC Codec Information + * Elements. + */ +#define AVDTP_SVC_CAT_MEDIA_CODEC 0x07 +#define AVDTP_CAP_HDR_LEN 2 +#define AVDTP_MEDIA_CODEC_HDR_LEN 2 +#define AVDTP_MEDIA_TYPE_AUDIO 0x00 + +#define A2DP_CODEC_SBC 0x00 +#define A2DP_CODEC_MPEG24 0x02 +#define A2DP_CODEC_VENDOR 0xff + +#define AVDTP_SBC_CIE_LEN 4 +#define AVDTP_SBC_LOSC (AVDTP_MEDIA_CODEC_HDR_LEN + \ + AVDTP_SBC_CIE_LEN) +#define AVDTP_SBC_CAP_LEN (AVDTP_CAP_HDR_LEN + \ + AVDTP_SBC_LOSC) + +#define AVDTP_AAC_CIE_LEN 6 +#define AVDTP_AAC_LOSC (AVDTP_MEDIA_CODEC_HDR_LEN + \ + AVDTP_AAC_CIE_LEN) +#define AVDTP_AAC_CAP_LEN (AVDTP_CAP_HDR_LEN + \ + AVDTP_AAC_LOSC) +#define AVDTP_AAC_VBR_BIT 0x80 +#define AVDTP_AAC_BITRATE_HI_MASK 0x7f +#define AVDTP_AAC_MIN_BITRATE 128000 +#define AVDTP_AAC_MAX_BITRATE 192000 + +/* Intel Bluetooth Assigned Company ID and vendor A2DP offload codec IDs */ +#define BT_COMPID_INTEL 0x0002 +#define BTINTEL_A2DP_VID_SBC 0x0000 +#define BTINTEL_A2DP_VID_AAC 0x0002 + +#define MSFT_OP_AVDTP_CAPABILITIES_CONF 0x07 +struct msft_cp_avdtp_capabilities_conf { + __u8 sub_opcode; + __u8 ext_codec_count; + __u8 audio_if_param_count; +} __packed; + +struct msft_rp_avdtp_capabilities_conf { + __u8 status; + __u8 sub_opcode; + __u8 int_codec_count; + __u8 data[]; +} __packed; + +#define MSFT_OP_AVDTP_OPEN 0x08 +struct msft_cp_avdtp_open { + __u8 sub_opcode; + __le16 acl_handle; + __le16 l2cap_dcid; + __le16 l2cap_mtu; + __u8 data[]; +} __packed; + +struct msft_rp_avdtp_open { + __u8 status; + __u8 sub_opcode; + __le16 avdtp_handle; + __u8 audio_if_param_count; +} __packed; + +#define MSFT_OP_AVDTP_START 0x09 +#define MSFT_OP_AVDTP_SUSPEND 0x0a +#define MSFT_OP_AVDTP_CLOSE 0x0b +struct msft_cp_avdtp_handle { + __u8 sub_opcode; + __le16 avdtp_handle; +} __packed; + +struct msft_rp_avdtp_handle { + __u8 status; + __u8 sub_opcode; +} __packed; + #define MSFT_EV_LE_MONITOR_DEVICE 0x02 struct msft_ev_le_monitor_device { __u8 addr_type; @@ -138,6 +221,394 @@ bool msft_monitor_supported(struct hci_dev *hdev) return !!(msft_get_features(hdev) & MSFT_FEATURE_MASK_LE_ADV_MONITOR); } +bool msft_avdtp_supported(struct hci_dev *hdev) +{ + return !!(msft_get_features(hdev) & MSFT_FEATURE_MASK_AVDTP); +} + +/* Validate an AVDTP Media Codec Service Capability TLV element */ +static bool msft_avdtp_valid_codec_cap(const u8 *pos, size_t rem, + size_t *elem_len, u8 *codec_id) +{ + size_t len; + u8 losc; + u8 id; + + if (rem < AVDTP_CAP_HDR_LEN + AVDTP_MEDIA_CODEC_HDR_LEN) + return false; + + if (pos[0] != AVDTP_SVC_CAT_MEDIA_CODEC) + return false; + + losc = pos[1]; + if (losc < AVDTP_MEDIA_CODEC_HDR_LEN) + return false; + + len = AVDTP_CAP_HDR_LEN + (size_t)losc; + if (len > rem) + return false; + + if (pos[2] != AVDTP_MEDIA_TYPE_AUDIO) + return false; + + id = pos[3]; + switch (id) { + case A2DP_CODEC_SBC: + if (losc != AVDTP_SBC_LOSC) + return false; + break; + case A2DP_CODEC_MPEG24: + if (losc != AVDTP_AAC_LOSC) + return false; + break; + default: + return false; + } + + *elem_len = len; + *codec_id = id; + return true; +} + +/* This function requires the caller holds hci_req_sync_lock */ +static void msft_read_avdtp_capabilities(struct hci_dev *hdev) +{ + struct msft_cp_avdtp_capabilities_conf cp = { + .sub_opcode = MSFT_OP_AVDTP_CAPABILITIES_CONF, + .ext_codec_count = 0, + .audio_if_param_count = 0, + }; + struct msft_rp_avdtp_capabilities_conf *rp; + struct sk_buff *skb; + const u8 *pos; + size_t rem; + u8 i; + + skb = __hci_cmd_sync(hdev, hdev->msft_opcode, sizeof(cp), &cp, + HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + bt_dev_err(hdev, "Failed to read MSFT AVDTP capabilities (%ld)", + PTR_ERR(skb)); + return; + } + + if (skb->len < sizeof(*rp)) { + bt_dev_err(hdev, "MSFT AVDTP capabilities length mismatch"); + goto done; + } + + rp = (void *)skb->data; + if (rp->status || rp->sub_opcode != MSFT_OP_AVDTP_CAPABILITIES_CONF) + goto done; + + pos = rp->data; + rem = skb->len - sizeof(*rp); + + /* Iterate through each internal AVDTP codec capability block returned by the controller */ + for (i = 0; + i < rp->int_codec_count && + rem >= AVDTP_CAP_HDR_LEN + AVDTP_MEDIA_CODEC_HDR_LEN; + i++) { + struct codec_list *c, *entry; + size_t elem_len; + u8 codec_id; + bool exists = false; + + /* Validate AVDTP Service Category, LOSC bounds, media type, and codec ID */ + if (!msft_avdtp_valid_codec_cap(pos, rem, &elem_len, &codec_id)) + break; + + hci_dev_lock(hdev); + list_for_each_entry(c, &hdev->local_codecs, list) { + if (c->transport == HCI_TRANSPORT_ACL && + c->id == codec_id) { + exists = true; + break; + } + } + + /* Register the AVDTP offload codec if not already present in local_codecs */ + if (!exists) { + entry = kzalloc(sizeof(*entry) + + sizeof(struct hci_codec_caps) + + elem_len, GFP_KERNEL); + if (entry) { + entry->id = codec_id; + entry->transport = HCI_TRANSPORT_ACL; + entry->num_caps = 1; + entry->len = sizeof(struct hci_codec_caps) + + elem_len; + entry->caps[0].len = elem_len; + memcpy(entry->caps[0].data, pos, elem_len); + list_add(&entry->list, &hdev->local_codecs); + bt_dev_info(hdev, + "MSFT AVDTP offload codec 0x%02x registered (%zu bytes)", + codec_id, elem_len); + } + } + hci_dev_unlock(hdev); + + pos += elem_len; + rem -= elem_len; + } + +done: + kfree_skb(skb); +} + +int msft_avdtp_close(struct hci_dev *hdev, __u16 avdtp_handle) +{ + struct msft_cp_avdtp_handle cp; + struct sk_buff *skb; + int err = 0; + + if (!avdtp_handle || !msft_avdtp_supported(hdev)) + return 0; + + cp.sub_opcode = MSFT_OP_AVDTP_SUSPEND; + cp.avdtp_handle = cpu_to_le16(avdtp_handle); + skb = hci_cmd_sync(hdev, hdev->msft_opcode, sizeof(cp), &cp, + HCI_CMD_TIMEOUT); + if (!IS_ERR(skb)) + kfree_skb(skb); + + cp.sub_opcode = MSFT_OP_AVDTP_CLOSE; + cp.avdtp_handle = cpu_to_le16(avdtp_handle); + skb = hci_cmd_sync(hdev, hdev->msft_opcode, sizeof(cp), &cp, + HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + bt_dev_err(hdev, "MSFT AVDTP close failed (%d)", err); + return err; + } + + kfree_skb(skb); + bt_dev_info(hdev, "MSFT AVDTP offload closed: handle 0x%04x", + avdtp_handle); + return 0; +} + +int msft_avdtp_open(struct hci_dev *hdev, struct l2cap_chan *chan, + struct bt_codec *codec, size_t caps_len) +{ + static const u8 default_sbc_cap[AVDTP_SBC_CAP_LEN] = { + AVDTP_SVC_CAT_MEDIA_CODEC, AVDTP_SBC_LOSC, + AVDTP_MEDIA_TYPE_AUDIO, A2DP_CODEC_SBC, + 0x11, 0x15, 0x02, 0x33 + }; + static const u8 default_aac_cap[AVDTP_AAC_CAP_LEN] = { + AVDTP_SVC_CAT_MEDIA_CODEC, AVDTP_AAC_LOSC, + AVDTP_MEDIA_TYPE_AUDIO, A2DP_CODEC_MPEG24, + 0x80, 0x00, 0x84, 0x82, 0xee, 0x00 + }; + struct msft_cp_avdtp_open *open_cp; + struct msft_rp_avdtp_open *open_rp; + struct msft_cp_avdtp_handle start_cp; + struct msft_rp_avdtp_handle *start_rp; + u8 custom_cap[AVDTP_AAC_CAP_LEN]; + const u8 *cap_ptr = NULL; + size_t cap_size = 0; + size_t open_len; + struct sk_buff *skb; + u16 acl_handle = 0; + u16 avdtp_handle; + u16 dcid, mtu; + bool is_sbc, is_aac; + bool found_conn = false; + int err; + + /* Verify the controller supports MSFT AVDTP offload */ + if (!msft_avdtp_supported(hdev)) + return -EOPNOTSUPP; + + /* Close any active offload stream before reconfiguring or disabling */ + if (chan->avdtp_handle) { + msft_avdtp_close(hdev, chan->avdtp_handle); + chan->avdtp_handle = 0; + } + + if (!codec->data_path) + return 0; + + /* Match standard A2DP codec IDs or Intel vendor-specific ACL codec IDs */ + is_sbc = (codec->id == A2DP_CODEC_SBC) || + (codec->id == A2DP_CODEC_VENDOR && + codec->cid == BT_COMPID_INTEL && + codec->vid == BTINTEL_A2DP_VID_SBC); + is_aac = (codec->id == A2DP_CODEC_MPEG24) || + (codec->id == A2DP_CODEC_VENDOR && + codec->cid == BT_COMPID_INTEL && + codec->vid == BTINTEL_A2DP_VID_AAC); + + if (!is_sbc && !is_aac) { + bt_dev_err(hdev, + "Unsupported MSFT AVDTP codec id 0x%02x (cid 0x%04x vid 0x%04x)", + codec->id, codec->cid, codec->vid); + return -EINVAL; + } + + /* Check if user-space supplied codec-specific capability parameters */ + if (codec->num_caps > 0) { + const struct bt_codec_caps *caps; + size_t parsed_len; + u8 parsed_id; + + if (caps_len < sizeof(*caps)) + return -EINVAL; + + caps = (const void *)&codec[1]; + if (caps_len < sizeof(*caps) + caps->len) + return -EINVAL; + + /* Use a full AVDTP Media Codec Service Capability TLV directly if valid */ + if (msft_avdtp_valid_codec_cap(caps->data, caps->len, + &parsed_len, &parsed_id) && + parsed_len == caps->len) { + if ((is_sbc && parsed_id != A2DP_CODEC_SBC) || + (is_aac && parsed_id != A2DP_CODEC_MPEG24)) + return -EINVAL; + cap_ptr = caps->data; + cap_size = caps->len; + /* Wrap a raw 4-byte SBC CIE into an 8-byte AVDTP Service Capability TLV */ + } else if (is_sbc && caps->len == AVDTP_SBC_CIE_LEN) { + custom_cap[0] = AVDTP_SVC_CAT_MEDIA_CODEC; + custom_cap[1] = AVDTP_SBC_LOSC; + custom_cap[2] = AVDTP_MEDIA_TYPE_AUDIO; + custom_cap[3] = A2DP_CODEC_SBC; + memcpy(&custom_cap[4], caps->data, AVDTP_SBC_CIE_LEN); + cap_ptr = custom_cap; + cap_size = AVDTP_SBC_CAP_LEN; + /* Wrap a raw 6-byte AAC CIE into a 10-byte AVDTP Service Capability TLV */ + } else if (is_aac && caps->len == AVDTP_AAC_CIE_LEN) { + u32 br; + + custom_cap[0] = AVDTP_SVC_CAT_MEDIA_CODEC; + custom_cap[1] = AVDTP_AAC_LOSC; + custom_cap[2] = AVDTP_MEDIA_TYPE_AUDIO; + custom_cap[3] = A2DP_CODEC_MPEG24; + memcpy(&custom_cap[4], caps->data, AVDTP_AAC_CIE_LEN); + br = ((u32)(custom_cap[7] & + AVDTP_AAC_BITRATE_HI_MASK) << 16) | + ((u32)custom_cap[8] << 8) | custom_cap[9]; + /* Reject nonzero bitrates below 128 kbps; cap 0 or >192 kbps */ + if (br && br < AVDTP_AAC_MIN_BITRATE) + return -EINVAL; + if (!br || br > AVDTP_AAC_MAX_BITRATE) { + custom_cap[7] = (custom_cap[7] & + AVDTP_AAC_VBR_BIT) | + ((AVDTP_AAC_MAX_BITRATE >> 16) & + AVDTP_AAC_BITRATE_HI_MASK); + custom_cap[8] = (AVDTP_AAC_MAX_BITRATE >> 8) & + 0xff; + custom_cap[9] = AVDTP_AAC_MAX_BITRATE & 0xff; + } + cap_ptr = custom_cap; + cap_size = AVDTP_AAC_CAP_LEN; + } else { + return -EINVAL; + } + } else { + /* Use default SBC or AAC capability when no capability was supplied */ + if (is_sbc) { + cap_ptr = default_sbc_cap; + cap_size = sizeof(default_sbc_cap); + } else { + cap_ptr = default_aac_cap; + cap_size = sizeof(default_aac_cap); + } + } + + /* Require a connected BR/EDR ACL L2CAP channel with an assigned DCID */ + l2cap_chan_lock(chan); + if (chan->state == BT_CONNECTED && chan->conn && chan->conn->hcon && + chan->conn->hcon->type == ACL_LINK && + !HCI_CONN_HANDLE_UNSET(chan->conn->hcon->handle) && chan->dcid) { + acl_handle = chan->conn->hcon->handle; + dcid = chan->dcid; + mtu = chan->omtu ? chan->omtu : L2CAP_DEFAULT_MTU; + found_conn = true; + } + l2cap_chan_unlock(chan); + + if (!found_conn) { + bt_dev_err(hdev, + "MSFT AVDTP offload requires a connected BR/EDR ACL L2CAP channel"); + return -ENOTCONN; + } + + open_len = sizeof(*open_cp) + cap_size + 1; + open_cp = kzalloc(open_len, GFP_KERNEL); + if (!open_cp) + return -ENOMEM; + + open_cp->sub_opcode = MSFT_OP_AVDTP_OPEN; + open_cp->acl_handle = cpu_to_le16(acl_handle); + open_cp->l2cap_dcid = cpu_to_le16(dcid); + open_cp->l2cap_mtu = cpu_to_le16(mtu); + memcpy(open_cp->data, cap_ptr, cap_size); + open_cp->data[cap_size] = 0x00; /* Audio_interface_parameter_count */ + + /* Send MSFT AVDTP Open command to allocate an offloaded stream handle on the controller */ + skb = hci_cmd_sync(hdev, hdev->msft_opcode, open_len, open_cp, + HCI_CMD_TIMEOUT); + kfree(open_cp); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + bt_dev_err(hdev, "MSFT AVDTP Open failed (%d)", err); + return err; + } + + if (skb->len < sizeof(*open_rp)) { + kfree_skb(skb); + return -EIO; + } + + open_rp = (void *)skb->data; + if (open_rp->status || open_rp->sub_opcode != MSFT_OP_AVDTP_OPEN) { + err = -bt_to_errno(open_rp->status); + kfree_skb(skb); + return err ? err : -EIO; + } + + avdtp_handle = __le16_to_cpu(open_rp->avdtp_handle); + kfree_skb(skb); + + start_cp.sub_opcode = MSFT_OP_AVDTP_START; + start_cp.avdtp_handle = cpu_to_le16(avdtp_handle); + + /* Send MSFT AVDTP Start command to start SSP TDM audio and hardware encoding */ + skb = hci_cmd_sync(hdev, hdev->msft_opcode, sizeof(start_cp), &start_cp, + HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) { + err = PTR_ERR(skb); + bt_dev_err(hdev, "MSFT AVDTP Start failed (%d)", err); + msft_avdtp_close(hdev, avdtp_handle); + return err; + } + + if (skb->len < sizeof(*start_rp)) { + kfree_skb(skb); + msft_avdtp_close(hdev, avdtp_handle); + return -EIO; + } + + start_rp = (void *)skb->data; + if (start_rp->status || start_rp->sub_opcode != MSFT_OP_AVDTP_START) { + err = -bt_to_errno(start_rp->status); + kfree_skb(skb); + msft_avdtp_close(hdev, avdtp_handle); + return err ? err : -EIO; + } + + kfree_skb(skb); + chan->avdtp_handle = avdtp_handle; + + bt_dev_info(hdev, + "MSFT AVDTP offload started: acl 0x%04x dcid 0x%04x mtu %u codec 0x%02x handle 0x%04x", + acl_handle, dcid, mtu, codec->id, avdtp_handle); + return 0; +} + static bool read_supported_features(struct hci_dev *hdev, struct msft_data *msft) { @@ -669,6 +1140,9 @@ void msft_do_open(struct hci_dev *hdev) */ reregister_monitor(hdev); } + + if (msft_avdtp_supported(hdev)) + msft_read_avdtp_capabilities(hdev); } void msft_do_close(struct hci_dev *hdev) diff --git a/net/bluetooth/msft.h b/net/bluetooth/msft.h index fe538e9c91c019..f44cc850679e15 100644 --- a/net/bluetooth/msft.h +++ b/net/bluetooth/msft.h @@ -9,10 +9,17 @@ #define MSFT_FEATURE_MASK_LE_ADV_MONITOR BIT(3) #define MSFT_FEATURE_MASK_CURVE_VALIDITY BIT(4) #define MSFT_FEATURE_MASK_CONCURRENT_ADV_MONITOR BIT(5) +#define MSFT_FEATURE_MASK_AVDTP BIT(10) + +struct l2cap_chan; #if IS_ENABLED(CONFIG_BT_MSFTEXT) bool msft_monitor_supported(struct hci_dev *hdev); +bool msft_avdtp_supported(struct hci_dev *hdev); +int msft_avdtp_open(struct hci_dev *hdev, struct l2cap_chan *chan, + struct bt_codec *codec, size_t caps_len); +int msft_avdtp_close(struct hci_dev *hdev, __u16 avdtp_handle); void msft_register(struct hci_dev *hdev); void msft_release(struct hci_dev *hdev); void msft_do_open(struct hci_dev *hdev); @@ -34,6 +41,23 @@ static inline bool msft_monitor_supported(struct hci_dev *hdev) return false; } +static inline bool msft_avdtp_supported(struct hci_dev *hdev) +{ + return false; +} + +static inline int msft_avdtp_open(struct hci_dev *hdev, + struct l2cap_chan *chan, + struct bt_codec *codec, size_t caps_len) +{ + return -EOPNOTSUPP; +} + +static inline int msft_avdtp_close(struct hci_dev *hdev, __u16 avdtp_handle) +{ + return -EOPNOTSUPP; +} + static inline void msft_register(struct hci_dev *hdev) {} static inline void msft_release(struct hci_dev *hdev) {} static inline void msft_do_open(struct hci_dev *hdev) {} From 63719f34e79cb6c7a64c2edf560a9c859c1ee794 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:26 +0000 Subject: [PATCH 8/9] Bluetooth: hci_conn, btintel: support u-law and A-law SCO offload Configure 8 kHz SSP PCM preset 0x00 in btintel_get_codec_config_data() for 8 kHz synchronous codecs (0x00 u-law, 0x01 A-law, 0x02 CVSD, 0x03 Transparent, and 0x04 Linear PCM) while keeping 16 kHz preset 0x01 for 0x05 mSBC. Update hci_enhanced_setup_sync() and hci_enhanced_accept_sync() to pass the requested over-the-air coding format ID for G.711 u-law (0x00) and A-law (0x01) connections instead of hardcoding CVSD (0x02). Signed-off-by: Liam Girdwood --- drivers/bluetooth/btintel.c | 8 ++++---- net/bluetooth/hci_conn.c | 12 ++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 290c9653d8f581..1c08237407552d 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2589,13 +2589,13 @@ static int btintel_get_codec_config_data(struct hci_dev *hdev, case ESCO_LINK: case SCO_LINK: switch (codec->id) { - case 0x02: - **ven_data = 0x00; - break; case 0x00: case 0x01: - case 0x03: + case 0x02: case 0x04: + case 0x03: + **ven_data = 0x00; + break; case 0x05: **ven_data = 0x01; break; diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index aa1bb522d2a78d..62878c914e8c85 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -402,6 +402,8 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) cp.out_transport_unit_size = 1; break; + case 0x00: /* u-law */ + case 0x01: /* A-law */ case BT_CODEC_CVSD: if (conn->parent && lmp_esco_capable(conn->parent)) { if (!find_next_esco_param(conn, esco_param_cvsd, @@ -413,8 +415,8 @@ static int hci_enhanced_setup_sync(struct hci_dev *hdev, void *data) goto unlock; param = &sco_param_cvsd[conn->attempt - 1]; } - cp.tx_coding_format.id = 2; - cp.rx_coding_format.id = 2; + cp.tx_coding_format.id = conn->codec.id; + cp.rx_coding_format.id = conn->codec.id; cp.tx_codec_frame_size = __cpu_to_le16(60); cp.rx_codec_frame_size = __cpu_to_le16(60); cp.in_bandwidth = __cpu_to_le32(16000); @@ -612,10 +614,12 @@ static int hci_enhanced_accept_sync(struct hci_dev *hdev, void *data) cp.retrans_effort = 0x02; break; + case 0x00: /* u-law */ + case 0x01: /* A-law */ case BT_CODEC_CVSD: default: - cp.tx_coding_format.id = 2; - cp.rx_coding_format.id = 2; + cp.tx_coding_format.id = (conn->codec.id <= 0x02) ? conn->codec.id : 2; + cp.rx_coding_format.id = (conn->codec.id <= 0x02) ? conn->codec.id : 2; cp.tx_codec_frame_size = __cpu_to_le16(60); cp.rx_codec_frame_size = __cpu_to_le16(60); cp.in_bandwidth = __cpu_to_le32(16000); From 8aa43ec28be535462a7d3b3d5456568e8d4d17c2 Mon Sep 17 00:00:00 2001 From: Liam Girdwood Date: Fri, 13 Nov 2026 21:02:26 +0000 Subject: [PATCH 9/9] Bluetooth: hci_conn, iso, btintel: support LE CIS LC3 SSP offload Support hardware LC3 offload over SSP for LE Audio CIS and BIS links: - Validate and store user-supplied LC3 Codec_Specific_Configuration LTV capabilities from setsockopt(BT_CODEC) on iso_pi(sk) and hci_conn (checking Sampling_Frequency via btintel_get_codec_config_data()). - Update btintel_lc3_freq_preset() to map LC3 Sampling_Frequency 16 kHz (0x03) to preset 0x01 and 48 kHz (0x08) to preset 0x22, rejecting unsupported rates with -EINVAL. - Build direction-specific LC3 LTV configuration and QoS parameters for TX and RX paths (both ucast CIS and bcast BIS) in hci_iso_setup_path(), handling 75-octet 7.5 ms frames (48_1) cleanly when deriving defaults. - Propagate listener codec configuration onto hci_conn before hci_iso_setup_path() runs in hci_le_cis_req_evt() and hci_le_big_sync_established_evt(), covering non-deferred CIS and BIG sync paths as well as BT_DEFER_SETUP and iso_conn_ready(). Signed-off-by: Liam Girdwood --- drivers/bluetooth/btintel.c | 56 ++++++++++++- include/net/bluetooth/hci_core.h | 12 +++ net/bluetooth/hci_conn.c | 124 ++++++++++++++++++++++------ net/bluetooth/hci_event.c | 5 ++ net/bluetooth/iso.c | 137 +++++++++++++++++++++++++++++-- 5 files changed, 301 insertions(+), 33 deletions(-) diff --git a/drivers/bluetooth/btintel.c b/drivers/bluetooth/btintel.c index 1c08237407552d..5fc0c22cf9bc10 100644 --- a/drivers/bluetooth/btintel.c +++ b/drivers/bluetooth/btintel.c @@ -2567,13 +2567,53 @@ static int btintel_prepare_fw_download_tlv(struct hci_dev *hdev, return err; } +static int btintel_lc3_freq_preset(struct bt_codec *codec, __u8 *preset) +{ + const struct bt_codec_caps *caps; + const u8 *pos, *end; + + if (!codec->num_caps) { + *preset = 0x01; + return 0; + } + + caps = (const void *)&codec[1]; + pos = caps->data; + end = pos + caps->len; + + while (pos < end) { + u8 ltv_len = pos[0]; + + if (!ltv_len || pos + 1 + ltv_len > end) + return -EINVAL; + + if (pos[1] == 0x01) { /* Sampling_Frequency */ + if (ltv_len != 2) + return -EINVAL; + switch (pos[2]) { + case 0x03: /* 16 kHz */ + *preset = 0x01; + return 0; + case 0x08: /* 48 kHz */ + *preset = 0x22; + return 0; + default: + return -EINVAL; + } + } + pos += 1 + ltv_len; + } + + return -EINVAL; +} + static int btintel_get_codec_config_data(struct hci_dev *hdev, __u8 link, struct bt_codec *codec, __u8 *ven_len, __u8 **ven_data) { int err = 0; - if (!ven_data || !ven_len) + if (!ven_data || !ven_len || !codec) return -EINVAL; *ven_len = 0; @@ -2606,11 +2646,21 @@ static int btintel_get_codec_config_data(struct hci_dev *hdev, } break; case ACL_LINK: - case CIS_LINK: - case BIS_LINK: /* Preset 0x22 configures 48 kHz 16-bit stereo SSP offload */ **ven_data = 0x22; break; + case CIS_LINK: + case BIS_LINK: + if (codec->id != BT_CODEC_LC3) { + **ven_data = 0x01; + break; + } + err = btintel_lc3_freq_preset(codec, *ven_data); + if (err) { + bt_dev_err(hdev, "Unsupported LC3 ISO offload config"); + goto error; + } + break; default: err = -EINVAL; bt_dev_err(hdev, "Invalid link type(%u)", link); diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 02f1a8a8a0ec00..74c1f8f2731c9d 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -789,7 +789,11 @@ struct hci_conn { struct hci_conn *parent; struct hci_link *link; +#define HCI_MAX_CODEC_CFG_LEN 32 + struct bt_codec codec; + __u8 codec_cfg[HCI_MAX_CODEC_CFG_LEN]; + __u8 codec_cfg_len; void (*connect_cfm_cb) (struct hci_conn *conn, u8 status); void (*security_cfm_cb) (struct hci_conn *conn, u8 status); @@ -908,6 +912,8 @@ static inline int sco_recv_scodata(struct hci_dev *hdev, u16 handle, #if IS_ENABLED(CONFIG_BT_LE) int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags); +void iso_conn_set_codec(struct hci_dev *hdev, struct hci_conn *conn, + const bdaddr_t *dst); int iso_recv(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags); #else @@ -917,6 +923,12 @@ static inline int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, return 0; } +static inline void iso_conn_set_codec(struct hci_dev *hdev, + struct hci_conn *conn, + const bdaddr_t *dst) +{ +} + static inline int iso_recv(struct hci_dev *hdev, u16 handle, struct sk_buff *skb, u16 flags) { diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index 62878c914e8c85..89cfd52bb7ac10 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -2342,46 +2342,120 @@ struct hci_conn *hci_bind_cis(struct hci_dev *hdev, bdaddr_t *dst, return cis; } -bool hci_iso_setup_path(struct hci_conn *conn) +static int hci_iso_build_lc3_cfg(const struct hci_conn *conn, + const struct bt_iso_io_qos *io, + __u8 *cfg, __u8 *cfg_len) +{ + __u16 sdu = io->sdu ? io->sdu : 40; + __u8 freq; + + if (conn->codec_cfg_len > 0) { + if (conn->codec_cfg_len > HCI_MAX_CODEC_CFG_LEN) + return -EINVAL; + memcpy(cfg, conn->codec_cfg, conn->codec_cfg_len); + *cfg_len = conn->codec_cfg_len; + return 0; + } + + if (io->interval == 7500) + freq = (sdu >= 75) ? 0x08 : 0x03; + else if (io->interval == 10000) + freq = (sdu >= 100) ? 0x08 : 0x03; + else + freq = (sdu >= 75) ? 0x08 : 0x03; + + /* Sampling_Frequency (0x01): 0x03 = 16 kHz, 0x08 = 48 kHz */ + cfg[0] = 0x02; + cfg[1] = 0x01; + cfg[2] = freq; + /* Frame_Duration (0x02): 0x00 = 7.5 ms, 0x01 = 10 ms */ + cfg[3] = 0x02; + cfg[4] = 0x02; + cfg[5] = (io->interval == 7500) ? 0x00 : 0x01; + /* Octets_Per_Codec_Frame (0x04) */ + cfg[6] = 0x03; + cfg[7] = 0x04; + cfg[8] = sdu & 0xff; + cfg[9] = (sdu >> 8) & 0xff; + *cfg_len = 10; + + return 0; +} + +static bool hci_iso_setup_dir_path(struct hci_conn *conn, __u8 direction, + const struct bt_iso_io_qos *io, + bool *dp_configured) { struct hci_dev *hdev = conn->hdev; - struct hci_cp_le_setup_iso_path cmd; + struct { + struct hci_cp_le_setup_iso_path cp; + __u8 cfg[HCI_MAX_CODEC_CFG_LEN]; + } __packed cmd; __u8 path = conn->codec.data_path; __u8 codec = path ? conn->codec.id : 0x03; /* Transparent Data */ __le16 cid = path ? cpu_to_le16(conn->codec.cid) : 0; __le16 vid = path ? cpu_to_le16(conn->codec.vid) : 0; - - if (path) - hci_configure_datapath(hdev, conn->type, &conn->codec); + __u8 cfg_len = 0; memset(&cmd, 0, sizeof(cmd)); - if (conn->iso_qos.ucast.out.sdu) { - cmd.handle = cpu_to_le16(conn->handle); - cmd.direction = 0x00; /* Input (Host to Controller) */ - cmd.path = path; - cmd.codec = codec; - cmd.codec_cid = cid; - cmd.codec_vid = vid; - - if (hci_send_cmd(hdev, HCI_OP_LE_SETUP_ISO_PATH, sizeof(cmd), - &cmd) < 0) + if (path && codec == BT_CODEC_LC3) { + if (hci_iso_build_lc3_cfg(conn, io, cmd.cfg, &cfg_len) < 0) return false; + cmd.cp.codec_cfg_len = cfg_len; + } + + if (path && !*dp_configured) { + struct { + struct bt_codec codec; + struct bt_codec_caps caps; + __u8 data[HCI_MAX_CODEC_CFG_LEN]; + } __packed cbuf; + + memset(&cbuf, 0, sizeof(cbuf)); + cbuf.codec = conn->codec; + if (cfg_len) { + cbuf.codec.num_caps = 1; + cbuf.caps.len = cfg_len; + memcpy(cbuf.data, cmd.cfg, cfg_len); + } + if (hci_configure_datapath(hdev, conn->type, &cbuf.codec) < 0) + return false; + *dp_configured = true; } - if (conn->iso_qos.ucast.in.sdu) { - cmd.handle = cpu_to_le16(conn->handle); - cmd.direction = 0x01; /* Output (Controller to Host) */ - cmd.path = path; - cmd.codec = codec; - cmd.codec_cid = cid; - cmd.codec_vid = vid; + cmd.cp.handle = cpu_to_le16(conn->handle); + cmd.cp.direction = direction; + cmd.cp.path = path; + cmd.cp.codec = codec; + cmd.cp.codec_cid = cid; + cmd.cp.codec_vid = vid; - if (hci_send_cmd(hdev, HCI_OP_LE_SETUP_ISO_PATH, sizeof(cmd), - &cmd) < 0) - return false; + return hci_send_cmd(hdev, HCI_OP_LE_SETUP_ISO_PATH, + sizeof(cmd.cp) + cfg_len, &cmd) >= 0; +} + +bool hci_iso_setup_path(struct hci_conn *conn) +{ + const struct bt_iso_io_qos *out, *in; + bool dp_configured = false; + + if (conn->type == BIS_LINK) { + out = &conn->iso_qos.bcast.out; + in = &conn->iso_qos.bcast.in; + } else { + out = &conn->iso_qos.ucast.out; + in = &conn->iso_qos.ucast.in; } + if (out->sdu && + !hci_iso_setup_dir_path(conn, 0x00, out, &dp_configured)) + return false; + + if (in->sdu && + !hci_iso_setup_dir_path(conn, 0x01, in, &dp_configured)) + return false; + return true; } diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c index 3536ef416fa837..ab17b7541a2f62 100644 --- a/net/bluetooth/hci_event.c +++ b/net/bluetooth/hci_event.c @@ -7276,6 +7276,7 @@ static void hci_le_cis_req_evt(struct hci_dev *hdev, void *data, cis->iso_qos.ucast.cig = ev->cig_id; cis->iso_qos.ucast.cis = ev->cis_id; + iso_conn_set_codec(hdev, cis, &acl->dst); if (!(flags & HCI_PROTO_DEFER)) { hci_le_accept_cis(hdev, ev->cis_handle); @@ -7426,6 +7427,10 @@ static void hci_le_big_sync_established_evt(struct hci_dev *hdev, void *data, bis->iso_qos.bcast.in.sdu = le16_to_cpu(ev->max_pdu); if (!ev->status) { + bis->codec = conn->codec; + memcpy(bis->codec_cfg, conn->codec_cfg, + sizeof(bis->codec_cfg)); + bis->codec_cfg_len = conn->codec_cfg_len; bis->state = BT_CONNECTED; set_bit(HCI_CONN_BIG_SYNC, &bis->flags); hci_debugfs_create_conn(bis); diff --git a/net/bluetooth/iso.c b/net/bluetooth/iso.c index 58b0719847b8b3..f765f9970d8fc1 100644 --- a/net/bluetooth/iso.c +++ b/net/bluetooth/iso.c @@ -80,12 +80,21 @@ struct iso_pinfo { __u8 base_len; __u8 base[BASE_MAX_LENGTH]; struct bt_codec codec; + __u8 codec_cfg[HCI_MAX_CODEC_CFG_LEN]; + __u8 codec_cfg_len; struct iso_conn *conn; struct delayed_work timeout_work; }; static struct bt_iso_qos default_qos; +static void iso_copy_codec(struct hci_conn *hcon, const struct iso_pinfo *pi) +{ + hcon->codec = pi->codec; + memcpy(hcon->codec_cfg, pi->codec_cfg, sizeof(hcon->codec_cfg)); + hcon->codec_cfg_len = pi->codec_cfg_len; +} + static bool check_ucast_qos(struct bt_iso_qos *qos); static bool check_bcast_qos(struct bt_iso_qos *qos); static bool iso_match_sid(struct sock *sk, void *data); @@ -469,7 +478,7 @@ static int iso_connect_bis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); - hcon->codec = iso_pi(sk)->codec; + iso_copy_codec(hcon, iso_pi(sk)); conn = iso_conn_add(hcon); if (!conn) { @@ -584,7 +593,7 @@ static int iso_connect_cis(struct sock *sk) goto unlock; } - hcon->codec = iso_pi(sk)->codec; + iso_copy_codec(hcon, iso_pi(sk)); conn = iso_conn_add(hcon); if (!conn) { @@ -988,6 +997,56 @@ static void iso_sock_close(struct sock *sk) release_sock(sk); } +static int iso_parse_codec_caps(struct bt_codec *codec, size_t caps_len, + __u8 *cfg, __u8 *cfg_len) +{ + const u8 *ptr = (const u8 *)&codec[1]; + size_t rem = caps_len; + int i; + + memset(cfg, 0, HCI_MAX_CODEC_CFG_LEN); + *cfg_len = 0; + + if (!codec->num_caps) + return 0; + + for (i = 0; i < codec->num_caps; i++) { + const struct bt_codec_caps *caps; + + if (rem < sizeof(*caps)) + return -EINVAL; + + caps = (const void *)ptr; + if (rem < sizeof(*caps) + caps->len) + return -EINVAL; + + if (i == 0 && codec->id == BT_CODEC_LC3) { + const u8 *p, *end; + + if (!caps->len || caps->len > HCI_MAX_CODEC_CFG_LEN) + return -EINVAL; + + p = caps->data; + end = p + caps->len; + while (p < end) { + u8 ltv_len = p[0]; + + if (!ltv_len || p + 1 + ltv_len > end) + return -EINVAL; + p += 1 + ltv_len; + } + + memcpy(cfg, caps->data, caps->len); + *cfg_len = caps->len; + } + + ptr += sizeof(*caps) + caps->len; + rem -= sizeof(*caps) + caps->len; + } + + return 0; +} + static void iso_sock_init(struct sock *sk, struct sock *parent) { BT_DBG("sk %p", sk); @@ -996,6 +1055,9 @@ static void iso_sock_init(struct sock *sk, struct sock *parent) sk->sk_type = parent->sk_type; bt_sk(sk)->flags = bt_sk(parent)->flags; iso_pi(sk)->codec = iso_pi(parent)->codec; + memcpy(iso_pi(sk)->codec_cfg, iso_pi(parent)->codec_cfg, + sizeof(iso_pi(sk)->codec_cfg)); + iso_pi(sk)->codec_cfg_len = iso_pi(parent)->codec_cfg_len; security_sk_clone(parent, sk); } } @@ -1400,7 +1462,7 @@ static int iso_listen_bis(struct sock *sk) lockdep_assert_held(&hcon->hdev->lock); - hcon->codec = iso_pi(sk)->codec; + iso_copy_codec(hcon, iso_pi(sk)); conn = iso_conn_add(hcon); if (!conn) { @@ -1756,6 +1818,7 @@ static int iso_conn_big_sync(struct sock *sk) } if (!test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags)) { + iso_copy_codec(conn->hcon, iso_pi(sk)); err = hci_conn_big_create_sync(hdev, conn->hcon, &iso_pi(sk)->qos, iso_pi(sk)->sync_handle, @@ -2049,6 +2112,11 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; case BT_CODEC: + { + size_t caps_len; + __u8 cfg[HCI_MAX_CODEC_CFG_LEN]; + __u8 cfg_len = 0; + if (sk->sk_state != BT_OPEN && sk->sk_state != BT_BOUND && sk->sk_state != BT_LISTEN && sk->sk_state != BT_CONNECT2) { err = -EINVAL; @@ -2092,11 +2160,47 @@ static int iso_sock_setsockopt(struct socket *sock, int level, int optname, break; } + caps_len = optlen - struct_size(codecs, codecs, 1); + err = iso_parse_codec_caps(&codecs->codecs[0], caps_len, cfg, + &cfg_len); + if (err) { + hci_dev_put(hdev); + break; + } + + if (codecs->codecs[0].data_path && + codecs->codecs[0].id == BT_CODEC_LC3 && + cfg_len && hdev->get_codec_config_data) { + struct { + struct bt_codec codec; + struct bt_codec_caps caps; + __u8 data[HCI_MAX_CODEC_CFG_LEN]; + } __packed cbuf; + __u8 ven_len, *ven_data = NULL; + + memset(&cbuf, 0, sizeof(cbuf)); + cbuf.codec = codecs->codecs[0]; + cbuf.codec.num_caps = 1; + cbuf.caps.len = cfg_len; + memcpy(cbuf.data, cfg, cfg_len); + err = hdev->get_codec_config_data(hdev, CIS_LINK, + &cbuf.codec, + &ven_len, &ven_data); + kfree(ven_data); + if (err < 0) { + hci_dev_put(hdev); + break; + } + } + iso_pi(sk)->codec = codecs->codecs[0]; + memcpy(iso_pi(sk)->codec_cfg, cfg, sizeof(cfg)); + iso_pi(sk)->codec_cfg_len = cfg_len; if (iso_pi(sk)->conn && iso_pi(sk)->conn->hcon) - iso_pi(sk)->conn->hcon->codec = iso_pi(sk)->codec; + iso_copy_codec(iso_pi(sk)->conn->hcon, iso_pi(sk)); hci_dev_put(hdev); break; + } default: err = -ENOPROTOOPT; @@ -2554,6 +2658,7 @@ static void iso_conn_ready(struct iso_conn *conn) iso_pi(sk)->sync_handle = iso_pi(parent)->sync_handle; memcpy(iso_pi(sk)->base, iso_pi(parent)->base, iso_pi(parent)->base_len); iso_pi(sk)->base_len = iso_pi(parent)->base_len; + iso_copy_codec(hcon, iso_pi(sk)); hci_conn_hold(hcon); iso_chan_add(conn, sk, parent); @@ -2613,6 +2718,25 @@ static bool iso_match_sync_handle_pa_report(struct sock *sk, void *data) } /* ----- ISO interface with lower layer (HCI) ----- */ +void iso_conn_set_codec(struct hci_dev *hdev, struct hci_conn *conn, + const bdaddr_t *dst) +{ + struct sock *parent; + + parent = iso_get_sock(hdev, &hdev->bdaddr, BDADDR_ANY, BT_LISTEN, + iso_match_dst, (void *)dst); + if (!parent) + parent = iso_get_sock(hdev, &hdev->bdaddr, BDADDR_ANY, + BT_LISTEN, iso_match_dst, BDADDR_ANY); + if (!parent) + return; + + lock_sock(parent); + if (parent->sk_state == BT_LISTEN && !sock_flag(parent, SOCK_ZAPPED)) + iso_copy_codec(conn, iso_pi(parent)); + release_sock(parent); + sock_put(parent); +} int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) { @@ -2703,12 +2827,15 @@ int iso_connect_ind(struct hci_dev *hdev, bdaddr_t *bdaddr, __u8 *flags) big_sync = !test_bit(BT_SK_DEFER_SETUP, &bt_sk(sk)->flags) && !test_and_set_bit(BT_SK_BIG_SYNC, &iso_pi(sk)->flags); - if (big_sync) + if (big_sync) { + if (hcon) + iso_copy_codec(hcon, iso_pi(sk)); err = hci_conn_big_create_sync(hdev, hcon, &iso_pi(sk)->qos, iso_pi(sk)->sync_handle, iso_pi(sk)->bc_num_bis, iso_pi(sk)->bc_bis); + } release_sock(sk);