linux-bluetooth.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 untested] Bluetooth: Add framework for Microsoft vendor extension
@ 2020-03-26  9:05 Marcel Holtmann
  2020-03-27  0:06 ` Miao-chen Chou
  0 siblings, 1 reply; 2+ messages in thread
From: Marcel Holtmann @ 2020-03-26  9:05 UTC (permalink / raw)
  To: linux-bluetooth

From: Miao-chen Chou <mcchou@chromium.org>

Micrsoft defined a set for HCI vendor extensions. Check the following
link for details:

https://docs.microsoft.com/en-us/windows-hardware/drivers/bluetooth/microsoft-defined-bluetooth-hci-commands-and-events

This provides the basic framework to enable the extension and read its
supported features. Drivers still have to declare support for this
extension before it can be utilized by the host stack.

Signed-off-by: Miao-chen Chou <mcchou@chromium.org>
Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
---
 include/net/bluetooth/hci_core.h |   5 ++
 net/bluetooth/Kconfig            |   7 ++
 net/bluetooth/Makefile           |   1 +
 net/bluetooth/hci_core.c         |   5 ++
 net/bluetooth/hci_event.c        |   5 ++
 net/bluetooth/msft.c             | 150 +++++++++++++++++++++++++++++++
 net/bluetooth/msft.h             |  20 +++++
 7 files changed, 193 insertions(+)
 create mode 100644 net/bluetooth/msft.c
 create mode 100644 net/bluetooth/msft.h

diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
index d4e28773d378..59ddcd3a52cc 100644
--- a/include/net/bluetooth/hci_core.h
+++ b/include/net/bluetooth/hci_core.h
@@ -484,6 +484,11 @@ struct hci_dev {
 	struct led_trigger	*power_led;
 #endif
 
+#if IS_ENABLED(CONFIG_BT_MSFTEXT)
+	__u16			msft_opcode;
+	void			*msft_data;
+#endif
+
 	int (*open)(struct hci_dev *hdev);
 	int (*close)(struct hci_dev *hdev);
 	int (*flush)(struct hci_dev *hdev);
diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig
index 165148c7c4ce..d439be5c534e 100644
--- a/net/bluetooth/Kconfig
+++ b/net/bluetooth/Kconfig
@@ -93,6 +93,13 @@ config BT_LEDS
 	  This option selects a few LED triggers for different
 	  Bluetooth events.
 
+config BT_MSFTEXT
+	bool "Enable Microsoft extensions"
+	depends on BT
+	help
+	  This options enables support for the Microsoft defined HCI
+	  vendor extensions.
+
 config BT_SELFTEST
 	bool "Bluetooth self testing support"
 	depends on BT && DEBUG_KERNEL
diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
index fda41c0b4781..41dd541a44a5 100644
--- a/net/bluetooth/Makefile
+++ b/net/bluetooth/Makefile
@@ -19,5 +19,6 @@ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
 bluetooth-$(CONFIG_BT_BREDR) += sco.o
 bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o
 bluetooth-$(CONFIG_BT_LEDS) += leds.o
+bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o
 bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o
 bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o
diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
index 2e7bc2da8371..09625e2cb02a 100644
--- a/net/bluetooth/hci_core.c
+++ b/net/bluetooth/hci_core.c
@@ -44,6 +44,7 @@
 #include "hci_debugfs.h"
 #include "smp.h"
 #include "leds.h"
+#include "msft.h"
 
 static void hci_rx_work(struct work_struct *work);
 static void hci_cmd_work(struct work_struct *work);
@@ -1563,6 +1564,8 @@ static int hci_dev_do_open(struct hci_dev *hdev)
 	    hci_dev_test_flag(hdev, HCI_VENDOR_DIAG) && hdev->set_diag)
 		ret = hdev->set_diag(hdev, true);
 
+	msft_do_open(hdev);
+
 	clear_bit(HCI_INIT, &hdev->flags);
 
 	if (!ret) {
@@ -1758,6 +1761,8 @@ int hci_dev_do_close(struct hci_dev *hdev)
 
 	hci_sock_dev_event(hdev, HCI_DEV_DOWN);
 
+	msft_do_close(hdev);
+
 	if (hdev->flush)
 		hdev->flush(hdev);
 
diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
index 0a591be8b0ae..ed2a96f1ef55 100644
--- a/net/bluetooth/hci_event.c
+++ b/net/bluetooth/hci_event.c
@@ -35,6 +35,7 @@
 #include "a2mp.h"
 #include "amp.h"
 #include "smp.h"
+#include "msft.h"
 
 #define ZERO_KEY "\x00\x00\x00\x00\x00\x00\x00\x00" \
 		 "\x00\x00\x00\x00\x00\x00\x00\x00"
@@ -6145,6 +6146,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
 		hci_num_comp_blocks_evt(hdev, skb);
 		break;
 
+	case HCI_EV_VENDOR:
+		msft_vendor_evt(hdev, skb);
+		break;
+
 	default:
 		BT_DBG("%s event 0x%2.2x", hdev->name, event);
 		break;
diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c
new file mode 100644
index 000000000000..6f2af20507a1
--- /dev/null
+++ b/net/bluetooth/msft.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 Google Corporation
+ */
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "msft.h"
+
+#define MSFT_OP_READ_SUPPORTED_FEATURES		0x00
+struct msft_cp_read_supported_features {
+	__u8   sub_opcode;
+} __packed;
+struct msft_rp_read_supported_features {
+	__u8   status;
+	__u8   sub_opcode;
+	__le64 features;
+	__u8   evt_prefix_len;
+	__u8   evt_prefix[0];
+} __packed;
+
+struct msft_data {
+	__u64 features;
+	__u8  evt_prefix_len;
+	__u8  *evt_prefix;
+};
+
+void msft_set_opcode(struct hci_dev *hdev, __u16 opcode)
+{
+	hdev->msft_opcode = opcode;
+
+	bt_dev_info(hdev, "Enabling MSFT extensions with opcode 0x%2.2x",
+		    hdev->msft_opcode);
+}
+
+static bool read_supported_features(struct hci_dev *hdev)
+{
+	struct msft_data *msft = hdev->msft_data;
+	struct msft_cp_read_supported_features cp;
+	struct msft_rp_read_supported_features *rp;
+	struct sk_buff *skb;
+
+	cp.sub_opcode = MSFT_OP_READ_SUPPORTED_FEATURES;
+
+	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 supported features (%ld)",
+			   PTR_ERR(skb));
+		return false;
+	}
+
+	if (skb->len < sizeof(*rp)) {
+		bt_dev_err(hdev, "MSFT supported features length mismatch");
+		goto failed;
+	}
+
+	rp = (struct msft_rp_read_supported_features *)skb->data;
+
+	if (rp->sub_opcode != MSFT_OP_READ_SUPPORTED_FEATURES)
+		goto failed;
+
+	if (rp->evt_prefix_len > 0) {
+		msft->evt_prefix = kmemdup(rp->evt_prefix, rp->evt_prefix_len,
+					   GFP_KERNEL);
+		if (!msft->evt_prefix)
+			goto failed;
+	}
+
+	msft->evt_prefix_len = rp->evt_prefix_len;
+	msft->features = __le64_to_cpu(rp->features);
+	kfree_skb(skb);
+
+	bt_dev_info(hdev, "MSFT supported features %llx", msft->features);
+	return true;
+
+failed:
+	kfree_skb(skb);
+	return false;
+}
+
+void msft_do_open(struct hci_dev *hdev)
+{
+	struct msft_data *msft;
+
+	if (hdev->msft_opcode == HCI_OP_NOP)
+		return;
+
+	bt_dev_dbg(hdev, "Initialize MSFT extension");
+
+	msft = kzalloc(sizeof(*msft), GFP_KERNEL);
+	if (!msft)
+		return;
+
+	if (!read_supported_features(hdev)) {
+		kfree(msft);
+		return;
+	}
+
+	hdev->msft_data = msft;
+}
+
+void msft_do_close(struct hci_dev *hdev)
+{
+	struct msft_data *msft = hdev->msft_data;
+
+	if (!msft)
+		return;
+
+	bt_dev_dbg(hdev, "Cleanup of MSFT extension");
+
+	hdev->msft_data = NULL;
+
+	kfree(msft->evt_prefix);
+	kfree(msft);
+}
+
+void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb)
+{
+	struct msft_data *msft = hdev->msft_data;
+	u8 event;
+
+	if (!msft)
+		return;
+
+	/* When the extension has defined an event prefix, check that it
+	 * matches, and otherwise just return.
+	 */
+	if (msft->evt_prefix_len > 0) {
+		if (skb->len < msft->evt_prefix_len)
+			return;
+
+		if (memcmp(skb->data, msft->evt_prefix, msft->evt_prefix_len))
+			return;
+
+		skb_pull(skb, msft->evt_prefix_len);
+	}
+
+	/* Every event starts at least with an event code and the rest of
+	 * the data is variable and depends on the event code.
+	 */
+	if (skb->len < 1)
+		return;
+
+	event = *skb->data;
+	skb_pull(skb, 1);
+
+	bt_dev_dbg(hdev, "MSFT vendor event %u", event);
+}
diff --git a/net/bluetooth/msft.h b/net/bluetooth/msft.h
new file mode 100644
index 000000000000..f3cf404457a8
--- /dev/null
+++ b/net/bluetooth/msft.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2020 Google Corporation
+ */
+
+#if IS_ENABLED(CONFIG_BT_MSFTEXT)
+
+void msft_set_opcode(struct hci_dev *hdev, __u16 opcode);
+void msft_do_open(struct hci_dev *hdev);
+void msft_do_close(struct hci_dev *hdev);
+void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb);
+
+#else
+
+static inline void msft_set_opcode(struct hci_dev *hdev, __u16 opcode) {}
+static inline void msft_do_open(struct hci_dev *hdev) {}
+static inline void msft_do_close(struct hci_dev *hdev) {}
+static inline void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb) {}
+
+#endif
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* Re: [PATCH v2 untested] Bluetooth: Add framework for Microsoft vendor extension
  2020-03-26  9:05 [PATCH v2 untested] Bluetooth: Add framework for Microsoft vendor extension Marcel Holtmann
@ 2020-03-27  0:06 ` Miao-chen Chou
  0 siblings, 0 replies; 2+ messages in thread
