diff mbox series

usb: common: add driver for USB Billboard devices

Message ID 20240206125623.1208161-1-niklas.neronin@linux.intel.com
State New
Headers show
Series usb: common: add driver for USB Billboard devices | expand

Commit Message

Niklas Neronin Feb. 6, 2024, 12:56 p.m. UTC
This patch introduces the USB Billboard Driver. Its purpose is to display,
via debugfs, basic information about connected Billboard devices.

USB-C devices that support Alternate Modes (AUMs), such as DisplayPort
and HDMI, can expose a simple USB 2 billboard device that describes the
Alternate Modes the USB-C device supports. This enables users to see
which Alternate Modes are supported by the USB-C device, even if the
host system doesn't support them. All USB-C hosts support USB 2 devices.

The AUM information is communicated through a 'Billboard Capability
Descriptor' and one or more 'Billboard AUM Capability Descriptors'. The
values described in the aforementioned descriptors are exposed by this
driver via debugfs

The driver will create a "billboards" directory within
'/sys/kernel/debug/usb'. Each connected billboard device will have a
corresponding file added to this "billboards" directory.

Example:

$ cat /sys/kernel/debug/usb/billboards/1-1:1.0
Billboard:
iAddtionalInfoURL               USB-C ADAPTOR
bNumberOfAlternateOrUSB4Modes   1
bPreferredAlternateOrUSB4Modes  0
VCONNPower                      1W
bvdVersion                      v1.21
bAdditionalFailureInfo          0
bReserved                       0

AUM-00:
bwAlternateModesVdo             0x405
bmConfigured                    AUM configuration not attempted or exited
wSVID                           0xff01
bAlternateOrUSB4Mode            0x0
iAlternateOrUSB4ModeString      Generic

Link: https://www.usb.org/document-library/billboard-device-class-spec-revision-122-and-adopters-agreement
Signed-off-by: Niklas Neronin <niklas.neronin@linux.intel.com>
---
 drivers/usb/common/Kconfig     |  11 ++
 drivers/usb/common/Makefile    |   1 +
 drivers/usb/common/billboard.c | 334 +++++++++++++++++++++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 drivers/usb/common/billboard.c

Comments

Oliver Neukum Feb. 12, 2024, 2:16 p.m. UTC | #1
On 06.02.24 13:56, Niklas Neronin wrote:

Hi,

this part should be part of uapi regardless what
you think about the rest of the driver.
Could you make a patch for that?

	Regards
		Oliver

> +
> +/* Struct for Billboard Capability Descriptor */
> +struct usb_billboard_cap_descriptor {
> +	__u8	bLength;
> +	__u8	bDescriptorType;
> +	__u8	bDevCapabilityType;
> +	__u8	iAddtionalInfoURL;
> +	__u8	bNumberOfAlternateOrUSB4Modes;
> +	__u8	bPreferredAlternateOrUSB4Modes;
> +	__le16	VCONNPower;
> +	__u8	bmConfigured[32];
> +	__u8	bvdVersion[2];
> +	__u8	bAdditionalFailureInfo;
> +	__u8	bReserved;
> +	DECLARE_FLEX_ARRAY(struct {
> +		__le16	wSVID;
> +		__u8	bAlternateOrUSB4Mode;
> +		__u8	iAlternateOrUSB4ModeString;
> +	}, aum) __packed;
> +} __packed;
> +
> +/* Struct for Billboard AUM Capability Descriptor */
> +struct usb_billboard_aum_cap_descriptor {
> +	__u8	bLength;
> +	__u8	bDescriptorType;
> +	__u8	bDevCapabilityType;
> +	__u8	bIndex;
> +	__le32	bwAlternateModesVdo;
> +} __packed;
diff mbox series

Patch

diff --git a/drivers/usb/common/Kconfig b/drivers/usb/common/Kconfig
index b856622431a7..68f1154acf81 100644
--- a/drivers/usb/common/Kconfig
+++ b/drivers/usb/common/Kconfig
@@ -49,3 +49,14 @@  config USB_CONN_GPIO
 
 	  To compile the driver as a module, choose M here: the module will
 	  be called usb-conn-gpio.ko
+
+config USB_BILLBOARD
+	tristate "USB Billboard Driver"
+	depends on USB && DEBUG_FS
+	help
+	  Say "y" to display, via debugfs, basic information about connected
+	  USB Billboard devices.
+
+	  USB Billboard Devices communicate the Alternate Modes (AUMs) supported
+	  by a Device Container to a host system. For example, the mode could be
+	  for a DisplayPort, HDMI or any other type of data transfer.
\ No newline at end of file
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index 8ac4d21ef5c8..19fba22185a4 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -11,3 +11,4 @@  usb-common-$(CONFIG_USB_LED_TRIG) += led.o
 obj-$(CONFIG_USB_CONN_GPIO)	+= usb-conn-gpio.o
 obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
 obj-$(CONFIG_USB_ULPI_BUS)	+= ulpi.o
