Very first revision doing something useful. Initializes xhci host
controller, detects devices, can handle control and bulk transfers.
Good enougth to boot from usb storage devices.
To be done:
* Add support for interrupt transfers (needed for kbd+mouse).
* Add support for streams (needed for uas devices on usb3 ports).
* Add support for usb hubs.
Tested on qemu only.
Signed-off-by: Gerd Hoffmann <kraxel(a)redhat.com>
---
Makefile | 2 +-
src/Kconfig | 6 +
src/pci_ids.h | 1 +
src/usb-xhci.c | 979 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/usb-xhci.h | 141 ++++++++
src/usb.c | 11 +
src/usb.h | 16 +-
7 files changed, 1148 insertions(+), 8 deletions(-)
create mode 100644 src/usb-xhci.c
create mode 100644 src/usb-xhci.h
diff --git a/Makefile b/Makefile
index 759bbbb..6de1d95 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ OUT=out/
SRCBOTH=misc.c stacks.c output.c util.c block.c floppy.c ata.c mouse.c \
kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \
pnpbios.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
- usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
+ usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-xhci.c usb-hid.c usb-msc.c \
virtio-ring.c virtio-pci.c virtio-blk.c virtio-scsi.c apm.c ahci.c \
usb-uas.c lsi-scsi.c esp-scsi.c megasas.c
SRC16=$(SRCBOTH) system.c disk.c font.c
diff --git a/src/Kconfig b/src/Kconfig
index 3c80132..0298abf 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -207,6 +207,12 @@ menu "Hardware support"
default y
help
Support USB EHCI controllers.
+ config USB_XHCI
+ depends on USB
+ bool "USB XHCI controllers"
+ default y
+ help
+ Support USB XHCI controllers.
config USB_MSC
depends on USB && DRIVES
bool "USB drives"
diff --git a/src/pci_ids.h b/src/pci_ids.h
index 665e945..322d156 100644
--- a/src/pci_ids.h
+++ b/src/pci_ids.h
@@ -104,6 +104,7 @@
#define PCI_CLASS_SERIAL_USB_UHCI 0x0c0300
#define PCI_CLASS_SERIAL_USB_OHCI 0x0c0310
#define PCI_CLASS_SERIAL_USB_EHCI 0x0c0320
+#define PCI_CLASS_SERIAL_USB_XHCI 0x0c0330
#define PCI_CLASS_SERIAL_FIBER 0x0c04
#define PCI_CLASS_SERIAL_SMBUS 0x0c05
diff --git a/src/usb-xhci.c b/src/usb-xhci.c
new file mode 100644
index 0000000..d906b83
--- /dev/null
+++ b/src/usb-xhci.c
@@ -0,0 +1,979 @@
+#include "config.h" // CONFIG_*
+#include "util.h" // dprintf
+#include "pci.h" // pci_bdf_to_bus
+#include "pci_regs.h" // PCI_BASE_ADDRESS_0
+#include "usb.h" // struct usb_s
+#include "usb-xhci.h" // struct ehci_qh
+#include "biosvar.h" // GET_LOWFLAT
+
+// --------------------------------------------------------------
+// configuration
+
+#define XHCI_RING_SIZE 16
+
+// --------------------------------------------------------------
+// bit definitions
+
+#define XHCI_CMD_RS (1<<0)
+#define XHCI_CMD_HCRST (1<<1)
+#define XHCI_CMD_INTE (1<<2)
+#define XHCI_CMD_HSEE (1<<3)
+#define XHCI_CMD_LHCRST (1<<7)
+#define XHCI_CMD_CSS (1<<8)
+#define XHCI_CMD_CRS (1<<9)
+#define XHCI_CMD_EWE (1<<10)
+#define XHCI_CMD_EU3S (1<<11)
+
+#define XHCI_STS_HCH (1<<0)
+#define XHCI_STS_HSE (1<<2)
+#define XHCI_STS_EINT (1<<3)
+#define XHCI_STS_PCD (1<<4)
+#define XHCI_STS_SSS (1<<8)
+#define XHCI_STS_RSS (1<<9)
+#define XHCI_STS_SRE (1<<10)
+#define XHCI_STS_CNR (1<<11)
+#define XHCI_STS_HCE (1<<12)
+
+#define XHCI_PORTSC_CCS (1<<0)
+#define XHCI_PORTSC_PED (1<<1)
+#define XHCI_PORTSC_OCA (1<<3)
+#define XHCI_PORTSC_PR (1<<4)
+#define XHCI_PORTSC_PLS_SHIFT 5
+#define XHCI_PORTSC_PLS_MASK 0xf
+#define XHCI_PORTSC_PP (1<<9)
+#define XHCI_PORTSC_SPEED_SHIFT 10
+#define XHCI_PORTSC_SPEED_MASK 0xf
+#define XHCI_PORTSC_SPEED_FULL (1<<10)
+#define XHCI_PORTSC_SPEED_LOW (2<<10)
+#define XHCI_PORTSC_SPEED_HIGH (3<<10)
+#define XHCI_PORTSC_SPEED_SUPER (4<<10)
+#define XHCI_PORTSC_PIC_SHIFT 14
+#define XHCI_PORTSC_PIC_MASK 0x3
+#define XHCI_PORTSC_LWS (1<<16)
+#define XHCI_PORTSC_CSC (1<<17)
+#define XHCI_PORTSC_PEC (1<<18)
+#define XHCI_PORTSC_WRC (1<<19)
+#define XHCI_PORTSC_OCC (1<<20)
+#define XHCI_PORTSC_PRC (1<<21)
+#define XHCI_PORTSC_PLC (1<<22)
+#define XHCI_PORTSC_CEC (1<<23)
+#define XHCI_PORTSC_CAS (1<<24)
+#define XHCI_PORTSC_WCE (1<<25)
+#define XHCI_PORTSC_WDE (1<<26)
+#define XHCI_PORTSC_WOE (1<<27)
+#define XHCI_PORTSC_DR (1<<30)
+#define XHCI_PORTSC_WPR (1<<31)
+
+#define TRB_C (1<<0)
+#define TRB_TYPE_SHIFT 10
+#define TRB_TYPE_MASK 0x3f
+#define TRB_TYPE(t) (((t) >> TRB_TYPE_SHIFT) & TRB_TYPE_MASK)
+
+#define TRB_EV_ED (1<<2)
+
+#define TRB_TR_ENT (1<<1)
+#define TRB_TR_ISP (1<<2)
+#define TRB_TR_NS (1<<3)
+#define TRB_TR_CH (1<<4)
+#define TRB_TR_IOC (1<<5)
+#define TRB_TR_IDT (1<<6)
+#define TRB_TR_TBC_SHIFT 7
+#define TRB_TR_TBC_MASK 0x3
+#define TRB_TR_BEI (1<<9)
+#define TRB_TR_TLBPC_SHIFT 16
+#define TRB_TR_TLBPC_MASK 0xf
+#define TRB_TR_FRAMEID_SHIFT 20
+#define TRB_TR_FRAMEID_MASK 0x7ff
+#define TRB_TR_SIA (1<<31)
+
+#define TRB_TR_DIR (1<<16)
+
+#define TRB_CR_SLOTID_SHIFT 24
+#define TRB_CR_SLOTID_MASK 0xff
+#define TRB_CR_EPID_SHIFT 16
+#define TRB_CR_EPID_MASK 0x1f
+
+#define TRB_CR_BSR (1<<9)
+#define TRB_CR_DC (1<<9)
+
+#define TRB_LK_TC (1<<1)
+
+#define TRB_INTR_SHIFT 22
+#define TRB_INTR_MASK 0x3ff
+#define TRB_INTR(t) (((t).status >> TRB_INTR_SHIFT) & TRB_INTR_MASK)
+
+typedef enum TRBType {
+ TRB_RESERVED = 0,
+ TR_NORMAL,
+ TR_SETUP,
+ TR_DATA,
+ TR_STATUS,
+ TR_ISOCH,
+ TR_LINK,
+ TR_EVDATA,
+ TR_NOOP,
+ CR_ENABLE_SLOT,
+ CR_DISABLE_SLOT,
+ CR_ADDRESS_DEVICE,
+ CR_CONFIGURE_ENDPOINT,
+ CR_EVALUATE_CONTEXT,
+ CR_RESET_ENDPOINT,
+ CR_STOP_ENDPOINT,
+ CR_SET_TR_DEQUEUE,
+ CR_RESET_DEVICE,
+ CR_FORCE_EVENT,
+ CR_NEGOTIATE_BW,
+ CR_SET_LATENCY_TOLERANCE,
+ CR_GET_PORT_BANDWIDTH,
+ CR_FORCE_HEADER,
+ CR_NOOP,
+ ER_TRANSFER = 32,
+ ER_COMMAND_COMPLETE,
+ ER_PORT_STATUS_CHANGE,
+ ER_BANDWIDTH_REQUEST,
+ ER_DOORBELL,
+ ER_HOST_CONTROLLER,
+ ER_DEVICE_NOTIFICATION,
+ ER_MFINDEX_WRAP,
+} TRBType;
+
+typedef enum TRBCCode {
+ CC_INVALID = 0,
+ CC_SUCCESS,
+ CC_DATA_BUFFER_ERROR,
+ CC_BABBLE_DETECTED,
+ CC_USB_TRANSACTION_ERROR,
+ CC_TRB_ERROR,
+ CC_STALL_ERROR,
+ CC_RESOURCE_ERROR,
+ CC_BANDWIDTH_ERROR,
+ CC_NO_SLOTS_ERROR,
+ CC_INVALID_STREAM_TYPE_ERROR,
+ CC_SLOT_NOT_ENABLED_ERROR,
+ CC_EP_NOT_ENABLED_ERROR,
+ CC_SHORT_PACKET,
+ CC_RING_UNDERRUN,
+ CC_RING_OVERRUN,
+ CC_VF_ER_FULL,
+ CC_PARAMETER_ERROR,
+ CC_BANDWIDTH_OVERRUN,
+ CC_CONTEXT_STATE_ERROR,
+ CC_NO_PING_RESPONSE_ERROR,
+ CC_EVENT_RING_FULL_ERROR,
+ CC_INCOMPATIBLE_DEVICE_ERROR,
+ CC_MISSED_SERVICE_ERROR,
+ CC_COMMAND_RING_STOPPED,
+ CC_COMMAND_ABORTED,
+ CC_STOPPED,
+ CC_STOPPED_LENGTH_INVALID,
+ CC_MAX_EXIT_LATENCY_TOO_LARGE_ERROR = 29,
+ CC_ISOCH_BUFFER_OVERRUN = 31,
+ CC_EVENT_LOST_ERROR,
+ CC_UNDEFINED_ERROR,
+ CC_INVALID_STREAM_ID_ERROR,
+ CC_SECONDARY_BANDWIDTH_ERROR,
+ CC_SPLIT_TRANSACTION_ERROR
+} TRBCCode;
+
+enum {
+ PLS_U0 = 0,
+ PLS_U1 = 1,
+ PLS_U2 = 2,
+ PLS_U3 = 3,
+ PLS_DISABLED = 4,
+ PLS_RX_DETECT = 5,
+ PLS_INACTIVE = 6,
+ PLS_POLLING = 7,
+ PLS_RECOVERY = 8,
+ PLS_HOT_RESET = 9,
+ PLS_COMPILANCE_MODE = 10,
+ PLS_TEST_MODE = 11,
+ PLS_RESUME = 15,
+};
+
+#define xhci_get_field(data, field) \
+ (((data) >> field##_SHIFT) & field##_MASK)
+
+// --------------------------------------------------------------
+// state structs
+
+struct xhci_ring_s {
+ u32 idx;
+ u32 cs;
+};
+
+struct usb_xhci_s {
+ struct usb_s usb;
+ struct usbhub_s hub;
+
+ /* devinfo */
+ u32 baseaddr;
+ u32 xcap;
+ u32 ports;
+ u32 slots;
+
+ /* xhci registers */
+ struct xhci_caps *caps;
+ struct xhci_op *op;
+ struct xhci_pr *pr;
+ struct xhci_ir *ir;
+ struct xhci_db *db;
+
+ /* xhci data structures */
+ struct xhci_devlist *devs;
+ struct xhci_trb *cmds;
+ struct xhci_trb *evts;
+ struct xhci_er_seg *eseg;
+
+ /* usb devices */
+ struct xhci_device *list;
+
+ /* xhci state */
+ struct xhci_ring_s cmd;
+ struct xhci_ring_s evt;
+};
+
+struct xhci_device {
+ struct xhci_devctx devctx;
+ struct xhci_inctx inctx;
+
+ struct usbdevice_s *usbdev;
+ struct usb_xhci_s *xhci;
+ struct xhci_device *next;
+ u32 slotid;
+};
+
+struct xhci_pipe {
+ struct xhci_trb ring[XHCI_RING_SIZE];
+
+ struct usb_pipe pipe;
+ struct xhci_device *dev;
+ u32 epid;
+ struct xhci_ring_s rs;
+};
+
+// --------------------------------------------------------------
+// tables
+
+static const char *speed_name[16] = {
+ [ 0 ] = " - ",
+ [ 1 ] = "Full",
+ [ 2 ] = "Low",
+ [ 3 ] = "High",
+ [ 4 ] = "Super",
+};
+
+static const int speed_from_xhci[16] = {
+ [ 0 ... 15 ] = -1,
+ [ 1 ] = USB_FULLSPEED,
+ [ 2 ] = USB_LOWSPEED,
+ [ 3 ] = USB_HIGHSPEED,
+ [ 4 ] = USB_SUPERSPEED,
+};
+
+static const int speed_to_xhci[] = {
+ [ USB_FULLSPEED ] = 1,
+ [ USB_LOWSPEED ] = 2,
+ [ USB_HIGHSPEED ] = 3,
+ [ USB_SUPERSPEED ] = 4,
+};
+
+static const int eptype_to_xhci_in[] = {
+ [ USB_ENDPOINT_XFER_CONTROL] = 4,
+ [ USB_ENDPOINT_XFER_ISOC ] = 5,
+ [ USB_ENDPOINT_XFER_BULK ] = 6,
+ [ USB_ENDPOINT_XFER_INT ] = 7,
+};
+
+static const int eptype_to_xhci_out[] = {
+ [ USB_ENDPOINT_XFER_CONTROL] = 4,
+ [ USB_ENDPOINT_XFER_ISOC ] = 1,
+ [ USB_ENDPOINT_XFER_BULK ] = 2,
+ [ USB_ENDPOINT_XFER_INT ] = 3,
+};
+
+// --------------------------------------------------------------
+// internal functions, 16bit + 32bit
+
+static void xhci_doorbell(struct usb_xhci_s *xhci, u32 slotid, u32 value)
+{
+ struct xhci_db *db = GET_LOWFLAT(xhci->db);
+ u32 addr = (u32)(&db[slotid].doorbell);
+ pci_writel(addr, value);
+}
+
+static int xhci_event_wait(struct usb_xhci_s *xhci,
+ struct xhci_trb *evt,
+ u32 type, u32 timeout)
+{
+ u64 end = calc_future_tsc(timeout);
+
+ for (;;) {
+ u32 idx = GET_LOWFLAT(xhci->evt.idx);
+ u32 cs = GET_LOWFLAT(xhci->evt.cs);
+ struct xhci_trb *trb = GET_LOWFLAT(xhci->evts) + idx;
+ u32 control = GET_LOWFLAT(trb->control);
+ while ((control & TRB_C) != (cs ? 1 : 0)) {
+ if (check_tsc(end)) {
+ warn_timeout();
+ return -1;
+ }
+ yield();
+ control = GET_LOWFLAT(trb->control);
+ }
+
+ u32 evt_type = TRB_TYPE(control);
+ u32 evt_cc = (GET_LOWFLAT(trb->status) >> 24) & 0xff;
+ if (evt)
+ memcpy_fl(evt, trb, sizeof(*trb));
+
+ idx++;
+ if (idx == XHCI_RING_SIZE) {
+ idx = 0;
+ cs = cs ? 0 : 1;
+ SET_LOWFLAT(xhci->evt.cs, cs);
+ }
+ SET_LOWFLAT(xhci->evt.idx, idx);
+
+ struct xhci_ir *ir = GET_LOWFLAT(xhci->ir);
+ u32 addr = (u32)(&ir->erdp_low);
+ u32 erdp = (u32)(GET_LOWFLAT(xhci->evts) + idx);
+ pci_writel(addr, erdp);
+
+ if (evt_type == type)
+ return evt_cc;
+ }
+}
+
+static void xhci_trb_queue(struct xhci_trb *ring,
+ struct xhci_ring_s *rs,
+ struct xhci_trb *trb)
+{
+ u32 idx = GET_LOWFLAT(rs->idx);
+ u32 cs = GET_LOWFLAT(rs->cs);
+ struct xhci_trb *dst;
+ u32 control;
+
+ if (idx == XHCI_RING_SIZE-1) {
+ dst = ring + idx;
+ control = (TR_LINK << 10); // trb type
+ control |= TRB_LK_TC;
+ control |= (cs ? TRB_C : 0);
+ SET_LOWFLAT(dst->ptr_low, (u32)&ring[0]);
+ SET_LOWFLAT(dst->ptr_high, 0);
+ SET_LOWFLAT(dst->status, 0);
+ SET_LOWFLAT(dst->control, control);
+ idx = 0;
+ cs = cs ? 0 : 1;
+ SET_LOWFLAT(rs->idx, idx);
+ SET_LOWFLAT(rs->cs, cs);
+ }
+
+ dst = ring + idx;
+ control = GET_LOWFLAT(trb->control) | (cs ? TRB_C : 0);
+
+ SET_LOWFLAT(dst->ptr_low, GET_LOWFLAT(trb->ptr_low));
+ SET_LOWFLAT(dst->ptr_high, GET_LOWFLAT(trb->ptr_high));
+ SET_LOWFLAT(dst->status, GET_LOWFLAT(trb->status));
+ SET_LOWFLAT(dst->control, control);
+ idx++;
+ SET_LOWFLAT(rs->idx, idx);
+}
+
+static void xhci_xfer_queue(struct xhci_pipe *pipe,
+ struct xhci_trb *trb)
+{
+ xhci_trb_queue(pipe->ring, &pipe->rs, trb);
+}
+
+static void xhci_xfer_kick(struct xhci_pipe *pipe)
+{
+ struct xhci_device *dev = GET_LOWFLAT(pipe->dev);
+ struct usb_xhci_s *xhci = GET_LOWFLAT(dev->xhci);
+ u32 slotid = GET_LOWFLAT(dev->slotid);
+ u32 epid = GET_LOWFLAT(pipe->epid);
+
+ xhci_doorbell(xhci, slotid, epid);
+}
+
+static void xhci_xfer_normal(struct xhci_pipe *pipe,
+ void *data, int datalen)
+{
+ struct xhci_trb trb;
+
+ memset(&trb, 0, sizeof(trb));
+ trb.ptr_low = (u32)data;
+ trb.status = datalen;
+ trb.control |= (TR_NORMAL << 10); // trb type
+ trb.control |= TRB_TR_IOC;
+
+ xhci_xfer_queue(pipe, MAKE_FLATPTR(GET_SEG(SS), &trb));
+ xhci_xfer_kick(pipe);
+}
+
+// --------------------------------------------------------------
+// internal functions, pure 32bit
+
+static int wait_bit(u32 *reg, u32 mask, int value, u32 timeout)
+{
+ ASSERT32FLAT();
+ u64 end = calc_future_tsc(timeout);
+
+ while ((readl(reg) & mask) != value) {
+ if (check_tsc(end)) {
+ warn_timeout();
+ return -1;
+ }
+ yield();
+ }
+ return 0;
+}
+
+static int xhci_cmd_submit(struct usb_xhci_s *xhci,
+ struct xhci_trb *cmd,
+ struct xhci_trb *evt)
+{
+ ASSERT32FLAT();
+
+ xhci_trb_queue(xhci->cmds, &xhci->cmd, cmd);
+ xhci_doorbell(xhci, 0, 0);
+ return xhci_event_wait(xhci, evt, ER_COMMAND_COMPLETE, 100);
+}
+
+static int xhci_cmd_enable_slot(struct usb_xhci_s *xhci)
+{
+ ASSERT32FLAT();
+ struct xhci_trb cmd = {
+ .ptr_low = 0,
+ .ptr_high = 0,
+ .status = 0,
+ .control = (CR_ENABLE_SLOT << 10)
+ };
+ struct xhci_trb evt;
+ int cc = xhci_cmd_submit(xhci, &cmd, &evt);
+ if (cc != CC_SUCCESS)
+ return -1;
+ return (evt.control >> 24) & 0xff;
+}
+
+static int xhci_cmd_disable_slot(struct xhci_device *dev)
+{
+ ASSERT32FLAT();
+ struct xhci_trb cmd = {
+ .ptr_low = 0,
+ .ptr_high = 0,
+ .status = 0,
+ .control = (dev->slotid << 24) | (CR_DISABLE_SLOT << 10)
+ };
+ return xhci_cmd_submit(dev->xhci, &cmd, NULL);
+}
+
+static int xhci_cmd_address_device(struct xhci_device *dev)
+{
+ ASSERT32FLAT();
+ struct xhci_trb cmd = {
+ .ptr_low = (u32)&dev->inctx,
+ .ptr_high = 0,
+ .status = 0,
+ .control = (dev->slotid << 24) | (CR_ADDRESS_DEVICE << 10)
+ };
+ return xhci_cmd_submit(dev->xhci, &cmd, NULL);
+}
+
+static int xhci_cmd_configure_endpoint(struct xhci_device *dev)
+{
+ ASSERT32FLAT();
+ struct xhci_trb cmd = {
+ .ptr_low = (u32)&dev->inctx,
+ .ptr_high = 0,
+ .status = 0,
+ .control = (dev->slotid << 24) | (CR_CONFIGURE_ENDPOINT << 10)
+ };
+ return xhci_cmd_submit(dev->xhci, &cmd, NULL);
+}
+
+static void xhci_xfer_setup(struct xhci_pipe *pipe,
+ const struct usb_ctrlrequest *req,
+ int dir, int datalen)
+{
+ ASSERT32FLAT();
+ struct xhci_trb trb;
+
+ memset(&trb, 0, sizeof(trb));
+ trb.ptr_low |= req->bRequestType;
+ trb.ptr_low |= (req->bRequest) << 8;
+ trb.ptr_low |= (req->wValue) << 16;
+ trb.ptr_high |= req->wIndex;
+ trb.ptr_high |= (req->wLength) << 16;
+ trb.status |= 8; // length
+ trb.control |= (TR_SETUP << 10); // trb type
+ trb.control |= TRB_TR_IDT;
+ if (datalen)
+ trb.control |= (dir ? 3 : 2) << 16; // transfer type
+ xhci_xfer_queue(pipe, &trb);
+}
+
+static void xhci_xfer_data(struct xhci_pipe *pipe,
+ int dir, void *data, int datalen)
+{
+ ASSERT32FLAT();
+ struct xhci_trb trb;
+
+ memset(&trb, 0, sizeof(trb));
+ trb.ptr_low = (u32)data;
+ trb.status = datalen;
+ trb.control |= (TR_DATA << 10); // trb type
+ if (dir)
+ trb.control |= (1 << 16);
+ xhci_xfer_queue(pipe, &trb);
+}
+
+static void xhci_xfer_status(struct xhci_pipe *pipe, int dir)
+{
+ ASSERT32FLAT();
+ struct xhci_trb trb;
+
+ memset(&trb, 0, sizeof(trb));
+ trb.control |= (TR_STATUS << 10); // trb type
+ trb.control |= TRB_TR_IOC;
+ if (dir)
+ trb.control |= (1 << 16);
+
+ xhci_xfer_queue(pipe, &trb);
+ xhci_xfer_kick(pipe);
+}
+
+static struct xhci_device *xhci_find_alloc_device(struct usb_xhci_s *xhci,
+ struct usbdevice_s *usbdev)
+{
+ ASSERT32FLAT();
+ struct xhci_device *dev;
+
+ for (dev = xhci->list; dev != NULL; dev = dev->next) {
+ if (dev->usbdev == usbdev) {
+ return dev;
+ }
+ }
+
+ dev = memalign_low(64, sizeof(*dev));
+ if (!dev) {
+ warn_noalloc();
+ return NULL;
+ }
+ memset(dev, 0, sizeof(*dev));
+ dev->usbdev = usbdev;
+ dev->xhci = xhci;
+ dev->next = xhci->list;
+ xhci->list = dev;
+ return dev;
+}
+
+static void
+configure_xhci(void *data)
+{
+ ASSERT32FLAT();
+ struct usb_xhci_s *xhci = data;
+ u32 reg;
+
+ xhci->devs = memalign_high(64, sizeof(*xhci->devs) * (xhci->slots + 1));
+ xhci->eseg = memalign_high(64, sizeof(*xhci->eseg));
+ xhci->cmds = memalign_high(64, sizeof(*xhci->cmds) * XHCI_RING_SIZE);
+ xhci->evts = memalign_low(64, sizeof(*xhci->evts) * XHCI_RING_SIZE);
+ if (!xhci->devs || !xhci->cmds || !xhci->evts || !xhci->eseg) {
+ warn_noalloc();
+ goto fail;
+ }
+ memset(xhci->devs, 0, sizeof(*xhci->devs) * (xhci->slots + 1));
+ memset(xhci->cmds, 0, sizeof(*xhci->cmds) * XHCI_RING_SIZE);
+ memset(xhci->evts, 0, sizeof(*xhci->evts) * XHCI_RING_SIZE);
+ memset(xhci->eseg, 0, sizeof(*xhci->eseg));
+
+ reg = readl(&xhci->op->usbcmd);
+ if (reg & XHCI_CMD_RS) {
+ reg &= ~XHCI_CMD_RS;
+ writel(&xhci->op->usbcmd, reg);
+ if (wait_bit(&xhci->op->usbsts, XHCI_STS_HCH, XHCI_STS_HCH, 32) != 0)
+ goto fail;
+ }
+
+ writel(&xhci->op->usbcmd, XHCI_CMD_HCRST);
+ if (wait_bit(&xhci->op->usbcmd, XHCI_CMD_HCRST, 0, 100) != 0)
+ goto fail;
+ if (wait_bit(&xhci->op->usbsts, XHCI_STS_CNR, 0, 100) != 0)
+ goto fail;
+
+ writel(&xhci->op->config, xhci->slots);
+ writel(&xhci->op->dcbaap_low, (u32)xhci->devs);
+ writel(&xhci->op->dcbaap_high, 0);
+ writel(&xhci->op->crcr_low, (u32)xhci->cmds);
+ writel(&xhci->op->crcr_high, 0);
+ xhci->cmd.cs = 1;
+
+ xhci->eseg->ptr_low = (u32)xhci->evts;
+ xhci->eseg->ptr_high = 0;
+ xhci->eseg->size = XHCI_RING_SIZE;
+ writel(&xhci->ir->erstsz, 1);
+ writel(&xhci->ir->erdp_low, (u32)xhci->evts);
+ writel(&xhci->ir->erdp_high, 0);
+ writel(&xhci->ir->erstba_low, (u32)xhci->eseg);
+ writel(&xhci->ir->erstba_high, 0);
+ xhci->evt.cs = 1;
+
+ reg = readl(&xhci->op->usbcmd);
+ reg |= XHCI_CMD_RS;
+ writel(&xhci->op->usbcmd, reg);
+
+ usb_enumerate(&xhci->hub);
+ if (xhci->hub.devcount)
+ return;
+
+ // No devices found - shutdown and free controller.
+ dprintf(1, "%s: no devices found\n", __func__);
+ reg = readl(&xhci->op->usbcmd);
+ reg &= ~XHCI_CMD_RS;
+ writel(&xhci->op->usbcmd, reg);
+ wait_bit(&xhci->op->usbsts, XHCI_STS_HCH, XHCI_STS_HCH, 32);
+
+fail:
+ free(xhci->eseg);
+ free(xhci->evts);
+ free(xhci->cmds);
+ free(xhci->devs);
+ free(xhci);
+}
+
+// --------------------------------------------------------------
+// xhci root hub
+
+// Check if device attached to port
+static void
+xhci_print_port_state(const char *prefix, u32 port, u32 portsc)
+{
+ ASSERT32FLAT();
+ u32 pls = xhci_get_field(portsc, XHCI_PORTSC_PLS);
+ u32 speed = xhci_get_field(portsc, XHCI_PORTSC_SPEED);
+
+ dprintf(1, "%s %d:%s%s pls %d, speed %d [%s]\n", prefix, port,
+ (portsc & XHCI_PORTSC_PP) ? " powered," : "",
+ (portsc & XHCI_PORTSC_PED) ? " enabled," : "",
+ pls, speed, speed_name[speed]);
+}
+
+static int
+xhci_hub_detect(struct usbhub_s *hub, u32 port)
+{
+ ASSERT32FLAT();
+ struct usb_xhci_s *xhci = container_of(hub->cntl, struct usb_xhci_s, usb);
+ u32 portsc = readl(&xhci->pr[port].portsc);
+
+ switch (xhci_get_field(portsc, XHCI_PORTSC_PLS)) {
+ case PLS_U0:
+ case PLS_POLLING:
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+// Reset device on port
+static int
+xhci_hub_reset(struct usbhub_s *hub, u32 port)
+{
+ ASSERT32FLAT();
+ struct usb_xhci_s *xhci = container_of(hub->cntl, struct usb_xhci_s, usb);
+ u32 portsc = readl(&xhci->pr[port].portsc);
+ int rc;
+
+ switch (xhci_get_field(portsc, XHCI_PORTSC_PLS)) {
+ case PLS_U0:
+ rc = speed_from_xhci[xhci_get_field(portsc, XHCI_PORTSC_SPEED)];
+ break;
+ case PLS_POLLING:
+ portsc |= XHCI_PORTSC_PR;
+ writel(&xhci->pr[port].portsc, portsc);
+ if (wait_bit(&xhci->pr[port].portsc, XHCI_PORTSC_PED, XHCI_PORTSC_PED, 100) != 0)
+ return -1;
+ portsc = readl(&xhci->pr[port].portsc);
+ xhci_print_port_state(__func__, port, portsc);
+ rc = speed_from_xhci[xhci_get_field(portsc, XHCI_PORTSC_SPEED)];
+ break;
+ default:
+ rc = -1;
+ break;
+ }
+
+ xhci_print_port_state(__func__, port, portsc);
+ return rc;
+}
+
+static void
+xhci_hub_disconnect(struct usbhub_s *hub, u32 port)
+{
+ ASSERT32FLAT();
+ struct usb_xhci_s *xhci = container_of(hub->cntl, struct usb_xhci_s, usb);
+ struct xhci_device *dev;
+
+ for (dev = xhci->list; dev != NULL; dev = dev->next) {
+ if (dev->usbdev->hub == hub &&
+ dev->usbdev->port == port &&
+ dev->slotid != 0) {
+ xhci_cmd_disable_slot(dev);
+ return;
+ }
+ }
+}
+
+static struct usbhub_op_s xhci_hub_ops = {
+ .detect = xhci_hub_detect,
+ .reset = xhci_hub_reset,
+ .disconnect = xhci_hub_disconnect,
+};
+
+// --------------------------------------------------------------
+// external interface
+
+struct usb_pipe *
+xhci_alloc_pipe(struct usbdevice_s *usbdev
+ , struct usb_endpoint_descriptor *epdesc)
+{
+ ASSERT32FLAT();
+ if (!CONFIG_USB_XHCI)
+ return NULL;
+ u8 eptype = epdesc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+ struct usb_xhci_s *xhci = container_of(
+ usbdev->hub->cntl, struct usb_xhci_s, usb);
+ struct xhci_pipe *pipe;
+ u32 epid;
+
+ if (epdesc->bEndpointAddress == 0) {
+ epid = 1;
+ } else {
+ epid = (epdesc->bEndpointAddress & 0x0f) * 2;
+ epid += (epdesc->bEndpointAddress & USB_DIR_IN) ? 1 : 0;
+ }
+
+#if 1
+ /* dirty hack time: try to dig our pipe out of the freelist */
+ struct usb_pipe **pfree = &xhci->usb.freelist;
+ for (;;) {
+ struct usb_pipe *upipe = *pfree;
+ if (!upipe)
+ break;
+ pipe = container_of(upipe, struct xhci_pipe, pipe);
+ if (pipe->dev->usbdev == usbdev &&
+ pipe->epid == epid) {
+ *pfree = upipe->freenext;
+ upipe->cntl = &xhci->usb;
+ return &pipe->pipe;
+ }
+ pfree = &upipe->freenext;
+ }
+#endif
+
+ if (eptype == USB_ENDPOINT_XFER_CONTROL)
+ pipe = memalign_high(64, sizeof(*pipe));
+ else
+ pipe = memalign_low(64, sizeof(*pipe));
+ if (!pipe) {
+ warn_noalloc();
+ return NULL;
+ }
+ memset(pipe, 0, sizeof(*pipe));
+
+ usb_desc2pipe(&pipe->pipe, usbdev, epdesc);
+ pipe->dev = xhci_find_alloc_device(xhci, usbdev);
+ if (!pipe->dev) {
+ free(pipe);
+ return NULL;
+ }
+ pipe->epid = epid;
+ pipe->rs.cs = 1;
+
+ if (pipe->epid > 1 && pipe->dev->slotid) {
+ struct xhci_inctx *in = &pipe->dev->inctx;
+ in->add = (1 << pipe->epid) | 1;
+ in->del = 0;
+
+ int e = pipe->epid-1;
+ in->ep[e].ctx[1] |= (eptype << 3);
+ if (epdesc->bEndpointAddress & USB_DIR_IN)
+ in->ep[e].ctx[1] |= (1 << 5);
+ in->ep[e].ctx[1] |= (pipe->pipe.maxpacket << 16);
+ in->ep[e].deq_low = (u32)&pipe->ring[0];
+ in->ep[e].deq_low |= 1; // dcs
+ in->ep[e].deq_high = 0;
+ in->ep[e].length = pipe->pipe.maxpacket;
+
+ int cc = xhci_cmd_configure_endpoint(pipe->dev);
+ if (cc != CC_SUCCESS) {
+ dprintf(1, "%s: configure endpoint: failed (cc %d)\n", __func__, cc);
+ free(pipe);
+ return NULL;
+ }
+ }
+
+ return &pipe->pipe;
+}
+
+int
+xhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
+ , void *data, int datalen)
+{
+ ASSERT32FLAT();
+ if (!CONFIG_USB_XHCI)
+ return -1;
+ const struct usb_ctrlrequest *req = cmd;
+ struct xhci_pipe *pipe = container_of(p, struct xhci_pipe, pipe);
+ struct usb_xhci_s *xhci = pipe->dev->xhci;
+ struct xhci_trb evt;
+ int cc;
+
+ if (req->bRequest == USB_REQ_SET_ADDRESS) {
+ int slotid = xhci_cmd_enable_slot(xhci);
+ if (slotid < 0) {
+ dprintf(1, "%s: enable slot: failed\n", __func__);
+ return -1;
+ }
+ dprintf(1, "%s: enable slot: got slotid %d\n", __func__, slotid);
+ pipe->dev->slotid = slotid;
+ xhci->devs[slotid].ptr_low = (u32)&pipe->dev->devctx;
+ xhci->devs[slotid].ptr_high = 0;
+
+ struct xhci_inctx *in = &pipe->dev->inctx;
+ in->add = 0x03;
+ in->slot.ctx[0] |= (31 << 27); // context entries
+ in->slot.ctx[0] |= speed_to_xhci[pipe->dev->usbdev->speed] << 20;
+ /* TODO ctx0: hub bit, route string */
+ in->slot.ctx[1] |= (pipe->dev->usbdev->port+1) << 16;
+ /* TODO ctx1: hub ports */
+
+ in->ep[0].ctx[1] |= (4 << 3); // control pipe
+ in->ep[0].ctx[1] |= (pipe->pipe.maxpacket << 16);
+ in->ep[0].deq_low = (u32)&pipe->ring[0];
+ in->ep[0].deq_low |= 1; // dcs
+ in->ep[0].deq_high = 0;
+ in->ep[0].length = pipe->pipe.maxpacket;
+
+ cc = xhci_cmd_address_device(pipe->dev);
+ if (cc != CC_SUCCESS) {
+ dprintf(1, "%s: address device: failed (cc %d)\n", __func__, cc);
+ return -1;
+ }
+ return 0;
+ }
+
+ xhci_xfer_setup(pipe, req, dir, datalen);
+ if (datalen)
+ xhci_xfer_data(pipe, dir, data, datalen);
+ xhci_xfer_status(pipe, dir);
+
+ cc = xhci_event_wait(xhci, &evt, ER_TRANSFER, 100);
+ if (cc != CC_SUCCESS) {
+ dprintf(1, "%s: control xfer failed (cc %d)\n", __func__, cc);
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+xhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datalen)
+{
+ if (!CONFIG_USB_XHCI)
+ return -1;
+
+ struct xhci_pipe *pipe = container_of(p, struct xhci_pipe, pipe);
+ struct xhci_device *dev = GET_LOWFLAT(pipe->dev);
+ struct usb_xhci_s *xhci = GET_LOWFLAT(dev->xhci);
+
+ xhci_xfer_normal(pipe, data, datalen);
+ int cc = xhci_event_wait(xhci, NULL, ER_TRANSFER, 1000);
+ if (cc != CC_SUCCESS) {
+ dprintf(1, "%s: bulk xfer failed (cc %d)\n", __func__, cc);
+ return -1;
+ }
+ return 0;
+}
+
+int
+xhci_poll_intr(struct usb_pipe *p, void *data)
+{
+ if (!CONFIG_USB_XHCI)
+ return -1;
+ dprintf(1, "%s: FIXME\n", __func__);
+ return -1;
+}
+
+int
+xhci_setup(struct pci_device *pci, int busid)
+{
+ ASSERT32FLAT();
+ if (!CONFIG_USB_XHCI)
+ return -1;
+
+ struct usb_xhci_s *xhci = malloc_low(sizeof(*xhci));
+ if (!xhci) {
+ warn_noalloc();
+ return -1;
+ }
+ memset(xhci, 0, sizeof(*xhci));
+
+ xhci->baseaddr = pci_config_readl(pci->bdf, PCI_BASE_ADDRESS_0)
+ & PCI_BASE_ADDRESS_MEM_MASK;
+ xhci->caps = (void*)(xhci->baseaddr);
+ xhci->op = (void*)(xhci->baseaddr + readb(&xhci->caps->caplength));
+ xhci->pr = (void*)(xhci->baseaddr + readb(&xhci->caps->caplength) + 0x400);
+ xhci->db = (void*)(xhci->baseaddr + readl(&xhci->caps->dboff));
+ xhci->ir = (void*)(xhci->baseaddr + readl(&xhci->caps->rtsoff) + 0x20);
+
+ u32 hcs1 = readl(&xhci->caps->hcsparams1);
+ u32 hcc = readl(&xhci->caps->hccparams);
+ xhci->ports = (hcs1 >> 24) & 0xff;
+ xhci->slots = hcs1 & 0xff;
+ xhci->xcap = ((hcc >> 16) & 0xffff) << 2;
+
+ xhci->usb.busid = busid;
+ xhci->usb.pci = pci;
+ xhci->usb.type = USB_TYPE_XHCI;
+ xhci->hub.cntl = &xhci->usb;
+ xhci->hub.portcount = xhci->ports;
+ xhci->hub.op = &xhci_hub_ops;
+
+ dprintf(1, "XHCI init on dev %02x:%02x.%x: regs @ %p, %d ports, %d slots\n"
+ , pci_bdf_to_bus(pci->bdf), pci_bdf_to_dev(pci->bdf)
+ , pci_bdf_to_fn(pci->bdf), xhci->caps
+ , xhci->ports, xhci->slots);
+
+ if (xhci->xcap) {
+ u32 off, addr = xhci->baseaddr + xhci->xcap;
+ do {
+ struct xhci_xcap *xcap = (void*)addr;
+ u32 ports, name, cap = readl(&xcap->cap);
+ switch (cap & 0xff) {
+ case 0x02:
+ name = readl(&xcap->data[0]);
+ ports = readl(&xcap->data[1]);
+ dprintf(1, " protocol %c%c%c%c %x.%02x, %d ports (offset %d)\n"
+ , (name >> 0) & 0xff
+ , (name >> 8) & 0xff
+ , (name >> 16) & 0xff
+ , (name >> 24) & 0xff
+ , (cap >> 24) & 0xff
+ , (cap >> 16) & 0xff
+ , (ports >> 8) & 0xff
+ , (ports >> 0) & 0xff);
+ break;
+ default:
+ dprintf(1, " extcap 0x%x @ %x\n", cap & 0xff, addr);
+ break;
+ }
+ off = (cap >> 8) & 0xff;
+ addr += off << 2;
+ } while (off > 0);
+ }
+
+ pci_config_maskw(pci->bdf, PCI_COMMAND, 0, PCI_COMMAND_MASTER);
+
+ run_thread(configure_xhci, xhci);
+ return 0;
+}
diff --git a/src/usb-xhci.h b/src/usb-xhci.h
new file mode 100644
index 0000000..a4f3f7d
--- /dev/null
+++ b/src/usb-xhci.h
@@ -0,0 +1,141 @@
+#ifndef __USB_XHCI_H
+#define __USB_XHCI_H
+
+struct usbdevice_s;
+struct usb_endpoint_descriptor;
+struct usb_pipe;
+
+// --------------------------------------------------------------
+
+// usb-xhci.c
+int xhci_setup(struct pci_device *pci, int busid);
+struct usb_pipe *xhci_alloc_pipe(struct usbdevice_s *usbdev
+ , struct usb_endpoint_descriptor *epdesc);
+int xhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
+ , void *data, int datasize);
+int xhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize);
+int xhci_poll_intr(struct usb_pipe *p, void *data);
+
+// --------------------------------------------------------------
+// register interface
+
+// capabilities
+struct xhci_caps {
+ u8 caplength;
+ u8 reserved_01;
+ u16 hciversion;
+ u32 hcsparams1;
+ u32 hcsparams2;
+ u32 hcsparams3;
+ u32 hccparams;
+ u32 dboff;
+ u32 rtsoff;
+} PACKED;
+
+// extended capabilities
+struct xhci_xcap {
+ u32 cap;
+ u32 data[];
+} PACKED;
+
+// operational registers
+struct xhci_op {
+ u32 usbcmd;
+ u32 usbsts;
+ u32 pagesize;
+ u32 reserved_01[2];
+ u32 dnctl;
+ u32 crcr_low;
+ u32 crcr_high;
+ u32 reserved_02[4];
+ u32 dcbaap_low;
+ u32 dcbaap_high;
+ u32 config;
+} PACKED;
+
+// port registers
+struct xhci_pr {
+ u32 portsc;
+ u32 portpmsc;
+ u32 portli;
+ u32 reserved_01;
+} PACKED;
+
+// doorbell registers
+struct xhci_db {
+ u32 doorbell;
+} PACKED;
+
+// runtime registers
+struct xhci_rts {
+ u32 mfindex;
+} PACKED;
+
+// interrupter registers
+struct xhci_ir {
+ u32 iman;
+ u32 imod;
+ u32 erstsz;
+ u32 reserved_01;
+ u32 erstba_low;
+ u32 erstba_high;
+ u32 erdp_low;
+ u32 erdp_high;
+} PACKED;
+
+// --------------------------------------------------------------
+// memory data structs
+
+// slot context
+struct xhci_slotctx {
+ u32 ctx[4];
+ u32 reserved_01[4];
+} PACKED;
+
+// endpoint context
+struct xhci_epctx {
+ u32 ctx[2];
+ u32 deq_low;
+ u32 deq_high;
+ u32 length;
+ u32 reserved_01[3];
+} PACKED;
+
+// device context
+struct xhci_devctx {
+ struct xhci_slotctx slot;
+ struct xhci_epctx ep[31];
+} PACKED;
+
+// device context array element
+struct xhci_devlist {
+ u32 ptr_low;
+ u32 ptr_high;
+} PACKED;
+
+// input context
+struct xhci_inctx {
+ u32 del;
+ u32 add;
+ u32 reserved_01[6];
+ struct xhci_slotctx slot;
+ struct xhci_epctx ep[31];
+} PACKED;
+
+// transfer block (ring element)
+struct xhci_trb {
+ u32 ptr_low;
+ u32 ptr_high;
+ u32 status;
+ u32 control;
+} PACKED;
+
+// event ring segment
+struct xhci_er_seg {
+ u32 ptr_low;
+ u32 ptr_high;
+ u32 size;
+ u32 reserved_01;
+} PACKED;
+
+#endif // usb-xhci.h
diff --git a/src/usb.c b/src/usb.c
index 6e43f13..b1841dc 100644
--- a/src/usb.c
+++ b/src/usb.c
@@ -12,6 +12,7 @@
#include "usb-uhci.h" // uhci_setup
#include "usb-ohci.h" // ohci_setup
#include "usb-ehci.h" // ehci_setup
+#include "usb-xhci.h" // xhci_setup
#include "usb-hid.h" // usb_keyboard_setup
#include "usb-hub.h" // usb_hub_setup
#include "usb-msc.h" // usb_msc_setup
@@ -37,6 +38,8 @@ usb_alloc_pipe(struct usbdevice_s *usbdev
return ohci_alloc_pipe(usbdev, epdesc);
case USB_TYPE_EHCI:
return ehci_alloc_pipe(usbdev, epdesc);
+ case USB_TYPE_XHCI:
+ return xhci_alloc_pipe(usbdev, epdesc);
}
}
@@ -54,6 +57,8 @@ send_control(struct usb_pipe *pipe, int dir, const void *cmd, int cmdsize
return ohci_control(pipe, dir, cmd, cmdsize, data, datasize);
case USB_TYPE_EHCI:
return ehci_control(pipe, dir, cmd, cmdsize, data, datasize);
+ case USB_TYPE_XHCI:
+ return xhci_control(pipe, dir, cmd, cmdsize, data, datasize);
}
}
@@ -68,6 +73,8 @@ usb_send_bulk(struct usb_pipe *pipe_fl, int dir, void *data, int datasize)
return ohci_send_bulk(pipe_fl, dir, data, datasize);
case USB_TYPE_EHCI:
return ehci_send_bulk(pipe_fl, dir, data, datasize);
+ case USB_TYPE_XHCI:
+ return xhci_send_bulk(pipe_fl, dir, data, datasize);
}
}
@@ -82,6 +89,8 @@ usb_poll_intr(struct usb_pipe *pipe_fl, void *data)
return ohci_poll_intr(pipe_fl, data);
case USB_TYPE_EHCI:
return ehci_poll_intr(pipe_fl, data);
+ case USB_TYPE_XHCI:
+ return xhci_poll_intr(pipe_fl, data);
}
}
@@ -457,5 +466,7 @@ usb_setup(void)
uhci_setup(pci, count++);
else if (pci_classprog(pci) == PCI_CLASS_SERIAL_USB_OHCI)
ohci_setup(pci, count++);
+ else if (pci_classprog(pci) == PCI_CLASS_SERIAL_USB_XHCI)
+ xhci_setup(pci, count++);
}
}
diff --git a/src/usb.h b/src/usb.h
index a43e829..51561ac 100644
--- a/src/usb.h
+++ b/src/usb.h
@@ -60,15 +60,17 @@ struct usbhub_op_s {
void (*disconnect)(struct usbhub_s *hub, u32 port);
};
-#define USB_TYPE_UHCI 1
-#define USB_TYPE_OHCI 2
-#define USB_TYPE_EHCI 3
+#define USB_TYPE_UHCI 1
+#define USB_TYPE_OHCI 2
+#define USB_TYPE_EHCI 3
+#define USB_TYPE_XHCI 4
-#define USB_FULLSPEED 0
-#define USB_LOWSPEED 1
-#define USB_HIGHSPEED 2
+#define USB_FULLSPEED 0
+#define USB_LOWSPEED 1
+#define USB_HIGHSPEED 2
+#define USB_SUPERSPEED 3
-#define USB_MAXADDR 127
+#define USB_MAXADDR 127
/****************************************************************
--
1.7.9.7