xhci: dbc: Provide sysfs option to configure dbc descriptors
authorMathias Nyman <mathias.nyman@linux.intel.com>
Fri, 17 Mar 2023 15:47:09 +0000 (17:47 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 23 Mar 2023 16:25:22 +0000 (17:25 +0100)
When DbC is enabled the first port on the xHC host acts as a usb device.
xHC provides the descriptors automatically when the DbC device is
enumerated. Most of the values are hardcoded, but some fields such as
idProduct, idVendor, bcdDevice and bInterfaceProtocol can be modified.

Add sysfs entries that allow userspace to change these.
User can only change them before dbc is enabled, i.e. before writing
"enable" to dbc sysfs file as we don't want these values to change while
device is connected, or during  enumeration.

Add documentation for these entries in
Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Link: https://lore.kernel.org/r/20230317154715.535523-9-mathias.nyman@linux.intel.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Documentation/ABI/testing/sysfs-bus-pci-drivers-xhci_hcd
drivers/usb/host/xhci-dbgcap.c
drivers/usb/host/xhci-dbgcap.h

index 0088aba..5a775b8 100644 (file)
@@ -23,3 +23,55 @@ Description:
                Reading this attribute gives the state of the DbC. It
                can be one of the following states: disabled, enabled,
                initialized, connected, configured and stalled.
+
+What:          /sys/bus/pci/drivers/xhci_hcd/.../dbc_idVendor
+Date:          March 2023
+Contact:       Mathias Nyman <mathias.nyman@linux.intel.com>
+Description:
+               This dbc_idVendor attribute lets us change the idVendor field
+               presented in the USB device descriptor by this xhci debug
+               device.
+               Value can only be changed while debug capability (DbC) is in
+               disabled state to prevent USB device descriptor change while
+               connected to a USB host.
+               The default value is 0x1d6b (Linux Foundation).
+               It can be any 16-bit integer.
+
+What:          /sys/bus/pci/drivers/xhci_hcd/.../dbc_idProduct
+Date:          March 2023
+Contact:       Mathias Nyman <mathias.nyman@linux.intel.com>
+Description:
+               This dbc_idProduct attribute lets us change the idProduct field
+               presented in the USB device descriptor by this xhci debug
+               device.
+               Value can only be changed while debug capability (DbC) is in
+               disabled state to prevent USB device descriptor change while
+               connected to a USB host.
+               The default value is 0x0010. It can be any 16-bit integer.
+
+What:          /sys/bus/pci/drivers/xhci_hcd/.../dbc_bcdDevice
+Date:          March 2023
+Contact:       Mathias Nyman <mathias.nyman@linux.intel.com>
+Description:
+               This dbc_bcdDevice attribute lets us change the bcdDevice field
+               presented in the USB device descriptor by this xhci debug
+               device.
+               Value can only be changed while debug capability (DbC) is in
+               disabled state to prevent USB device descriptor change while
+               connected to a USB host.
+               The default value is 0x0010. (device rev 0.10)
+               It can be any 16-bit integer.
+
+What:          /sys/bus/pci/drivers/xhci_hcd/.../dbc_bInterfaceProtocol
+Date:          March 2023
+Contact:       Mathias Nyman <mathias.nyman@linux.intel.com>
+Description:
+               This attribute lets us change the bInterfaceProtocol field
+               presented in the USB Interface descriptor by the xhci debug
+               device.
+               Value can only be changed while debug capability (DbC) is in
+               disabled state to prevent USB descriptor change while
+               connected to a USB host.
+               The default value is 1  (GNU Remote Debug command).
+               Other permissible value is 0 which is for vendor defined debug
+               target.
index f1367b5..b40d923 100644 (file)
@@ -124,10 +124,10 @@ static void xhci_dbc_init_contexts(struct xhci_dbc *dbc, u32 string_length)
        /* Set DbC context and info registers: */
        lo_hi_writeq(dbc->ctx->dma, &dbc->regs->dccp);
 
-       dev_info = cpu_to_le32((DBC_VENDOR_ID << 16) | DBC_PROTOCOL);
+       dev_info = (dbc->idVendor << 16) | dbc->bInterfaceProtocol;
        writel(dev_info, &dbc->regs->devinfo1);
 
-       dev_info = cpu_to_le32((DBC_DEVICE_REV << 16) | DBC_PRODUCT_ID);
+       dev_info = (dbc->bcdDevice << 16) | dbc->idProduct;
        writel(dev_info, &dbc->regs->devinfo2);
 }
 
