diff --git a/Makefile b/Makefile index e346662..a9959bc 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) @@ -80,6 +86,10 @@ clean: %: %.c $(HUSH_CC) $(CC) $(CFLAGS) -o $@ $< +# Disable these builtin pattern rules +%.o: %.mod +%: %.mod + -include $(DEPFILES) $(OUTDIR)/%.d: %.asm $(YASMDEP) @@ -120,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 @@ -161,7 +172,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 usb WANT_STRING = acpica @@ -315,7 +326,15 @@ $(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 := cuser/usb +USB_SRCS := $(USB)/main.c +USB_OBJS := $(USB_SRCS:%.c=$(OUTDIR)/%.o) + +-include $(USB_OBJS:.o=.d) + +$(OUTDIR)/cuser/usb.elf: cuser/linker.ld $(USB_OBJS) + @mkdir -p $(@D) + $(HUSH_LD) $(LD) $(USER_LDFLAGS) -o $@ -T $^ yasm/yasm: yasm/Makefile $(MAKE) -C yasm 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..44fdd93 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) */ @@ -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"); @@ -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/dma_buffer.h b/cuser/dma_buffer.h new file mode 100644 index 0000000..136a340 --- /dev/null +++ b/cuser/dma_buffer.h @@ -0,0 +1,45 @@ +#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/common.h b/cuser/usb/common.h new file mode 100644 index 0000000..e084ecb --- /dev/null +++ b/cuser/usb/common.h @@ -0,0 +1,166 @@ +#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 +{ + /** + * Sent from a recently activated controller to notify the USB system about + * newly found buses. Should only be sent once for each controller instance. + * + * 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. + * + * FIXME This leads to deadlocks when the controller wants to send this. + * Should use a pulse instead. + */ + MSG_USB_NEW_DEVICE, + /** + * 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: 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 + * 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, + /** + * Finish enumerating the device at addr 0 on the bus, and give it a new + * address. + * + * arg1 = slot number + * return: + * arg1 = address + */ + MSG_USB_ADDR_DEVICE, +}; + +/*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 { + u8 addr; + u8 ep : 4; + u8 flags : 4; /*usb_transfer_flags*/ + u8 type; /*usb_transfer_type*/ + u8 pad1; + u16 length; + u16 pad2; + }; + u64 i; +} usb_transfer_arg; + +enum usb_transfer_flags +{ + 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; +}; + +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 + * 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 +} usb_transfer_type; + +// Maybe bad name? For communication between USB class/device drivers and the +// "main" USB system, which wrangles the host controller(s). +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, +}; diff --git a/cuser/usb/main.c b/cuser/usb/main.c new file mode 100644 index 0000000..61a3823 --- /dev/null +++ b/cuser/usb/main.c @@ -0,0 +1,111 @@ +#include "common.h" + +#define log(fmt, ...) printf("USB: " fmt, ## __VA_ARGS__) +#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. + +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 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; +} + +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: { + 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; + 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\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; + } + } +} + +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; + 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 (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 new file mode 100644 index 0000000..d6394e4 --- /dev/null +++ b/cuser/usb/xhci.c @@ -0,0 +1,1115 @@ +#include "common.h" +#include "../dma_buffer.h" + +#define log(fmt, ...) printf("xhci: " fmt, ## __VA_ARGS__) +#if 1 +#define debug log +#else +#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; +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 +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 << 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 +static volatile u32* opregs; +// Note: dword offsets +enum OperationalRegisters +{ + USBCMD = 0, + USBSTS, + PAGESIZE, + // 3,4: RsvdZ + DNCTRL = 5, + // Command Ring Control Register + CRCR = 6, // u64 + CRCRH = 7, + // 8..11, RsvdZ + DCBAAP = 12, + DCBAAPH, + CONFIG, + // 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, + PORTPMSC = 1, + PORTLI = 2, + PORTHLPMC = 3, + PORT_NUMREGS = 4, // Number of dwords per port - 0x10 bytes => 4 dwords. +}; +enum PSCRBits +{ + 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; +enum RuntimeRegs +{ + MFINDEX = 0, +}; +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, +}; +enum ERDPBits +{ + // bits 0..2: Dequeue ERST Segment Index + ERDP_EHB = 8, +}; +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; +} + +typedef struct command_trb +{ + u64 parameter; + u32 status; + u32 control; +} 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_assert(sizeof(struct normal_trb) == 16, "normal TRB size doesn't match spec"); + +enum TRBType +{ + // 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, + + // 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, +}; +typedef enum TRBType TRBType; +enum TRBFlags +{ + 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 +{ + CC_Invalid = 0, + CC_Success = 1, +}; +typedef union trb +{ + struct command_trb cmd; + struct normal_trb normal; + struct { + u64 parameter; + u32 status; + u32 control; + }; +} trb; +typedef trb event_trb; +static TRBType trb_type(trb *trb_) { + return (trb_->control >> 10) & 0x3f; +} + +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 TRANSFER_RING_SIZE 255 +#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 u8 command_data[COMMAND_RING_SIZE]; +static u8 command_enqueue = 0; +// The PCS bit to put into created TRBs +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; + +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. +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, +}; + +enum EcpCapIds +{ + CAPID_SUPPORTED_PROTOCOL = 2, +}; +static uint8_t maxport, numports; +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]; + 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, + firstport, lastport); + if (lastport > maxport) { + maxport = lastport; + } + numports += portcount; + 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 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) { + 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) + __attribute__((nonnull)); + +static bool enqueue_command(struct command_trb *cmd, TRBType command, u8 data) { + cmd->control |= (command << 10) | (command_pcs ? TRB_Cycle : 0); + return enqueue_command2(cmd, data); +} + +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); + // 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); + } + } 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; + } + } +} + +#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, bool block_set) { + 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(); + 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); + // 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. + __barrier(); + + command_trb cmd = { input.phys, 0, slot << 24 }; + if (block_set) { + cmd.control |= TRB_BSR; + } + 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((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: + { + if (ccode == CC_Success) { + address_device(data, slot, true); + } + break; + } + case TRB_CMD_AddressDevice: + { + u8 port = data; + free_dma_buffer(trb.parameter); + 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: + { + 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: + debug("Command %d completed, but I don't know what I did?", cmd); + break; + } +} + +static void proceed_transfer(transfer_data *trdata); + +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; + 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; + } + case TRB_TransferEvent: + { + u64 trbptr = ev->parameter; + u32 residual = cparam; + u8 ep = (ev->control >> 16) & 31; + 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: + debug("Unknown event %d\n", type); + break; + } +} + +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) { + 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; + } +} + +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); +} + +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 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 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); + 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: + // 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); + // 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. + 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 + // 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_Chain; + // 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); + trb.parameter = (uintptr_t)trdata; + trb.status = 0; + 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; + } + } +} + +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); + 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", + 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); + 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); + 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); + assert(hccparams1 & HCCP1_AC64); + 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); + + // 4kB pages *must* be supported. + assert(opregs[PAGESIZE] & 1); + + // Initialization - section 4.2 of xhci reference + + 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 + // 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 = dma_map(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 = 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; + 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 + // 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; + } + } + + // 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; + arg = 0; + arg2 = 0; + debug("receiving...\n"); + uintptr_t msg = recv2(&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; + } + if (rcpt == fresh && (msg & 0xff) == MSG_USB_CONTROLLER_INIT) { + uintptr_t bus = arg; + hmod_rename(rcpt, bus_handle_base + bus); + 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) + { + case MSG_PFAULT: { + // TODO + break; + } + } + } +fail: + abort(); +} diff --git a/mkgrubcfg.sh b/mkgrubcfg.sh index 8163a63..59680d4 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.mod USB + module /cuser/usb/xhci.mod xHCI + boot +} + menuentry "lwIP" { multiboot /$kernel module /kern/irq.mod irq diff --git a/run_qemu.sh b/run_qemu.sh index c212626..b2efc1c 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 -device usb-mouse -device usb-audio" +${KVM-kvm} -m 32M $VGA "$@" -cdrom $ISO -netdev $NETDEV -device e1000,netdev=vmnet0 $USB