uio_usbdebug: User-space-i/o framework for usbdebug
uio_usbdebug enables you to debug coreboot's usbdebug driver inside a
running operating system (only Linux at this time). This comes very
handy if you're hacking the usbdebug driver and don't have any other
debug output from coreboot itself.
Currently, only Intel chipsets are supported.
Change-Id: Iaf0bcd4b4c01ae0b099d1206d553344054a62f31
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: http://review.coreboot.org/4695
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
diff --git a/util/uio_usbdebug/Makefile b/util/uio_usbdebug/Makefile
new file mode 100644
index 0000000..74bc80e
--- /dev/null
+++ b/util/uio_usbdebug/Makefile
@@ -0,0 +1,54 @@
+include ../../.config
+
+ARCHDIR-$(CONFIG_ARCH_ARMV7) := armv7
+ARCHDIR-$(CONFIG_ARCH_X86) := x86
+
+# Only Intel chipsets supported, currently.
+OBJ-$(CONFIG_SOUTHBRIDGE_INTEL_COMMON) += uio_usbdebug_intel.o
+
+PROGRAM := uio_usbdebug
+
+CB_SRC := $(shell realpath ../../src)
+CB_SOURCES := drivers/usb/ehci_debug.c
+CB_INCLUDES := \
+ drivers/usb/ehci.h \
+ drivers/usb/ehci_debug.h \
+ drivers/usb/usb_ch9.h
+INCLUDES := \
+ include/device/device.h
+OBJECTS := \
+ uio_usbdebug.o \
+ drivers/usb/pci_ehci.o \
+ console/printk.o \
+ lib/cbmem.o \
+ $(OBJ-y) \
+ $(patsubst %.c,%.o,$(CB_SOURCES))
+
+KCONFIG_H := ../../src/include/kconfig.h
+
+CFLAGS += \
+ -m32 -g \
+ -Wall -Wextra -Werror \
+ -Wno-unused-parameter -Wno-error=sign-compare
+CPPFLAGS += \
+ -Iinclude/ \
+ -I../../src/include/ -I../../src/arch/$(ARCHDIR-y)/include/ \
+ -I../../build/ -include$(KCONFIG_H)
+
+LIBS := -lpci -lz
+
+all: $(PROGRAM)
+
+$(PROGRAM): $(OBJECTS)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJECTS) $(LIBS)
+
+$(CB_SOURCES) $(CB_INCLUDES):
+ @mkdir -p $(dir $@)
+ @ln -sf $(CB_SRC)/$@ $@
+
+$(OBJECTS): $(CONFIG_H) $(CB_INCLUDES) $(INCLUDES)
+
+clean:
+ -@rm -rf $(CB_SOURCES) $(CB_INCLUDES) $(OBJECTS) $(PROGRAM)
+
+.PHONY: all clean
diff --git a/util/uio_usbdebug/README b/util/uio_usbdebug/README
new file mode 100644
index 0000000..2d52338
--- /dev/null
+++ b/util/uio_usbdebug/README
@@ -0,0 +1,77 @@
+
+uio_usbdebug - Run coreboot's usbdebug driver in userspace
+==========================================================
+
+
+## Purpose
+
+uio_usbdebug enables you to debug coreboot's usbdebug driver inside a
+running operating system (only Linux at this time). This comes very
+handy if you're hacking the usbdebug driver and don't have any other
+debug output from coreboot itself.
+
+
+## State
+
+Currently only Intel chipsets are supported. Support for other chipsets
+should be straightforward (normally just some port-enable code has to
+be implemented).
+
+The Linux kernel driver (see linux/uio_ehci_pci.c) has only one PCI ID
+hardcoded (for ICH7). The whole setup has been developed and tested on
+a ThinkPad T60.
+
+### Files
+
+uio_usbdebug.c - The userspace part of the uio interface.
+
+uio_usbdebug_intel.c - Port enable code for Intel chipsets.
+
+linux/uio_ehci_pci.c - Kernel part of the uio interface.
+
+console/printk.c - A do_printk() implementation so you can see debug
+ output with CONFIG_DEBUG_USBDEBUG enabled.
+
+device/*.c lib/*.c - Some stubs for (hopefully) unneeded functions for
+ proper linking.
+
+
+## Usage
+
+### Preparations
+
+The MMIO space has to be a whole 4K page in size and alignment to be
+mapped into userspace. This is very uncommon, so you'll most probably
+have to remap the MMIO space. The Linux kernel does that for you with
+the `pci=resource_alignment=<pci address>` kernel parameter (e.g.
+`pci=resource_alignment=0:1d.7` for ICH7).
+
+If your PCI device isn't listed in the kernel driver yet, you might want
+to add it to the `ehci_pci_ids` table in `linux/uio_ehci_pci.c` (or do
+some module alias magic if you know how to).
+
+### Build / Install
+
+Somehow like this:
+
+$ # Configure coreboot for your board and enable CONFIG_USBDEBUG
+$ make menuconfig
+$ cd util/uio_usbdebug/
+$ make -Clinux/
+$ sudo make -Clinux/ install
+$ make
+
+### Run
+
+$ # Unload Linux' EHCI driver (high-speed devices will stop working)
+$ sudo modprobe -r ehci-pci
+$ # Load the uio driver
+$ sudo modprobe uio-ehci-pci
+$ # Find your uio device
+$ ls /sys/module/uio_ehci_pci/drivers/*/*/uio/
+uio0
+$ # Run uio_usbdebug on this device
+$ sudo ./uio_usbdebug /dev/uio0
+
+Sadly, uio_usbdebug has to be run with root privileges since there are
+port-80 writes in the usbdebug driver.
diff --git a/util/uio_usbdebug/console/printk.c b/util/uio_usbdebug/console/printk.c
new file mode 100644
index 0000000..a73407c
--- /dev/null
+++ b/util/uio_usbdebug/console/printk.c
@@ -0,0 +1,34 @@
+/*
+ * This file is part of uio_usbdebug
+ *
+ * Copyright (C) 2013 Nico Huber <nico.h@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <console/console.h>
+
+int do_printk(int msg_level, const char *const fmt, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, fmt);
+ i = vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ return i;
+}
diff --git a/util/uio_usbdebug/drivers/usb/pci_ehci.c b/util/uio_usbdebug/drivers/usb/pci_ehci.c
new file mode 100644
index 0000000..6a316a7
--- /dev/null
+++ b/util/uio_usbdebug/drivers/usb/pci_ehci.c
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 Nico Huber <nico.h@gmx.de>
+ *
+ * Code borrowed from pci_early.c:
+ * Copyright (C) 2011 Google Inc
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+#include <device/pci_ehci.h>
+
+static unsigned pci_find_next_capability(pci_devfn_t dev, unsigned cap, unsigned last)
+{
+ unsigned pos = 0;
+ u16 status;
+ unsigned reps = 48;
+
+ status = pci_read_config16(dev, PCI_STATUS);
+ if (!(status & PCI_STATUS_CAP_LIST))
+ return 0;
+
+ u8 hdr_type = pci_read_config8(dev, PCI_HEADER_TYPE);
+ switch (hdr_type & 0x7f) {
+ case PCI_HEADER_TYPE_NORMAL:
+ case PCI_HEADER_TYPE_BRIDGE:
+ pos = PCI_CAPABILITY_LIST;
+ break;
+ case PCI_HEADER_TYPE_CARDBUS:
+ pos = PCI_CB_CAPABILITY_LIST;
+ break;
+ default:
+ return 0;
+ }
+
+ pos = pci_read_config8(dev, pos);
+ while (reps-- && (pos >= 0x40)) { /* Loop through the linked list. */
+ unsigned this_cap;
+
+ pos &= ~3;
+ this_cap = pci_read_config8(dev, pos + PCI_CAP_LIST_ID);
+ if (this_cap == 0xff)
+ break;
+
+ if (!last && (this_cap == cap))
+ return pos;
+
+ if (last == pos)
+ last = 0;
+
+ pos = pci_read_config8(dev, pos + PCI_CAP_LIST_NEXT);
+ }
+ return 0;
+}
+
+static unsigned pci_find_capability(pci_devfn_t dev, unsigned cap)
+{
+ return pci_find_next_capability(dev, cap, 0);
+}
+
+extern void *ehci_bar;
+int ehci_debug_hw_enable(unsigned int *base, unsigned int *dbg_offset)
+{
+ pci_devfn_t dbg_dev = pci_ehci_dbg_dev(CONFIG_USBDEBUG_HCD_INDEX);
+ pci_ehci_dbg_enable(dbg_dev, CONFIG_EHCI_BAR);
+ pci_devfn_t dev = dbg_dev;
+
+ u8 pos = pci_find_capability(dev, PCI_CAP_ID_EHCI_DEBUG);
+ if (!pos)
+ return -1;
+
+ u32 cap = pci_read_config32(dev, pos);
+
+ /* FIXME: We should remove static EHCI_BAR_INDEX. */
+ u8 dbg_bar = 0x10 + 4 * ((cap >> 29) - 1);
+ if (dbg_bar != EHCI_BAR_INDEX)
+ return -1;
+
+ *base = (u32)ehci_bar;
+ *dbg_offset = (cap>>16) & 0x1ffc;
+ return 0;
+}
+
+void ehci_debug_select_port(unsigned int port)
+{
+ pci_devfn_t dbg_dev = pci_ehci_dbg_dev(CONFIG_USBDEBUG_HCD_INDEX);
+ pci_ehci_dbg_set_port(dbg_dev, port);
+}
diff --git a/util/uio_usbdebug/include/device/device.h b/util/uio_usbdebug/include/device/device.h
new file mode 100644
index 0000000..1838f4d
--- /dev/null
+++ b/util/uio_usbdebug/include/device/device.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2014 Nico Huber <nico.h@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+
+#ifndef _DEVICE_DEVICE_H
+#define _DEVICE_DEVICE_H
+
+#include <pci/pci.h>
+
+typedef struct pci_dev *pci_devfn_t;
+
+#define pci_read_config8 pci_read_byte
+#define pci_read_config16 pci_read_word
+#define pci_read_config32 pci_read_long
+
+#define PCI_CAP_ID_EHCI_DEBUG PCI_CAP_ID_DBG
+
+extern struct pci_access *pci_access;
+#define PCI_DEV(b, d, f) pci_get_dev(pci_access, 0, b, d, f)
+
+#endif
diff --git a/util/uio_usbdebug/lib/cbmem.c b/util/uio_usbdebug/lib/cbmem.c
new file mode 100644
index 0000000..6d87880
--- /dev/null
+++ b/util/uio_usbdebug/lib/cbmem.c
@@ -0,0 +1,8 @@
+
+#include <stdint.h>
+#include <stddef.h>
+
+void *cbmem_find(u32 id)
+{
+ return NULL;
+}
diff --git a/util/uio_usbdebug/linux/Makefile b/util/uio_usbdebug/linux/Makefile
new file mode 100644
index 0000000..fd60b4f
--- /dev/null
+++ b/util/uio_usbdebug/linux/Makefile
@@ -0,0 +1,13 @@
+
+obj-m := uio_ehci_pci.o
+
+all: uio_ehci_pci.c
+ @$(MAKE) -C/lib/modules/`uname -r`/build M=$(CURDIR) modules
+
+install:
+ @$(MAKE) -C/lib/modules/`uname -r`/build M=$(CURDIR) modules_install
+
+clean:
+ -@$(MAKE) -C/lib/modules/`uname -r`/build M=$(CURDIR) clean
+
+.PHONY: all install clean
diff --git a/util/uio_usbdebug/linux/uio_ehci_pci.c b/util/uio_usbdebug/linux/uio_ehci_pci.c
new file mode 100644
index 0000000..d5c33e3
--- /dev/null
+++ b/util/uio_usbdebug/linux/uio_ehci_pci.c
@@ -0,0 +1,106 @@
+/*
+ * uio_ehci_pci - UIO driver for PCI EHCI devices
+ *
+ * Copyright (C) 2013 Nico Huber <nico.h@gmx.de>
+ *
+ * This only implements MMIO access (no interrupts).
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA, 02110-1301 USA
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/uio_driver.h>
+
+#define DRIVER_VERSION "0.0.1"
+#define DRIVER_AUTHOR "Nico Huber <nico.h@gmx.de>"
+#define DRIVER_DESC "UIO driver for PCI EHCI devices"
+#define DRIVER_TAG "uio_ehci_pci"
+
+static int probe(struct pci_dev *const pci_dev,
+ const struct pci_device_id *const did)
+{
+ struct uio_info *info;
+ int ret;
+
+ ret = pci_enable_device(pci_dev);
+ if (ret)
+ goto return_;
+
+ ret = pci_request_regions(pci_dev, DRIVER_TAG);
+ if (ret)
+ goto return_disable;
+
+ info = kzalloc(sizeof(struct uio_info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto return_release;
+ }
+
+ info->name = DRIVER_TAG;
+ info->version = DRIVER_VERSION;
+
+ info->mem[0].name = "EHCI MMIO area";
+ info->mem[0].addr = pci_resource_start(pci_dev, 0);
+ if (!info->mem[0].addr) {
+ ret = -ENODEV;
+ goto return_free;
+ }
+ info->mem[0].size = pci_resource_len(pci_dev, 0);
+ info->mem[0].memtype = UIO_MEM_PHYS;
+
+ ret = uio_register_device(&pci_dev->dev, info);
+ if (ret)
+ goto return_free;
+ pci_set_drvdata(pci_dev, info);
+
+ return 0;
+return_free:
+ kfree(info);
+return_release:
+ pci_release_regions(pci_dev);
+return_disable:
+ pci_disable_device(pci_dev);
+return_:
+ return ret;
+}
+
+static void remove(struct pci_dev *const pci_dev)
+{
+ struct uio_info *const info = pci_get_drvdata(pci_dev);
+
+ uio_unregister_device(info);
+ kfree(info);
+ pci_release_regions(pci_dev);
+ pci_disable_device(pci_dev);
+}
+
+static DEFINE_PCI_DEVICE_TABLE(ehci_pci_ids) = {
+ { PCI_DEVICE(0x8086, 0x27cc) },
+ { 0, }
+};
+
+static struct pci_driver uio_ehci_pci_driver = {
+ .name = DRIVER_TAG,
+ .id_table = ehci_pci_ids,
+ .probe = probe,
+ .remove = remove,
+};
+
+module_pci_driver(uio_ehci_pci_driver);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/util/uio_usbdebug/uio_usbdebug.c b/util/uio_usbdebug/uio_usbdebug.c
new file mode 100644
index 0000000..e2836fc
--- /dev/null
+++ b/util/uio_usbdebug/uio_usbdebug.c
@@ -0,0 +1,67 @@
+/*
+ * uio_usbdebug - Run coreboot's usbdebug driver in userspace
+ *
+ * Copyright (C) 2013 Nico Huber <nico.h@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <pci/pci.h>
+
+/* coreboot's arch/io.h conflicts with libc's sys/io.h, so declare this here: */
+int ioperm(unsigned long from, unsigned long num, int turn_on);
+
+#include <arch/io.h>
+#include <console/usb.h>
+
+void *ehci_bar;
+struct pci_access *pci_access;
+
+int main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s <uio-dev>\n", argv[0]);
+ return 1;
+ }
+ const int fd = open(argv[1], O_RDWR);
+ if (fd < 0) {
+ perror("Failed to open uio device");
+ return 2;
+ }
+ ehci_bar =
+ mmap(NULL, 1 << 8, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (MAP_FAILED == ehci_bar) {
+ perror("Failed to map ehci bar");
+ close(fd);
+ return 3;
+ }
+
+ ioperm(0x80, 1, 1);
+
+ pci_access = pci_alloc();
+ pci_init(pci_access);
+
+ usbdebug_init();
+
+ pci_cleanup(pci_access);
+ munmap(ehci_bar, 1 << 8);
+ close(fd);
+ return 0;
+}
diff --git a/util/uio_usbdebug/uio_usbdebug_intel.c b/util/uio_usbdebug/uio_usbdebug_intel.c
new file mode 100644
index 0000000..5e4d926
--- /dev/null
+++ b/util/uio_usbdebug/uio_usbdebug_intel.c
@@ -0,0 +1,67 @@
+/*
+ * This file is part of uio_usbdebug
+ *
+ * Copyright (C) 2013 Nico Huber <nico.h@gmx.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <arch/io.h>
+#include <device/device.h>
+#include <device/pci_ehci.h>
+#include <console/usb.h>
+
+extern void *ehci_bar;
+
+pci_devfn_t pci_ehci_dbg_dev(unsigned hcd_idx)
+{
+ u32 class;
+ pci_devfn_t dev;
+
+#if CONFIG_HAVE_USBDEBUG_OPTIONS
+ if (hcd_idx==2)
+ dev = PCI_DEV(0, 0x1a, 0);
+ else
+ dev = PCI_DEV(0, 0x1d, 0);
+#else
+ dev = PCI_DEV(0, 0x1d, 7);
+#endif
+
+ class = pci_read_config32(dev, PCI_CLASS_REVISION) >> 8;
+#if CONFIG_HAVE_USBDEBUG_OPTIONS
+ if (class != PCI_EHCI_CLASSCODE) {
+ /* If we enter here before RCBA programming, EHCI function may
+ * appear with the highest function number instead.
+ */
+ dev |= PCI_DEV(0, 0, 7);
+ class = pci_read_config32(dev, PCI_CLASS_REVISION) >> 8;
+ }
+#endif
+ if (class != PCI_EHCI_CLASSCODE)
+ return 0;
+
+ return dev;
+}
+
+void pci_ehci_dbg_set_port(pci_devfn_t dev, unsigned int port)
+{
+ /* claim usb debug port */
+ const unsigned long dbgctl_addr =
+ ((unsigned long)ehci_bar) + CONFIG_EHCI_DEBUG_OFFSET;
+ write32(dbgctl_addr, read32(dbgctl_addr) | (1 << 30));
+}
+
+void pci_ehci_dbg_enable(pci_devfn_t dev, unsigned long base)
+{
+}