| From 2aa1bbf85ad29ebccec2176db2b2e5c751c71f9a Mon Sep 17 00:00:00 2001 |
| From: Joseph Hwang <josephsih@chromium.org> |
| Date: Fri, 12 Nov 2021 23:20:52 +0800 |
| Subject: [PATCH] CHROMIUM: Bluetooth: surface AOSP quality report through mgmt |
| |
| When receiving a HCI vendor event, the kernel checks if it is an |
| AOSP bluetooth quality report. If yes, the event is sent to bluez |
| user space through the mgmt socket. |
| |
| BUG=b:203035611,b:208921096,b:242124116 |
| TEST=Run an audio a2dp or hfp test and check logs. |
| |
| Signed-off-by: Joseph Hwang <josephsih@chromium.org> |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/3281850 |
| Reviewed-by: Archie Pusaka <apusaka@chromium.org> |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/3554311 |
| Change-Id: I2015b42d2d0a502334c9c3a2983438b89716d4f0 |
| Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/kernel/+/3823820 |
| --- |
| include/net/bluetooth/hci_core.h | 2 ++ |
| include/net/bluetooth/mgmt.h | 7 ++++ |
| net/bluetooth/aosp.c | 61 ++++++++++++++++++++++++++++++++ |
| net/bluetooth/aosp.h | 12 +++++++ |
| net/bluetooth/hci_event.c | 32 ++++++++++++++++- |
| net/bluetooth/mgmt.c | 22 ++++++++++++ |
| 6 files changed, 135 insertions(+), 1 deletion(-) |
| |
| diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h |
| index 652422dad75bc302126628b887ab2e4878c35a13..50733362c2c227af923838897435793f0cf00018 100644 |
| --- a/include/net/bluetooth/hci_core.h |
| +++ b/include/net/bluetooth/hci_core.h |
| @@ -2120,6 +2120,8 @@ void mgmt_adv_monitor_removed(struct hci_dev *hdev, u16 handle); |
| int mgmt_phy_configuration_changed(struct hci_dev *hdev, struct sock *skip); |
| void mgmt_adv_monitor_device_lost(struct hci_dev *hdev, u16 handle, |
| bdaddr_t *bdaddr, u8 addr_type); |
| +int mgmt_quality_report(struct hci_dev *hdev, struct sk_buff *skb, |
| + u8 quality_spec); |
| |
| int hci_abort_conn(struct hci_conn *conn, u8 reason); |
| u8 hci_le_conn_update(struct hci_conn *conn, u16 min, u16 max, u16 latency, |
| diff --git a/include/net/bluetooth/mgmt.h b/include/net/bluetooth/mgmt.h |
| index 7363c9ec392159277025776c8dc6b2ca3dd2c13d..a1c49162e8a82b18c90299fe64f5e4079beb054c 100644 |
| --- a/include/net/bluetooth/mgmt.h |
| +++ b/include/net/bluetooth/mgmt.h |
| @@ -1209,3 +1209,10 @@ struct mgmt_ev_mesh_device_found { |
| struct mgmt_ev_mesh_pkt_cmplt { |
| __u8 handle; |
| } __packed; |
| + |
| +#define MGMT_EV_QUALITY_REPORT 0x0033 |
| +struct mgmt_ev_quality_report { |
| + __u8 quality_spec; |
| + __u8 data_len; |
| + __u8 data[0]; |
| +} __packed; |
| diff --git a/net/bluetooth/aosp.c b/net/bluetooth/aosp.c |
| index 1d67836e95e16935bcfd52abd2ba6e33c2eb6e89..90d7baa5395bbe99b8f594bcb278d60ba7663e59 100644 |
| --- a/net/bluetooth/aosp.c |
| +++ b/net/bluetooth/aosp.c |
| @@ -208,3 +208,64 @@ int aosp_set_quality_report(struct hci_dev *hdev, bool enable) |
| else |
| return disable_quality_report(hdev); |
| } |
| + |
| +#define BLUETOOTH_QUALITY_REPORT_EV 0x58 |
| +struct bqr_data { |
| + __u8 quality_report_id; |
| + __u8 packet_type; |
| + __le16 conn_handle; |
| + __u8 conn_role; |
| + __s8 tx_power_level; |
| + __s8 rssi; |
| + __u8 snr; |
| + __u8 unused_afh_channel_count; |
| + __u8 afh_select_unideal_channel_count; |
| + __le16 lsto; |
| + __le32 conn_piconet_clock; |
| + __le32 retransmission_count; |
| + __le32 no_rx_count; |
| + __le32 nak_count; |
| + __le32 last_tx_ack_timestamp; |
| + __le32 flow_off_count; |
| + __le32 last_flow_on_timestamp; |
| + __le32 buffer_overflow_bytes; |
| + __le32 buffer_underflow_bytes; |
| + |
| + /* Vendor Specific Parameter */ |
| + __u8 vsp[0]; |
| +} __packed; |
| + |
| +struct aosp_hci_vs_data { |
| + __u8 code; |
| + __u8 data[0]; |
| +} __packed; |
| + |
| +bool aosp_is_quality_report_evt(struct sk_buff *skb) |
| +{ |
| + struct aosp_hci_vs_data *ev; |
| + |
| + if (skb->len < sizeof(struct aosp_hci_vs_data)) |
| + return false; |
| + |
| + ev = (struct aosp_hci_vs_data *)skb->data; |
| + |
| + return ev->code == BLUETOOTH_QUALITY_REPORT_EV; |
| +} |
| + |
| +bool aosp_pull_quality_report_data(struct sk_buff *skb) |
| +{ |
| + size_t bqr_data_len = sizeof(struct bqr_data); |
| + |
| + skb_pull(skb, sizeof(struct aosp_hci_vs_data)); |
| + |
| + /* skb->len is allowed to be larger than bqr_data_len to have |
| + * the Vendor Specific Parameter (vsp) field. |
| + */ |
| + if (skb->len < bqr_data_len) { |
| + BT_ERR("AOSP evt data len %d too short (%u expected)", |
| + skb->len, bqr_data_len); |
| + return false; |
| + } |
| + |
| + return true; |
| +} |
| diff --git a/net/bluetooth/aosp.h b/net/bluetooth/aosp.h |
| index 2fd8886d51b26a9fbffa09a626c5cad501e8b781..49894a9956472df90c3b115ef3fd53d886752d17 100644 |
| --- a/net/bluetooth/aosp.h |
| +++ b/net/bluetooth/aosp.h |
| @@ -10,6 +10,8 @@ void aosp_do_close(struct hci_dev *hdev); |
| |
| bool aosp_has_quality_report(struct hci_dev *hdev); |
| int aosp_set_quality_report(struct hci_dev *hdev, bool enable); |
| +bool aosp_is_quality_report_evt(struct sk_buff *skb); |
| +bool aosp_pull_quality_report_data(struct sk_buff *skb); |
| |
| #else |
| |
| @@ -26,4 +28,14 @@ static inline int aosp_set_quality_report(struct hci_dev *hdev, bool enable) |
| return -EOPNOTSUPP; |
| } |
| |
| +static inline bool aosp_is_quality_report_evt(struct sk_buff *skb) |
| +{ |
| + return false; |
| +} |
| + |
| +static inline bool aosp_pull_quality_report_data(struct sk_buff *skb) |
| +{ |
| + return false; |
| +} |
| + |
| #endif |
| diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c |
| index 3913fe1915f2dd2eba01e9c2c9e8cd2bbd3dd8ec..7078d5f15d826d4fa5e2ebde8e15cb99752ee1af 100644 |
| --- a/net/bluetooth/hci_event.c |
| +++ b/net/bluetooth/hci_event.c |
| @@ -34,6 +34,7 @@ |
| #include "hci_debugfs.h" |
| #include "a2mp.h" |
| #include "amp.h" |
| +#include "aosp.h" |
| #include "smp.h" |
| #include "msft.h" |
| #include "eir.h" |
| @@ -5754,6 +5755,35 @@ static void hci_disconn_phylink_complete_evt(struct hci_dev *hdev, void *data, |
| } |
| #endif |
| |
| +#define QUALITY_SPEC_NA 0x0 |
| +#define QUALITY_SPEC_INTEL_TELEMETRY 0x1 |
| +#define QUALITY_SPEC_AOSP_BQR 0x2 |
| + |
| +static bool quality_report_evt(struct hci_dev *hdev, struct sk_buff *skb) |
| +{ |
| + if (aosp_is_quality_report_evt(skb)) { |
| + if (aosp_has_quality_report(hdev) && |
| + aosp_pull_quality_report_data(skb)) |
| + mgmt_quality_report(hdev, skb, QUALITY_SPEC_AOSP_BQR); |
| + |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +static void hci_vendor_evt(struct hci_dev *hdev, void *data, |
| + struct sk_buff *skb) |
| +{ |
| + /* Every specification must have a well-defined condition |
| + * to determine if an event meets the specification. |
| + * The skb is consumed by a specification only if the event |
| + * meets the specification. |
| + */ |
| + if (!quality_report_evt(hdev, skb)) |
| + msft_vendor_evt(hdev, data, skb); |
| +} |
| + |
| static void le_conn_update_addr(struct hci_conn *conn, bdaddr_t *bdaddr, |
| u8 bdaddr_type, bdaddr_t *local_rpa) |
| { |
| @@ -7451,7 +7481,7 @@ static const struct hci_ev { |
| HCI_EV(HCI_EV_NUM_COMP_BLOCKS, hci_num_comp_blocks_evt, |
| sizeof(struct hci_ev_num_comp_blocks)), |
| /* [0xff = HCI_EV_VENDOR] */ |
| - HCI_EV_VL(HCI_EV_VENDOR, msft_vendor_evt, 0, HCI_MAX_EVENT_SIZE), |
| + HCI_EV_VL(HCI_EV_VENDOR, hci_vendor_evt, 0, HCI_MAX_EVENT_SIZE), |
| }; |
| |
| static void hci_event_func(struct hci_dev *hdev, u8 event, struct sk_buff *skb, |
| diff --git a/net/bluetooth/mgmt.c b/net/bluetooth/mgmt.c |
| index f55350e99dda5e576535238d51fc914d7286430f..7b142b3eb8dc31b50d8a23c07669710ef00f6769 100644 |
| --- a/net/bluetooth/mgmt.c |
| +++ b/net/bluetooth/mgmt.c |
| @@ -5041,6 +5041,28 @@ static u32 get_params_flags(struct hci_dev *hdev, |
| return flags; |
| } |
| |
| +int mgmt_quality_report(struct hci_dev *hdev, struct sk_buff *skb, |
| + u8 quality_spec) |
| +{ |
| + struct mgmt_ev_quality_report *ev; |
| + size_t ev_len; |
| + int err; |
| + |
| + /* The ev comes with a variable-length data field. */ |
| + ev_len = sizeof(*ev) + skb->len; |
| + ev = kmalloc(ev_len, GFP_KERNEL); |
| + if (!ev) |
| + return -ENOMEM; |
| + |
| + ev->quality_spec = quality_spec; |
| + ev->data_len = skb->len; |
| + memcpy(ev->data, skb->data, skb->len); |
| + err = mgmt_event(MGMT_EV_QUALITY_REPORT, hdev, ev, ev_len, NULL); |
| + kfree(ev); |
| + |
| + return err; |
| +} |
| + |
| static int get_device_flags(struct sock *sk, struct hci_dev *hdev, void *data, |
| u16 data_len) |
| { |
| -- |
| 2.38.1.584.g0f3c55d4c2-goog |
| |