diff mbox series

usb: common: add driver for USB Billboard devices

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

Commit Message

Neronin, Niklas 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

Greg KH Feb. 6, 2024, 2:47 p.m. UTC | #1
On Tue, Feb 06, 2024 at 02:56:23PM +0200, Niklas Neronin wrote:
> This patch introduces the USB Billboard Driver. Its purpose is to display,
> via debugfs, basic information about connected Billboard devices.

Very cool, I was wondering if/when someone was going to write a kernel
driver for this type of hardware.

But why debugfs?  Normally that is locked down for root-access-only by
the system (rightfully so), why is this information restricted?

And why is this a kernel driver at all?  Why can't you just do this in
userspace and add support to 'lsusb' for it?

> 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

Looks like lsusb output, so again, why is this needed in the kernel and
only accessable by root users?

> 
> 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>

I'm not going to actually review this patch, as you didn't follow the
rules that all Intel kernel developers need to follow, sorry.  Please
work with your Linux kernel team to do this correctly, otherwise I have
can't do anything with it even if I did want to merge it :(

thanks,

greg k-h
Heikki Krogerus Feb. 6, 2024, 3:31 p.m. UTC | #2
On Tue, Feb 06, 2024 at 02:47:04PM +0000, Greg KH wrote:
> On Tue, Feb 06, 2024 at 02:56:23PM +0200, Niklas Neronin wrote:
> > This patch introduces the USB Billboard Driver. Its purpose is to display,
> > via debugfs, basic information about connected Billboard devices.
> 
> Very cool, I was wondering if/when someone was going to write a kernel
> driver for this type of hardware.
> 
> But why debugfs?  Normally that is locked down for root-access-only by
> the system (rightfully so), why is this information restricted?
> 
> And why is this a kernel driver at all?  Why can't you just do this in
> userspace and add support to 'lsusb' for it?

I'm to blame for that. I wanted a way to see the billboard information
when something goes wrong with the alt mode entry in an environment
where I don't necessarily have tools like lsusb - I think I need to
include usbtools package to my Buildroot to get that app. I also
proposed debugfs, because for me this would be purely for debugging
purposes.

Later I was hoping to use this information in the Type-C drivers to
help in situations where the alt mode entry fails and UCSI does not
give any information about the partner (which unfortunately is the
reality on several platforms).

This is really just a proposal - perhaps we should have started with
RFC first. But I think Niklas has done a great job in any case.


thanks,
Mathias Nyman Feb. 7, 2024, 7:48 a.m. UTC | #3
> I'm not going to actually review this patch, as you didn't follow the
> rules that all Intel kernel developers need to follow, sorry.  Please
> work with your Linux kernel team to do this correctly, otherwise I have
> can't do anything with it even if I did want to merge it :(
> 

Sorry about this.

I should at least have added my tags as we did do internal review rounds
of this patch, and told Niklas that it now looks upstream ready and can be
submitted.

Thanks
Mathias
Greg KH Feb. 7, 2024, 9:46 a.m. UTC | #4
On Tue, Feb 06, 2024 at 05:31:07PM +0200, Heikki Krogerus wrote:
> On Tue, Feb 06, 2024 at 02:47:04PM +0000, Greg KH wrote:
> > On Tue, Feb 06, 2024 at 02:56:23PM +0200, Niklas Neronin wrote:
> > > This patch introduces the USB Billboard Driver. Its purpose is to display,
> > > via debugfs, basic information about connected Billboard devices.
> > 
> > Very cool, I was wondering if/when someone was going to write a kernel
> > driver for this type of hardware.
> > 
> > But why debugfs?  Normally that is locked down for root-access-only by
> > the system (rightfully so), why is this information restricted?
> > 
> > And why is this a kernel driver at all?  Why can't you just do this in
> > userspace and add support to 'lsusb' for it?
> 
> I'm to blame for that. I wanted a way to see the billboard information
> when something goes wrong with the alt mode entry in an environment
> where I don't necessarily have tools like lsusb - I think I need to
> include usbtools package to my Buildroot to get that app. I also
> proposed debugfs, because for me this would be purely for debugging
> purposes.

But you are also going to want this info in lsusb for all of the
non-root users, so why not just do it in one place?

> Later I was hoping to use this information in the Type-C drivers to
> help in situations where the alt mode entry fails and UCSI does not
> give any information about the partner (which unfortunately is the
> reality on several platforms).

Sure, but this is just debugfs, no interaction with any other kernel
code at the moment, so we have no hint anyone else might want it :(

> This is really just a proposal - perhaps we should have started with
> RFC first. But I think Niklas has done a great job in any case.

RFC might have been nice :)

Anyway, patches for lsusb are gladly accepted, let's keep this out of
debugfs for now as again, almost no one has access to it.  But if you do
want it in debugfs, please fix up the code and resubmit it with some
more justification.

thanks,

greg k-h
Neronin, Niklas Feb. 7, 2024, 10:51 a.m. UTC | #5
> I'm not going to actually review this patch, as you didn't follow the
> rules that all Intel kernel developers need to follow, sorry.  Please
> work with your Linux kernel team to do this correctly, otherwise I have
> can't do anything with it even if I did want to merge it :(
> 

Thank you for the feedback and apologies for my mistakes.
I will make sure that subsequent patches are up to code.

Thanks,
Niklas
Oliver Neukum Feb. 12, 2024, 2:16 p.m. UTC | #6
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;
Neronin, Niklas Feb. 13, 2024, 8:06 a.m. UTC | #7
On 12/02/2024 16.16, Oliver Neukum wrote:
> 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
> 

Sure, I'll create a patch.

Thanks,
Niklas

>> +
>> +/* 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);