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...@linux.intel.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to