From: Miao-chen Chou @ 2020-03-27  0:06 UTC (permalink / raw)
  To: Marcel Holtmann; +Cc: Bluetooth Kernel Mailing List

On Thu, Mar 26, 2020 at 2:05 AM Marcel Holtmann <marcel@holtmann.org> wrote:
>
> From: Miao-chen Chou <mcchou@chromium.org>
>
> Micrsoft defined a set for HCI vendor extensions. Check the following
> link for details:
>
> https://docs.microsoft.com/en-us/windows-hardware/drivers/bluetooth/microsoft-defined-bluetooth-hci-commands-and-events
>
> This provides the basic framework to enable the extension and read its
> supported features. Drivers still have to declare support for this
> extension before it can be utilized by the host stack.
>
> Signed-off-by: Miao-chen Chou <mcchou@chromium.org>
> Signed-off-by: Marcel Holtmann <marcel@holtmann.org>
> ---
>  include/net/bluetooth/hci_core.h |   5 ++
>  net/bluetooth/Kconfig            |   7 ++
>  net/bluetooth/Makefile           |   1 +
>  net/bluetooth/hci_core.c         |   5 ++
>  net/bluetooth/hci_event.c        |   5 ++
>  net/bluetooth/msft.c             | 150 +++++++++++++++++++++++++++++++
>  net/bluetooth/msft.h             |  20 +++++
>  7 files changed, 193 insertions(+)
>  create mode 100644 net/bluetooth/msft.c
>  create mode 100644 net/bluetooth/msft.h
>
> diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h
> index d4e28773d378..59ddcd3a52cc 100644
> --- a/include/net/bluetooth/hci_core.h
> +++ b/include/net/bluetooth/hci_core.h
> @@ -484,6 +484,11 @@ struct hci_dev {
>         struct led_trigger      *power_led;
>  #endif
>
> +#if IS_ENABLED(CONFIG_BT_MSFTEXT)
> +       __u16                   msft_opcode;
> +       void                    *msft_data;
> +#endif
> +
>         int (*open)(struct hci_dev *hdev);
>         int (*close)(struct hci_dev *hdev);
>         int (*flush)(struct hci_dev *hdev);
> diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig
> index 165148c7c4ce..d439be5c534e 100644
> --- a/net/bluetooth/Kconfig
> +++ b/net/bluetooth/Kconfig
> @@ -93,6 +93,13 @@ config BT_LEDS
>           This option selects a few LED triggers for different
>           Bluetooth events.
>
> +config BT_MSFTEXT
> +       bool "Enable Microsoft extensions"
> +       depends on BT
> +       help
> +         This options enables support for the Microsoft defined HCI
> +         vendor extensions.
> +
>  config BT_SELFTEST
>         bool "Bluetooth self testing support"
>         depends on BT && DEBUG_KERNEL
> diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile
> index fda41c0b4781..41dd541a44a5 100644
> --- a/net/bluetooth/Makefile
> +++ b/net/bluetooth/Makefile
> @@ -19,5 +19,6 @@ bluetooth-y := af_bluetooth.o hci_core.o hci_conn.o hci_event.o mgmt.o \
>  bluetooth-$(CONFIG_BT_BREDR) += sco.o
>  bluetooth-$(CONFIG_BT_HS) += a2mp.o amp.o
>  bluetooth-$(CONFIG_BT_LEDS) += leds.o
> +bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o
>  bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o
>  bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o
> diff --git a/net/bluetooth/hci_core.c b/net/bluetooth/hci_core.c
> index 2e7bc2da8371..09625e2cb02a 100644
> --- a/net/bluetooth/hci_core.c
> +++ b/net/bluetooth/hci_core.c
> @@ -44,6 +44,7 @@
>  #include "hci_debugfs.h"
>  #include "smp.h"
>  #include "leds.h"
> +#include "msft.h"
>
>  static void hci_rx_work(struct work_struct *work);
>  static void hci_cmd_work(struct work_struct *work);
> @@ -1563,6 +1564,8 @@ static int hci_dev_do_open(struct hci_dev *hdev)
>             hci_dev_test_flag(hdev, HCI_VENDOR_DIAG) && hdev->set_diag)
>                 ret = hdev->set_diag(hdev, true);
>
> +       msft_do_open(hdev);
> +
>         clear_bit(HCI_INIT, &hdev->flags);
>
>         if (!ret) {
> @@ -1758,6 +1761,8 @@ int hci_dev_do_close(struct hci_dev *hdev)
>
>         hci_sock_dev_event(hdev, HCI_DEV_DOWN);
>
> +       msft_do_close(hdev);
> +
>         if (hdev->flush)
>                 hdev->flush(hdev);
>
> diff --git a/net/bluetooth/hci_event.c b/net/bluetooth/hci_event.c
> index 0a591be8b0ae..ed2a96f1ef55 100644
> --- a/net/bluetooth/hci_event.c
> +++ b/net/bluetooth/hci_event.c
> @@ -35,6 +35,7 @@
>  #include "a2mp.h"
>  #include "amp.h"
>  #include "smp.h"
> +#include "msft.h"
>
>  #define ZERO_KEY "\x00\x00\x00\x00\x00\x00\x00\x00" \
>                  "\x00\x00\x00\x00\x00\x00\x00\x00"
> @@ -6145,6 +6146,10 @@ void hci_event_packet(struct hci_dev *hdev, struct sk_buff *skb)
>                 hci_num_comp_blocks_evt(hdev, skb);
>                 break;
>
> +       case HCI_EV_VENDOR:
> +               msft_vendor_evt(hdev, skb);
> +               break;
> +
>         default:
>                 BT_DBG("%s event 0x%2.2x", hdev->name, event);
>                 break;
> diff --git a/net/bluetooth/msft.c b/net/bluetooth/msft.c
> new file mode 100644
> index 000000000000..6f2af20507a1
> --- /dev/null
> +++ b/net/bluetooth/msft.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2020 Google Corporation
> + */
> +
> +#include <net/bluetooth/bluetooth.h>
> +#include <net/bluetooth/hci_core.h>
> +
> +#include "msft.h"
> +
> +#define MSFT_OP_READ_SUPPORTED_FEATURES                0x00
> +struct msft_cp_read_supported_features {
> +       __u8   sub_opcode;
> +} __packed;
> +struct msft_rp_read_supported_features {
> +       __u8   status;
> +       __u8   sub_opcode;
> +       __le64 features;
> +       __u8   evt_prefix_len;
> +       __u8   evt_prefix[0];
> +} __packed;
> +
> +struct msft_data {
> +       __u64 features;
> +       __u8  evt_prefix_len;
> +       __u8  *evt_prefix;
> +};
> +
> +void msft_set_opcode(struct hci_dev *hdev, __u16 opcode)
> +{
> +       hdev->msft_opcode = opcode;
> +
> +       bt_dev_info(hdev, "Enabling MSFT extensions with opcode 0x%2.2x",
> +                   hdev->msft_opcode);
> +}
> +
> +static bool read_supported_features(struct hci_dev *hdev)
> +{
> +       struct msft_data *msft = hdev->msft_data;
> +       struct msft_cp_read_supported_features cp;
> +       struct msft_rp_read_supported_features *rp;
> +       struct sk_buff *skb;
> +
> +       cp.sub_opcode = MSFT_OP_READ_SUPPORTED_FEATURES;
> +
> +       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 supported features (%ld)",
> +                          PTR_ERR(skb));
> +               return false;
> +       }
> +
> +       if (skb->len < sizeof(*rp)) {
> +               bt_dev_err(hdev, "MSFT supported features length mismatch");
> +               goto failed;
> +       }
> +
> +       rp = (struct msft_rp_read_supported_features *)skb->data;
> +
> +       if (rp->sub_opcode != MSFT_OP_READ_SUPPORTED_FEATURES)
> +               goto failed;
> +
> +       if (rp->evt_prefix_len > 0) {
> +               msft->evt_prefix = kmemdup(rp->evt_prefix, rp->evt_prefix_len,
> +                                          GFP_KERNEL);
> +               if (!msft->evt_prefix)
> +                       goto failed;
> +       }
> +
> +       msft->evt_prefix_len = rp->evt_prefix_len;
> +       msft->features = __le64_to_cpu(rp->features);
> +       kfree_skb(skb);
> +
> +       bt_dev_info(hdev, "MSFT supported features %llx", msft->features);
> +       return true;
> +
> +failed:
> +       kfree_skb(skb);
> +       return false;
> +}
> +
> +void msft_do_open(struct hci_dev *hdev)
> +{
> +       struct msft_data *msft;
> +
> +       if (hdev->msft_opcode == HCI_OP_NOP)
> +               return;
> +
> +       bt_dev_dbg(hdev, "Initialize MSFT extension");
> +
> +       msft = kzalloc(sizeof(*msft), GFP_KERNEL);
> +       if (!msft)
> +               return;
> +
> +       if (!read_supported_features(hdev)) {
> +               kfree(msft);
> +               return;
> +       }
> +
> +       hdev->msft_data = msft;
> +}
> +
> +void msft_do_close(struct hci_dev *hdev)
> +{
> +       struct msft_data *msft = hdev->msft_data;
> +
> +       if (!msft)
> +               return;
> +
> +       bt_dev_dbg(hdev, "Cleanup of MSFT extension");
> +
> +       hdev->msft_data = NULL;
> +
> +       kfree(msft->evt_prefix);
> +       kfree(msft);
> +}
> +
> +void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb)
> +{
> +       struct msft_data *msft = hdev->msft_data;
> +       u8 event;
> +
> +       if (!msft)
> +               return;
> +
> +       /* When the extension has defined an event prefix, check that it
> +        * matches, and otherwise just return.
> +        */
> +       if (msft->evt_prefix_len > 0) {
> +               if (skb->len < msft->evt_prefix_len)
> +                       return;
> +
> +               if (memcmp(skb->data, msft->evt_prefix, msft->evt_prefix_len))
> +                       return;
> +
> +               skb_pull(skb, msft->evt_prefix_len);
> +       }
> +
> +       /* Every event starts at least with an event code and the rest of
> +        * the data is variable and depends on the event code.
> +        */
> +       if (skb->len < 1)
> +               return;
> +
> +       event = *skb->data;
> +       skb_pull(skb, 1);
> +
> +       bt_dev_dbg(hdev, "MSFT vendor event %u", event);
> +}
> diff --git a/net/bluetooth/msft.h b/net/bluetooth/msft.h
> new file mode 100644
> index 000000000000..f3cf404457a8
> --- /dev/null
> +++ b/net/bluetooth/msft.h
> @@ -0,0 +1,20 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2020 Google Corporation
> + */
> +
> +#if IS_ENABLED(CONFIG_BT_MSFTEXT)
> +
> +void msft_set_opcode(struct hci_dev *hdev, __u16 opcode);
> +void msft_do_open(struct hci_dev *hdev);
> +void msft_do_close(struct hci_dev *hdev);
> +void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb);
> +
> +#else
> +
> +static inline void msft_set_opcode(struct hci_dev *hdev, __u16 opcode) {}
> +static inline void msft_do_open(struct hci_dev *hdev) {}
> +static inline void msft_do_close(struct hci_dev *hdev) {}
> +static inline void msft_vendor_evt(struct hci_dev *hdev, struct sk_buff *skb) {}
> +
> +#endif
> --
> 2.25.1
>
Thanks for spending time brainstorming the framework. Somehow I
received email only from your example patches v1 but not v2. I will
upload v4 shortly based on v2.

Regards,
Miao

^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2020-03-27  0:07 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-03-26  9:05 [PATCH v2 untested] Bluetooth: Add framework for Microsoft vendor extension Marcel Holtmann
2020-03-27  0:06 ` Miao-chen Chou

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).