From 8e07aae245c3e39303ae0395912e98e7bde9ebb2 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 15 Feb 2015 21:11:08 +0100 Subject: [PATCH 01/22] Start of xHCI driver for USB --- Makefile | 25 +++- cuser/usb/common.h | 1 + cuser/usb/main.c | 16 +++ cuser/usb/xhci.c | 303 +++++++++++++++++++++++++++++++++++++++++++++ mkgrubcfg.sh | 13 ++ 5 files changed, 353 insertions(+), 5 deletions(-) create mode 100644 cuser/usb/common.h create mode 100644 cuser/usb/main.c create mode 100644 cuser/usb/xhci.c diff --git a/Makefile b/Makefile index e346662..e7b35f9 100644 --- a/Makefile +++ b/Makefile @@ -53,10 +53,16 @@ ASMFILES := kstart.asm $(MOD_ASMFILES) MOD_CFILES := cuser/helloworld.c cuser/physmem.c cuser/zeropage.c MOD_CFILES += cuser/test_maps.c cuser/e1000.c cuser/apic.c cuser/timer_test.c MOD_CFILES += cuser/bochsvga.c cuser/fbtest.c cuser/acpi_debugger.c +MOD_CFILES += cuser/usb/xhci.c MOD_OFILES := $(MOD_CFILES:%.c=$(OUTDIR)/%.o) MOD_ELFS := $(MOD_CFILES:%.c=$(OUTDIR)/%.elf) -MOD_ELFS += $(OUTDIR)/cuser/acpica.elf $(OUTDIR)/cuser/lwip.elf -MODFILES := $(MOD_ASMFILES:%.asm=$(GRUBDIR)/%.mod) $(MOD_CFILES:%.c=$(GRUBDIR)/%.mod) $(GRUBDIR)/cuser/acpica.mod $(GRUBDIR)/cuser/lwip.mod +BIG_MODS := acpica lwip usb +MOD_ELFS += $(BIG_MODS:%=$(OUTDIR)/cuser/%.elf) + +MODFILES := $(MOD_ASMFILES:%.asm=$(GRUBDIR)/%.mod) +MODFILES += $(MOD_CFILES:%.c=$(GRUBDIR)/%.mod) +MODFILES += $(BIG_MODS:%=$(GRUBDIR)/cuser/%.mod) + DEPFILES := $(ASMFILES:%.asm=$(OUTDIR)/%.d) $(MOD_OFILES:.o=.d) ASMOUTS := \ $(GRUBDIR)/kstart.b \ @@ -66,7 +72,7 @@ ASMOUTS := \ $(DEPFILES) all: cpuid rflags $(OUTDIR)/grub.iso -all: $(MOD_ELFS) +all: $(MOD_ELFS) $(MODFILES) .SECONDARY: $(ASMFILES:%.asm=$(OUTDIR)/%.b) $(MOD_OFILES) @@ -161,7 +167,7 @@ $(OUTDIR)/%.elf: cuser/linker.ld $(OUTDIR)/%.o WANT_PRINTF = test_maps zeropage WANT_PRINTF += timer_test -WANT_REAL_PRINTF = e1000 apic bochsvga fbtest +WANT_REAL_PRINTF = e1000 apic bochsvga fbtest usb/xhci WANT_STRING = acpica @@ -315,7 +321,16 @@ $(OUTDIR)/cuser/lwip.elf: cuser/linker.ld $(LWIP_DEP_OBJS) @mkdir -p $(@D) $(HUSH_LD) $(LD) $(USER_LDFLAGS) -o $@ -T $^ -all: $(GRUBDIR)/cuser/lwip.mod +USB_SRCS := $(USB)/main.c +USB_OBJS := $(USB_SRCS:%.c:$(OUTDIR)/%.o) + +-include $(USB_OBJS:.o=.d) + +USB_DEP_OBJS := $(USB_OBJS) $(OUTDIR)/cuser/acpica/printf.o $(OUTDIR)/cuser/acpica/source/components/utilities/utclib.o + +$(OUTDIR)/cuser/usb.elf: cuser/linker.ld $(USB_DEP_OBJS) + @mkdir -p $(@D) + $(HUSH_LD) $(LD) $(USER_LDFLAGS) -o $@ -T $^ yasm/yasm: yasm/Makefile $(MAKE) -C yasm diff --git a/cuser/usb/common.h b/cuser/usb/common.h new file mode 100644 index 0000000..1898d06 --- /dev/null +++ b/cuser/usb/common.h @@ -0,0 +1 @@ +#include "../common.h" diff --git a/cuser/usb/main.c b/cuser/usb/main.c new file mode 100644 index 0000000..b0599ee --- /dev/null +++ b/cuser/usb/main.c @@ -0,0 +1,16 @@ +#include "common.h" + +#define log printf +#if 1 +#define debug log +#else +#define debug(...) (void)0 +#endif + +// "main" USB driver - this talks to both host controllers and USB device +// drivers and manages communication between them. + +void start() +{ + for (;;); +} diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c new file mode 100644 index 0000000..9169f9c --- /dev/null +++ b/cuser/usb/xhci.c @@ -0,0 +1,303 @@ +#include "common.h" + +#define log(fmt, ...) printf("xhci: " fmt, ## __VA_ARGS__) +#if 1 +#define debug log +#else +#define debug(...) (void)0 +#endif + +static const uintptr_t acpi_handle = 4; +static const uintptr_t pic_handle = 2; +static const uintptr_t pin0_irq_handle = 0x100; +static const uintptr_t fresh = 0x101; + +static volatile u8 mmiospace[128 * 1024] PLACEHOLDER_SECTION ALIGN(128*1024); +// NB: Byte offsets +enum HostCntrlCapRegs +{ + // u8 + CAPLENGTH = 0, + // u8 rsvd + // u16 + HCIVERSION = 2, + // u32 + HCSPARAMS1 = 4, + HCSPARAMS2 = 8, + HCSPARAMS3 = 0x0c, + HCCPARAMS1 = 0x10, + // Doorbell Offset. Bits 1:0 are reserved, so with those masked out you + // have a byte offset, alternatively bits 31:2 are a dword offset. + DBOFF = 0x14, + // Runtime Register Space Offset, bits 4:0 are reserved, bits 31:5 are + // a 32-byte-aligned offset from mmiobase. + RTSOFF = 0x18, + HCCPARAMS2 = 0x1c, +}; +enum HCCP1Bits +{ + HCCP1_AC64 = 1, + // Plus others... I'm not sure those are interesting. +}; +// Offset into mmiospace based on values in capability registers +static volatile u32* opregs; +// Note: dword offsets +enum OperationalRegisters +{ + USBCMD = 0, + USBSTS, + PAGESIZE, + // 3,4: RsvdZ + DNCTRL = 5, + CRCR = 6, // u64 + CRCRH = 7, + // 8..11, RsvdZ + DCBAAP = 12, + DCBAAPH, + CONFIG, + // RsvdZ up to 0x400 + PORTREGSET = 256, +}; +enum PortStatusRegisters +{ + PSCR = 0, + PORTPMSC = 1, + PORTLI = 2, + PORTHLPMC = 3, + NUMREGS = 4, // Number of dwords per port - 0x10 bytes => 4 dwords. +}; +static volatile u32* doorbells; +static volatile u32* runtimeregs; +enum RuntimeRegs +{ + MFINDEX = 0, +}; +static volatile u32* interruptregs; + +static u32 read_mmio32(unsigned byte_offset) +{ + return *(volatile u32*)(mmiospace + byte_offset); +} +static u16 read_mmio16(unsigned byte_offset) +{ + u32 res = read_mmio32(byte_offset & -4); + if (byte_offset & 2) { + res >>= 16; + } + return res; +} + +struct command_trb +{ + u64 address; + u32 status; + u32 command; +}; +static struct command_trb command_ring[256] PLACEHOLDER_SECTION ALIGN(4096); + +enum EcpCapIds +{ + CAPID_SUPPORTED_PROTOCOL = 2, +}; +static void iterate_ecps(volatile const u32* ecp) +{ + while (ecp) + { + u32 ecpr = ecp[0]; + u8 capid = ecpr & 0xff; + u8 next = (ecpr >> 8) & 0xff; + switch (capid) + { + case CAPID_SUPPORTED_PROTOCOL: + { + u64 nm = ecp[1]; + u32 ports = ecp[2]; + debug("%p: Supported protocol %s %x.%x, %u ports at %u\n", ecp, + &nm, ecpr >> 24, (ecpr >> 16) & 0xff, + (ports >> 8) & 0xff, ports & 0xff); + break; + } + default: + debug("Found cap %u at %p\n", capid, ecp); + } + ecp = next ? ecp + next : NULL; + } +} + +// xHCI host controller driver. Talks to "the USB system" and provides +// communication between USB class drivers and the devices. + +/* Random Notes... + * + * - device connected: + * 1. Enable Slot to get a Slot ID + * 2. Put a Device Context at the provided index + * 3. Address Device to signal finished Device Context and to give the USB + * device its address. + * * Address Device TRB points to an "Input Context" + * + * - device configuration: + * SET_CONFIG/SET_INTERFACE must be preceded by a Configure Endpoint command, + * that tells the xhci which endpoints will be active, and to confirm that + * bandwidth and resources are available. + * + * - what are scratchpad buffers? + */ + +u32 readpci32(u32 addr, u8 reg) +{ + assert(!(reg & 3)); + uintptr_t arg = addr << 8 | reg; + sendrcv1(MSG_ACPI_READ_PCI, acpi_handle, &arg); + return arg; +} + +u16 readpci16(u32 addr, u8 reg) +{ + assert(!(reg & 1)); + u32 val = readpci32(addr, reg & 0xfc); + if (reg & 2) { + val >>= 16; + } + return val; +} + +u8 readpci8(u32 addr, u8 reg) +{ + u16 val = readpci16(addr, reg & 0xfe); + if (reg & 1) { + val >>= 8; + } + return val; +} + +static void handle_irq() +{ + log("IRQ"); +} + +void start() +{ + // FIXME So much copy-pasta from xhci.c here - extract more PCI utility + // functions somewhere. + __default_section_init(); + + uintptr_t arg; + log("looking for PCI device...\n"); + // Serial bus controller (0x0c), USB controller (0x03), + // USB 3.0 controller (0x30) => xHCI controller. + arg = (u64)0x0c0330 << 32; + sendrcv1(MSG_ACPI_FIND_PCI, acpi_handle, &arg); + if (arg == ACPI_PCI_NOT_FOUND) { + log("No devices found\n"); + goto fail; + } + log("found %x\n", arg); + // bus << 8 | dev << 3 | func + const uintptr_t pci_id = arg; + uintptr_t arg2 = ACPI_PCI_CLAIM_MASTER | 1; // Just claim pin 0 + sendrcv2(MSG_ACPI_CLAIM_PCI, acpi_handle, &arg, &arg2); + if (!arg) { + log("failed :(\n"); + goto fail; + } + const u8 irq = arg2 &= 0xff; + log("claimed! irq %x\n", irq); + hmod(pic_handle, pic_handle, pin0_irq_handle); + sendrcv1(MSG_REG_IRQ, pin0_irq_handle, &arg2); + + u32 cmd = readpci16(pci_id, PCI_COMMAND); + debug("PCI CMD: %04x master=%d\n", cmd, !!(cmd & PCI_COMMAND_MASTER)); + //writepci16(pci_id, PCI_COMMAND, cmd | PCI_COMMAND_MASTER); + + u32 bar0 = readpci32(pci_id, PCI_BAR_0); + debug("BAR 0: %s %s %s: %x\n", + bar0 & 1 ? "io" : "mem", + (bar0 >> 1) & 3 ? "64-bit" : "32-bit", + (bar0 >> 3) & 1 ? "prefetchable" : "non-prefetchable", + bar0 & ~0xf); + uintptr_t mmiobase = bar0 & ~0xf; + if (((bar0 >> 1) & 3) == 2) // 64-bit + { + uintptr_t bar1 = readpci32(pci_id, PCI_BAR_1); + debug("BAR0 was 64-bit, adding %x:%x.\n", bar1, mmiobase); + mmiobase |= bar1 << 32; + } + debug("Mapping mmiospace %p to BAR %p\n", (void*)mmiospace, mmiobase); + map(0, MAP_PHYS | PROT_READ | PROT_WRITE | PROT_NO_CACHE, + (void*)mmiospace, mmiobase, sizeof(mmiospace)); + + u8 sbrn = readpci8(pci_id, 0x60); + debug("SBRN is %#x\n", sbrn); + + u8 caplength = mmiospace[CAPLENGTH]; + debug("Capabilities length %u\n", caplength); + opregs = (u32*)(mmiospace + caplength); + doorbells = (u32*)(mmiospace + (read_mmio32(DBOFF) & -4)); + runtimeregs = (u32*)(mmiospace + (read_mmio32(RTSOFF) & -32)); + interruptregs = runtimeregs + (0x20 / 4); + debug("Operational regs at %p, doorbells at %p, runtimeregs at %p\n", + opregs, doorbells, runtimeregs); + debug("HCI version %#x\n", read_mmio16(HCIVERSION)); + + u32 hcsparams1 = read_mmio32(HCSPARAMS1); + u8 device_maxports = hcsparams1 & 0xff; + debug("Max ports/slots/intrs: %u/%u/%u\n", device_maxports, + (hcsparams1 >> 24) & 0xff, (hcsparams1 >> 8) & 0x7ff); + u32 hcsparams2 = read_mmio32(HCSPARAMS2); + u32 max_scratchpads = + ((hcsparams2 >> (21 - 5)) & (0x1f << 5)) | ((hcsparams2 >> 27) & 0x1f); + debug("Max Scratchpad Buffers = %u\n", max_scratchpads); + debug("IST: %u %s\n", hcsparams2 & 7, hcsparams2 & 8 ? "Frames" : "Microframes"); + u32 hccparams1 = read_mmio32(HCCPARAMS1); + debug("hccparams1: %#x\n", hccparams1 & 0xffff); + assert(hccparams1 & HCCP1_AC64); + u16 xECP = hccparams1 >> 16; + debug("xECP: %#x (%p)\n", xECP, mmiospace + (xECP * 4)); + iterate_ecps((u32*)mmiospace + xECP); + + // Initialization - section 4.2 of xhci reference + + // 1. wait until the Controller Not Ready (CNR) flag in the USBSTS is ‘0’ + // before writing any xHC Operational or Runtime registers. + + // 2. Program the Max Device Slots Enabled + // TODO We probably need to allocate structures for these somewhere. + opregs[CONFIG] = device_maxports; + + // 3. Program the Device Context Base Address Array Pointer + + // 4. Define the Command Ring Dequeue Pointer by programming the Command + // Ring Control Register (5.4.5) with a 64-bit address pointing to the + // starting address of the first TRB of the Command Ring. + + // 5. Initialize interrupters (requires(?) MSI) + + // 6. Write the USBCMD (5.4.1) to turn the host controller ON via setting + // the Run/Stop (R/S) bit to ‘1’. This operation allows the xHC to begin + // accepting doorbell references. + + for(;;) { + uintptr_t rcpt = fresh; + arg = 0; + arg2 = 0; + uintptr_t msg = recv2(&rcpt, &arg, &arg2); + debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); + if (rcpt == pin0_irq_handle && msg == MSG_PULSE) { + // Disable all interrupts, then ACK receipt to PIC + send1(MSG_IRQ_ACK, rcpt, arg); + handle_irq(); + continue; + } + + switch (msg & 0xff) + { + case MSG_PFAULT: { + // TODO + break; + } + } + } +fail: + abort(); +} diff --git a/mkgrubcfg.sh b/mkgrubcfg.sh index 8163a63..51099a5 100644 --- a/mkgrubcfg.sh +++ b/mkgrubcfg.sh @@ -23,6 +23,19 @@ menuentry "irq+pic+console+shell" { boot } +menuentry "USB" { + multiboot /$kernel + module /kern/irq.mod irq + module /kern/pic.mod pic + module /kern/console.mod console + module /cuser/acpica.mod acpica + # e1000? + module /cuser/apic.mod apic + module /cuser/usb USB + module /cuser/usb/xhci.mod xHCI + boot +} + menuentry "lwIP" { multiboot /$kernel module /kern/irq.mod irq From 14ad20dbcc7b371979ac6428f79d67684b9f3ea2 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 15 Feb 2015 21:15:26 +0100 Subject: [PATCH 02/22] ACPICA/PCI code for finding devices by class --- cuser/acpica/acpica.c | 9 +++++-- cuser/acpica/acpica.h | 1 + cuser/acpica/pci.c | 57 ++++++++++++++++++++++++++++--------------- cuser/common.h | 10 ++++---- 4 files changed, 51 insertions(+), 26 deletions(-) diff --git a/cuser/acpica/acpica.c b/cuser/acpica/acpica.c index 53df181..b45950e 100644 --- a/cuser/acpica/acpica.c +++ b/cuser/acpica/acpica.c @@ -460,11 +460,16 @@ static void MsgFindPci(uintptr_t rcpt, uintptr_t arg) u16 vendor = arg >> 16; u16 device = arg; uintptr_t addr = -1; - printf("acpica: find pci %#x:%#x.\n", vendor, device); - ACPI_STATUS status = FindPCIDevByVendor(vendor, device, &temp); + ACPI_STATUS status; + if (vendor && device) { + status = FindPCIDevByVendor(vendor, device, &temp); + } else { + status = FindPCIDevByClass(arg >> 32, &temp); + } if (ACPI_SUCCESS(status)) { addr = temp.Bus << 16 | temp.Device << 3 | temp.Function; } + send1(MSG_ACPI_FIND_PCI, rcpt, addr); } diff --git a/cuser/acpica/acpica.h b/cuser/acpica/acpica.h index 194fa89..dfc9420 100644 --- a/cuser/acpica/acpica.h +++ b/cuser/acpica/acpica.h @@ -13,6 +13,7 @@ UINT32 PciReadWord(UINT32 Addr); UINT32 AddrFromPciId(ACPI_PCI_ID* PciId, UINT32 Register); ACPI_STATUS FindPCIDevByVendor(u16 vendor, u16 device, ACPI_PCI_ID* id); +ACPI_STATUS FindPCIDevByClass(u32 classcode, ACPI_PCI_ID* id); void init_heap(void); diff --git a/cuser/acpica/pci.c b/cuser/acpica/pci.c index f2ae776..9fd51fa 100644 --- a/cuser/acpica/pci.c +++ b/cuser/acpica/pci.c @@ -14,21 +14,21 @@ typedef struct PCIEnum { // Return AE_CTRL_TERMINATE to stop iterating ACPI_STATUS (*cb)(struct PCIEnum* context); - union { - struct { u16 vendor, device; }; + struct { + u16 vendor, device; + u32 class; } in; struct { ACPI_PCI_ID pci_id; u16 vendor; u16 device; + u32 class; } cur; } PCIEnum; #define getVendorID(b,d,f) getPCIConfig(b,d,f, 0, 16) #define getDeviceID(b,d,f) getPCIConfig(b,d,f, 2, 16) #define getHeaderType(b,d,f) getPCIConfig(b,d,f, 14, 8) -#define getSubClass(b,d,f) getPCIConfig(b,d,f, 10, 8) -#define getBaseClass(b,d,f) getPCIConfig(b,d,f, 11, 8) #define getSecondaryBus(b,d,f) getPCIConfig(b,d,f, 0x19, 8) #define ACPI_RETURN_IF(e) \ @@ -56,8 +56,11 @@ static ACPI_STATUS EnumPCIFunction(u8 bus, u8 dev, u8 func, PCIEnum* cb) { return AE_OK; } u8 headerType = getHeaderType(bus, dev, func); - u8 baseClass = getBaseClass(bus, dev, func); - u8 subClass = getSubClass(bus, dev, func); + u32 classRevision = getPCIConfig(bus, dev, func, 8, 32); + u32 class = classRevision >> 8; + u8 baseClass = (class >> 16) & 0xff; + u8 subClass = (class >> 8) & 0xff; + u8 programInterface = class & 0xff; u16 device = getDeviceID(bus, dev, func); ACPI_STATUS status = AE_OK; if (cb) @@ -66,12 +69,14 @@ static ACPI_STATUS EnumPCIFunction(u8 bus, u8 dev, u8 func, PCIEnum* cb) { cb->cur.pci_id = id; cb->cur.vendor = vendor; cb->cur.device = device; + cb->cur.class = class; status = cb->cb(cb); } if (!cb || status == AE_CTRL_TERMINATE) { - printf("%02x:%02x.%x: Found device %#04x:%#04x class %#x:%#x\n", - bus, dev, func, vendor, device, baseClass, subClass); + printf("%02x:%02x.%x: Found device %#04x:%#04x class %#x:%#x pi %#x\n", + bus, dev, func, vendor, device, baseClass, subClass, + programInterface); } ACPI_RETURN_IF(status); if (baseClass == 6 && subClass == 4) @@ -105,28 +110,23 @@ ACPI_STATUS EnumeratePCI(void) { } static ACPI_STATUS FindPCIDevCB(PCIEnum* context) { - if (context->cur.vendor == context->in.vendor && - context->cur.device == context->in.device) { + if ((context->cur.vendor == context->in.vendor && + context->cur.device == context->in.device) || + context->cur.class == context->in.class) { return AE_CTRL_TERMINATE; } return AE_OK; } -ACPI_STATUS FindPCIDevByVendor(u16 vendor, u16 device, ACPI_PCI_ID* id) { - PCIEnum cb; - memset(&cb, 0, sizeof(cb)); - cb.cb = FindPCIDevCB; - cb.in.vendor = vendor; - cb.in.device = device; - printf("acpica: Looking for %#04x:%#04x devices...\n", vendor, device); - ACPI_STATUS status = EnumPCIBus(0, &cb); +static ACPI_STATUS FindPCIDev(PCIEnum *cb, ACPI_PCI_ID* id) { + ACPI_STATUS status = EnumPCIBus(0, cb); if (status == AE_OK) { return AE_NOT_FOUND; } else if (status == AE_CTRL_TERMINATE) { - *id = cb.cur.pci_id; + *id = cb->cur.pci_id; return AE_OK; } else @@ -134,3 +134,22 @@ ACPI_STATUS FindPCIDevByVendor(u16 vendor, u16 device, ACPI_PCI_ID* id) { return status; } } + +ACPI_STATUS FindPCIDevByClass(u32 class, ACPI_PCI_ID* id) { + PCIEnum cb; + memset(&cb, 0, sizeof(cb)); + cb.cb = FindPCIDevCB; + cb.in.class = class; + printf("acpica: Looking for %#06x devices...\n", class); + return FindPCIDev(&cb, id); +} + +ACPI_STATUS FindPCIDevByVendor(u16 vendor, u16 device, ACPI_PCI_ID* id) { + PCIEnum cb; + memset(&cb, 0, sizeof(cb)); + cb.cb = FindPCIDevCB; + cb.in.vendor = vendor; + cb.in.device = device; + printf("acpica: Looking for %#04x:%#04x devices...\n", vendor, device); + return FindPCIDev(&cb, id); +} diff --git a/cuser/common.h b/cuser/common.h index f3c7b5e..25ce029 100644 --- a/cuser/common.h +++ b/cuser/common.h @@ -71,13 +71,13 @@ enum msg_irq { enum msg_acpi { /* Find (unclaimed) PCI device. * - * arg1: pci vendor/device - * arg2: index (0..) + * arg1: pci vendor/device or class code + * class code = (arg1 >> 32) & 0xffffff (24-bit class code) + * vendor = (arg >> 16) & 0xffff + * device = arg & 0xffff + * The class code is used for search if both vendor and device are 0. * Returns: * arg1: pci bus/device/function, or -1 if not found - * - * Iterate index upwards to find multiple matching PCI devices until -1 is - * returned. */ MSG_ACPI_FIND_PCI = MSG_USER, /* Wrappers around PCI IRQ routing (to PIC or I/O APIC) */ From 11b05d162255ef626f4ef6926f54e65c366ea124 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 15 Feb 2015 21:16:09 +0100 Subject: [PATCH 03/22] Add an xhci and a USB mouse to qemu configuration --- run_qemu.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run_qemu.sh b/run_qemu.sh index c212626..5b6a073 100755 --- a/run_qemu.sh +++ b/run_qemu.sh @@ -3,4 +3,5 @@ make -j4 || exit $? VGA="-vga std" NETDEV="user,id=vmnet0 -redir tcp:5555::80" #NETDEV=${NETDEV-tap,id=vmnet0,script=no,ifname=tap0,downscript=no} -${KVM-kvm} -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 +USB="-usb -device nec-usb-xhci,id=xhci -usbdevice mouse" +${KVM-kvm} -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 $USB From 99177ed5d6e4e4076efb9100108c87b45ddf179f Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Tue, 17 Feb 2015 21:29:58 +0100 Subject: [PATCH 04/22] Workaround for weird m2c error from Make For some reason, it wants to build .../usb from .../usb.mod using m2c. --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index e7b35f9..f241f4c 100644 --- a/Makefile +++ b/Makefile @@ -86,6 +86,10 @@ clean: %: %.c $(HUSH_CC) $(CC) $(CFLAGS) -o $@ $< +# Disable these builtin pattern rules +%.o: %.mod +%: %.mod + -include $(DEPFILES) $(OUTDIR)/%.d: %.asm $(YASMDEP) From fbda97cef6e67d9098942099cbacf4d3c6db1dc1 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Wed, 18 Feb 2015 22:47:04 +0100 Subject: [PATCH 05/22] xhci: Initialize everything(?), at least it doesn't explode --- cuser/common.h | 4 + cuser/usb/main.c | 1 + cuser/usb/xhci.c | 220 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 216 insertions(+), 9 deletions(-) diff --git a/cuser/common.h b/cuser/common.h index 25ce029..fa4c0a4 100644 --- a/cuser/common.h +++ b/cuser/common.h @@ -585,6 +585,10 @@ enum pci_command_bits PCI_COMMAND_MEMSPACE = 2, PCI_COMMAND_MASTER = 4, }; +enum pci_status_bits +{ + PCI_STATUS_INTERRUPT = 8, +}; /** * A simple (compiler) barrier. Memory writes to volatile variables before the diff --git a/cuser/usb/main.c b/cuser/usb/main.c index b0599ee..5220a64 100644 --- a/cuser/usb/main.c +++ b/cuser/usb/main.c @@ -12,5 +12,6 @@ void start() { + __default_section_init(); for (;;); } diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index 9169f9c..ac82523 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -49,6 +49,7 @@ enum OperationalRegisters PAGESIZE, // 3,4: RsvdZ DNCTRL = 5, + // Command Ring Control Register CRCR = 6, // u64 CRCRH = 7, // 8..11, RsvdZ @@ -58,6 +59,24 @@ enum OperationalRegisters // RsvdZ up to 0x400 PORTREGSET = 256, }; +enum USBCMDBits +{ + USBCMD_Run = 1, + USBCMD_Reset = 2, + USBCMD_IntEnable = 4, +}; +enum USBSTSBits +{ + USBSTS_Halted = 1, + USBSTS_NotReady = 1 << 11, +}; +enum CRCRBits +{ + CRCR_RingCycleState = 1, + CRCR_CommandStop = 2, + CRCR_CommandAbort = 4, + CRCR_CommandRingRunning = 8 +}; enum PortStatusRegisters { PSCR = 0, @@ -72,7 +91,23 @@ enum RuntimeRegs { MFINDEX = 0, }; -static volatile u32* interruptregs; +enum InterruptRegs +{ + IMAN = 0, + IMOD = 1, + ERSTSZ = 2, + // reserved + ERSTBA = 4, + ERSTBAH = 5, + ERDP = 6, + ERDPH = 7, +}; +enum IMANBits +{ + IMAN_IntPending = 1, + IMAN_IntEnabled = 2, +}; +static volatile u32 *interruptregs; static u32 read_mmio32(unsigned byte_offset) { @@ -87,13 +122,102 @@ static u16 read_mmio16(unsigned byte_offset) return res; } -struct command_trb +typedef struct command_trb { u64 address; u32 status; u32 command; +} command_trb; +_Static_assert(sizeof(struct command_trb) == 16, "command TRB size doesn't match spec"); + +typedef struct erse +{ + u64 base; + // number of TRBs in this Event Ring Segment, >= 16, < 4096, rounded up to + // nearest multiple of 4 to make the Event Ring size 64 byte aligned. + u64 size; +} erse; +static erse create_erse(uintptr_t physAddr, u16 ringSize) +{ + erse res; + res.base = physAddr; + res.size = ringSize; + return res; +}; + +struct normal_trb +{ + u64 address; + u16 length; + u8 td_size; + u8 interrupter_target; + u16 flags; + u16 reserved; }; -static struct command_trb command_ring[256] PLACEHOLDER_SECTION ALIGN(4096); +_Static_assert(sizeof(struct normal_trb) == 16, "normal TRB size doesn't match spec"); + +enum TRBTypes +{ + // 0 = reserved + // 1..8: generally transfer ring only + TRB_Normal = 1, + TRB_SetupStage = 2, + TRB_DataStage = 3, + TRB_StatusStage = 4, + TRB_Isoch = 5, + // Link: Command and Transfer but not Event Ring + TRB_Link = 6, + TRB_EventData = 7, + TRB_NoOp = 8, + // 9..23: commmands + TRB_CMD_EnableSlot = 9, + TRB_CMD_DisableSlot = 10, + TRB_CMD_AddressDevice = 11, + TRB_CMD_ConfigureEndpoint = 12, + TRB_CMD_NoOp = 23, + + TRB_PortStatusChange = 34, +}; +enum TRBFlags +{ + TRB_Cycle = 1, + TRB_ToggleC = 2, + TRB_Chain = 1 << 4, + TRB_IOC = 1 << 5, +}; +typedef union trb +{ + struct command_trb cmd; + struct normal_trb normal; + struct { + u64 parameter; + u32 status; + u32 control; + }; +} trb; + +static union trb create_link_trb(u64 address, u8 flags) +{ + union trb res; + res.parameter = address; + res.status = 0; + res.control = flags | (TRB_Link << 10); + return res; +} + +#define COMMAND_RING_SIZE 16 +#define EVENT_RING_SIZE 16 +// The Event Ring Segment Table also has 16-byte entries +#define ERST_SIZE 1 +static struct page1 { + command_trb command_ring[COMMAND_RING_SIZE]; + trb event_ring[EVENT_RING_SIZE]; + erse erst[ERST_SIZE]; +} page1 PLACEHOLDER_SECTION ALIGN(4096); +static volatile struct command_trb *command_enqueue = page1.command_ring; +// The PCS bit to put into created TRBs +static bool command_pcs; +static volatile u64 device_context_addrs[256] PLACEHOLDER_SECTION ALIGN(4096); enum EcpCapIds { @@ -206,9 +330,13 @@ void start() hmod(pic_handle, pic_handle, pin0_irq_handle); sendrcv1(MSG_REG_IRQ, pin0_irq_handle, &arg2); - u32 cmd = readpci16(pci_id, PCI_COMMAND); - debug("PCI CMD: %04x master=%d\n", cmd, !!(cmd & PCI_COMMAND_MASTER)); - //writepci16(pci_id, PCI_COMMAND, cmd | PCI_COMMAND_MASTER); + { + u32 cmd = readpci16(pci_id, PCI_COMMAND); + debug("PCI CMD: %04x master=%d\n", cmd, !!(cmd & PCI_COMMAND_MASTER)); + //writepci16(pci_id, PCI_COMMAND, cmd | PCI_COMMAND_MASTER); + u16 status = readpci16(pci_id, PCI_STATUS); + debug("PCI STATUS: %04x int=%d\n", status, !!(status & PCI_STATUS_INTERRUPT)); + } u32 bar0 = readpci32(pci_id, PCI_BAR_0); debug("BAR 0: %s %s %s: %x\n", @@ -248,6 +376,8 @@ void start() u32 max_scratchpads = ((hcsparams2 >> (21 - 5)) & (0x1f << 5)) | ((hcsparams2 >> 27) & 0x1f); debug("Max Scratchpad Buffers = %u\n", max_scratchpads); + // Scratchpads not supported - need to allocate the Scratchpad Buffer Array + assert(max_scratchpads == 0); debug("IST: %u %s\n", hcsparams2 & 7, hcsparams2 & 8 ? "Frames" : "Microframes"); u32 hccparams1 = read_mmio32(HCCPARAMS1); debug("hccparams1: %#x\n", hccparams1 & 0xffff); @@ -256,31 +386,103 @@ void start() debug("xECP: %#x (%p)\n", xECP, mmiospace + (xECP * 4)); iterate_ecps((u32*)mmiospace + xECP); + // 4kB pages *must* be supported. + assert(opregs[PAGESIZE] & 1); + // Initialization - section 4.2 of xhci reference - // 1. wait until the Controller Not Ready (CNR) flag in the USBSTS is ‘0’ - // before writing any xHC Operational or Runtime registers. + debug("Resetting...\n"); + opregs[USBCMD] = (opregs[USBCMD] & ~USBCMD_Run) | USBCMD_Reset; + unsigned counter = 0; + for (;;) { + u32 status = opregs[USBSTS]; + u32 cmd = opregs[USBCMD]; + debug("Waiting for reset (%u): status %#x cmd %#x\n", counter++, status, cmd); + if (status & USBSTS_NotReady) { + debug("USB status: controller not ready\n"); + } else if (cmd & USBCMD_Reset) { + debug("USB command: reset still set\n"); + } else if (!(status & USBSTS_Halted)) { + debug("USB status: still running\n"); + } else { + debug("Reset complete!\n"); + break; + } + } // 2. Program the Max Device Slots Enabled // TODO We probably need to allocate structures for these somewhere. opregs[CONFIG] = device_maxports; // 3. Program the Device Context Base Address Array Pointer + uintptr_t dcbaaPhysAddr = + (uintptr_t)map(0, MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE, + &device_context_addrs, 0, sizeof(device_context_addrs)); + opregs[DCBAAP] = dcbaaPhysAddr; + opregs[DCBAAPH] = dcbaaPhysAddr >> 32; + // The DCBAA has MaxSlotsEn+1 entries, entry 0 has the scratchpad entries. // 4. Define the Command Ring Dequeue Pointer by programming the Command // Ring Control Register (5.4.5) with a 64-bit address pointing to the // starting address of the first TRB of the Command Ring. + uintptr_t page1p = + (uintptr_t)map(0, MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE, + &page1, 0, sizeof(page1)); + uintptr_t commandRingPhys = page1p + offsetof(struct page1, command_ring); + page1.command_ring[COMMAND_RING_SIZE - 1] = + create_link_trb(commandRingPhys, TRB_ToggleC).cmd; + u32 flags = opregs[CRCR] & 0x38; + // The command ring should be stopped here. + opregs[CRCR] = commandRingPhys | flags | CRCR_RingCycleState; + opregs[CRCRH] = commandRingPhys >> 32; + command_pcs = true; - // 5. Initialize interrupters (requires(?) MSI) + // 5. Initialize interrupters + // The primary interrupter must be initialized before enabling Run, the + // other interrupters may be ignored. I believe because they are not used + // unless something is configured to use that interrupter for notifications. + u32 erstsz = interruptregs[ERSTSZ]; + interruptregs[ERSTSZ] = (erstsz & ~0xffff) | 1; + uintptr_t eventRingPhys = page1p + offsetof(struct page1, event_ring); + page1.erst[0] = create_erse(eventRingPhys, EVENT_RING_SIZE); + uintptr_t erstbaPhys = page1p + offsetof(struct page1, erst); + *(volatile u64 *)(interruptregs + ERDP) = eventRingPhys; + *(volatile u64 *)(interruptregs + ERSTBA) = erstbaPhys; + // Enable the primary interrupter, and clear pending interrupts by writing + // a 1 to the IP bit. + interruptregs[IMAN] = IMAN_IntEnabled | IMAN_IntPending; + // Disable moderation, just spam the interrupts please. + interruptregs[IMOD] = 0; + + __barrier(); // 6. Write the USBCMD (5.4.1) to turn the host controller ON via setting // the Run/Stop (R/S) bit to ‘1’. This operation allows the xHC to begin // accepting doorbell references. + u32 cmd = opregs[USBCMD]; + cmd |= USBCMD_Run | USBCMD_IntEnable; + opregs[USBCMD] = cmd; + + counter = 0; + for (;;) { + u32 status = opregs[USBSTS]; + u32 cmd = opregs[USBCMD]; + debug("Waiting for running state (%u): status %#x cmd %#x\n", counter++, status, cmd); + if (status & USBSTS_NotReady) { + debug("USB status: controller not ready\n"); + } else if (status & USBSTS_Halted) { + debug("USB status: not running yet\n"); + } else { + debug("Initialization complete!\n"); + break; + } + } for(;;) { uintptr_t rcpt = fresh; arg = 0; arg2 = 0; + debug("receiving...\n"); uintptr_t msg = recv2(&rcpt, &arg, &arg2); debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); if (rcpt == pin0_irq_handle && msg == MSG_PULSE) { From 2094943d366fc8414ae0754e3f41136629988aca Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Fri, 20 Feb 2015 10:51:01 +0100 Subject: [PATCH 06/22] Use the correct option format to actually the USB mouse --- run_qemu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_qemu.sh b/run_qemu.sh index 5b6a073..c23b624 100755 --- a/run_qemu.sh +++ b/run_qemu.sh @@ -3,5 +3,5 @@ make -j4 || exit $? VGA="-vga std" NETDEV="user,id=vmnet0 -redir tcp:5555::80" #NETDEV=${NETDEV-tap,id=vmnet0,script=no,ifname=tap0,downscript=no} -USB="-usb -device nec-usb-xhci,id=xhci -usbdevice mouse" +USB="-usb -device nec-usb-xhci,id=xhci -device usb-mouse" ${KVM-kvm} -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 $USB From a8709ecf6717cf4b0e710ddc8792511a30a96b0d Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Fri, 20 Feb 2015 10:54:14 +0100 Subject: [PATCH 07/22] Reset all ports after initialization Wrong number of ports though. --- cuser/usb/xhci.c | 46 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index ac82523..f95013d 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -83,7 +83,21 @@ enum PortStatusRegisters PORTPMSC = 1, PORTLI = 2, PORTHLPMC = 3, - NUMREGS = 4, // Number of dwords per port - 0x10 bytes => 4 dwords. + PORT_NUMREGS = 4, // Number of dwords per port - 0x10 bytes => 4 dwords. +}; +enum PSCRBits +{ + PSCR_CurentConnectStatus = 1 << 0, + PSCR_Enabled = 1 << 1, + // 2: RsvdZ + PSCR_OverCurrentActive = 1 << 3, + PSCR_Reset = 1 << 4, + // 5..8: link state + PSCR_PortPower = 1 << 9, + // 10..13: port speed + // 14..15: Port Indicator (LED color) + PSCR_LinkStateWriteStrobe = 1 << 16, + PSCR_ConnectStatusChange = 1 << 17, }; static volatile u32* doorbells; static volatile u32* runtimeregs; @@ -297,7 +311,9 @@ u8 readpci8(u32 addr, u8 reg) static void handle_irq() { - log("IRQ"); + log("TODO: Handle IRQ\n"); + // TODO Handle the reason for the interrupt here... + //send1(MSG_IRQ_ACK, rcpt, arg); } void start() @@ -369,7 +385,7 @@ void start() debug("HCI version %#x\n", read_mmio16(HCIVERSION)); u32 hcsparams1 = read_mmio32(HCSPARAMS1); - u8 device_maxports = hcsparams1 & 0xff; + const u8 device_maxports = hcsparams1 & 0xff; debug("Max ports/slots/intrs: %u/%u/%u\n", device_maxports, (hcsparams1 >> 24) & 0xff, (hcsparams1 >> 8) & 0x7ff); u32 hcsparams2 = read_mmio32(HCSPARAMS2); @@ -478,6 +494,28 @@ void start() } } + // FIXME device_maxports does not agree with the controller's real number + // (qemu: device_maxports = 64, number of ports = 8) + for (unsigned port = 0; port < device_maxports; port++) { + u32 pscr = opregs[PORTREGSET + port * PORT_NUMREGS + PSCR]; + u8 linkState = (pscr >> 5) & 0xf; + u32 knownbits = PSCR_Reset | PSCR_ConnectStatusChange | PSCR_Enabled + | PSCR_PortPower | (0xf << 5); + debug("port %d: reset=%d enabled=%d power=%d statuschange=%d linkState=%d other=%#x\n", + port, !!(pscr & PSCR_Reset), !!(pscr & PSCR_Enabled), + !!(pscr & PSCR_PortPower), !!(pscr & PSCR_ConnectStatusChange), + linkState, pscr & ~knownbits); + //pscr |= PSCR_ConnectStatusChange; + pscr |= PSCR_Reset; + opregs[PORTREGSET + port * PORT_NUMREGS + PSCR] = pscr; + + pscr = opregs[PORTREGSET + port * PORT_NUMREGS + PSCR]; + debug("port %d: reset=%d enabled=%d power=%d statuschange=%d linkState=%d other=%#x\n", + port, !!(pscr & PSCR_Reset), !!(pscr & PSCR_Enabled), + !!(pscr & PSCR_PortPower), !!(pscr & PSCR_ConnectStatusChange), + (pscr >> 5) & 0xf, pscr & ~knownbits); + } + for(;;) { uintptr_t rcpt = fresh; arg = 0; @@ -486,8 +524,6 @@ void start() uintptr_t msg = recv2(&rcpt, &arg, &arg2); debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); if (rcpt == pin0_irq_handle && msg == MSG_PULSE) { - // Disable all interrupts, then ACK receipt to PIC - send1(MSG_IRQ_ACK, rcpt, arg); handle_irq(); continue; } From 2240a33c6e46d171c99e676716cdb7940e4f69a1 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 00:23:26 +0100 Subject: [PATCH 08/22] xhci: Get number of ports from protocol capabilities --- cuser/usb/xhci.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index f95013d..20b1b75 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -36,7 +36,10 @@ enum HostCntrlCapRegs }; enum HCCP1Bits { - HCCP1_AC64 = 1, + HCCP1_AC64 = 1 << 0, + // Bandwidth negotiation capability + HCCP1_ContextSize = 1 << 2, + HCCP1_PowerPowerControl = 1 << 3, // Plus others... I'm not sure those are interesting. }; // Offset into mmiospace based on values in capability registers @@ -237,6 +240,7 @@ enum EcpCapIds { CAPID_SUPPORTED_PROTOCOL = 2, }; +static uint8_t maxport, numports; static void iterate_ecps(volatile const u32* ecp) { while (ecp) @@ -250,9 +254,16 @@ static void iterate_ecps(volatile const u32* ecp) { u64 nm = ecp[1]; u32 ports = ecp[2]; - debug("%p: Supported protocol %s %x.%x, %u ports at %u\n", ecp, + u8 portcount = ports >> 8; + u8 firstport = ports; + u8 lastport = firstport + portcount - 1; + debug("%p: Supported protocol %s %x.%x, ports %u..%u\n", ecp, &nm, ecpr >> 24, (ecpr >> 16) & 0xff, - (ports >> 8) & 0xff, ports & 0xff); + firstport, lastport); + if (lastport > maxport) { + maxport = lastport; + } + numports += portcount; break; } default: @@ -398,6 +409,7 @@ void start() u32 hccparams1 = read_mmio32(HCCPARAMS1); debug("hccparams1: %#x\n", hccparams1 & 0xffff); assert(hccparams1 & HCCP1_AC64); + assert(!(hccparams1 & HCCP1_ContextSize)); u16 xECP = hccparams1 >> 16; debug("xECP: %#x (%p)\n", xECP, mmiospace + (xECP * 4)); iterate_ecps((u32*)mmiospace + xECP); @@ -427,8 +439,11 @@ void start() } // 2. Program the Max Device Slots Enabled - // TODO We probably need to allocate structures for these somewhere. - opregs[CONFIG] = device_maxports; + // FIXME Are these the same as the DCBAA entries? Then we can have 256. + // I'm not sure if the number of ports is the appropriate value (= only + // root-port-connected devices have slots), or if we'd rather enable all + // the slots we can, so that we can support devices on hubs. + opregs[CONFIG] = numports; // 3. Program the Device Context Base Address Array Pointer uintptr_t dcbaaPhysAddr = From 6966b7da0b82e0ae37b90ca78fa7e2a824af8ea1 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 00:24:56 +0100 Subject: [PATCH 09/22] Handle IRQs and process the event ring --- cuser/usb/xhci.c | 142 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 29 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index 20b1b75..4aa5cdd 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -90,17 +90,22 @@ enum PortStatusRegisters }; enum PSCRBits { - PSCR_CurentConnectStatus = 1 << 0, + PSCR_CurrentConnectStatus = 1 << 0, PSCR_Enabled = 1 << 1, // 2: RsvdZ PSCR_OverCurrentActive = 1 << 3, PSCR_Reset = 1 << 4, // 5..8: link state + PSCR_LinkStateShift = 5, + PSCR_LinkStateMask = 0xf << PSCR_LinkStateShift, PSCR_PortPower = 1 << 9, // 10..13: port speed + PSCR_PortSpeedShift = 10, + PSCR_PortSpeedMask = 0xf << PSCR_PortSpeedShift, // 14..15: Port Indicator (LED color) PSCR_LinkStateWriteStrobe = 1 << 16, PSCR_ConnectStatusChange = 1 << 17, + PSCR_ResetChange = 1 << 21, }; static volatile u32* doorbells; static volatile u32* runtimeregs; @@ -124,6 +129,11 @@ enum IMANBits IMAN_IntPending = 1, IMAN_IntEnabled = 2, }; +enum ERDPBits +{ + // bits 0..2: Dequeue ERST Segment Index + ERDP_EHB = 8, +}; static volatile u32 *interruptregs; static u32 read_mmio32(unsigned byte_offset) @@ -173,7 +183,7 @@ struct normal_trb }; _Static_assert(sizeof(struct normal_trb) == 16, "normal TRB size doesn't match spec"); -enum TRBTypes +enum TRBType { // 0 = reserved // 1..8: generally transfer ring only @@ -193,7 +203,14 @@ enum TRBTypes TRB_CMD_ConfigureEndpoint = 12, TRB_CMD_NoOp = 23, + // 32..39: events + TRB_TransferEvent = 32, + TRB_CommandCompleted = 33, TRB_PortStatusChange = 34, + // 35 = bandwidth request, 36 = doorbell event (for virtualization) + TRB_HostControllerEvent = 37, + TRB_DeviceNotification = 38, + TRB_MFIndexWrap = 39, }; enum TRBFlags { @@ -212,9 +229,12 @@ typedef union trb u32 control; }; } trb; +typedef trb event_trb; +static enum TRBType trb_type(trb *trb_) { + return (trb_->control >> 10) & 0x3f; +} -static union trb create_link_trb(u64 address, u8 flags) -{ +static union trb create_link_trb(u64 address, u8 flags) { union trb res; res.parameter = address; res.status = 0; @@ -231,9 +251,14 @@ static struct page1 { trb event_ring[EVENT_RING_SIZE]; erse erst[ERST_SIZE]; } page1 PLACEHOLDER_SECTION ALIGN(4096); + static volatile struct command_trb *command_enqueue = page1.command_ring; // The PCS bit to put into created TRBs static bool command_pcs; + +static u8 event_ring_dequeue; +static bool event_ring_pcs = true; + static volatile u64 device_context_addrs[256] PLACEHOLDER_SECTION ALIGN(4096); enum EcpCapIds @@ -320,11 +345,74 @@ u8 readpci8(u32 addr, u8 reg) return val; } -static void handle_irq() +static void print_pscr(u8 port, u32 pscr); + +static void handle_event(event_trb* ev) { - log("TODO: Handle IRQ\n"); - // TODO Handle the reason for the interrupt here... - //send1(MSG_IRQ_ACK, rcpt, arg); + u8 type = trb_type(ev); + switch (type) { + case TRB_PortStatusChange: + { + u8 port = ev->parameter >> 24; + volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; + debug("Port status change: port=%d\n", port); + print_pscr(port, regs[PSCR]); + regs[PSCR] = PSCR_ResetChange | PSCR_ConnectStatusChange | PSCR_PortPower; + print_pscr(port, regs[PSCR]); + break; + } + default: + debug("Unknown event %d\n", type); + break; + } +} + +static void handle_irq(uintptr_t rcpt, uintptr_t arg) +{ + // For each interrupter (but since we're not MSI-X capable, there is only + // one, the Primary Interrupter). + if (interruptregs[IMAN] & IMAN_IntPending) { + bool event = false; + while (!!(page1.event_ring[event_ring_dequeue].control & TRB_Cycle) == + event_ring_pcs) { + event = true; + debug("Event at %d\n", event_ring_dequeue); + handle_event(&page1.event_ring[event_ring_dequeue]); + if (++event_ring_dequeue == EVENT_RING_SIZE) { + debug("Event ring wrapped!\n"); + event_ring_pcs ^= 1; + event_ring_dequeue = 0; + } + } + volatile u64 *erdpp = (u64 *)(interruptregs + ERDP); + if (event) { + u64 erdp = *erdpp, prev_erdp = erdp; + u8 new_erdp = event_ring_dequeue; + erdp &= ~(0xf << 4); + erdp |= (new_erdp << 4) | ERDP_EHB; + debug("ERDP %#lx -> %#lx\n", prev_erdp, erdp); + *erdpp = erdp; + } + interruptregs[IMAN] = IMAN_IntPending | IMAN_IntEnabled; + } + + send1(MSG_IRQ_ACK, rcpt, arg); +} + +static void print_pscr(u8 port, u32 pscr) { + u8 linkState = (pscr >> PSCR_LinkStateShift) & 0xf; + u8 portSpeed = (pscr >> PSCR_PortSpeedShift) & 0xf; + u32 knownbits = PSCR_CurrentConnectStatus | PSCR_Reset | + PSCR_ConnectStatusChange | PSCR_ResetChange | PSCR_Enabled | + PSCR_PortPower | PSCR_LinkStateMask | PSCR_PortSpeedMask; + debug("port %d: reset=%d resetchange=%d enabled=%d power=%d status=%d statuschange=%d\n", + port, !!(pscr & PSCR_Reset), !!(pscr & PSCR_ResetChange), + !!(pscr & PSCR_Enabled), + !!(pscr & PSCR_PortPower), + !!(pscr & PSCR_CurrentConnectStatus), + !!(pscr & PSCR_ConnectStatusChange)); + debug("... linkState=%d portSpeed=%d other=%#x\n", + linkState, portSpeed, pscr & ~knownbits); } void start() @@ -509,26 +597,22 @@ void start() } } - // FIXME device_maxports does not agree with the controller's real number - // (qemu: device_maxports = 64, number of ports = 8) - for (unsigned port = 0; port < device_maxports; port++) { - u32 pscr = opregs[PORTREGSET + port * PORT_NUMREGS + PSCR]; - u8 linkState = (pscr >> 5) & 0xf; - u32 knownbits = PSCR_Reset | PSCR_ConnectStatusChange | PSCR_Enabled - | PSCR_PortPower | (0xf << 5); - debug("port %d: reset=%d enabled=%d power=%d statuschange=%d linkState=%d other=%#x\n", - port, !!(pscr & PSCR_Reset), !!(pscr & PSCR_Enabled), - !!(pscr & PSCR_PortPower), !!(pscr & PSCR_ConnectStatusChange), - linkState, pscr & ~knownbits); - //pscr |= PSCR_ConnectStatusChange; - pscr |= PSCR_Reset; - opregs[PORTREGSET + port * PORT_NUMREGS + PSCR] = pscr; - - pscr = opregs[PORTREGSET + port * PORT_NUMREGS + PSCR]; - debug("port %d: reset=%d enabled=%d power=%d statuschange=%d linkState=%d other=%#x\n", - port, !!(pscr & PSCR_Reset), !!(pscr & PSCR_Enabled), - !!(pscr & PSCR_PortPower), !!(pscr & PSCR_ConnectStatusChange), - (pscr >> 5) & 0xf, pscr & ~knownbits); + // TODO Don't need to do this - just check the connect= status, then start + // enumeration for each connected port. + for (unsigned port = 1; port <= maxport; port++) { + volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; + u32 pscr = regs[PSCR]; + if (!(pscr & PSCR_PortPower)) { + debug("port %d: not powered\n", port); + continue; + } + + print_pscr(port, pscr); + // Assume that when this is set, all other bits are ignored. There are + // many bits in the PSCR that are e.g. write-1-to-clear or similar... + regs[PSCR] = PSCR_Reset; + + print_pscr(port, regs[PSCR]); } for(;;) { @@ -539,7 +623,7 @@ void start() uintptr_t msg = recv2(&rcpt, &arg, &arg2); debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); if (rcpt == pin0_irq_handle && msg == MSG_PULSE) { - handle_irq(); + handle_irq(rcpt, arg); continue; } From 54d008fe937c377fe4b36e18e7c39ad259fb2d64 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 02:01:12 +0100 Subject: [PATCH 10/22] Start of device enumeration Check PSCR for each port, figure out what kind of port and what the next step in enumeration is. --- cuser/usb/xhci.c | 65 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index 4aa5cdd..fb091b2 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -347,6 +347,51 @@ u8 readpci8(u32 addr, u8 reg) static void print_pscr(u8 port, u32 pscr); +static void proceed_port(u8 port) { + volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; + u32 pscr = regs[PSCR]; + if (!(pscr & PSCR_PortPower)) { + debug("port %d: not powered\n", port); + return; + } + if (!(pscr & PSCR_CurrentConnectStatus) || (pscr & PSCR_Reset)) { + return; + } + + u8 linkState = (pscr & PSCR_LinkStateMask) >> PSCR_LinkStateShift; + // Failed USB3 device. Ignore. (Would it help to reset the port here?) + if (!(pscr & PSCR_Enabled) && linkState == 5) { + return; + } + + // USB3: + // a) successful: enabled=1 reset=0 linkstate=0 + // b) unsuccessful: enabled=0 reset=0 linkstate=5 (RxDetect) + // USB2: + // 1. enabled=0 reset=0 linkstate=7 (Polling) + // 2. reset it + // 3. enabled=1 reset=0 linkstate=0 (U0) + + regs[PSCR] = PSCR_ResetChange | PSCR_ConnectStatusChange | PSCR_PortPower; + print_pscr(port, pscr); + + if (pscr & PSCR_Enabled) { + if (!(pscr & (PSCR_Reset | PSCR_LinkStateMask))) { + debug("port %d: Connected!\n", port); + // Connected! Proceed to set up a device slot etc. + // Section 4.3 step 4) + } + } else { + if (linkState == 7) { + debug("port %d: USB2 device, resetting to proceed to Enabled\n", port); + // Assume that when this is set, all other bits are ignored. + // There are many bits in the PSCR that are e.g. + // write-1-to-clear or similar... + regs[PSCR] = PSCR_Reset; + } + } +} + static void handle_event(event_trb* ev) { u8 type = trb_type(ev); @@ -356,9 +401,7 @@ static void handle_event(event_trb* ev) u8 port = ev->parameter >> 24; volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; debug("Port status change: port=%d\n", port); - print_pscr(port, regs[PSCR]); - regs[PSCR] = PSCR_ResetChange | PSCR_ConnectStatusChange | PSCR_PortPower; - print_pscr(port, regs[PSCR]); + proceed_port(port); break; } default: @@ -597,22 +640,8 @@ void start() } } - // TODO Don't need to do this - just check the connect= status, then start - // enumeration for each connected port. for (unsigned port = 1; port <= maxport; port++) { - volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; - u32 pscr = regs[PSCR]; - if (!(pscr & PSCR_PortPower)) { - debug("port %d: not powered\n", port); - continue; - } - - print_pscr(port, pscr); - // Assume that when this is set, all other bits are ignored. There are - // many bits in the PSCR that are e.g. write-1-to-clear or similar... - regs[PSCR] = PSCR_Reset; - - print_pscr(port, regs[PSCR]); + proceed_port(port); } for(;;) { From 730a2b2c6964250b1a596c6de6b8fe531e3384ae Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 03:02:23 +0100 Subject: [PATCH 11/22] Issue EnableSlot command and handle the completion event --- cuser/usb/xhci.c | 68 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index fb091b2..c66be02 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -151,9 +151,9 @@ static u16 read_mmio16(unsigned byte_offset) typedef struct command_trb { - u64 address; + u64 parameter; u32 status; - u32 command; + u32 control; } command_trb; _Static_assert(sizeof(struct command_trb) == 16, "command TRB size doesn't match spec"); @@ -212,6 +212,7 @@ enum TRBType TRB_DeviceNotification = 38, TRB_MFIndexWrap = 39, }; +typedef enum TRBType TRBType; enum TRBFlags { TRB_Cycle = 1, @@ -230,7 +231,7 @@ typedef union trb }; } trb; typedef trb event_trb; -static enum TRBType trb_type(trb *trb_) { +static TRBType trb_type(trb *trb_) { return (trb_->control >> 10) & 0x3f; } @@ -252,9 +253,10 @@ static struct page1 { erse erst[ERST_SIZE]; } page1 PLACEHOLDER_SECTION ALIGN(4096); -static volatile struct command_trb *command_enqueue = page1.command_ring; +static u8 command_data[COMMAND_RING_SIZE]; +static u8 command_enqueue = 0; // The PCS bit to put into created TRBs -static bool command_pcs; +static bool command_pcs = true; static u8 event_ring_dequeue; static bool event_ring_pcs = true; @@ -347,6 +349,22 @@ u8 readpci8(u32 addr, u8 reg) static void print_pscr(u8 port, u32 pscr); +static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { + cmd->control |= (command << 10) | (command_pcs ? TRB_Cycle : 0); + if (!!(page1.command_ring[command_enqueue].control & TRB_Cycle) == + command_pcs) { + debug("Eww... Command ring is full.\n"); + return false; + } + page1.command_ring[command_enqueue] = *cmd; + command_data[command_enqueue] = data; + if (++command_enqueue == COMMAND_RING_SIZE) { + command_enqueue = 0; + command_pcs ^= 1; + } + doorbells[0] = 0; +} + static void proceed_port(u8 port) { volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; u32 pscr = regs[PSCR]; @@ -378,8 +396,10 @@ static void proceed_port(u8 port) { if (pscr & PSCR_Enabled) { if (!(pscr & (PSCR_Reset | PSCR_LinkStateMask))) { debug("port %d: Connected!\n", port); - // Connected! Proceed to set up a device slot etc. - // Section 4.3 step 4) + // TODO Should copy the slot type form the Supported Protocol + // capability for the port, 0 works only for USB2 and USB3. + struct command_trb cmd = { 0, 0, 0 }; + enqueue_command(&cmd, TRB_CMD_EnableSlot, port); } } else { if (linkState == 7) { @@ -392,18 +412,50 @@ static void proceed_port(u8 port) { } } +static void address_device(u8 port, u8 slot) { + debug("address device: port %d -> slot %d\n", port, slot); + // Section 4.3.3 ... +} + +static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { + command_trb trb = page1.command_ring[cmdpos]; + u8 cmd = trb_type(&trb); + u8 data = command_data[cmdpos]; + switch (cmd) { + case TRB_CMD_EnableSlot: + { + // TODO if (ccode == 1 /*Successful*/) + address_device(data, slot); + break; + } + default: + debug("Command %d completed, but I don't know what I did?", cmd); + break; + } +} + static void handle_event(event_trb* ev) { u8 type = trb_type(ev); + // Not all applicable to all events, but they are always in the same place. + u8 ccode = ev->status >> 24; + u32 cparam = ev->status & 0xffffff; + u8 slot = ev->control >> 24; switch (type) { case TRB_PortStatusChange: { u8 port = ev->parameter >> 24; - volatile u32* regs = opregs + PORTREGSET + (port - 1) * PORT_NUMREGS; debug("Port status change: port=%d\n", port); proceed_port(port); break; } + case TRB_CommandCompleted: + { + u8 cmdpos = (ev->parameter >> 4) & (COMMAND_RING_SIZE - 1); + debug("Command completed: %d completion code=%d,param=%#x slot %d\n", cmdpos, ccode, cparam, slot); + command_complete(cmdpos, ccode, slot); + break; + } default: debug("Unknown event %d\n", type); break; From ad19155207b8194d2163eca9ac423c2d6cb1d382 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 15:49:21 +0100 Subject: [PATCH 12/22] Do AddressDevice on connected devices --- cuser/usb/xhci.c | 227 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 212 insertions(+), 15 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index c66be02..700b4b9 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -220,6 +220,11 @@ enum TRBFlags TRB_Chain = 1 << 4, TRB_IOC = 1 << 5, }; +enum CompletionCodes +{ + CC_Invalid = 0, + CC_Success = 1, +}; typedef union trb { struct command_trb cmd; @@ -262,6 +267,120 @@ static u8 event_ring_dequeue; static bool event_ring_pcs = true; static volatile u64 device_context_addrs[256] PLACEHOLDER_SECTION ALIGN(4096); +// Bytes per context entry. +static u8 context_size; +typedef struct input_context { + u32 drop; + u32 add; + u32 rsvd[5]; + u32 control; +} input_context; +_Static_assert(sizeof(input_context) == 32, "input context size doesn't match spec"); +typedef struct slot_context { + u32 route : 20; + u32 speed : 4; + u32 : 1; + u32 mtt : 1; + u32 hub : 1; + u32 entries : 5; + u16 exit_latency; + u8 root_hub_port; + u8 num_ports; + u8 tt_hub_slot; + u8 tt_port; + u16 ttt : 2; + u16 : 4; + u16 interrupter : 10; + u32 device_address : 8; + u32 : 19; + u32 slot_state : 5; + u32 rsvdO[4]; +} slot_context; +_Static_assert(sizeof(slot_context) == 32, "slot context size doesn't match spec"); +typedef struct endpoint_context { + // u32 #0 + u8 ep_state; // Really just 3 bits + u8 mult : 2; + u8 max_primary_streams : 5; + u8 linear_stream_array : 1; + u8 interval; + u8 max_esit_payload_hi; + // u32 #1 + u8 : 1; + u8 error_count : 2; + u8 type : 3; + u8 : 1; + u8 hid : 1; + u8 max_burst_size; + u16 max_packet_size; + // u32 #2, #3 + u64 trdp; + // u32 #4 + u16 avg_trb_length; + u16 max_esit_payload_lo; + // u32 5..8 + u32 rsvd[3]; +} endpoint_context; +_Static_assert(sizeof(endpoint_context) == 32, "endpoint context size doesn't match spec"); +enum EPType +{ + EP_NotValid = 0, + EP_IsochOut, + EP_BulkOut, + EP_InterruptOut, + EP_Control = 4, + EP_IsochIn, + EP_BulkIn, + EP_InterruptIn, +}; +enum TRDPBits +{ + TRDP_DCS = 1, +}; + +static u64 dma_map(const volatile void* addr, size_t size) { + const enum prot flags = MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE; + return (u64)map(0, flags, addr, 0, size); +} +#define dma_map(obj) dma_map(&(obj), sizeof(obj)) + +#define MAX_DMA_BUFFERS 16 +static u8 dma_buffer_space[MAX_DMA_BUFFERS][4096] PLACEHOLDER_SECTION ALIGN(4096); +typedef struct dma_buffer { + u64 phys; // 0 for unmapped buffers +} dma_buffer; +static dma_buffer dma_buffers[MAX_DMA_BUFFERS]; +typedef struct dma_buffer_ref { + u64 phys; + u8 *virtual; +} dma_buffer_ref; +static dma_buffer_ref allocate_dma_buffer() { + dma_buffer_ref res = { 0, NULL }; + for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { + dma_buffer *buf = dma_buffers + i; + if (buf->phys & 1) continue; + + if (!buf->phys) { + buf->phys = dma_map(dma_buffer_space[i]); + } + res.phys = buf->phys; + res.virtual = dma_buffer_space[i]; + memset(res.virtual, 0, 4096); + + buf->phys |= 1; + return res; + } + assert(!"Ran out of DMA buffers..."); + return res; +} +static void free_dma_buffer(u64 phys) { + for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { + dma_buffer *buf = dma_buffers + i; + if (buf->phys != (phys | 1)) continue; + buf->phys ^= 1; + return; + } +} enum EcpCapIds { @@ -349,8 +468,7 @@ u8 readpci8(u32 addr, u8 reg) static void print_pscr(u8 port, u32 pscr); -static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { - cmd->control |= (command << 10) | (command_pcs ? TRB_Cycle : 0); +static bool enqueue_command2(struct command_trb *cmd, u8 data) { if (!!(page1.command_ring[command_enqueue].control & TRB_Cycle) == command_pcs) { debug("Eww... Command ring is full.\n"); @@ -359,10 +477,19 @@ static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { page1.command_ring[command_enqueue] = *cmd; command_data[command_enqueue] = data; if (++command_enqueue == COMMAND_RING_SIZE) { + debug("enqueue_command: command ring wrapped!\n"); command_enqueue = 0; command_pcs ^= 1; } doorbells[0] = 0; + return true; +} + +// TODO warn_unused_result +static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { + assert(cmd); + cmd->control |= (command << 10) | (command_pcs ? TRB_Cycle : 0); + return enqueue_command2(cmd, data); } static void proceed_port(u8 port) { @@ -396,7 +523,7 @@ static void proceed_port(u8 port) { if (pscr & PSCR_Enabled) { if (!(pscr & (PSCR_Reset | PSCR_LinkStateMask))) { debug("port %d: Connected!\n", port); - // TODO Should copy the slot type form the Supported Protocol + // TODO Should copy the slot type from the Supported Protocol // capability for the port, 0 works only for USB2 and USB3. struct command_trb cmd = { 0, 0, 0 }; enqueue_command(&cmd, TRB_CMD_EnableSlot, port); @@ -412,20 +539,94 @@ static void proceed_port(u8 port) { } } +#if 0 +static void disable_slot(u8 slot) { + // Do a TRB_CMD_DisableSlot +} +#endif + +static slot_context *get_slot_ctx(void *p) { + return (slot_context*)((u8*)p + context_size); +} +static endpoint_context *get_ep_ctx(void *p, unsigned ep) { + return (endpoint_context*)((u8*)p + (ep + 2) * context_size); +} + static void address_device(u8 port, u8 slot) { debug("address device: port %d -> slot %d\n", port, slot); + dma_buffer_ref input = allocate_dma_buffer(); + dma_buffer_ref devctx = allocate_dma_buffer(); + dma_buffer_ref ring = allocate_dma_buffer(); + device_context_addrs[slot] = devctx.phys; + // Section 4.3.3 ... + input_context* ic = (input_context*)input.virtual; + ic->add = 3; // slot context and one endpoint context + + slot_context* sc = get_slot_ctx(ic); + sc->root_hub_port = port; + sc->route = 0; + sc->entries = 1; // One, the control endpoint + + endpoint_context* ep = get_ep_ctx(ic, 0); + ep->type = EP_Control; + // TODO Check PORTSC Port Speed, set the appropriate max packet size for the control endpoint. + ep->max_packet_size = 8; + ep->max_burst_size = 0; + ep->trdp = ring.phys | TRDP_DCS; + ep->interval = 0; + ep->max_primary_streams = 0; + ep->mult = 0; + ep->error_count = 3; + + trb* ring_trbs = (trb *)ring.virtual; + ring_trbs[(4096 / sizeof(trb)) - 1] = + create_link_trb(ring.phys, TRB_ToggleC); + + // TODO More barrier to make sure input changes are in RAM before we submit + // the command. + __barrier(); + + command_trb cmd = { input.phys, 0, slot << 24 }; + enqueue_command(&cmd, TRB_CMD_AddressDevice, port); } static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { command_trb trb = page1.command_ring[cmdpos]; - u8 cmd = trb_type(&trb); + u8 cmd = trb_type((union trb *)&trb); u8 data = command_data[cmdpos]; + if (ccode != CC_Success) { + debug("Command %d failed: completion code %d\n", cmdpos, ccode); + } switch (cmd) { case TRB_CMD_EnableSlot: { - // TODO if (ccode == 1 /*Successful*/) - address_device(data, slot); + if (ccode == CC_Success) { + address_device(data, slot); + } + break; + } + case TRB_CMD_AddressDevice: + { + u8 port = data; + free_dma_buffer(trb.parameter); + if (ccode == CC_Success) { + // Device is addressed, now what? + debug("AddressDevice completed! slot %d port %d ready to configure\n", slot, port); + } else { + debug("AddressDevice failed! freeing slot %d port %d\n", slot, port); + command_trb cmd = { 0, 0, 0 }; + enqueue_command(&cmd, TRB_CMD_DisableSlot, port); + } + } + case TRB_CMD_DisableSlot: + { + u8 port = data; + // TODO Find the transfer rings and free them. + free_dma_buffer(device_context_addrs[slot]); + device_context_addrs[slot] = 0; + // More? + // Disable the port that the device was connected on? break; } default: @@ -464,6 +665,8 @@ static void handle_event(event_trb* ev) static void handle_irq(uintptr_t rcpt, uintptr_t arg) { + send1(MSG_IRQ_ACK, rcpt, arg); + // For each interrupter (but since we're not MSI-X capable, there is only // one, the Primary Interrupter). if (interruptregs[IMAN] & IMAN_IntPending) { @@ -490,8 +693,6 @@ static void handle_irq(uintptr_t rcpt, uintptr_t arg) } interruptregs[IMAN] = IMAN_IntPending | IMAN_IntEnabled; } - - send1(MSG_IRQ_ACK, rcpt, arg); } static void print_pscr(u8 port, u32 pscr) { @@ -592,7 +793,7 @@ void start() u32 hccparams1 = read_mmio32(HCCPARAMS1); debug("hccparams1: %#x\n", hccparams1 & 0xffff); assert(hccparams1 & HCCP1_AC64); - assert(!(hccparams1 & HCCP1_ContextSize)); + context_size = (hccparams1 & HCCP1_ContextSize) ? 64 : 32; u16 xECP = hccparams1 >> 16; debug("xECP: %#x (%p)\n", xECP, mmiospace + (xECP * 4)); iterate_ecps((u32*)mmiospace + xECP); @@ -629,9 +830,7 @@ void start() opregs[CONFIG] = numports; // 3. Program the Device Context Base Address Array Pointer - uintptr_t dcbaaPhysAddr = - (uintptr_t)map(0, MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE, - &device_context_addrs, 0, sizeof(device_context_addrs)); + uintptr_t dcbaaPhysAddr = dma_map(device_context_addrs); opregs[DCBAAP] = dcbaaPhysAddr; opregs[DCBAAPH] = dcbaaPhysAddr >> 32; // The DCBAA has MaxSlotsEn+1 entries, entry 0 has the scratchpad entries. @@ -639,9 +838,7 @@ void start() // 4. Define the Command Ring Dequeue Pointer by programming the Command // Ring Control Register (5.4.5) with a 64-bit address pointing to the // starting address of the first TRB of the Command Ring. - uintptr_t page1p = - (uintptr_t)map(0, MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE, - &page1, 0, sizeof(page1)); + uintptr_t page1p = dma_map(page1); uintptr_t commandRingPhys = page1p + offsetof(struct page1, command_ring); page1.command_ring[COMMAND_RING_SIZE - 1] = create_link_trb(commandRingPhys, TRB_ToggleC).cmd; From e31f5c8a373e4912597101d48541251b486e989f Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 22 Feb 2015 16:42:05 +0100 Subject: [PATCH 13/22] Start the "main USB" driver, tell it about new devices --- Makefile | 9 +++--- cuser/usb/common.h | 79 ++++++++++++++++++++++++++++++++++++++++++++++ cuser/usb/main.c | 28 ++++++++++++++-- cuser/usb/xhci.c | 2 ++ mkgrubcfg.sh | 2 +- 5 files changed, 112 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index f241f4c..ca9cd26 100644 --- a/Makefile +++ b/Makefile @@ -171,7 +171,7 @@ $(OUTDIR)/%.elf: cuser/linker.ld $(OUTDIR)/%.o WANT_PRINTF = test_maps zeropage WANT_PRINTF += timer_test -WANT_REAL_PRINTF = e1000 apic bochsvga fbtest usb/xhci +WANT_REAL_PRINTF = e1000 apic bochsvga fbtest usb/xhci usb WANT_STRING = acpica @@ -325,14 +325,13 @@ $(OUTDIR)/cuser/lwip.elf: cuser/linker.ld $(LWIP_DEP_OBJS) @mkdir -p $(@D) $(HUSH_LD) $(LD) $(USER_LDFLAGS) -o $@ -T $^ +USB := cuser/usb USB_SRCS := $(USB)/main.c -USB_OBJS := $(USB_SRCS:%.c:$(OUTDIR)/%.o) +USB_OBJS := $(USB_SRCS:%.c=$(OUTDIR)/%.o) -include $(USB_OBJS:.o=.d) -USB_DEP_OBJS := $(USB_OBJS) $(OUTDIR)/cuser/acpica/printf.o $(OUTDIR)/cuser/acpica/source/components/utilities/utclib.o - -$(OUTDIR)/cuser/usb.elf: cuser/linker.ld $(USB_DEP_OBJS) +$(OUTDIR)/cuser/usb.elf: cuser/linker.ld $(USB_OBJS) @mkdir -p $(@D) $(HUSH_LD) $(LD) $(USER_LDFLAGS) -o $@ -T $^ diff --git a/cuser/usb/common.h b/cuser/usb/common.h index 1898d06..8d9852d 100644 --- a/cuser/usb/common.h +++ b/cuser/usb/common.h @@ -1 +1,80 @@ #include "../common.h" + +enum usb_controller_msg +{ + /** + * The controller has found a device at slot N. + * + * arg1 = slot number + */ + MSG_USB_NEW_DEVICE = MSG_USER, + /** + * Send a control message to a slot. Must be sendrcv'd. + * (TODO define asynchronous variant, with a pulse and (e.g.) relevant + * data stored inside the buffer, or with a separate pickup message.) + * + * arg1: + * bit 0..7: slot number + * bit 8..11: endpoint (0..15) + * bit 12: direction (for non-0 endpoints): 0 = OUT, 1 = IN + * bit 13: (for endpoint 0): 1 if there is a data stage, direction decides + * which direction + * bit 14: immediate data + * bit 16..18: transfer type + * bit 32..47: length, 1..4096 (only single page transfers are allowed) + * arg2 = + * if immediate and output direction: inline data (8 bytes) + * otherwise: index of buffer to send from or receive to + * return: + * arg1: slot number, status?, length of received data + * arg2 = if input direction: inline data + */ + MSG_USB_TRANSFER, +}; + +// Relative to the arg1 qword for USB_TRANSFER* +enum usb_transfer_flags +{ + UTF_SetupHasData = 1 << 13, + UTF_ImmediateData = 1 << 14, +}; + +enum usb_transfer_type +{ + /** + * Data on a normal endpoint. + */ + UTT_Normal, + /** + * The data is in the xHCI format for Setup Stage Control TRBs: + * bits 0..7: bmRequestType + * bits 8..15: bRequest + * bits 16..31: wValue + * bits 32..47: wIndex + * bits 48..63: wLength + * + * The transfer length is always 8. + * + * The Transfer Type flags are valid for this transfer only. + */ + UTT_ControlSetup, + /** + * Data for a data stage of a control transaction, if applicable. + * + * May be chained with normal transfers, except that immediate data + * TRBs cannot be chained. + */ + UTT_ControlData, + /** + * Finish a control transaction after sending data. + * + * The direction must be the opposite of the data stage, or IN if there + * was no data stage. + */ + UTT_ControlStatus, + /** + * Isochronous, etc. + */ + UTT_Isoch, + //UTT_NoOp +}; diff --git a/cuser/usb/main.c b/cuser/usb/main.c index 5220a64..87534d4 100644 --- a/cuser/usb/main.c +++ b/cuser/usb/main.c @@ -1,6 +1,6 @@ #include "common.h" -#define log printf +#define log(fmt, ...) printf("USB: " fmt, ## __VA_ARGS__) #if 1 #define debug log #else @@ -10,8 +10,32 @@ // "main" USB driver - this talks to both host controllers and USB device // drivers and manages communication between them. +static const uintptr_t xhci_handle = 7; +static const uintptr_t fresh = 0x100; + +static void handle_xhci_msg(uintptr_t msg, uintptr_t arg, uintptr_t arg2) { + switch (msg & 0xff) + { + case MSG_USB_NEW_DEVICE: { + debug("New device, slot %u\n", arg); + break; + } + } +} + void start() { __default_section_init(); - for (;;); + for (;;) { + uintptr_t rcpt = fresh; + uintptr_t arg = 0; + uintptr_t arg2 = 0; + debug("receiving...\n"); + uintptr_t msg = recv2(&rcpt, &arg, &arg2); + debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); + + if (rcpt == xhci_handle) { + handle_xhci_msg(msg, arg, arg2); + } + } } diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index 700b4b9..a679c48 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -7,6 +7,7 @@ #define debug(...) (void)0 #endif +static const uintptr_t usb_handle = 6; static const uintptr_t acpi_handle = 4; static const uintptr_t pic_handle = 2; static const uintptr_t pin0_irq_handle = 0x100; @@ -613,6 +614,7 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { if (ccode == CC_Success) { // Device is addressed, now what? debug("AddressDevice completed! slot %d port %d ready to configure\n", slot, port); + send1(MSG_USB_NEW_DEVICE, usb_handle, slot); } else { debug("AddressDevice failed! freeing slot %d port %d\n", slot, port); command_trb cmd = { 0, 0, 0 }; diff --git a/mkgrubcfg.sh b/mkgrubcfg.sh index 51099a5..59680d4 100644 --- a/mkgrubcfg.sh +++ b/mkgrubcfg.sh @@ -31,7 +31,7 @@ menuentry "USB" { module /cuser/acpica.mod acpica # e1000? module /cuser/apic.mod apic - module /cuser/usb USB + module /cuser/usb.mod USB module /cuser/usb/xhci.mod xHCI boot } From a2aaf969d95f85e71f86865049c129398ed9bfb3 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Fri, 10 Apr 2015 18:33:36 +0200 Subject: [PATCH 14/22] Start of enumeration API --- cuser/usb/common.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/cuser/usb/common.h b/cuser/usb/common.h index 8d9852d..335664b 100644 --- a/cuser/usb/common.h +++ b/cuser/usb/common.h @@ -78,3 +78,29 @@ enum usb_transfer_type UTT_Isoch, //UTT_NoOp }; + +// Maybe bad name? For communication between USB class/device drviers and the +// "main" USB system, which wrangles the host controller(s). +// Should vaguely conform to libusb20's backend operations somehow. +enum usb_device_msg +{ + /** + * Find an unopened device with a given class or vendor:product. If + * last-device-id is given, continue enumerating after the given device. + * + * arg1: class/vendor/product (encoded) + * arg2: enumeration last-device-id (or 0) + * returns: + * arg1: If found: bus/device/etc identifier. Opaque 64-bit non-zero id. + * If not found: 0 + */ + USB_FIND_DEVICE, + /** + * Open a device. Should be called on a fresh handle. + * + * arg1: device id to open + * + * TODO: should probably return some basic device info too? + */ + USB_OPEN_DEVICE, +}; From 1a66a549875f5c263ee5f924faff9b5ede63d1fa Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Fri, 10 Apr 2015 18:33:54 +0200 Subject: [PATCH 15/22] Add USB audio device --- run_qemu.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_qemu.sh b/run_qemu.sh index c23b624..b2efc1c 100755 --- a/run_qemu.sh +++ b/run_qemu.sh @@ -3,5 +3,5 @@ make -j4 || exit $? VGA="-vga std" NETDEV="user,id=vmnet0 -redir tcp:5555::80" #NETDEV=${NETDEV-tap,id=vmnet0,script=no,ifname=tap0,downscript=no} -USB="-usb -device nec-usb-xhci,id=xhci -device usb-mouse" +USB="-usb -device nec-usb-xhci,id=xhci -device usb-mouse -device usb-audio" ${KVM-kvm} -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 $USB From 710f1a6ebdb4705bd0739425c5b03a9f19cfd030 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Mon, 8 Jun 2015 08:07:43 +0200 Subject: [PATCH 16/22] Start of new controller/bus/system API --- cuser/usb/common.h | 48 +++++++++++++++++++++++++++++++++++++++------- cuser/usb/main.c | 31 ++++++++++++++++++++++++++---- cuser/usb/xhci.c | 29 +++++++++++++++++++++------- 3 files changed, 90 insertions(+), 18 deletions(-) diff --git a/cuser/usb/common.h b/cuser/usb/common.h index 335664b..3719b7a 100644 --- a/cuser/usb/common.h +++ b/cuser/usb/common.h @@ -1,20 +1,34 @@ #include "../common.h" +/* A "controller" provides 0 or more buses, this defines the interface both for + * the controller and its buses. */ enum usb_controller_msg { /** - * The controller has found a device at slot N. + * Sent from a recently activated controller to notify the USB system about + * newly found buses. Should only be sent once for each controller instance. * - * arg1 = slot number + * The handle will be duplicated for each bus, and this message will be sent + * to associate the handle with a bus. + * + * from controller: + * arg1 = (max.) number of buses available from this controller. + * from USB system: + * arg1 = bus number (starting at bus 0) + */ + MSG_USB_CONTROLLER_INIT = MSG_USER, + /** + * The bus has found a device. The device will be addressable as device + * 0 until it's assigned an address with ADDR_DEVICE. */ - MSG_USB_NEW_DEVICE = MSG_USER, + MSG_USB_NEW_DEVICE, /** - * Send a control message to a slot. Must be sendrcv'd. + * Send a control message to a device. Must be sendrcv'd. * (TODO define asynchronous variant, with a pulse and (e.g.) relevant * data stored inside the buffer, or with a separate pickup message.) * * arg1: - * bit 0..7: slot number + * bit 0..7: device address (0 for the unregistered device) * bit 8..11: endpoint (0..15) * bit 12: direction (for non-0 endpoints): 0 = OUT, 1 = IN * bit 13: (for endpoint 0): 1 if there is a data stage, direction decides @@ -30,6 +44,27 @@ enum usb_controller_msg * arg2 = if input direction: inline data */ MSG_USB_TRANSFER, + /** + * Finish enumerating the device at addr 0 on the bus, and give it a new + * address. + * + * arg1 = slot number + * return: + * arg1 = address + */ + MSG_ADDR_DEVICE, +}; + +union usb_transfer_arg +{ + struct { + u16 addr_ep_flags; + u8 /*usb_transfer_type*/ type; + u8 pad1; + u16 length; + u16 pad2; + } s; + u64 i; }; // Relative to the arg1 qword for USB_TRANSFER* @@ -79,9 +114,8 @@ enum usb_transfer_type //UTT_NoOp }; -// Maybe bad name? For communication between USB class/device drviers and the +// Maybe bad name? For communication between USB class/device drivers and the // "main" USB system, which wrangles the host controller(s). -// Should vaguely conform to libusb20's backend operations somehow. enum usb_device_msg { /** diff --git a/cuser/usb/main.c b/cuser/usb/main.c index 87534d4..0c93d64 100644 --- a/cuser/usb/main.c +++ b/cuser/usb/main.c @@ -12,20 +12,40 @@ static const uintptr_t xhci_handle = 7; static const uintptr_t fresh = 0x100; +static const uintptr_t bus_handle_base = 0x200; +static uintptr_t bus_handle_max; -static void handle_xhci_msg(uintptr_t msg, uintptr_t arg, uintptr_t arg2) { +static uintptr_t bus_from_handle(uintptr_t h) { + return h - bus_handle_base; +} +static uintptr_t handle_from_bus(uintptr_t b) { + return bus_handle_base + b; +} + +static void handle_bus_msg(uintptr_t bus, uintptr_t msg, uintptr_t arg, uintptr_t arg2) { switch (msg & 0xff) { case MSG_USB_NEW_DEVICE: { - debug("New device, slot %u\n", arg); + debug("New device, bus %u slot %u\n", bus_from_handle(bus), arg); + // TODO: Get (8 bytes of) descriptor 0, then address the device break; } } } +static void register_buses(uintptr_t rcpt, const size_t buses) { + log("%x: %d buses: %d..%d\n", rcpt, buses, bus_from_handle(bus_handle_max), bus_from_handle(bus_handle_max) + buses - 1); + for (uintptr_t n = 0; n < buses; n++) { + const uintptr_t bus = bus_handle_max++; + hmod_copy(rcpt, bus); + send1(MSG_USB_CONTROLLER_INIT, bus, n); + } +} + void start() { __default_section_init(); + bus_handle_max = bus_handle_base; for (;;) { uintptr_t rcpt = fresh; uintptr_t arg = 0; @@ -34,8 +54,11 @@ void start() uintptr_t msg = recv2(&rcpt, &arg, &arg2); debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); - if (rcpt == xhci_handle) { - handle_xhci_msg(msg, arg, arg2); + if (msg == MSG_USB_CONTROLLER_INIT) { + register_buses(rcpt, arg); + } + if (rcpt >= bus_handle_base && rcpt < bus_handle_max) { + handle_bus_msg(rcpt, msg, arg, arg2); } } } diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index a679c48..c86f02f 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -12,6 +12,7 @@ static const uintptr_t acpi_handle = 4; static const uintptr_t pic_handle = 2; static const uintptr_t pin0_irq_handle = 0x100; static const uintptr_t fresh = 0x101; +static const uintptr_t bus_handle_base = 0x200; static volatile u8 mmiospace[128 * 1024] PLACEHOLDER_SECTION ALIGN(128*1024); // NB: Byte offsets @@ -220,6 +221,8 @@ enum TRBFlags TRB_ToggleC = 2, TRB_Chain = 1 << 4, TRB_IOC = 1 << 5, + // Block SetAddress Request + TRB_BSR = 1 << 9, }; enum CompletionCodes { @@ -487,8 +490,10 @@ static bool enqueue_command2(struct command_trb *cmd, u8 data) { } // TODO warn_unused_result +static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) + __attribute__((nonnull)); + static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { - assert(cmd); cmd->control |= (command << 10) | (command_pcs ? TRB_Cycle : 0); return enqueue_command2(cmd, data); } @@ -553,7 +558,7 @@ static endpoint_context *get_ep_ctx(void *p, unsigned ep) { return (endpoint_context*)((u8*)p + (ep + 2) * context_size); } -static void address_device(u8 port, u8 slot) { +static void address_device(u8 port, u8 slot, bool block_set) { debug("address device: port %d -> slot %d\n", port, slot); dma_buffer_ref input = allocate_dma_buffer(); dma_buffer_ref devctx = allocate_dma_buffer(); @@ -589,6 +594,9 @@ static void address_device(u8 port, u8 slot) { __barrier(); command_trb cmd = { input.phys, 0, slot << 24 }; + if (block_set) { + cmd.control |= TRB_BSR; + } enqueue_command(&cmd, TRB_CMD_AddressDevice, port); } @@ -603,7 +611,7 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { case TRB_CMD_EnableSlot: { if (ccode == CC_Success) { - address_device(data, slot); + address_device(data, slot, true); } break; } @@ -614,7 +622,7 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { if (ccode == CC_Success) { // Device is addressed, now what? debug("AddressDevice completed! slot %d port %d ready to configure\n", slot, port); - send1(MSG_USB_NEW_DEVICE, usb_handle, slot); + send1(MSG_USB_NEW_DEVICE, bus_handle_base + port, slot); } else { debug("AddressDevice failed! freeing slot %d port %d\n", slot, port); command_trb cmd = { 0, 0, 0 }; @@ -891,9 +899,10 @@ void start() } } - for (unsigned port = 1; port <= maxport; port++) { - proceed_port(port); - } + // Ports are a bus each?? + debug("Sending controller init for %u buses...\n", maxport); + send1(MSG_USB_CONTROLLER_INIT, usb_handle, maxport); + debug("Sent controller init\n"); for(;;) { uintptr_t rcpt = fresh; @@ -906,6 +915,12 @@ void start() handle_irq(rcpt, arg); continue; } + if (rcpt == fresh && (msg & 0xff) == MSG_USB_CONTROLLER_INIT) { + uintptr_t bus = arg; + hmod_rename(rcpt, bus_handle_base + bus); + proceed_port(bus); + continue; + } switch (msg & 0xff) { From 4244ba8132703b5cef25e3a6ce01053e1a5875d9 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Thu, 18 Jun 2015 01:08:09 +0200 Subject: [PATCH 17/22] Code for sending control messages, try to GET_DESCRIPTOR --- cuser/usb/common.h | 46 +++++++++++--- cuser/usb/main.c | 45 ++++++++++++- cuser/usb/xhci.c | 154 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 229 insertions(+), 16 deletions(-) diff --git a/cuser/usb/common.h b/cuser/usb/common.h index 3719b7a..1ec5c85 100644 --- a/cuser/usb/common.h +++ b/cuser/usb/common.h @@ -20,6 +20,9 @@ enum usb_controller_msg /** * The bus has found a device. The device will be addressable as device * 0 until it's assigned an address with ADDR_DEVICE. + * + * FIXME This leads to deadlocks when the controller wants to send this. + * Should use a pulse instead. */ MSG_USB_NEW_DEVICE, /** @@ -55,31 +58,54 @@ enum usb_controller_msg MSG_ADDR_DEVICE, }; -union usb_transfer_arg +/*static void usb_read_descriptor(uintptr_t bus, uintptr_t slot, u8 descriptor, void* dest, size_t max_length) +{ + sendrcv1(bus, +}*/ + +typedef union usb_transfer_arg { struct { - u16 addr_ep_flags; - u8 /*usb_transfer_type*/ type; + u8 addr; + u8 ep : 4; + u8 flags : 4; /*usb_transfer_flags*/ + u8 type; /*usb_transfer_type*/ u8 pad1; u16 length; u16 pad2; - } s; + }; u64 i; -}; +} usb_transfer_arg; -// Relative to the arg1 qword for USB_TRANSFER* enum usb_transfer_flags { - UTF_SetupHasData = 1 << 13, - UTF_ImmediateData = 1 << 14, + UTF_DirectionOut = 0 << 0, + UTF_DirectionIn = 1 << 0, + UTF_SetupHasData = 1 << 1, + UTF_ImmediateData = 1 << 2, +}; + +struct usb_control_setup +{ + u8 bmRequestType; + u8 bRequest; + u16 wValue; + u16 wIndex; + u16 wLength; }; -enum usb_transfer_type +typedef enum usb_transfer_type { /** * Data on a normal endpoint. */ UTT_Normal, + /** + * Shorthand for a whole control transaction. The outgoing data is the same + * format as UTT_ControlSetup. If there's a data stage, UTF_SetupHasData + * should be set and direction should be 1. + */ + UTT_ControlTransaction, /** * The data is in the xHCI format for Setup Stage Control TRBs: * bits 0..7: bmRequestType @@ -112,7 +138,7 @@ enum usb_transfer_type */ UTT_Isoch, //UTT_NoOp -}; +} usb_transfer_type; // Maybe bad name? For communication between USB class/device drivers and the // "main" USB system, which wrangles the host controller(s). diff --git a/cuser/usb/main.c b/cuser/usb/main.c index 0c93d64..b477559 100644 --- a/cuser/usb/main.c +++ b/cuser/usb/main.c @@ -22,12 +22,51 @@ static uintptr_t handle_from_bus(uintptr_t b) { return bus_handle_base + b; } -static void handle_bus_msg(uintptr_t bus, uintptr_t msg, uintptr_t arg, uintptr_t arg2) { +enum BMRequestType { + ReqType_HostToDev = 0 << 7, + ReqType_DevToHost = 1 << 7, + ReqType_Standard = 0 << 5, + ReqType_Class = 1 << 5, + ReqType_Vendor = 2 << 5, + // 3 << 5: Reserved + ReqType_Device = 0, + ReqType_Interface = 1, + ReqType_EndPoint = 2, + ReqType_Other = 3, + // 4..31: Reserved +}; +enum BMRequest { + Req_GetStatus, + Req_ClearFeature, + // 2: reserved + Req_SetFeature = 3, + // 4: reserved + Req_SetAddress = 5, // Not used - buses handle this + Req_GetDescriptor +}; + +static void handle_bus_msg(const uintptr_t bus, uintptr_t msg, uintptr_t arg, uintptr_t arg2) { switch (msg & 0xff) { case MSG_USB_NEW_DEVICE: { - debug("New device, bus %u slot %u\n", bus_from_handle(bus), arg); - // TODO: Get (8 bytes of) descriptor 0, then address the device + u8 slot = arg; + debug("New device, bus %u slot %u\n", bus_from_handle(bus), slot); + usb_transfer_arg targ; + targ.addr = slot; + targ.ep = 0; + targ.flags = UTF_ImmediateData | UTF_DirectionIn | UTF_SetupHasData; + targ.type = UTT_ControlTransaction; + targ.length = 8; + arg = targ.i; + struct usb_control_setup control_setup = { + ReqType_DevToHost | ReqType_Standard | ReqType_Device, + Req_GetDescriptor, + 1 << 8, 0, 8 }; + arg2 = *(u64*)&control_setup; + log("Sending: transfer to %ld with %lx,%lx\n", bus_from_handle(bus), arg, arg2); + msg = sendrcv2(MSG_USB_TRANSFER, bus, &arg, &arg2); + log("Transfer reply: %lx with %lx,%lx\n", msg, arg, arg2); + // Done? break; } } diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index c86f02f..33bd5f5 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -217,12 +217,21 @@ enum TRBType typedef enum TRBType TRBType; enum TRBFlags { - TRB_Cycle = 1, - TRB_ToggleC = 2, + TRB_Cycle = 1 << 0, + TRB_ToggleC = 1 << 1, + TRB_ED = 1 << 2, TRB_Chain = 1 << 4, TRB_IOC = 1 << 5, + // ImmediateDaTa (or ImmeDiaTe?) + TRB_IDT = 1 << 6, // Block SetAddress Request TRB_BSR = 1 << 9, + TRB_DirectionIn = 1 << 16, +}; +enum ControlTransferTypes +{ + TRT_DirectionIn = 3, + TRT_DirectionOut = 2, }; enum CompletionCodes { @@ -252,6 +261,7 @@ static union trb create_link_trb(u64 address, u8 flags) { return res; } +#define TRANSFER_RING_SIZE 255 #define COMMAND_RING_SIZE 16 #define EVENT_RING_SIZE 16 // The Event Ring Segment Table also has 16-byte entries @@ -270,6 +280,15 @@ static bool command_pcs = true; static u8 event_ring_dequeue; static bool event_ring_pcs = true; +typedef struct transfer_ring +{ + trb *trbs; + u8 enqueue; // Max 256 entries per ring, sorry :D + bool pcs; + // data per TRB entry? Used for 'command' at least... +} transfer_ring; + +static transfer_ring slot_rings[256]; static volatile u64 device_context_addrs[256] PLACEHOLDER_SECTION ALIGN(4096); // Bytes per context entry. static u8 context_size; @@ -472,12 +491,14 @@ u8 readpci8(u32 addr, u8 reg) static void print_pscr(u8 port, u32 pscr); +// TODO Merge with enqueue_trb_ring, use transfer_ring struct, etc. static bool enqueue_command2(struct command_trb *cmd, u8 data) { if (!!(page1.command_ring[command_enqueue].control & TRB_Cycle) == command_pcs) { debug("Eww... Command ring is full.\n"); return false; } + // FIXME What says cmd->control & TRB_Cycle is correct here? page1.command_ring[command_enqueue] = *cmd; command_data[command_enqueue] = data; if (++command_enqueue == COMMAND_RING_SIZE) { @@ -588,6 +609,11 @@ static void address_device(u8 port, u8 slot, bool block_set) { trb* ring_trbs = (trb *)ring.virtual; ring_trbs[(4096 / sizeof(trb)) - 1] = create_link_trb(ring.phys, TRB_ToggleC); + // FIXME Each *endpoint* has its own transfer ring, this is just the + // control endpoint.. + slot_rings[slot].trbs = ring_trbs; + slot_rings[slot].pcs = true; + slot_rings[slot].enqueue = 0; // TODO More barrier to make sure input changes are in RAM before we submit // the command. @@ -619,6 +645,7 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { { u8 port = data; free_dma_buffer(trb.parameter); + // Check TRB_BSR if (ccode == CC_Success) { // Device is addressed, now what? debug("AddressDevice completed! slot %d port %d ready to configure\n", slot, port); @@ -667,6 +694,16 @@ static void handle_event(event_trb* ev) command_complete(cmdpos, ccode, slot); break; } + case TRB_TransferEvent: + { + u64 trbptr = ev->parameter; + u32 residual = cparam; + u8 ep = (ev->control >> 16) & 31; + debug("Transfer to %d ep %d completed: %p ccode=%d ED=%d\n", + slot, ep, trbptr, ccode, !!(cparam & TRB_ED)); + + break; + } default: debug("Unknown event %d\n", type); break; @@ -721,6 +758,113 @@ static void print_pscr(u8 port, u32 pscr) { linkState, portSpeed, pscr & ~knownbits); } +static const char *usb_transfer_type_name(usb_transfer_type type) { + switch (type) { + case UTT_ControlTransaction: + return "Control Transaction"; + default: + return "Unknown"; + } +} + +static bool ring_enqueue(transfer_ring *ring, union trb* trb) __attribute__((nonnull)); +static bool ring_enqueue(transfer_ring *ring, union trb* trb) { + union trb* trbs = ring->trbs; + assert(trbs); + if (!!(trbs[ring->enqueue].control & TRB_Cycle) == ring->pcs) { + return false; + } + if (ring->pcs) trb->control |= TRB_Cycle; + trbs[ring->enqueue] = *trb; + //command_data[command_enqueue] = data; + if (++ring->enqueue == TRANSFER_RING_SIZE) { + debug("ring_enqueue: ring wrapped!\n"); + ring->enqueue = 0; + ring->pcs ^= 1; + } + return true; +} + +static bool enqueue_trb(u8 slot, u8 ep, union trb trb) { + if (ring_enqueue(&slot_rings[slot], &trb)) { + doorbells[slot] = ep; + return true; + } + debug("Eww... Ring for slot %u ep %u is full.\n", slot, ep); + return false; +} + +static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { + union trb trb; + usb_transfer_arg targ = { .i = arg1 }; + switch (msg & 0xff) { + case MSG_USB_TRANSFER: + assert(targ.flags & UTF_ImmediateData); + log("transfer to %d:%d: flags %x type %x (%s) data %lx length %u\n", targ.addr, targ.ep, + targ.flags, targ.type, + usb_transfer_type_name(targ.type), arg2, targ.length); + u8 slot = targ.addr; + switch (targ.type) { + case UTT_ControlTransaction: + // Fill in some TRBs: + // ControlSetup data + trb.parameter = arg2; + // TRB Transfer length, always 8 + trb.status = 8; + trb.control = TRB_IDT | (TRB_SetupStage << 10); + if (targ.flags & UTF_SetupHasData) { + trb.control |= (targ.flags & UTF_DirectionIn ? + TRT_DirectionIn : TRT_DirectionOut) << 16; + } + // IOC will only be set on the last TRB, ControlStatus + enqueue_trb(targ.addr, 1, trb); + // TODO If flags ask for ImmediateData and DirectionIn, we want to + // allow that even though it's not allowed by xhci. We need to have + // a buffer and copy it back before responding. + // Also need to free this buffer and have some sort of outstanding + // requests handling. + // if there's data: set up a ControlData stage + if (targ.flags & UTF_SetupHasData) { + trb.parameter = trb.status = trb.control = 0; + if (targ.flags & UTF_DirectionIn || !(targ.flags & UTF_ImmediateData)) { + // Err, should have a buffer index somewhere to shared + // memory with the client. + dma_buffer_ref buf = allocate_dma_buffer(); + trb.normal.address = buf.phys; + log("Leaking a buffer..."); + } else { + // Inline outgoing data. Would be arg2, but that's the + // setup-stage data. + //trb.normal.address = arg3; + assert(!"Immediate outgoing setup data unimplemented"); + } + trb.normal.length = targ.length; // 8 + log("SETUP data stage length is %u\n", trb.normal.length); + // Not sure about the TD size stuff! But this is the only + // "control data stage" TRB. + trb.normal.td_size = 0; + trb.normal.interrupter_target = 0; + trb.control = (TRB_DataStage << 10); + if (targ.flags & UTF_DirectionIn) { + trb.control |= 1 << 16; + } + enqueue_trb(targ.addr, 1, trb); + } + // StatusStage + trb.parameter = 0; + trb.status = 0; + trb.control = (TRB_StatusStage << 10) | TRB_IOC; + // Unless a data stage with incoming data, status stage is always + // incoming. + if (!(targ.flags & UTF_DirectionIn) + || !(targ.flags & UTF_SetupHasData)) { + trb.control |= TRB_DirectionIn; + } + enqueue_trb(targ.addr, 1, trb); + } + } +} + void start() { // FIXME So much copy-pasta from xhci.c here - extract more PCI utility @@ -910,7 +1054,7 @@ void start() arg2 = 0; debug("receiving...\n"); uintptr_t msg = recv2(&rcpt, &arg, &arg2); - debug("received %x from %x: %x %x\n", msg, rcpt, arg, arg2); + debug("received %lx from %lx: %lx %lx\n", msg, rcpt, arg, arg2); if (rcpt == pin0_irq_handle && msg == MSG_PULSE) { handle_irq(rcpt, arg); continue; @@ -921,6 +1065,10 @@ void start() proceed_port(bus); continue; } + if (rcpt >= bus_handle_base && rcpt < bus_handle_base + maxport) { + port_msg(rcpt - bus_handle_base, msg, arg, arg2); + continue; + } switch (msg & 0xff) { From 79340aa3a8e9337645a0217f805f466fb9363083 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Thu, 18 Jun 2015 01:08:29 +0200 Subject: [PATCH 18/22] Make return-type (missing return) warning an error --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index ca9cd26..a9959bc 100644 --- a/Makefile +++ b/Makefile @@ -130,6 +130,7 @@ GRUB_CFG = $(GRUBDIR)/boot/grub/grub.cfg USER_CFLAGS := -ffreestanding -g -Os -W -Wall -Wextra -march=native -mno-avx -std=gnu99 USER_CFLAGS += -Wno-unused-function -Wno-unused-parameter +USER_CFLAGS += -Werror=return-type USER_CFLAGS += -ffunction-sections -fdata-sections LDFLAGS := --check-sections From 27fc74296e62761b42ce240e31d7a278670d5273 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Sun, 21 Jun 2015 17:35:00 +0200 Subject: [PATCH 19/22] Extract dma_buffer code to separate header --- cuser/dma_buffer.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++ cuser/usb/xhci.c | 45 +-------------------------------------------- 2 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 cuser/dma_buffer.h diff --git a/cuser/dma_buffer.h b/cuser/dma_buffer.h new file mode 100644 index 0000000..828a67c --- /dev/null +++ b/cuser/dma_buffer.h @@ -0,0 +1,46 @@ +#include "common.h" + +static u64 dma_map(const volatile void* addr, size_t size) { + const enum prot flags = MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE; + return (u64)map(0, flags, addr, 0, size); +} +#define dma_map(obj) dma_map(&(obj), sizeof(obj)) + +#define MAX_DMA_BUFFERS 16 +static u8 dma_buffer_space[MAX_DMA_BUFFERS][4096] PLACEHOLDER_SECTION ALIGN(4096); +typedef struct dma_buffer { + u64 phys; // 0 for unmapped buffers +} dma_buffer; +static dma_buffer dma_buffers[MAX_DMA_BUFFERS]; +typedef struct dma_buffer_ref { + u64 phys; + u8 *virtual; +} dma_buffer_ref; +static dma_buffer_ref allocate_dma_buffer() { + dma_buffer_ref res = { 0, NULL }; + for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { + dma_buffer *buf = dma_buffers + i; + if (buf->phys & 1) continue; + + if (!buf->phys) { + buf->phys = dma_map(dma_buffer_space[i]); + } + res.phys = buf->phys; + res.virtual = dma_buffer_space[i]; + memset(res.virtual, 0, 4096); + + buf->phys |= 1; + return res; + } + assert(!"Ran out of DMA buffers..."); + return res; +} +static void free_dma_buffer(u64 phys) { + for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { + dma_buffer *buf = dma_buffers + i; + if (buf->phys != (phys | 1)) continue; + buf->phys ^= 1; + return; + } +} + diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index 33bd5f5..df4171b 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -1,4 +1,5 @@ #include "common.h" +#include "../dma_buffer.h" #define log(fmt, ...) printf("xhci: " fmt, ## __VA_ARGS__) #if 1 @@ -361,50 +362,6 @@ enum TRDPBits TRDP_DCS = 1, }; -static u64 dma_map(const volatile void* addr, size_t size) { - const enum prot flags = MAP_DMA | PROT_READ | PROT_WRITE | PROT_NO_CACHE; - return (u64)map(0, flags, addr, 0, size); -} -#define dma_map(obj) dma_map(&(obj), sizeof(obj)) - -#define MAX_DMA_BUFFERS 16 -static u8 dma_buffer_space[MAX_DMA_BUFFERS][4096] PLACEHOLDER_SECTION ALIGN(4096); -typedef struct dma_buffer { - u64 phys; // 0 for unmapped buffers -} dma_buffer; -static dma_buffer dma_buffers[MAX_DMA_BUFFERS]; -typedef struct dma_buffer_ref { - u64 phys; - u8 *virtual; -} dma_buffer_ref; -static dma_buffer_ref allocate_dma_buffer() { - dma_buffer_ref res = { 0, NULL }; - for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { - dma_buffer *buf = dma_buffers + i; - if (buf->phys & 1) continue; - - if (!buf->phys) { - buf->phys = dma_map(dma_buffer_space[i]); - } - res.phys = buf->phys; - res.virtual = dma_buffer_space[i]; - memset(res.virtual, 0, 4096); - - buf->phys |= 1; - return res; - } - assert(!"Ran out of DMA buffers..."); - return res; -} -static void free_dma_buffer(u64 phys) { - for (unsigned i = 0; i < MAX_DMA_BUFFERS; i++) { - dma_buffer *buf = dma_buffers + i; - if (buf->phys != (phys | 1)) continue; - buf->phys ^= 1; - return; - } -} - enum EcpCapIds { CAPID_SUPPORTED_PROTOCOL = 2, From b232bf28b0ca454ecdb2be036b99697fadb24a5d Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Mon, 22 Jun 2015 13:54:17 +0200 Subject: [PATCH 20/22] Add an array of transfer datas to be used as parameters to EventDatas --- cuser/usb/xhci.c | 60 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index df4171b..ae401ed 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -289,6 +289,21 @@ typedef struct transfer_ring // data per TRB entry? Used for 'command' at least... } transfer_ring; +typedef struct transfer_data +{ + dma_buffer_ref buf; + u8 port; + uintptr_t data; +} ALIGN(16) transfer_data; + +/* Safe as long as transfer_data is larger than 16. */ +#define NUM_TRANSFER_DATAS (4096 / sizeof(transfer_data)) +static transfer_data transfer_datas[NUM_TRANSFER_DATAS]; +// Index of first free transfer data +static u8 free_transfer_data; +static transfer_data *alloc_transfer_data(void); +static void transfer_data_free(transfer_data *trdata); + static transfer_ring slot_rings[256]; static volatile u64 device_context_addrs[256] PLACEHOLDER_SECTION ALIGN(4096); // Bytes per context entry. @@ -656,9 +671,18 @@ static void handle_event(event_trb* ev) u64 trbptr = ev->parameter; u32 residual = cparam; u8 ep = (ev->control >> 16) & 31; - debug("Transfer to %d ep %d completed: %p ccode=%d ED=%d\n", - slot, ep, trbptr, ccode, !!(cparam & TRB_ED)); - + bool ed = !!(ev->control & TRB_ED); + debug("Transfer to %d ep %d completed: %p ccode=%d ED=%d not-transferred=%u\n", + slot, ep, trbptr, ccode, ed, residual); + if (ed && trbptr) { + transfer_data *trdata = (transfer_data*)(trbptr & -16); + debug("Transfer done for port %u data %lx\n", trdata->port, trdata->data); + if (trdata->buf.virtual) { + hexdump(trdata->buf.virtual, 8); + } + //proceed_transfer(trdata); + transfer_data_free(trdata); + } break; } default: @@ -751,6 +775,23 @@ static bool enqueue_trb(u8 slot, u8 ep, union trb trb) { return false; } +static transfer_data *alloc_transfer_data(void) { + assert(free_transfer_data < NUM_TRANSFER_DATAS); + transfer_data *res = transfer_datas + free_transfer_data++; + return res; +} + +static void transfer_data_free(transfer_data *trdata) { + if (trdata->buf.virtual) { + free_dma_buffer(trdata->buf.phys); + trdata->buf.phys = 0; + trdata->buf.virtual = NULL; + } + if (trdata - transfer_datas == free_transfer_data - 1) { + free_transfer_data--; + } +} + static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { union trb trb; usb_transfer_arg targ = { .i = arg1 }; @@ -761,6 +802,9 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { targ.flags, targ.type, usb_transfer_type_name(targ.type), arg2, targ.length); u8 slot = targ.addr; + transfer_data *trdata = alloc_transfer_data(); + trdata->port = port; + trdata->data = arg1; switch (targ.type) { case UTT_ControlTransaction: // Fill in some TRBs: @@ -786,8 +830,8 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { if (targ.flags & UTF_DirectionIn || !(targ.flags & UTF_ImmediateData)) { // Err, should have a buffer index somewhere to shared // memory with the client. - dma_buffer_ref buf = allocate_dma_buffer(); - trb.normal.address = buf.phys; + trdata->buf = allocate_dma_buffer(); + trb.normal.address = trdata->buf.phys; log("Leaking a buffer..."); } else { // Inline outgoing data. Would be arg2, but that's the @@ -810,7 +854,7 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { // StatusStage trb.parameter = 0; trb.status = 0; - trb.control = (TRB_StatusStage << 10) | TRB_IOC; + trb.control = (TRB_StatusStage << 10) | TRB_Chain; // Unless a data stage with incoming data, status stage is always // incoming. if (!(targ.flags & UTF_DirectionIn) @@ -818,6 +862,10 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { trb.control |= TRB_DirectionIn; } enqueue_trb(targ.addr, 1, trb); + trb.parameter = (uintptr_t)trdata; + trb.status = 0; + trb.control = (TRB_EventData << 10) | TRB_IOC; + enqueue_trb(targ.addr, 1, trb); } } } From e161380068338c66f03ed4defb584bcde6b609c6 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Mon, 22 Jun 2015 15:08:18 +0200 Subject: [PATCH 21/22] Address the device after getting the first 8 bytes of the descriptor --- cuser/common.h | 4 ++-- cuser/dma_buffer.h | 1 - cuser/usb/common.h | 2 +- cuser/usb/main.c | 12 ++++++++++-- cuser/usb/xhci.c | 48 ++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 53 insertions(+), 14 deletions(-) diff --git a/cuser/common.h b/cuser/common.h index fa4c0a4..44fdd93 100644 --- a/cuser/common.h +++ b/cuser/common.h @@ -546,12 +546,12 @@ static void assert_failed(const char* file, int line, const char* msg) { #define assert(X) \ do { if (!(X)) assert_failed(__FILE__, __LINE__, #X); } while (0) -static void hexdump(char* data, size_t length) { +static void hexdump(const void* data, size_t length) { size_t pos = 0; while (pos < length) { printf("\n%04x: ", pos); for (int i = 0; i < 16 && pos < length; i++) { - printf("%02x ", (u8)data[pos++]); + printf("%02x ", ((const u8*)data)[pos++]); } } printf("\n"); diff --git a/cuser/dma_buffer.h b/cuser/dma_buffer.h index 828a67c..136a340 100644 --- a/cuser/dma_buffer.h +++ b/cuser/dma_buffer.h @@ -43,4 +43,3 @@ static void free_dma_buffer(u64 phys) { return; } } - diff --git a/cuser/usb/common.h b/cuser/usb/common.h index 1ec5c85..e084ecb 100644 --- a/cuser/usb/common.h +++ b/cuser/usb/common.h @@ -55,7 +55,7 @@ enum usb_controller_msg * return: * arg1 = address */ - MSG_ADDR_DEVICE, + MSG_USB_ADDR_DEVICE, }; /*static void usb_read_descriptor(uintptr_t bus, uintptr_t slot, u8 descriptor, void* dest, size_t max_length) diff --git a/cuser/usb/main.c b/cuser/usb/main.c index b477559..61a3823 100644 --- a/cuser/usb/main.c +++ b/cuser/usb/main.c @@ -51,6 +51,8 @@ static void handle_bus_msg(const uintptr_t bus, uintptr_t msg, uintptr_t arg, ui case MSG_USB_NEW_DEVICE: { u8 slot = arg; debug("New device, bus %u slot %u\n", bus_from_handle(bus), slot); + // Fetch the first 8 bytes of the device descriptor before addressing + // the device. usb_transfer_arg targ; targ.addr = slot; targ.ep = 0; @@ -65,8 +67,14 @@ static void handle_bus_msg(const uintptr_t bus, uintptr_t msg, uintptr_t arg, ui arg2 = *(u64*)&control_setup; log("Sending: transfer to %ld with %lx,%lx\n", bus_from_handle(bus), arg, arg2); msg = sendrcv2(MSG_USB_TRANSFER, bus, &arg, &arg2); - log("Transfer reply: %lx with %lx,%lx\n", msg, arg, arg2); - // Done? + log("Transfer reply: %lx with %lx\n", msg, arg2); + // Parse something interesting out of the descriptor? (Or wait until + // we have addressed it.) + arg = slot; + arg2 = 0; + msg = sendrcv2(MSG_USB_ADDR_DEVICE, bus, &arg, &arg2); + u8 addr = arg; + log("Device addressed to %u.%u\n", bus_from_handle(bus), addr); break; } } diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index ae401ed..f8e0946 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -552,7 +552,7 @@ static endpoint_context *get_ep_ctx(void *p, unsigned ep) { } static void address_device(u8 port, u8 slot, bool block_set) { - debug("address device: port %d -> slot %d\n", port, slot); + debug("address device: port %d -> slot %d (block set address=%d)\n", port, slot, block_set); dma_buffer_ref input = allocate_dma_buffer(); dma_buffer_ref devctx = allocate_dma_buffer(); dma_buffer_ref ring = allocate_dma_buffer(); @@ -617,15 +617,20 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { { u8 port = data; free_dma_buffer(trb.parameter); - // Check TRB_BSR - if (ccode == CC_Success) { - // Device is addressed, now what? - debug("AddressDevice completed! slot %d port %d ready to configure\n", slot, port); - send1(MSG_USB_NEW_DEVICE, bus_handle_base + port, slot); - } else { + if (ccode != CC_Success) { debug("AddressDevice failed! freeing slot %d port %d\n", slot, port); command_trb cmd = { 0, 0, 0 }; enqueue_command(&cmd, TRB_CMD_DisableSlot, port); + break; + } + if (trb.control & TRB_BSR) { + // Device is addressed, now what? + debug("AddressDevice(not set addr) completed! slot %d port %d ready to configure\n", slot, port); + send1(MSG_USB_NEW_DEVICE, bus_handle_base + port, slot); + } else { + debug("AddressDevice(set addr) completed! slot %d port %d ready to configure\n", slot, port); + // What address did we get??? + send1(MSG_USB_ADDR_DEVICE, bus_handle_base + port, slot); } } case TRB_CMD_DisableSlot: @@ -644,6 +649,8 @@ static void command_complete(u8 cmdpos, u8 ccode, u8 slot) { } } +static void proceed_transfer(transfer_data *trdata); + static void handle_event(event_trb* ev) { u8 type = trb_type(ev); @@ -680,7 +687,7 @@ static void handle_event(event_trb* ev) if (trdata->buf.virtual) { hexdump(trdata->buf.virtual, 8); } - //proceed_transfer(trdata); + proceed_transfer(trdata); transfer_data_free(trdata); } break; @@ -792,12 +799,30 @@ static void transfer_data_free(transfer_data *trdata) { } } +static void proceed_transfer(transfer_data *trdata) { + usb_transfer_arg targ = { .i = trdata->data }; + const uintptr_t rcpt = bus_handle_base + trdata->port; + switch (targ.type) { + case UTT_ControlTransaction: + { + u64 data = 0; + // If incoming data and immediate, put the data in arg2 and reply + if ((targ.flags & UTF_SetupHasData) && (targ.flags & UTF_DirectionIn) + && (targ.flags & UTF_ImmediateData)) { + data = *(u64*)trdata->buf.virtual; + } + send2(MSG_USB_TRANSFER, rcpt, trdata->data, data); + } + } +} + static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { union trb trb; usb_transfer_arg targ = { .i = arg1 }; switch (msg & 0xff) { case MSG_USB_TRANSFER: assert(targ.flags & UTF_ImmediateData); + assert(msg_get_kind(msg) == MSG_KIND_CALL); log("transfer to %d:%d: flags %x type %x (%s) data %lx length %u\n", targ.addr, targ.ep, targ.flags, targ.type, usb_transfer_type_name(targ.type), arg2, targ.length); @@ -867,6 +892,13 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { trb.control = (TRB_EventData << 10) | TRB_IOC; enqueue_trb(targ.addr, 1, trb); } + break; + case MSG_USB_ADDR_DEVICE: + { + u8 slot = arg1; + address_device(port, slot, false); + break; + } } } From 6540a86c45a487a06548876bdc70cf2d01bec975 Mon Sep 17 00:00:00 2001 From: Simon Brenner Date: Mon, 22 Jun 2015 18:58:42 +0200 Subject: [PATCH 22/22] TODO was implemented --- cuser/usb/xhci.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cuser/usb/xhci.c b/cuser/usb/xhci.c index f8e0946..d6394e4 100644 --- a/cuser/usb/xhci.c +++ b/cuser/usb/xhci.c @@ -844,11 +844,6 @@ static void port_msg(u8 port, uintptr_t msg, uintptr_t arg1, uintptr_t arg2) { } // IOC will only be set on the last TRB, ControlStatus enqueue_trb(targ.addr, 1, trb); - // TODO If flags ask for ImmediateData and DirectionIn, we want to - // allow that even though it's not allowed by xhci. We need to have - // a buffer and copy it back before responding. - // Also need to free this buffer and have some sort of outstanding - // requests handling. // if there's data: set up a ControlData stage if (targ.flags & UTF_SetupHasData) { trb.parameter = trb.status = trb.control = 0;