xHCI debug capability (DbC) is an optional functionality provided
by an xHCI host controller. Software learns this capability by
walking through the extended capability list in mmio of the host.
This patch introduces the code to probe and initialize the debug
capability hardware during early boot. With hardware initialization
done, the debug target (system under debug which has DbC enabled)
will present a debug device through the debug port. The debug device
is fully compliant with the USB framework and provides the equivalent
of a very high performance (USB3) full-duplex serial link between the
debug host and target.
Signed-off-by: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
---
MAINTAINERS | 7 +
arch/x86/Kconfig.debug | 12 +
drivers/usb/early/Makefile | 1 +
drivers/usb/early/xhci-dbc.c | 774 +++++++++++++++++++++++++++++++++++++++++++
include/linux/usb/xhci-dbc.h | 187 +++++++++++
5 files changed, 981 insertions(+)
create mode 100644 drivers/usb/early/xhci-dbc.c
create mode 100644 include/linux/usb/xhci-dbc.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 30aca4a..e6d7076 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -11505,6 +11505,13 @@ S: Supported
F: drivers/usb/host/xhci*
F: drivers/usb/host/pci-quirks*
+USB XHCI DEBUG PORT
+M: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
+L: linux-usb@xxxxxxxxxxxxxxx
+S: Supported
+F: drivers/usb/early/xhci-dbc.c
+F: include/linux/usb/xhci-dbc.h
+
USB ZD1201 DRIVER
L: linux-wireless@xxxxxxxxxxxxxxx
W: http://linux-lc100020.sourceforge.net
diff --git a/arch/x86/Kconfig.debug b/arch/x86/Kconfig.debug
index 9b18ed9..ba60cb1 100644
--- a/arch/x86/Kconfig.debug
+++ b/arch/x86/Kconfig.debug
@@ -48,6 +48,18 @@ config EARLY_PRINTK_EFI
This is useful for kernel debugging when your machine crashes very
early before the console code is initialized.
+config EARLY_PRINTK_XDBC
+ bool "Early printk via xHCI debug port"
+ depends on EARLY_PRINTK && PCI
+ ---help---
+ Write kernel log output directly into the xHCI debug port.
+
+ This is useful for kernel debugging when your machine crashes very
+ early before the console code is initialized. For normal operation
+ it is not recommended because it looks ugly and doesn't cooperate
+ with klogd/syslogd or the X server. You should normally N here,
+ unless you want to debug such a crash.
+
config X86_PTDUMP_CORE
def_bool n
diff --git a/drivers/usb/early/Makefile b/drivers/usb/early/Makefile
index 24bbe51..2db5906 100644
--- a/drivers/usb/early/Makefile
+++ b/drivers/usb/early/Makefile
@@ -3,3 +3,4 @@
#
obj-$(CONFIG_EARLY_PRINTK_DBGP) += ehci-dbgp.o
+obj-$(CONFIG_EARLY_PRINTK_XDBC) += xhci-dbc.o
diff --git a/drivers/usb/early/xhci-dbc.c b/drivers/usb/early/xhci-dbc.c
new file mode 100644
index 0000000..254a0a8
--- /dev/null
+++ b/drivers/usb/early/xhci-dbc.c
@@ -0,0 +1,774 @@
+/**
+ * xhci-dbc.c - xHCI debug capability driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * Author: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
+ * Some code shared with EHCI debug port and xHCI driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#include <linux/pci_regs.h>
+#include <linux/pci_ids.h>
+#include <linux/bootmem.h>
+#include <linux/io.h>
+#include <asm/pci-direct.h>
+#include <asm/fixmap.h>
+#include <linux/bcd.h>
+#include <linux/export.h>
+#include <linux/version.h>
+#include <linux/usb/xhci-dbc.h>
+
+#include "../host/xhci.h"
+
+#define XDBC_PROTOCOL 1 /* GNU Remote Debug Command Set */
+#define XDBC_VENDOR_ID 0x1d6b /* Linux Foundation 0x1d6b */
+#define XDBC_PRODUCT_ID 0x0004 /* __le16 idProduct; device 0004 */
+#define XDBC_DEVICE_REV 0x0010 /* 0.10 */
+
+static struct xdbc_state xdbc_stat;
+static struct xdbc_state *xdbcp = &xdbc_stat;
+
+
+#else
+static inline void xdbc_trace(const char *fmt, ...) { }
+static inline void xdbc_dbg_dump_regs(char *str) { }
+static inline void xdbc_dbg_dump_data(char *str) { }
+#endif /* DBC_DEBUG */
+
+/*
+ * FIXME: kernel provided delay interfaces, like usleep, isn't ready yet
+ * at the time DbC gets initialized. Below implementation is only
+ * for x86 platform. Need to reconsider this when porting it onto
+ * other architectures.
+ */
+static inline void xdbc_udelay(int us)
+{
+ while (us-- > 0)
+ outb(0x1, 0x80);
+}
+
+static void __iomem *xdbc_map_pci_mmio(u32 bus,
+ u32 dev, u32 func, u8 bar, size_t *length)
+{
+ u32 val, sz;
+ u64 val64, sz64, mask64;
+ u8 byte;
+ unsigned long idx, max_idx;
+ void __iomem *base;
+
+ val = read_pci_config(bus, dev, func, bar);
+ write_pci_config(bus, dev, func, bar, ~0);
+ sz = read_pci_config(bus, dev, func, bar);
+ write_pci_config(bus, dev, func, bar, val);
+ if (val == 0xffffffff || sz == 0xffffffff) {
+ xdbc_trace("invalid mmio bar\n");
+ return NULL;
+ }
+
+ val64 = val & PCI_BASE_ADDRESS_MEM_MASK;
+ sz64 = sz & PCI_BASE_ADDRESS_MEM_MASK;
+ mask64 = (u32)PCI_BASE_ADDRESS_MEM_MASK;
+
+ if ((val & PCI_BASE_ADDRESS_MEM_TYPE_MASK) ==
+ PCI_BASE_ADDRESS_MEM_TYPE_64) {
+ val = read_pci_config(bus, dev, func, bar + 4);
+ write_pci_config(bus, dev, func, bar + 4, ~0);
+ sz = read_pci_config(bus, dev, func, bar + 4);
+ write_pci_config(bus, dev, func, bar + 4, val);
+
+ val64 |= ((u64)val << 32);
+ sz64 |= ((u64)sz << 32);
+ mask64 |= ((u64)~0 << 32);
+ }
+
+ sz64 &= mask64;
+
+ if (sizeof(dma_addr_t) < 8 || !sz64) {
+ xdbc_trace("can't handle 64bit BAR\n");
+ return NULL;
+ }
+
+ sz64 = 1ULL << __ffs64(sz64);
+
+ if (sz64 > (FIX_XDBC_END - FIX_XDBC_BASE + 1) * PAGE_SIZE) {
+ xdbc_trace("mmio size beyond 64k not supported\n");
+ return NULL;
+ }
+
+ xdbc_trace("bar: base 0x%llx size 0x%llx offset %03x\n",
+ val64, sz64, bar);
+
+ /* check if the mem space is enabled */
+ byte = read_pci_config_byte(bus, dev, func, PCI_COMMAND);
+ if (!(byte & PCI_COMMAND_MEMORY)) {
+ byte |= PCI_COMMAND_MEMORY;
+ write_pci_config_byte(bus, dev, func, PCI_COMMAND, byte);
+ xdbc_trace("mmio for xhci enabled\n");
+ }
+
+ /* 64k mmio will be fix-mapped */
+ max_idx = FIX_XDBC_END - FIX_XDBC_BASE;
+ for (idx = 0; idx <= max_idx; idx++)
+ set_fixmap_nocache(FIX_XDBC_BASE + idx,
+ (val64 & PAGE_MASK) + (max_idx - idx) * PAGE_SIZE);
+ base = (void __iomem *)__fix_to_virt(FIX_XDBC_END);
+ base += val64 & ~PAGE_MASK;
+
+ /* save in the state block */
+ xdbcp->bus = bus;
+ xdbcp->dev = dev;
+ xdbcp->func = func;
+ xdbcp->bar = bar;
+ xdbcp->xhci_base = base;
+ xdbcp->xhci_length = sz64;
+
+ if (length)
+ *length = sz64;
+
+ return base;
+}
+
+/*
+ * FIXME: The bootmem allocator isn't ready at the time when DbC gets
+ * initialized. Below implementation reserves DMA memory blocks
+ * in the kernel static data segment.
+ */
+static void *xdbc_get_page(dma_addr_t *dma_addr,
+ enum xdbc_page_type type)
+{
+ void *virt;
+ static char event_page[PAGE_SIZE] __aligned(PAGE_SIZE);
+ static char in_ring_page[PAGE_SIZE] __aligned(PAGE_SIZE);
+ static char out_ring_page[PAGE_SIZE] __aligned(PAGE_SIZE);
+ static char table_page[PAGE_SIZE] __aligned(PAGE_SIZE);
+
+ switch (type) {
+ case XDBC_PAGE_EVENT:
+ virt = (void *)event_page;
+ break;
+ case XDBC_PAGE_TXIN:
+ virt = (void *)in_ring_page;
+ break;
+ case XDBC_PAGE_TXOUT:
+ virt = (void *)out_ring_page;
+ break;
+ case XDBC_PAGE_TABLE:
+ virt = (void *)table_page;
+ break;
+ default:
+ return NULL;
+ }
+
+ memset(virt, 0, PAGE_SIZE);
+
+ if (dma_addr)
+ *dma_addr = (dma_addr_t)__pa(virt);
+
+ return virt;
+}
+
+typedef void (*xdbc_walk_excap_cb)(int cap_offset, void *data);
+
+/*
+ * xdbc_walk_excap:
+ *
+ * xHCI extended capability list walker.
+ *
+ * @bus - xHC PCI bus#
+ * @dev - xHC PCI dev#
+ * @func - xHC PCI function#
+ * @cap - capability ID
+ * @oneshot - return immediately once hit match
+ * @cb - call back
+ * @data - callback private data
+ *
+ * Return the last cap offset, otherwize 0.
+ */
+static u32 xdbc_walk_excap(u32 bus, u32 dev, u32 func, int cap,
+ bool oneshot, xdbc_walk_excap_cb cb, void *data)
+{
+ void __iomem *base;
+ int offset = 0;
+ size_t len = 0;
+
+ if (xdbcp->xhci_base && xdbcp->xhci_length) {
+ if (xdbcp->bus != bus ||
+ xdbcp->dev != dev ||
+ xdbcp->func != func) {
+ xdbc_trace("only one DbC can be used\n");
+ return 0;
+ }
+
+ len = xdbcp->xhci_length;
+ base = xdbcp->xhci_base;
+ } else {
+ base = xdbc_map_pci_mmio(bus, dev, func,
+ PCI_BASE_ADDRESS_0, &len);
+ if (!base)
+ return 0;
+ }
+
+ do {
+ offset = xhci_find_next_ext_cap(base, offset, cap);
+ if (!offset)
+ break;
+
+ if (cb)
+ cb(offset, data);
+ if (oneshot)
+ break;
+ } while (1);
+
+ return offset;
+}
+
+static u32 __init xdbc_find_dbgp(int xdbc_num,
+ u32 *rbus, u32 *rdev, u32 *rfunc)
+{
+ u32 bus, dev, func, class;
+ unsigned cap;
+
+ for (bus = 0; bus < XDBC_PCI_MAX_BUSES; bus++) {
+ for (dev = 0; dev < XDBC_PCI_MAX_DEVICES; dev++) {
+ for (func = 0; func < XDBC_PCI_MAX_FUNCTION; func++) {
+ class = read_pci_config(bus, dev, func,
+ PCI_CLASS_REVISION);
+ if ((class >> 8) != PCI_CLASS_SERIAL_USB_XHCI)
+ continue;
+
+ if (xdbc_num-- != 0)
+ continue;
+
+ cap = xdbc_walk_excap(bus, dev, func,
+ XHCI_EXT_CAPS_DEBUG,
+ true, NULL, NULL);
+ *rbus = bus;
+ *rdev = dev;
+ *rfunc = func;
+ return cap;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int handshake(void __iomem *ptr, u32 mask, u32 done,
+ int wait_usec, int delay_usec)
+{
+ u32 result;
+
+ do {
+ result = readl(ptr);
+ result &= mask;
+ if (result == done)
+ return 0;
+ xdbc_udelay(delay_usec);
+ wait_usec -= delay_usec;
+ } while (wait_usec > 0);
+
+ return -ETIMEDOUT;
+}
+
+/*
+ * xdbc_start: start DbC
+ *
+ * Set DbC enable bit and wait until DbC run bit being set or timed out.
+ */
+static int xdbc_start(void)
+{
+ u32 ctrl, status;
+
+ ctrl = readl(&xdbcp->xdbc_reg->control);
+ writel(ctrl | CTRL_DCE | CTRL_LSE, &xdbcp->xdbc_reg->control);
+
+ if (handshake(&xdbcp->xdbc_reg->control, CTRL_DCE,
+ CTRL_DCE, 100000, 100) < 0) {
+ xdbc_trace("falied to initialize hardware\n");
+ return -ENODEV;
+ }
+
+ /* wait for port connection */
+ if (handshake(&xdbcp->xdbc_reg->portsc, PORTSC_CCS,
+ PORTSC_CCS, 5000000, 100) < 0) {
+ xdbc_trace("waiting for connection timed out\n");
+ return -ETIMEDOUT;
+ }
+ xdbc_trace("port connection detected\n");
+
+ /* wait for debug device to be configured */
+ if (handshake(&xdbcp->xdbc_reg->control, CTRL_DCR,
+ CTRL_DCR, 5000000, 100) < 0) {
+ xdbc_trace("waiting for device configuration timed out\n");
+ return -ETIMEDOUT;
+ }
+
+ /* port should have a valid port# */
+ status = readl(&xdbcp->xdbc_reg->status);
+ if (!DCST_DPN(status)) {
+ xdbc_trace("invalid root hub port number\n");
+ return -ENODEV;
+ }
+
+ xdbc_trace("root hub port number %d\n", DCST_DPN(status));
+
+ xdbc_trace("DbC is running now, control 0x%08x\n",
+ readl(&xdbcp->xdbc_reg->control));
+
+ return 0;
+}
+
+static int xdbc_setup(void)
+{
+ int ret;
+
+ writel(0, &xdbcp->xdbc_reg->control);
+ if (handshake(&xdbcp->xdbc_reg->control, CTRL_DCE,
+ 0, 100000, 100) < 0) {
+ xdbc_trace("falied to initialize hardware\n");
+ return -ETIMEDOUT;
+ }
+
+ /* allocate and initialize all memory data structures */
+ ret = xdbc_mem_init();
+ if (ret < 0) {
+ xdbc_trace("failed to initialize memory\n");
+ return ret;
+ }
+
+ /*
+ * Memory barrier to ensure hardware sees the bits
+ * setting above.
+ */
+ mmiowb();
+
+ /* dump registers and data structures */
+ xdbc_dbg_dump_regs("hardware setup completed");
+ xdbc_dbg_dump_data("hardware setup completed");
+
+ ret = xdbc_start();
+ if (ret < 0) {
+ xdbc_trace("failed to start DbC, cable connected?\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+int __init early_xdbc_init(char *s)
+{
+ u32 bus = 0, dev = 0, func = 0;
+ unsigned long dbgp_num = 0;
+ u32 offset;
+ int ret;
+
+ if (!early_pci_allowed())
+ return -EPERM;
+
+ /* FIXME: early printk "keep" option will be supported later */
+ if (strstr(s, "keep"))
+ return -EPERM;
+
+ if (xdbcp->xdbc_reg)
+ return 0;
+
+ if (*s && kstrtoul(s, 0, &dbgp_num))
+ dbgp_num = 0;
+
+ xdbc_trace("dbgp_num: %lu\n", dbgp_num);
+
+ offset = xdbc_find_dbgp(dbgp_num, &bus, &dev, &func);
+ if (!offset)
+ return -ENODEV;
+
+ xdbc_trace("Found xHCI debug capability on %02x:%02x.%1x\n",
+ bus, dev, func);
+
+ if (!xdbcp->xhci_base)
+ return -EINVAL;
+
+ xdbcp->xdbc_reg = (struct xdbc_regs __iomem *)
+ (xdbcp->xhci_base + offset);
+ xdbc_dbg_dump_regs("debug capability located");
+
+ /* hand over the owner of host from BIOS */
+ xdbc_bios_handoff();
+
+ ret = xdbc_setup();
+ if (ret < 0) {
+ pr_notice("failed to setup xHCI DbC connection\n");
+ xdbcp->xhci_base = NULL;
+ xdbcp->xdbc_reg = NULL;
+ return ret;
+ }
+
+ return 0;
+}
diff --git a/include/linux/usb/xhci-dbc.h b/include/linux/usb/xhci-dbc.h
new file mode 100644
index 0000000..153fb87
--- /dev/null
+++ b/include/linux/usb/xhci-dbc.h
@@ -0,0 +1,187 @@
+/*
+ * xHCI debug capability driver
+ *