| From 37aec884e7e99e27628d113f3bf42e5b01a707f5 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 |
| |
| [rebase61(tzungbi): |
| Squashed: |
| FIXUP: CHROMIUM: Bluetooth: surface AOSP quality report through mgmt |
| ] |
| Signed-off-by: Tzung-Bi Shih <tzungbi@chromium.org> |
| --- |
| 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 829d7f24b439ed8d226e32a8bb05590c344cb2fc..301d86716bd661c32b8e9f1c4244097474adbc76 100644 |
| --- a/include/net/bluetooth/hci_core.h |
| +++ b/include/net/bluetooth/hci_core.h |
| @@ -2360,6 +2360,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 612eec66ebe123e938f9ea00f0fcab14fa2b9de2..cefef87f6bdd05abc219967d8b24d2e5ceee6970 100644 |
| --- a/include/net/bluetooth/mgmt.h |
| +++ b/include/net/bluetooth/mgmt.h |
| @@ -1214,3 +1214,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..2454c1228377fb58bd9e425b1d060a1460460d13 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 %u too short (%zu 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 7d7ea893d4a870cb7792476f6e996790a3e693ee..bd54192073528576901e20c278ba8b455735503c 100644 |
| --- a/net/bluetooth/hci_event.c |
| +++ b/net/bluetooth/hci_event.c |
| @@ -36,6 +36,7 @@ |
| #include "hci_request.h" |
| #include "hci_debugfs.h" |
| #include "hci_codec.h" |
| +#include "aosp.h" |
| #include "smp.h" |
| #include "msft.h" |
| #include "eir.h" |
| @@ -5572,6 +5573,35 @@ static void hci_remote_oob_data_request_evt(struct hci_dev *hdev, void *edata, |
| hci_dev_unlock(hdev); |
| } |
| |
| +#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) |
| { |
| @@ -7396,7 +7426,7 @@ static const struct hci_ev { |
| HCI_EV_REQ_VL(HCI_EV_LE_META, hci_le_meta_evt, |
| sizeof(struct hci_ev_le_meta), HCI_MAX_EVENT_SIZE), |
| /* [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 a7fd629da0612f891806125b3b5ed1904343b653..ed63338cfc8bb75e606803ac2a607a7c598b9b40 100644 |
| --- a/net/bluetooth/mgmt.c |
| +++ b/net/bluetooth/mgmt.c |
| @@ -4988,6 +4988,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.45.2.803.g4e1b14247a-goog |
| |