The xHCI host exports xHCI-specific extended capabilities utilizing a method similar to PCI extended capabilities. In many cases, users want to know whether a specific extended capability is supported by a host. Unfortunately, currently there's no existing mechanisms in the kernel to do this. This patch exposes extended capabilities supported by the xHCI host via debugfs. Signed-off-by: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx> --- drivers/usb/host/xhci-dbg.c | 212 +++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-ext-caps.h | 9 +- drivers/usb/host/xhci.c | 27 ++++- drivers/usb/host/xhci.h | 10 ++ 4 files changed, 256 insertions(+), 2 deletions(-) diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index 74c42f7..d3dcfed 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -20,6 +20,11 @@ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#include <linux/vmalloc.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/usb.h> + #include "xhci.h" #define XHCI_INIT_VALUE 0x0 @@ -612,3 +617,210 @@ void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), va_end(args); } EXPORT_SYMBOL_GPL(xhci_dbg_trace); + +#ifdef CONFIG_DEBUG_FS +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); + struct usb_bus *bus; + struct mutex mutex; + size_t count; + char *output_buf; + size_t alloc_size; +}; + +static const char *get_extcap_desc(u32 cap_id) +{ + switch (cap_id) { + case XHCI_EXT_CAPS_LEGACY: + return "USB Legacy Support"; + case XHCI_EXT_CAPS_PROTOCOL: + return "Supported Protocol"; + case XHCI_EXT_CAPS_PM: + return "Extended Power Management"; + case XHCI_EXT_CAPS_VIRT: + return "I/O Virtualization (xHCI-IOV)"; + case XHCI_EXT_CAPS_ROUTE: + return "Message Interrupt"; + case XHCI_EXT_CAPS_LOCALMEM: + return "Local Memory"; + case XHCI_EXT_CAPS_DEBUG: + return "USB Debug Capability"; + default: + if (XHCI_EXT_CAPS_VENDOR(XHCI_EXT_CAPS_ID(cap_id))) + return "Vendor Defined"; + else + return "Unknown"; + } +} + +static ssize_t fill_extcap_buffer(struct debug_buffer *buf) +{ + __le32 __iomem *addr; + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + u32 offset, cap_id; + char *next; + int size, temp; + unsigned long flags; + int time_to_leave; + + hcd = bus_to_hcd(buf->bus); + xhci = hcd_to_xhci(hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave(&xhci->lock, flags); + + addr = &xhci->cap_regs->hcc_params; + offset = XHCI_HCC_EXT_CAPS(readl(addr)); + if (!HCD_HW_ACCESSIBLE(hcd) || !offset) { + size = scnprintf(next, size, + "bus %s, device %s\n%s\nNo extended capabilities\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc); + goto done; + } + + temp = scnprintf(next, size, "@addr(virt)\t\tCAP_ID\tDescription\n"); + size -= temp; + next += temp; + + addr = &xhci->cap_regs->hc_capbase + offset; + time_to_leave = XHCI_EXT_MAX_CAPID; + while (time_to_leave--) { + cap_id = readl(addr); + temp = scnprintf(next, size, "@%p\t%02x\t%s\n", + addr, XHCI_EXT_CAPS_ID(cap_id), + get_extcap_desc(XHCI_EXT_CAPS_ID(cap_id))); + size -= temp; + next += temp; + + offset = XHCI_EXT_CAPS_NEXT(cap_id); + if (!offset) + break; + addr += offset; + } + +done: + spin_unlock_irqrestore(&xhci->lock, flags); + + return buf->alloc_size - size; +} + +static struct debug_buffer *buffer_init(struct usb_bus *bus, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + if (!buf) + return NULL; + + buf->bus = bus; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret; + + if (buf->output_buf) + return -EINVAL; + + buf->alloc_size = PAGE_SIZE; + buf->output_buf = vmalloc(buf->alloc_size); + + if (!buf->output_buf) + return -ENOMEM; + + ret = buf->fill_func(buf); + if (ret < 0) + return ret; + + buf->count = ret; + + return 0; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (!buf->count) { + ret = fill_buffer(buf); + if (ret) { + mutex_unlock(&buf->mutex); + return ret; + } + } + mutex_unlock(&buf->mutex); + + return simple_read_from_buffer(user_buf, len, offset, + buf->output_buf, buf->count); +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + vfree(buf->output_buf); + kfree(buf); + } + + return 0; +} + +static int debug_extcap_open(struct inode *inode, struct file *file) +{ + file->private_data = buffer_init(inode->i_private, + fill_extcap_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static const struct file_operations debug_extcap_fops = { + .owner = THIS_MODULE, + .open = debug_extcap_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +struct dentry *xhci_debug_root; + +void xhci_create_debug_files(struct xhci_hcd *xhci) +{ + struct usb_bus *bus = &xhci_to_hcd(xhci)->self; + struct dentry *entry; + + if (!xhci_debug_root) + return; + + entry = debugfs_create_dir(bus->bus_name, xhci_debug_root); + if (!entry || IS_ERR(entry)) { + xhci_info(xhci, "failed to create debug dir"); + return; + } + xhci->debug_dir = entry; + + if (!debugfs_create_file("extcap", S_IRUGO, + xhci->debug_dir, bus, + &debug_extcap_fops)) + xhci_info(xhci, "failed to create extcap debug file"); +} + +void xhci_remove_debug_files(struct xhci_hcd *xhci) +{ + debugfs_remove_recursive(xhci->debug_dir); + xhci->debug_dir = NULL; +} + +#endif /* CONFIG_DEBUG_FS */ diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h index 9fe3225..e233c90 100644 --- a/drivers/usb/host/xhci-ext-caps.h +++ b/drivers/usb/host/xhci-ext-caps.h @@ -49,8 +49,15 @@ #define XHCI_EXT_CAPS_PM 3 #define XHCI_EXT_CAPS_VIRT 4 #define XHCI_EXT_CAPS_ROUTE 5 -/* IDs 6-9 reserved */ +#define XHCI_EXT_CAPS_LOCALMEM 6 +/* IDs 7-9 reserved */ #define XHCI_EXT_CAPS_DEBUG 10 +/* IDs 192-255 vendor specific */ +#define XHCI_EXT_CAPS_VEN_START 192 +#define XHCI_EXT_CAPS_VEN_END 255 +#define XHCI_EXT_CAPS_VENDOR(p) (((p) >= XHCI_EXT_CAPS_VEN_START) && \ + ((p) <= XHCI_EXT_CAPS_VEN_END)) +#define XHCI_EXT_MAX_CAPID XHCI_EXT_CAPS_VEN_END /* USB Legacy Support Capability - section 7.1.1 */ #define XHCI_HC_BIOS_OWNED (1 << 16) #define XHCI_HC_OS_OWNED (1 << 24) diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 6e7dc6f..ddcb4b7 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -28,6 +28,7 @@ #include <linux/slab.h> #include <linux/dmi.h> #include <linux/dma-mapping.h> +#include <linux/debugfs.h> #include "xhci.h" #include "xhci-trace.h" @@ -651,6 +652,11 @@ int xhci_run(struct usb_hcd *hcd) } xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Finished xhci_run for USB2 roothub"); + +#ifdef CONFIG_DEBUG_FS + xhci_create_debug_files(xhci); +#endif + return 0; } EXPORT_SYMBOL_GPL(xhci_run); @@ -669,6 +675,10 @@ void xhci_stop(struct usb_hcd *hcd) u32 temp; struct xhci_hcd *xhci = hcd_to_xhci(hcd); +#ifdef CONFIG_DEBUG_FS + xhci_remove_debug_files(xhci); +#endif + if (xhci->xhc_state & XHCI_STATE_HALTED) return; @@ -5041,6 +5051,15 @@ static int __init xhci_hcd_init(void) BUILD_BUG_ON(sizeof(struct xhci_intr_reg) != 8*32/8); /* xhci_run_regs has eight fields and embeds 128 xhci_intr_regs */ BUILD_BUG_ON(sizeof(struct xhci_run_regs) != (8+8*128)*32/8); + +#ifdef CONFIG_DEBUG_FS + xhci_debug_root = debugfs_create_dir("xhci", usb_debug_root); + if (!xhci_debug_root || IS_ERR(xhci_debug_root)) { + debugfs_remove(xhci_debug_root); + xhci_debug_root = NULL; + } +#endif + return 0; } @@ -5048,7 +5067,13 @@ static int __init xhci_hcd_init(void) * If an init function is provided, an exit function must also be provided * to allow module unload. */ -static void __exit xhci_hcd_fini(void) { } +static void __exit xhci_hcd_fini(void) +{ +#ifdef CONFIG_DEBUG_FS + debugfs_remove(xhci_debug_root); + xhci_debug_root = NULL; +#endif +} module_init(xhci_hcd_init); module_exit(xhci_hcd_fini); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index be9048e..dc3a5f8 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1657,6 +1657,10 @@ struct xhci_hcd { u32 port_status_u0; /* Compliance Mode Timer Triggered every 2 seconds */ #define COMP_MODE_RCVRY_MSECS 2000 + /* debug files */ +#ifdef CONFIG_DEBUG_FS + struct dentry *debug_dir; +#endif /* CONFIG_DEBUG_FS */ }; /* Platform specific overrides to generic XHCI hc_driver ops */ @@ -1743,6 +1747,12 @@ void xhci_dbg_ep_rings(struct xhci_hcd *xhci, void xhci_dbg_trace(struct xhci_hcd *xhci, void (*trace)(struct va_format *), const char *fmt, ...); +#ifdef CONFIG_DEBUG_FS +extern struct dentry *xhci_debug_root; +void xhci_create_debug_files(struct xhci_hcd *xhci); +void xhci_remove_debug_files(struct xhci_hcd *xhci); +#endif /* CONFIG_DEBUG_FS */ + /* xHCI memory management */ void xhci_mem_cleanup(struct xhci_hcd *xhci); int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags); -- 2.1.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html