+obj-$(CONFIG_USB_BILLBOARD)	+= billboard.o
diff --git a/drivers/usb/common/billboard.c b/drivers/usb/common/billboard.c
new file mode 100644
index 000000000000..8be9765c39ba
--- /dev/null
+++ b/drivers/usb/common/billboard.c
@@ -0,0 +1,334 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Billboard Driver
+ *
+ * Copyright (C) 2023, Intel Corporation.
+ * Author: Niklas Neronin <niklas.neronin@linux.intel.com>
+ */
+
+#define DRIVER_DESC	"USB Billboard Driver"
+#define DRIVER_AUTHOR	"Niklas Neronin <niklas.neronin@linux.intel.com>"
+
+#include <linux/usb.h>
+#include <linux/debugfs.h>
+
+#define USB_CAP_TYPE_BILLBOARD_AUM 0x0F
+#define USB_CAP_TYPE_BILLBOARD 0x0D
+#define MAX_NUM_ALT_OR_USB4_MODE 52
+#define USB_STRING_SIZE 256
+#define LPADD (-31)
+
+#define bb_dbg(billboard, ...) dev_dbg(&(billboard->interface)->dev, __VA_ARGS__)
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_LICENSE("GPL");
+
+static struct dentry *billboard_debug_root;
+static struct usb_driver usb_billboard_driver;
+
+/* Structure to hold all of our device specific stuff */
+struct usb_billboard {
+	struct usb_device			*udev;
+	struct usb_interface			*interface;
+	struct usb_billboard_cap_descriptor	*billboard_cap_desc;
+	struct usb_billboard_aum_cap_descriptor	**aum_cap_descs;
+	unsigned char				num_aums;
+};
+
+static void billboard_free(struct usb_billboard *billboard)
+{
+	if (!billboard)
+		return;
+	usb_put_dev(billboard->udev);
+	usb_put_intf(billboard->interface);
+	kfree(billboard->aum_cap_descs);
+	kfree(billboard);
+}
+
+static struct usb_device_id billboard_id_table[] = {
+	{ USB_DEVICE_INFO(0x11, 0, 0) },
+	{ } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, billboard_id_table);
+
+/* Struct for Billboard Capability Descriptor */
+struct usb_billboard_cap_descriptor {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDevCapabilityType;
+	__u8	iAddtionalInfoURL;
+	__u8	bNumberOfAlternateOrUSB4Modes;
+	__u8	bPreferredAlternateOrUSB4Modes;
+	__le16	VCONNPower;
+	__u8	bmConfigured[32];
+	__u8	bvdVersion[2];
+	__u8	bAdditionalFailureInfo;
+	__u8	bReserved;
+	DECLARE_FLEX_ARRAY(struct {
+		__le16	wSVID;
+		__u8	bAlternateOrUSB4Mode;
+		__u8	iAlternateOrUSB4ModeString;
+	}, aum) __packed;
+} __packed;
+
+/* Struct for Billboard AUM Capability Descriptor */
+struct usb_billboard_aum_cap_descriptor {
+	__u8	bLength;
+	__u8	bDescriptorType;
+	__u8	bDevCapabilityType;
+	__u8	bIndex;
+	__le32	bwAlternateModesVdo;
+} __packed;
+
+static int billboard_show(struct seq_file *s, void *unused)
+{
+	static const char *const power_table[] = {"1", "1.5", "2", "3", "4", "5", "6"};
+	struct usb_billboard *billboard = s->private;
+	struct usb_billboard_cap_descriptor *billboard_cap = billboard->billboard_cap_desc;
+	unsigned char bitpair;
+	char usb_str[USB_STRING_SIZE];
+	int vconn;
+
+	seq_puts(s, "Billboard:\n");
+	usb_string(billboard->udev, billboard_cap->iAddtionalInfoURL, usb_str, USB_STRING_SIZE);
+	seq_printf(s, "%*s %s\n", LPADD, "iAddtionalInfoURL", usb_str);
+
+	seq_printf(s, "%*s %d\n", LPADD, "bNumberOfAlternateOrUSB4Modes",
+		   billboard_cap->bNumberOfAlternateOrUSB4Modes);
+	seq_printf(s, "%*s %d\n", LPADD, "bPreferredAlternateOrUSB4Modes",
+		   billboard_cap->bPreferredAlternateOrUSB4Modes);
+
+	seq_printf(s, "%*s ", LPADD, "VCONNPower");
+	vconn = le16_to_cpu(billboard_cap->VCONNPower);
+	if (vconn & (1 << 15))
+		seq_puts(s, "Power not required\n");
+	else if (vconn < 7)
+		seq_printf(s, "%sW\n", power_table[vconn]);
+	else
+		seq_puts(s, "Reserved\n");
+
+	seq_printf(s, "%*s v%x.%x\n", LPADD, "bvdVersion",
+		   billboard_cap->bvdVersion[1], billboard_cap->bvdVersion[0]);
+	seq_printf(s, "%*s %d\n", LPADD, "bAdditionalFailureInfo",
+		   billboard_cap->bAdditionalFailureInfo);
+	seq_printf(s, "%*s %d\n", LPADD, "bReserved", billboard_cap->bReserved);
+
+	for (int i = 0; i < billboard->num_aums; i++) {
+		seq_printf(s, "\nAUM-%02d:\n", billboard->aum_cap_descs[i]->bIndex);
+		seq_printf(s, "%*s %#x\n", LPADD, "bwAlternateModesVdo",
+			   billboard->aum_cap_descs[i]->bwAlternateModesVdo);
+
+		/* In order, each bit pair in 'bmConfigured' describes a AUM configuration. */
+		bitpair = (billboard_cap->bmConfigured[i / 4] >> (i % 4)) & 0x03;
+		seq_printf(s, "%*s ", LPADD, "bmConfigured");
+		if (bitpair == 1)
+			seq_puts(s, "AUM configuration not attempted or exited\n");
+		else if (bitpair == 2)
+			seq_puts(s, "AUM configuration attempted but unsuccessful and not entered\n");
+		else if (bitpair == 3)
+			seq_puts(s, "AUM configuration successful\n");
+		else
+			seq_puts(s, "Unspecified Error\n");
+
+		seq_printf(s, "%*s %#x\n", LPADD, "wSVID",
+			   billboard_cap->aum[i].wSVID);
+		seq_printf(s, "%*s %#x\n", LPADD, "bAlternateOrUSB4Mode",
+			   billboard_cap->aum[i].bAlternateOrUSB4Mode);
+
+		usb_string(billboard->udev, billboard_cap->aum[i].iAlternateOrUSB4ModeString,
+			   usb_str, USB_STRING_SIZE);
+		seq_printf(s, "%*s %s\n", LPADD, "iAlternateOrUSB4ModeString", usb_str);
+	}
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(billboard);
+
+static int get_billboard_capability_descriptor(struct usb_billboard *billboard,
+					       struct usb_bos_descriptor *bos)
+{
+	struct usb_billboard_cap_descriptor *desc;
+	unsigned char *ptr = (unsigned char *)bos + bos->bLength;
+
+	for (int i = 0; i < bos->bNumDeviceCaps; i++) {
+		desc = (struct usb_billboard_cap_descriptor *)ptr;
+
+		if (desc->bDescriptorType != USB_DT_DEVICE_CAPABILITY)
+			goto skip_to_next_descriptor;
+
+		if (desc->bDevCapabilityType != USB_CAP_TYPE_BILLBOARD)
+			goto skip_to_next_descriptor;
+
+		if (desc->bLength < 48) {
+			bb_dbg(billboard, "incorrect billboard capability descriptor length");
+			goto descriptor_error;
+		}
+
+		if (!desc->bNumberOfAlternateOrUSB4Modes ||
+		    desc->bNumberOfAlternateOrUSB4Modes > MAX_NUM_ALT_OR_USB4_MODE) {
+			bb_dbg(billboard, "incorrect amount of AUMs");
+			goto descriptor_error;
+		}
+
+		if (desc->bLength != desc->bNumberOfAlternateOrUSB4Modes * 4 + 44) {
+			bb_dbg(billboard, "incorrect length of billboard AUM descriptors");
+			goto descriptor_error;
+		}
+
+		billboard->billboard_cap_desc = desc;
+		return 0;
+
+skip_to_next_descriptor:
+		ptr += desc->bLength;
+	}
+
+descriptor_error:
+	return -1;
+}
+
+static int get_billboard_aum_capability_descriptors(struct usb_billboard *billboard,
+						    struct usb_bos_descriptor *bos)
+{
+	struct usb_billboard_aum_cap_descriptor *desc;
+	unsigned char *ptr = (unsigned char *)bos + bos->bLength;
+	unsigned char total = 0;
+
+	for (int i = 0; i < bos->bNumDeviceCaps; i++) {
+		desc = (struct usb_billboard_aum_cap_descriptor *)ptr;
+
+		if (desc->bDescriptorType != USB_DT_DEVICE_CAPABILITY)
+			goto skip_to_next_descriptor;
+
+		if (desc->bDevCapabilityType != USB_CAP_TYPE_BILLBOARD_AUM)
+			goto skip_to_next_descriptor;
+
+		if (desc->bLength != 8) {
+			bb_dbg(billboard, "incorrect length of AUM capability descriptor");
+			goto descriptor_error;
+		}
+
+		if (desc->bIndex >= billboard->num_aums) {
+			bb_dbg(billboard, "incorrect index of AUM capability descriptor");
+			goto descriptor_error;
+		}
+
+		billboard->aum_cap_descs[desc->bIndex] = desc;
+		if (++total == billboard->num_aums)
+			return 0;
+
+skip_to_next_descriptor:
+		ptr += desc->bLength;
+	}
+
+descriptor_error:
+	return -1;
+}
+
+static int probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+	struct usb_billboard *billboard;
+	struct usb_device *udev;
+	int ret = -ENODEV;
+
+	udev = interface_to_usbdev(interface);
+	if (le16_to_cpu(udev->descriptor.bcdUSB) < 0x201 || !udev->bos)
+		return ret;
+
+	billboard = kzalloc(sizeof(*billboard), GFP_KERNEL);
+	if (!billboard) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	billboard->udev = usb_get_dev(udev);
+	billboard->interface = usb_get_intf(interface);
+
+	if (get_billboard_capability_descriptor(billboard, udev->bos->desc))
+		goto err;
+
+	billboard->num_aums = billboard->billboard_cap_desc->bNumberOfAlternateOrUSB4Modes;
+
+	billboard->aum_cap_descs = kcalloc(billboard->num_aums, sizeof(*billboard->aum_cap_descs),
+					   GFP_KERNEL);
+	if (!billboard->aum_cap_descs) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	if (get_billboard_aum_capability_descriptors(billboard, udev->bos->desc))
+		goto err;
+
+	usb_set_intfdata(interface, billboard);
+
+	debugfs_create_file(dev_name(&interface->dev), 0444, billboard_debug_root, billboard,
+			    &billboard_fops);
+
+	dev_info(&interface->dev, "device successfully attached\n");
+	return 0;
+
+err:
+	billboard_free(billboard);
+	return ret;
+}
+
+static void disconnect(struct usb_interface *interface)
+{
+	debugfs_lookup_and_remove(dev_name(&(interface)->dev), billboard_debug_root);
+	billboard_free(usb_get_intfdata(interface));
+}
+
+static int suspend(struct usb_interface *interface, pm_message_t message) { return 0; }
+static int resume(struct usb_interface *interface) { return 0; }
+
+static int reset_resume(struct usb_interface *interface)
+{
+	struct usb_billboard *billboard = usb_get_intfdata(interface);
+	struct usb_device *udev = interface_to_usbdev(interface);
+
+	if (!udev->bos)
+		return -ENODEV;
+
+	if (get_billboard_capability_descriptor(billboard, udev->bos->desc))
+		return -ENODEV;
+
+	if (billboard->num_aums != billboard->billboard_cap_desc->bNumberOfAlternateOrUSB4Modes) {
+		bb_dbg(billboard, "amount of AUMs changed");
+		return -ENODEV;
+	}
+
+	if (get_billboard_aum_capability_descriptors(billboard, udev->bos->desc))
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct usb_driver usb_billboard_driver = {
+	.name		= "billboard",
+	.probe		= probe,
+	.disconnect	= disconnect,
+	.suspend	= pm_ptr(suspend),
+	.resume		= pm_ptr(resume),
+	.reset_resume	= pm_ptr(reset_resume),
+	.id_table	= billboard_id_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init billboard_init(void)
+{
+	int ret;
+
+	billboard_debug_root = debugfs_create_dir("billboards", usb_debug_root);
+	ret = usb_register(&usb_billboard_driver);
+	if (ret < 0)
+		debugfs_remove(billboard_debug_root);
+
+	return ret;
+}
+module_init(billboard_init);
+
+static void __exit billboard_exit(void)
+{
+	usb_deregister(&usb_billboard_driver);
+	debugfs_remove(billboard_debug_root);
+}
+module_exit(billboard_exit);