[PATCH 01/12] usb: xhci: expose xhci extended capabilities via debugfs

From: Lu Baolu
Date: Wed Oct 28 2015 - 04:00:58 EST


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-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/