From 114592f000d5bc89b7d149e320a31ab209e20b41 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 28 Sep 2009 21:32:08 -0400 Subject: [PATCH] Initial support for USB, UHCI, and USB Keyboards. This adds preliminary support for USB controllers and keyboards. Add support for UHCI controllers. Add support for "HID" USB keyboards. Also, fix bug in hexdump() - len need not be power of 4. --- Makefile | 5 +- src/clock.c | 3 + src/config.h | 6 + src/kbd.c | 2 +- src/output.c | 4 +- src/pci.c | 8 ++ src/pci.h | 1 + src/post.c | 2 + src/usb-hid.c | 185 ++++++++++++++++++++++++++++++ src/usb-hid.h | 27 +++++ src/usb-uhci.c | 299 +++++++++++++++++++++++++++++++++++++++++++++++++ src/usb-uhci.h | 131 ++++++++++++++++++++++ src/usb.c | 206 ++++++++++++++++++++++++++++++++++ src/usb.h | 181 ++++++++++++++++++++++++++++++ src/util.h | 6 +- 15 files changed, 1060 insertions(+), 6 deletions(-) create mode 100644 src/usb-hid.c create mode 100644 src/usb-hid.h create mode 100644 src/usb-uhci.c create mode 100644 src/usb-uhci.h create mode 100644 src/usb.c create mode 100644 src/usb.h diff --git a/Makefile b/Makefile index a933f60..c4016e8 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,8 @@ OUT=out/ # Source files SRCBOTH=output.c util.c block.c floppy.c ata.c misc.c mouse.c kbd.c pci.c \ serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \ - pnpbios.c pirtable.c vgahooks.c pmm.c ramdisk.c + pnpbios.c pirtable.c vgahooks.c pmm.c ramdisk.c \ + usb.c usb-uhci.c usb-hid.c SRC16=$(SRCBOTH) system.c disk.c apm.c pcibios.c font.c SRC32=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \ acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \ @@ -136,7 +137,7 @@ $(OUT)rom32.o: $(OUT)code32.o $(OUT)romlayout32.lds $(Q)$(LD) -r -T $(OUT)romlayout32.lds $< -o $@ $(OUT)rom.o: $(OUT)rom16.o $(OUT)rom32.o $(OUT)rombios16.lds $(OUT)rombios.lds - @echo " Linking $@ (version \"$(VERSION)\")" + @echo " Linking $@" $(Q)$(LD) -T $(OUT)rombios16.lds $(OUT)rom16.o -R $(OUT)rom32.o -o $(OUT)rom16.reloc.o $(Q)$(STRIP) $(OUT)rom16.reloc.o -o $(OUT)rom16.final.o $(Q)$(OBJCOPY) --adjust-vma 0xf0000 $(OUT)rom16.o $(OUT)rom16.moved.o diff --git a/src/clock.c b/src/clock.c index dd81f9d..aaa2488 100644 --- a/src/clock.c +++ b/src/clock.c @@ -12,6 +12,7 @@ #include "pic.h" // eoi_pic1 #include "bregs.h" // struct bregs #include "biosvar.h" // GET_GLOBAL +#include "usb-hid.h" // usb_check_key // RTC register flags #define RTC_A_UIP 0x80 @@ -435,6 +436,8 @@ handle_08() SET_BDA(timer_counter, counter); + usb_check_key(); + // chain to user timer tick INT #0x1c u32 eax=0, flags; call16_simpint(0x1c, &eax, &flags); diff --git a/src/config.h b/src/config.h index df10bbe..e93b080 100644 --- a/src/config.h +++ b/src/config.h @@ -28,6 +28,12 @@ #define CONFIG_DRIVES 1 // Support floppy drive access #define CONFIG_FLOPPY 1 +// Support USB devices +#define CONFIG_USB 1 +// Support USB UHCI controllers +#define CONFIG_USB_UHCI 1 +// Support USB keyboards +#define CONFIG_USB_KEYBOARD 1 // Support for IDE disk code #define CONFIG_ATA 1 // Use 32bit PIO accesses on ATA (minor optimization on PCI transfers) diff --git a/src/kbd.c b/src/kbd.c index 650f1d4..6b14940 100644 --- a/src/kbd.c +++ b/src/kbd.c @@ -622,7 +622,7 @@ __process_key(u8 scancode) SET_BDA(kbd_flag2, flags2); } -static void +void process_key(u8 key) { if (CONFIG_KBD_CALL_INT15_4F) { diff --git a/src/output.c b/src/output.c index da585b4..28cb024 100644 --- a/src/output.c +++ b/src/output.c @@ -296,10 +296,10 @@ printf(const char *fmt, ...) } void -hexdump(void *d, int len) +hexdump(const void *d, int len) { int count=0; - while (len) { + while (len > 0) { if (count % 8 == 0) { putc(0, '\n'); puthex(0, count*4, 8); diff --git a/src/pci.c b/src/pci.c index bf0e549..e6b74d5 100644 --- a/src/pci.c +++ b/src/pci.c @@ -178,3 +178,11 @@ pci_find_class(u16 classid) } return -1; } + +void +pci_set_bus_master(u16 bdf) +{ + u16 val = pci_config_readw(bdf, PCI_COMMAND); + val |= PCI_COMMAND_MASTER; + pci_config_writew(bdf, PCI_COMMAND, val); +} diff --git a/src/pci.h b/src/pci.h index 8926584..166b775 100644 --- a/src/pci.h +++ b/src/pci.h @@ -29,6 +29,7 @@ u8 pci_config_readb(u16 bdf, u32 addr); int pci_find_vga(); int pci_find_device(u16 vendid, u16 devid); int pci_find_class(u16 classid); +void pci_set_bus_master(u16 bdf); int pci_next(int bdf, int *pmax); #define foreachpci(BDF, MAX) \ diff --git a/src/post.c b/src/post.c index 45a319d..f72e134 100644 --- a/src/post.c +++ b/src/post.c @@ -19,6 +19,7 @@ #include "bregs.h" // struct bregs #include "mptable.h" // mptable_init #include "boot.h" // IPL +#include "usb.h" // usb_setup void __set_irq(int vector, void *loc) @@ -177,6 +178,7 @@ post() pnp_setup(); vga_setup(); + usb_setup(); kbd_setup(); lpt_setup(); serial_setup(); diff --git a/src/usb-hid.c b/src/usb-hid.c new file mode 100644 index 0000000..7e5b309 --- /dev/null +++ b/src/usb-hid.c @@ -0,0 +1,185 @@ +// Code for handling USB Human Interface Devices (HID). +// +// Copyright (C) 2009 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "usb-hid.h" // usb_keyboard_setup +#include "config.h" // CONFIG_* +#include "usb.h" // usb_ctrlrequest +#include "biosvar.h" // GET_GLOBAL + +void *keyboard_pipe VAR16VISIBLE; + + +/**************************************************************** + * Setup + ****************************************************************/ + +static int +set_protocol(u32 endp, u16 val) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + req.bRequest = HID_REQ_SET_PROTOCOL; + req.wValue = val; + req.wIndex = 0; + req.wLength = 0; + return send_default_control(endp, &req, NULL); +} + +static int +set_idle(u32 endp, u8 val) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE; + req.bRequest = HID_REQ_SET_IDLE; + req.wValue = val<<8; + req.wIndex = 0; + req.wLength = 0; + return send_default_control(endp, &req, NULL); +} + +int +usb_keyboard_init(u32 endp, struct usb_interface_descriptor *iface, int imax) +{ + if (! CONFIG_USB_KEYBOARD) + return -1; + if (keyboard_pipe) + return -1; + dprintf(2, "usb_keyboard_setup %x\n", endp); + + struct usb_endpoint_descriptor *epdesc = (void*)&iface[1]; + for (;;) { + if ((void*)epdesc >= (void*)iface + imax + || epdesc->bDescriptorType == USB_DT_INTERFACE) { + dprintf(1, "No keyboard intr in?\n"); + return -1; + } + if (epdesc->bDescriptorType == USB_DT_ENDPOINT + && (epdesc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN + && ((epdesc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT) + && epdesc->wMaxPacketSize == 8) + break; + epdesc = (void*)epdesc + epdesc->bLength; + } + u32 inendp = mkendp(endp2cntl(endp), endp2devaddr(endp) + , epdesc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK + , endp2speed(endp), epdesc->wMaxPacketSize); + + // Enable "boot" protocol. + int ret = set_protocol(endp, 1); + if (ret) + return -1; + // Only send reports on a new key event. + ret = set_idle(endp, 0); + if (ret) + return -1; + + void *pipe = alloc_intr_pipe(inendp, epdesc->bInterval); + if (!pipe) + return -1; + keyboard_pipe = pipe; + + return 0; +} + +void +usb_keyboard_setup() +{ + if (! CONFIG_USB_KEYBOARD) + return; + keyboard_pipe = NULL; +} + + +/**************************************************************** + * Keyboard events + ****************************************************************/ + +static u16 KeyToScanCode[] VAR16 = { + 0x0000, 0x0000, 0x0000, 0x0000, 0x001e, 0x0030, 0x002e, 0x0020, + 0x0012, 0x0021, 0x0022, 0x0023, 0x0017, 0x0024, 0x0025, 0x0026, + 0x0032, 0x0031, 0x0018, 0x0019, 0x0010, 0x0013, 0x001f, 0x0014, + 0x0016, 0x002f, 0x0011, 0x002d, 0x0015, 0x002c, 0x0002, 0x0003, + 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, + 0x001c, 0x0001, 0x000e, 0x000f, 0x0039, 0x000c, 0x000d, 0x001a, + 0x001b, 0x002b, 0x0000, 0x0027, 0x0028, 0x0029, 0x0033, 0x0034, + 0x0035, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f, 0x0040, + 0x0041, 0x0042, 0x0043, 0x0044, 0x0057, 0x0058, 0xe037, 0x0046, + 0xe11d, 0xe052, 0xe047, 0xe049, 0xe053, 0xe04f, 0xe051, 0xe04d, + 0xe04b, 0xe050, 0xe048, 0x0045, 0xe035, 0x0037, 0x004a, 0x004e, + 0xe01c, 0x004f, 0x0050, 0x0051, 0x004b, 0x004c, 0x004d, 0x0047, + 0x0048, 0x0049, 0x0052, 0x0053 +}; + +static u16 ModifierToScanCode[] VAR16 = { + //lcntl, lshift, lalt, lgui, rcntl, rshift, ralt, rgui + 0x001d, 0x002a, 0x0038, 0xe05b, 0xe01d, 0x0036, 0xe038, 0xe05c +}; + +struct keyevent { + u8 modifiers; + u8 reserved; + u8 keys[6]; +}; + +static void +prockeys(u16 keys) +{ + if (keys > 0xff) { + u8 key = keys>>8; + if (key == 0xe1) { + // Pause key + process_key(0xe1); + process_key(0x1d | (keys & 0x80)); + process_key(0x45 | (keys & 0x80)); + return; + } + process_key(key); + } + process_key(keys); +} + +static void +handle_key(struct keyevent *data) +{ + dprintf(5, "Got key %x %x\n", data->modifiers, data->keys[0]); + // XXX + int i; + for (i=0; i<8; i++) + if (data->modifiers & (1<keys); i++) { + u8 key = data->keys[i]; + if (key >= ARRAY_SIZE(KeyToScanCode)) + continue; + key = GET_GLOBAL(KeyToScanCode[key]); + if (!key) + continue; + prockeys(key); + } + for (i=0; i<8; i++) + if (data->modifiers & (1< +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "pci.h" // pci_bdf_to_bus +#include "config.h" // CONFIG_* +#include "ioport.h" // outw +#include "usb-uhci.h" // USBLEGSUP +#include "pci_regs.h" // PCI_BASE_ADDRESS_4 +#include "usb.h" // struct usb_s +#include "farptr.h" // GET_FLATPTR +#include "biosvar.h" // GET_GLOBAL + +static void +reset_uhci(struct usb_s *cntl) +{ + // XXX - don't reset if not needed. + + // Reset PIRQ and SMI + pci_config_writew(cntl->bdf, USBLEGSUP, USBLEGSUP_RWC); + + // Reset the HC + outw(USBCMD_HCRESET, cntl->iobase + USBCMD); + udelay(5); + + // Disable interrupts and commands (just to be safe). + outw(0, cntl->iobase + USBINTR); + outw(0, cntl->iobase + USBCMD); +} + +static void +configure_uhci(struct usb_s *cntl) +{ + // Allocate ram for schedule storage + struct uhci_td *term_td = malloc_high(sizeof(*term_td)); + struct uhci_framelist *fl = memalign_high(sizeof(*fl), sizeof(*fl)); + struct uhci_qh *data_qh = malloc_low(sizeof(*data_qh)); + struct uhci_qh *term_qh = malloc_high(sizeof(*term_qh)); + if (!term_td || !fl || !data_qh || !term_qh) { + dprintf(1, "No ram for uhci init"); + return; + } + + // Work around for PIIX errata + memset(term_td, 0, sizeof(*term_td)); + term_td->link = UHCI_PTR_TERM; + term_td->token = (uhci_explen(0) | (0x7f << TD_TOKEN_DEVADDR_SHIFT) + | USB_PID_IN); + memset(term_qh, 0, sizeof(*term_qh)); + term_qh->element = (u32)term_td; + term_qh->link = UHCI_PTR_TERM; + + // Setup primary queue head. + memset(data_qh, 0, sizeof(*data_qh)); + data_qh->element = UHCI_PTR_TERM; + data_qh->link = (u32)term_qh | UHCI_PTR_QH; + cntl->qh = data_qh; + + // Set schedule to point to primary queue head + int i; + for (i=0; ilinks); i++) { + fl->links[i] = (u32)data_qh | UHCI_PTR_QH; + } + + // Set the frame length to the default: 1 ms exactly + outb(USBSOF_DEFAULT, cntl->iobase + USBSOF); + + // Store the frame list base address + outl((u32)fl->links, cntl->iobase + USBFLBASEADD); + + // Set the current frame number + outw(0, cntl->iobase + USBFRNUM); +} + +static void +start_uhci(struct usb_s *cntl) +{ + // Mark as configured and running with a 64-byte max packet. + outw(USBCMD_RS | USBCMD_CF | USBCMD_MAXP, cntl->iobase + USBCMD); +} + +// Find any devices connected to the root hub. +static int +check_ports(struct usb_s *cntl) +{ + u16 port1 = inw(cntl->iobase + USBPORTSC1); + u16 port2 = inw(cntl->iobase + USBPORTSC2); + + if (!((port1 & USBPORTSC_CCS) || (port2 & USBPORTSC_CCS))) + // No devices + return 0; + + // reset ports + if (port1 & USBPORTSC_CCS) + outw(USBPORTSC_PR, cntl->iobase + USBPORTSC1); + if (port2 & USBPORTSC_CCS) + outw(USBPORTSC_PR, cntl->iobase + USBPORTSC2); + mdelay(10); + outw(0, cntl->iobase + USBPORTSC1); + outw(0, cntl->iobase + USBPORTSC2); + mdelay(10); + + // Configure ports + int totalcount = 0; + port1 = inw(cntl->iobase + USBPORTSC1); + if (port1 & USBPORTSC_CCS) { + outw(USBPORTSC_PE, cntl->iobase + USBPORTSC1); + int count = configure_usb_device(cntl, !!(port1 & USBPORTSC_LSDA)); + if (! count) + outw(0, cntl->iobase + USBPORTSC1); + totalcount += count; + } + port2 = inw(cntl->iobase + USBPORTSC2); + if (port2 & USBPORTSC_CCS) { + outw(USBPORTSC_PE, cntl->iobase + USBPORTSC2); + int count = configure_usb_device(cntl, !!(port2 & USBPORTSC_LSDA)); + if (! count) + outw(0, cntl->iobase + USBPORTSC2); + totalcount += count; + } + return totalcount; +} + +int +uhci_init(struct usb_s *cntl) +{ + if (! CONFIG_USB_UHCI) + return 0; + + cntl->iobase = (pci_config_readl(cntl->bdf, PCI_BASE_ADDRESS_4) + & PCI_BASE_ADDRESS_IO_MASK); + + dprintf(3, "UHCI init on dev %02x:%02x.%x (io=%x)\n" + , pci_bdf_to_bus(cntl->bdf), pci_bdf_to_dev(cntl->bdf) + , pci_bdf_to_fn(cntl->bdf), cntl->iobase); + + pci_set_bus_master(cntl->bdf); + + reset_uhci(cntl); + configure_uhci(cntl); + start_uhci(cntl); + + int count = check_ports(cntl); + if (! count) { + // XXX - no devices; free data structures. + return 0; + } + + return count; +} + +static int +wait_qh(struct uhci_qh *qh) +{ + // XXX - 500ms just a guess + u64 end = calc_future_tsc(500); + for (;;) { + if (qh->element & UHCI_PTR_TERM) + return 0; + if (rdtscll() > end) { + dprintf(1, "Timeout on wait_qh %p\n", qh); + return -1; + } + cpu_relax(); + } +} + +int +uhci_control(u32 endp, int dir, const void *cmd, int cmdsize + , void *data, int datasize) +{ + if (! CONFIG_USB_UHCI) + return 0; + + dprintf(5, "uhci_control %x\n", endp); + struct usb_s *cntl = endp2cntl(endp); + int maxpacket = endp2maxsize(endp); + int lowspeed = endp2speed(endp); + int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7); + + // Setup transfer descriptors + int count = 2 + DIV_ROUND_UP(datasize, maxpacket); + struct uhci_td *tds = malloc_tmphigh(sizeof(*tds) * count); + + tds[0].link = (u32)&tds[1] | UHCI_PTR_DEPTH; + tds[0].status = (uhci_maxerr(3) | (lowspeed ? TD_CTRL_LS : 0) + | TD_CTRL_ACTIVE); + tds[0].token = (uhci_explen(cmdsize) | (devaddr << TD_TOKEN_DEVADDR_SHIFT) + | USB_PID_SETUP); + tds[0].buffer = (void*)cmd; + int toggle = TD_TOKEN_TOGGLE; + int i; + for (i=1; iqh; + data_qh->element = (u32)&tds[0]; + int ret = wait_qh(data_qh); + if (ret) + // XXX - leak tds + return ret; + // XXX - free(tds); + return 0; +} + +void * +uhci_alloc_intr_pipe(u32 endp, int period) +{ + if (! CONFIG_USB_UHCI) + return NULL; + + dprintf(7, "uhci_alloc_intr_pipe %x %d\n", endp, period); + struct usb_s *cntl = endp2cntl(endp); + int maxpacket = endp2maxsize(endp); + int lowspeed = endp2speed(endp); + int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7); + // XXX - just grab 20 for now. + int count = 20; + struct uhci_qh *qh = malloc_low(sizeof(*qh)); + struct uhci_td *tds = malloc_low(sizeof(*tds) * count); + if (!qh || !tds) + return NULL; + if (maxpacket > sizeof(tds[0].data)) + // XXX - free qh/tds + return NULL; + + qh->element = (u32)tds; + int toggle = 0; + int i; + for (i=0; inext_td = &tds[0]; + + // XXX - need schedule - just add to primary list for now. + struct uhci_qh *data_qh = cntl->qh; + qh->link = data_qh->link; + data_qh->link = (u32)qh | UHCI_PTR_QH; + + return qh; +} + +int +uhci_poll_intr(void *pipe, void *data) +{ + ASSERT16(); + if (! CONFIG_USB_UHCI) + return -1; + + struct uhci_qh *qh = pipe; + struct uhci_td *td = GET_FLATPTR(qh->next_td); + u32 status = GET_FLATPTR(td->status); + u32 token = GET_FLATPTR(td->token); + if (status & TD_CTRL_ACTIVE) + // No intrs found. + return -1; + // XXX - check for errors. + + // Copy data. + memcpy_far(GET_SEG(SS), data + , FLATPTR_TO_SEG(td->data), (void*)FLATPTR_TO_OFFSET(td->data) + , uhci_expected_length(token)); + + // Reenable this td. + u32 next = GET_FLATPTR(td->link); + SET_FLATPTR(td->status, (uhci_maxerr(0) | (status & TD_CTRL_LS) + | TD_CTRL_ACTIVE)); + SET_FLATPTR(qh->next_td, (void*)(next & ~UHCI_PTR_BITS)); + + return 0; +} diff --git a/src/usb-uhci.h b/src/usb-uhci.h new file mode 100644 index 0000000..6ad7e15 --- /dev/null +++ b/src/usb-uhci.h @@ -0,0 +1,131 @@ +#ifndef __USB_UHCI_H +#define __USB_UHCI_H + +// usb-uhci.c +struct usb_s; +int uhci_init(struct usb_s *cntl); +int uhci_control(u32 endp, int dir, const void *cmd, int cmdsize + , void *data, int datasize); +void *uhci_alloc_intr_pipe(u32 endp, int period); +int uhci_poll_intr(void *pipe, void *data); + + +/**************************************************************** + * uhci structs and flags + ****************************************************************/ + +/* USB port status and control registers */ +#define USBPORTSC1 16 +#define USBPORTSC2 18 +#define USBPORTSC_CCS 0x0001 /* Current Connect Status + * ("device present") */ +#define USBPORTSC_CSC 0x0002 /* Connect Status Change */ +#define USBPORTSC_PE 0x0004 /* Port Enable */ +#define USBPORTSC_PEC 0x0008 /* Port Enable Change */ +#define USBPORTSC_DPLUS 0x0010 /* D+ high (line status) */ +#define USBPORTSC_DMINUS 0x0020 /* D- high (line status) */ +#define USBPORTSC_RD 0x0040 /* Resume Detect */ +#define USBPORTSC_RES1 0x0080 /* reserved, always 1 */ +#define USBPORTSC_LSDA 0x0100 /* Low Speed Device Attached */ +#define USBPORTSC_PR 0x0200 /* Port Reset */ + +/* Legacy support register */ +#define USBLEGSUP 0xc0 +#define USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ + +/* Command register */ +#define USBCMD 0 +#define USBCMD_RS 0x0001 /* Run/Stop */ +#define USBCMD_HCRESET 0x0002 /* Host reset */ +#define USBCMD_GRESET 0x0004 /* Global reset */ +#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */ +#define USBCMD_FGR 0x0010 /* Force Global Resume */ +#define USBCMD_SWDBG 0x0020 /* SW Debug mode */ +#define USBCMD_CF 0x0040 /* Config Flag (sw only) */ +#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */ + +/* Status register */ +#define USBSTS 2 +#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */ +#define USBSTS_ERROR 0x0002 /* Interrupt due to error */ +#define USBSTS_RD 0x0004 /* Resume Detect */ +#define USBSTS_HSE 0x0008 /* Host System Error: PCI problems */ +#define USBSTS_HCPE 0x0010 /* Host Controller Process Error: + * the schedule is buggy */ +#define USBSTS_HCH 0x0020 /* HC Halted */ + +/* Interrupt enable register */ +#define USBINTR 4 +#define USBINTR_TIMEOUT 0x0001 /* Timeout/CRC error enable */ +#define USBINTR_RESUME 0x0002 /* Resume interrupt enable */ +#define USBINTR_IOC 0x0004 /* Interrupt On Complete enable */ +#define USBINTR_SP 0x0008 /* Short packet interrupt enable */ + +#define USBFRNUM 6 +#define USBFLBASEADD 8 +#define USBSOF 12 +#define USBSOF_DEFAULT 64 /* Frame length is exactly 1 ms */ + +struct uhci_framelist { + u32 links[1024]; +} PACKED; + +#define TD_CTRL_SPD (1 << 29) /* Short Packet Detect */ +#define TD_CTRL_C_ERR_MASK (3 << 27) /* Error Counter bits */ +#define TD_CTRL_C_ERR_SHIFT 27 +#define TD_CTRL_LS (1 << 26) /* Low Speed Device */ +#define TD_CTRL_IOS (1 << 25) /* Isochronous Select */ +#define TD_CTRL_IOC (1 << 24) /* Interrupt on Complete */ +#define TD_CTRL_ACTIVE (1 << 23) /* TD Active */ +#define TD_CTRL_STALLED (1 << 22) /* TD Stalled */ +#define TD_CTRL_DBUFERR (1 << 21) /* Data Buffer Error */ +#define TD_CTRL_BABBLE (1 << 20) /* Babble Detected */ +#define TD_CTRL_NAK (1 << 19) /* NAK Received */ +#define TD_CTRL_CRCTIMEO (1 << 18) /* CRC/Time Out Error */ +#define TD_CTRL_BITSTUFF (1 << 17) /* Bit Stuff Error */ +#define TD_CTRL_ACTLEN_MASK 0x7FF /* actual length, encoded as n - 1 */ + +#define TD_CTRL_ANY_ERROR (TD_CTRL_STALLED | TD_CTRL_DBUFERR | \ + TD_CTRL_BABBLE | TD_CTRL_CRCTIME | \ + TD_CTRL_BITSTUFF) +#define uhci_maxerr(err) ((err) << TD_CTRL_C_ERR_SHIFT) + +#define TD_TOKEN_DEVADDR_SHIFT 8 +#define TD_TOKEN_TOGGLE_SHIFT 19 +#define TD_TOKEN_TOGGLE (1 << 19) +#define TD_TOKEN_EXPLEN_SHIFT 21 +#define TD_TOKEN_EXPLEN_MASK 0x7FF /* expected length, encoded as n-1 */ +#define TD_TOKEN_PID_MASK 0xFF + +#define uhci_explen(len) ((((len) - 1) & TD_TOKEN_EXPLEN_MASK) << \ + TD_TOKEN_EXPLEN_SHIFT) + +#define uhci_expected_length(token) ((((token) >> TD_TOKEN_EXPLEN_SHIFT) + \ + 1) & TD_TOKEN_EXPLEN_MASK) + +struct uhci_td { + u32 link; + u32 status; + u32 token; + void *buffer; + + // Software fields + u32 data[4]; +} PACKED; + +struct uhci_qh { + u32 link; + u32 element; + + // Software fields + struct uhci_td *next_td; + u32 reserved; +} PACKED; + +#define UHCI_PTR_BITS 0x000F +#define UHCI_PTR_TERM 0x0001 +#define UHCI_PTR_QH 0x0002 +#define UHCI_PTR_DEPTH 0x0004 +#define UHCI_PTR_BREADTH 0x0000 + +#endif // usb-uhci.h diff --git a/src/usb.c b/src/usb.c new file mode 100644 index 0000000..8e14b8e --- /dev/null +++ b/src/usb.c @@ -0,0 +1,206 @@ +// Main code for handling USB controllers and devices. +// +// Copyright (C) 2009 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "pci.h" // foreachpci +#include "config.h" // CONFIG_* +#include "pci_regs.h" // PCI_CLASS_REVISION +#include "pci_ids.h" // PCI_CLASS_SERIAL_USB_UHCI +#include "usb-uhci.h" // uhci_init +#include "usb-hid.h" // usb_keyboard_setup +#include "usb.h" // struct usb_s + +struct usb_s USBControllers[16] VAR16VISIBLE; + +static int +send_control(u32 endp, int dir, const void *cmd, int cmdsize + , void *data, int datasize) +{ + return uhci_control(endp, dir, cmd, cmdsize, data, datasize); +} + +void * +alloc_intr_pipe(u32 endp, int period) +{ + return uhci_alloc_intr_pipe(endp, period); +} + +int +usb_poll_intr(void *pipe, void *data) +{ + return uhci_poll_intr(pipe, data); +} + +int +send_default_control(u32 endp, const struct usb_ctrlrequest *req, void *data) +{ + return send_control(endp, req->bRequestType & USB_DIR_IN + , req, sizeof(*req), data, req->wLength); +} + +// Get the first 8 bytes of the device descriptor. +static int +get_device_info8(struct usb_device_descriptor *dinfo, u32 endp) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = USB_DT_DEVICE<<8; + req.wIndex = 0; + req.wLength = 8; + return send_default_control(endp, &req, dinfo); +} + +static struct usb_config_descriptor * +get_device_config(u32 endp) +{ + struct usb_config_descriptor cfg; + + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = USB_DT_CONFIG<<8; + req.wIndex = 0; + req.wLength = sizeof(cfg); + int ret = send_default_control(endp, &req, &cfg); + if (ret) + return NULL; + + void *config = malloc_tmphigh(cfg.wTotalLength); + if (!config) + return NULL; + req.wLength = cfg.wTotalLength; + ret = send_default_control(endp, &req, config); + if (ret) + return NULL; + //hexdump(config, cfg.wTotalLength); + return config; +} + +static u32 +set_address(u32 endp) +{ + dprintf(3, "set_address %x\n", endp); + struct usb_s *cntl = endp2cntl(endp); + if (cntl->maxaddr >= USB_MAXADDR) + return 0; + + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_SET_ADDRESS; + req.wValue = cntl->maxaddr + 1; + req.wIndex = 0; + req.wLength = 0; + int ret = send_default_control(endp, &req, NULL); + if (ret) + return 0; + mdelay(2); + + cntl->maxaddr++; + return mkendp(cntl, cntl->maxaddr, 0, endp2speed(endp), endp2maxsize(endp)); +} + +static int +set_configuration(u32 endp, u16 val) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_SET_CONFIGURATION; + req.wValue = val; + req.wIndex = 0; + req.wLength = 0; + return send_default_control(endp, &req, NULL); +} + +// Called for every found device - see if a driver is available for +// this device and do setup if so. +int +configure_usb_device(struct usb_s *cntl, int lowspeed) +{ + dprintf(1, "config_usb: %p %d\n", cntl, lowspeed); + + // Get device info + u32 endp = mkendp(cntl, 0, 0, lowspeed, 8); + struct usb_device_descriptor dinfo; + int ret = get_device_info8(&dinfo, endp); + if (ret) + return 0; + dprintf(3, "device rev=%04x cls=%02x sub=%02x proto=%02x size=%02x\n" + , dinfo.bcdUSB, dinfo.bDeviceClass, dinfo.bDeviceSubClass + , dinfo.bDeviceProtocol, dinfo.bMaxPacketSize0); + if (dinfo.bMaxPacketSize0 < 8 || dinfo.bMaxPacketSize0 > 64) + return 0; + endp = mkendp(cntl, 0, 0, lowspeed, dinfo.bMaxPacketSize0); + + // Get configuration + struct usb_config_descriptor *config = get_device_config(endp); + if (!config) + return 0; + + // Determine if a driver exists for this device - only look at the + // first interface of the first configuration. + struct usb_interface_descriptor *iface = (void*)(&config[1]); + if (iface->bInterfaceClass != USB_CLASS_HID + || iface->bInterfaceSubClass != USB_INTERFACE_SUBCLASS_BOOT + || iface->bInterfaceProtocol != USB_INTERFACE_PROTOCOL_KEYBOARD) + // Not a "boot" keyboard + goto fail; + + // Set the address and configure device. + endp = set_address(endp); + if (!endp) + goto fail; + ret = set_configuration(endp, config->bConfigurationValue); + if (ret) + goto fail; + + // Configure driver. + ret = usb_keyboard_init(endp, iface, ((void*)config + config->wTotalLength + - (void*)iface)); + if (ret) + goto fail; + + // XXX - free(config); + return 1; +fail: + // XXX - free(config); + return 0; +} + +void +usb_setup() +{ + if (! CONFIG_USB) + return; + + dprintf(3, "init usb\n"); + + usb_keyboard_setup(); + + // Look for USB controllers + int count = 0; + int bdf, max; + foreachpci(bdf, max) { + u32 code = pci_config_readl(bdf, PCI_CLASS_REVISION) >> 8; + + if (code >> 8 != PCI_CLASS_SERIAL_USB) + continue; + + struct usb_s *cntl = &USBControllers[count]; + cntl->bdf = bdf; + + int devcount = 0; + if (code == PCI_CLASS_SERIAL_USB_UHCI) + devcount = uhci_init(cntl); + + if (devcount > 0) { + // Success + count++; + if (count >= ARRAY_SIZE(USBControllers)) + break; + } + } +} diff --git a/src/usb.h b/src/usb.h new file mode 100644 index 0000000..e8d7455 --- /dev/null +++ b/src/usb.h @@ -0,0 +1,181 @@ +// USB functions and data. +#ifndef __USB_H +#define __USB_H + +// Local information for a usb controller. +struct usb_s { + u16 bdf; + u16 iobase; + u8 maxaddr; + void *qh; +}; + +extern struct usb_s USBControllers[]; + +#define USB_MAXADDR 127 + +// usb.c +void usb_setup(); +int configure_usb_device(struct usb_s *cntl, int lowspeed); +struct usb_ctrlrequest; +int send_default_control(u32 endp, const struct usb_ctrlrequest *req + , void *data); +void *alloc_intr_pipe(u32 endp, int period); +int usb_poll_intr(void *pipe, void *data); + + +/**************************************************************** + * endpoint definition + ****************************************************************/ + +static inline u32 +mkendp(struct usb_s *cntl, u8 devaddr, u8 ep, u8 lowspeed, u8 maxsize) +{ + u8 bus = cntl-USBControllers; + u8 size = __ffs(maxsize); + return (size<<25) | (lowspeed<<24) | (bus<<16) | (devaddr<<8) | ep; +} + +static inline u8 endp2ep(u32 endp) { + return endp; +} +static inline u8 endp2devaddr(u32 endp) { + return endp>>8; +} +static inline struct usb_s *endp2cntl(u32 endp) { + u8 bus = endp>>16; + return &USBControllers[bus]; +} +static inline u8 endp2speed(u32 endp) { + return (endp>>24) & 1; +} +static inline u8 endp2maxsize(u32 endp) { + return 1 << (endp>>25); +} + + +/**************************************************************** + * usb structs and flags + ****************************************************************/ + +#define USB_PID_OUT 0xe1 +#define USB_PID_IN 0x69 +#define USB_PID_SETUP 0x2d + +#define USB_DIR_OUT 0 /* to device */ +#define USB_DIR_IN 0x80 /* to host */ + +#define USB_TYPE_MASK (0x03 << 5) +#define USB_TYPE_STANDARD (0x00 << 5) +#define USB_TYPE_CLASS (0x01 << 5) +#define USB_TYPE_VENDOR (0x02 << 5) +#define USB_TYPE_RESERVED (0x03 << 5) + +#define USB_RECIP_MASK 0x1f +#define USB_RECIP_DEVICE 0x00 +#define USB_RECIP_INTERFACE 0x01 +#define USB_RECIP_ENDPOINT 0x02 +#define USB_RECIP_OTHER 0x03 + +#define USB_REQ_GET_STATUS 0x00 +#define USB_REQ_CLEAR_FEATURE 0x01 +#define USB_REQ_SET_FEATURE 0x03 +#define USB_REQ_SET_ADDRESS 0x05 +#define USB_REQ_GET_DESCRIPTOR 0x06 +#define USB_REQ_SET_DESCRIPTOR 0x07 +#define USB_REQ_GET_CONFIGURATION 0x08 +#define USB_REQ_SET_CONFIGURATION 0x09 +#define USB_REQ_GET_INTERFACE 0x0A +#define USB_REQ_SET_INTERFACE 0x0B +#define USB_REQ_SYNCH_FRAME 0x0C + +struct usb_ctrlrequest { + u8 bRequestType; + u8 bRequest; + u16 wValue; + u16 wIndex; + u16 wLength; +} PACKED; + +#define USB_DT_DEVICE 0x01 +#define USB_DT_CONFIG 0x02 +#define USB_DT_STRING 0x03 +#define USB_DT_INTERFACE 0x04 +#define USB_DT_ENDPOINT 0x05 +#define USB_DT_DEVICE_QUALIFIER 0x06 +#define USB_DT_OTHER_SPEED_CONFIG 0x07 + +struct usb_device_descriptor { + u8 bLength; + u8 bDescriptorType; + + u16 bcdUSB; + u8 bDeviceClass; + u8 bDeviceSubClass; + u8 bDeviceProtocol; + u8 bMaxPacketSize0; + u16 idVendor; + u16 idProduct; + u16 bcdDevice; + u8 iManufacturer; + u8 iProduct; + u8 iSerialNumber; + u8 bNumConfigurations; +} PACKED; + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_AUDIO 1 +#define USB_CLASS_COMM 2 +#define USB_CLASS_HID 3 +#define USB_CLASS_PHYSICAL 5 +#define USB_CLASS_STILL_IMAGE 6 +#define USB_CLASS_PRINTER 7 +#define USB_CLASS_MASS_STORAGE 8 +#define USB_CLASS_HUB 9 + +struct usb_config_descriptor { + u8 bLength; + u8 bDescriptorType; + + u16 wTotalLength; + u8 bNumInterfaces; + u8 bConfigurationValue; + u8 iConfiguration; + u8 bmAttributes; + u8 bMaxPower; +} PACKED; + +struct usb_interface_descriptor { + u8 bLength; + u8 bDescriptorType; + + u8 bInterfaceNumber; + u8 bAlternateSetting; + u8 bNumEndpoints; + u8 bInterfaceClass; + u8 bInterfaceSubClass; + u8 bInterfaceProtocol; + u8 iInterface; +} PACKED; + +struct usb_endpoint_descriptor { + u8 bLength; + u8 bDescriptorType; + + u8 bEndpointAddress; + u8 bmAttributes; + u16 wMaxPacketSize; + u8 bInterval; +} PACKED; + +#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ +#define USB_ENDPOINT_DIR_MASK 0x80 + +#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 +#define USB_ENDPOINT_MAX_ADJUSTABLE 0x80 + +#endif // usb.h diff --git a/src/util.h b/src/util.h index a74bdec..fbcfb79 100644 --- a/src/util.h +++ b/src/util.h @@ -173,11 +173,12 @@ void __debug_isr(const char *fname); } while (0) #define debug_stub(regs) \ __debug_stub((regs), __LINE__, __func__) -void hexdump(void *d, int len); +void hexdump(const void *d, int len); // kbd.c void kbd_setup(); void handle_15c2(struct bregs *regs); +void process_key(u8 key); // mouse.c void mouse_setup(); @@ -277,6 +278,9 @@ static inline void *malloc_high(u32 size) { static inline void *malloc_fseg(u32 size) { return zone_malloc(&ZoneFSeg, size, MALLOC_MIN_ALIGN); } +static inline void *malloc_tmphigh(u32 size) { + return zone_malloc(&ZoneTmpHigh, size, MALLOC_MIN_ALIGN); +} static inline void *memalign_tmphigh(u32 align, u32 size) { return zone_malloc(&ZoneTmpHigh, size, align); } -- 2.25.1