@@ -971,7 +971,186 @@ static ssize_t dbc_store(struct device *dev,
        return count;
 }
 
+static ssize_t dbc_idVendor_show(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct xhci_dbc         *dbc;
+       struct xhci_hcd         *xhci;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+
+       return sprintf(buf, "%04x\n", dbc->idVendor);
+}
+
+static ssize_t dbc_idVendor_store(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t size)
+{
+       struct xhci_dbc         *dbc;
+       struct xhci_hcd         *xhci;
+       void __iomem            *ptr;
+       u16                     value;
+       u32                     dev_info;
+
+       if (kstrtou16(buf, 0, &value))
+               return -EINVAL;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+       if (dbc->state != DS_DISABLED)
+               return -EBUSY;
+
+       dbc->idVendor = value;
+       ptr = &dbc->regs->devinfo1;
+       dev_info = readl(ptr);
+       dev_info = (dev_info & ~(0xffffu << 16)) | (value << 16);
+       writel(dev_info, ptr);
+
+       return size;
+}
+
+static ssize_t dbc_idProduct_show(struct device *dev,
+                           struct device_attribute *attr,
+                           char *buf)
+{
+       struct xhci_dbc         *dbc;
+       struct xhci_hcd         *xhci;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+
+       return sprintf(buf, "%04x\n", dbc->idProduct);
+}
+
+static ssize_t dbc_idProduct_store(struct device *dev,
+                            struct device_attribute *attr,
+                            const char *buf, size_t size)
+{
+       struct xhci_dbc         *dbc;
+       struct xhci_hcd         *xhci;
+       void __iomem            *ptr;
+       u32                     dev_info;
+       u16                     value;
+
+       if (kstrtou16(buf, 0, &value))
+               return -EINVAL;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+       if (dbc->state != DS_DISABLED)
+               return -EBUSY;
+
+       dbc->idProduct = value;
+       ptr = &dbc->regs->devinfo2;
+       dev_info = readl(ptr);
+       dev_info = (dev_info & ~(0xffffu)) | value;
+       writel(dev_info, ptr);
+       return size;
+}
+
+static ssize_t dbc_bcdDevice_show(struct device *dev,
+                                  struct device_attribute *attr,
+                                  char *buf)
+{
+       struct xhci_dbc *dbc;
+       struct xhci_hcd *xhci;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+
+       return sprintf(buf, "%04x\n", dbc->bcdDevice);
+}
+
+static ssize_t dbc_bcdDevice_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t size)
+{
+       struct xhci_dbc *dbc;
+       struct xhci_hcd *xhci;
+       void __iomem *ptr;
+       u32 dev_info;
+       u16 value;
+
+       if (kstrtou16(buf, 0, &value))
+               return -EINVAL;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+       if (dbc->state != DS_DISABLED)
+               return -EBUSY;
+
+       dbc->bcdDevice = value;
+       ptr = &dbc->regs->devinfo2;
+       dev_info = readl(ptr);
+       dev_info = (dev_info & ~(0xffffu << 16)) | (value << 16);
+       writel(dev_info, ptr);
+
+       return size;
+}
+
+static ssize_t dbc_bInterfaceProtocol_show(struct device *dev,
+                                struct device_attribute *attr,
+                                char *buf)
+{
+       struct xhci_dbc *dbc;
+       struct xhci_hcd *xhci;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+
+       return sprintf(buf, "%02x\n", dbc->bInterfaceProtocol);
+}
+
+static ssize_t dbc_bInterfaceProtocol_store(struct device *dev,
+                                 struct device_attribute *attr,
+                                 const char *buf, size_t size)
+{
+       struct xhci_dbc *dbc;
+       struct xhci_hcd *xhci;
+       void __iomem *ptr;
+       u32 dev_info;
+       u8 value;
+       int ret;
+
+       /* bInterfaceProtocol is 8 bit, but xhci only supports values 0 and 1 */
+       ret = kstrtou8(buf, 0, &value);
+       if (ret || value > 1)
+               return -EINVAL;
+
+       xhci = hcd_to_xhci(dev_get_drvdata(dev));
+       dbc = xhci->dbc;
+       if (dbc->state != DS_DISABLED)
+               return -EBUSY;
+
+       dbc->bInterfaceProtocol = value;
+       ptr = &dbc->regs->devinfo1;
+       dev_info = readl(ptr);
+       dev_info = (dev_info & ~(0xffu)) | value;
+       writel(dev_info, ptr);
+
+       return size;
+}
+
 static DEVICE_ATTR_RW(dbc);
