@@ -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
@@ -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
new file mode 100644
@@ -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);
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