xhci: dbc: Provide sysfs option to configure dbc descriptors

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>
This commit is contained in:
Mathias Nyman 2023-03-17 17:47:09 +02:00 committed by Greg Kroah-Hartman
parent 4feb07d0ad
commit edf1664f32
3 changed files with 243 additions and 4 deletions

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

View 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);
}

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