+static DEVICE_ATTR_RW(dbc_idVendor);
+static DEVICE_ATTR_RW(dbc_idProduct);
+static DEVICE_ATTR_RW(dbc_bcdDevice);
+static DEVICE_ATTR_RW(dbc_bInterfaceProtocol);
+
+static struct attribute *dbc_dev_attributes[] = {
+       &dev_attr_dbc.attr,
+       &dev_attr_dbc_idVendor.attr,
+       &dev_attr_dbc_idProduct.attr,
+       &dev_attr_dbc_bcdDevice.attr,
+       &dev_attr_dbc_bInterfaceProtocol.attr,
+       NULL
+};
+
+static const struct attribute_group dbc_dev_attrib_grp = {
+       .attrs = dbc_dev_attributes,
+};
 
 struct xhci_dbc *
 xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *driver)
@@ -986,6 +1165,10 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *
        dbc->regs = base;
        dbc->dev = dev;
        dbc->driver = driver;
+       dbc->idProduct = DBC_PRODUCT_ID;
+       dbc->idVendor = DBC_VENDOR_ID;
+       dbc->bcdDevice = DBC_DEVICE_REV;
+       dbc->bInterfaceProtocol = DBC_PROTOCOL;
 
        if (readl(&dbc->regs->control) & DBC_CTRL_DBC_ENABLE)
                goto err;
@@ -993,7 +1176,7 @@ xhci_alloc_dbc(struct device *dev, void __iomem *base, const struct dbc_driver *
        INIT_DELAYED_WORK(&dbc->event_work, xhci_dbc_handle_events);
        spin_lock_init(&dbc->lock);
 
-       ret = device_create_file(dev, &dev_attr_dbc);
+       ret = sysfs_create_group(&dev->kobj, &dbc_dev_attrib_grp);
        if (ret)
                goto err;
 
@@ -1012,7 +1195,7 @@ void xhci_dbc_remove(struct xhci_dbc *dbc)
        xhci_dbc_stop(dbc);
 
        /* remove sysfs files */
-       device_remove_file(dbc->dev, &dev_attr_dbc);
+       sysfs_remove_group(&dbc->dev->kobj, &dbc_dev_attrib_grp);
 
        kfree(dbc);
 }
index ca04192..51a7ab3 100644 (file)
@@ -132,6 +132,10 @@ struct xhci_dbc {
        struct dbc_str_descs            *string;
        dma_addr_t                      string_dma;
        size_t                          string_size;
+       u16                             idVendor;
+       u16                             idProduct;
+       u16                             bcdDevice;
+       u8                              bInterfaceProtocol;
 
        enum dbc_state                  state;
        struct delayed_work             event_work;