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.
diff --git a/Makefile b/Makefile
index a933f60..c4016e8 100644
--- a/Makefile
+++ b/Makefile
@@ -13,7 +13,8 @@
 # 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 @@
 	$(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 @@
 
     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 @@
     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 @@
 }
 
 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 @@
     }
     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 @@
 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 @@
     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 <kevin@koconnor.net>
+//
+// 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<<i))
+            prockeys(GET_GLOBAL(ModifierToScanCode[i]));
+    for (i=0; i<ARRAY_SIZE(data->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<<i))
+            prockeys(GET_GLOBAL(ModifierToScanCode[i]) | 0x80);
+}
+
+void
+usb_check_key()
+{
+    if (! CONFIG_USB_KEYBOARD)
+        return;
+    void *pipe = GET_GLOBAL(keyboard_pipe);
+    if (!pipe)
+        return;
+
+    for (;;) {
+        struct keyevent data;
+        int ret = usb_poll_intr(pipe, &data);
+        if (ret)
+            break;
+        handle_key(&data);
+    }
+}
diff --git a/src/usb-hid.h b/src/usb-hid.h
new file mode 100644
index 0000000..26ca91a
--- /dev/null
+++ b/src/usb-hid.h
@@ -0,0 +1,27 @@
+#ifndef __USB_HID_H
+#define __USB_HID_H
+
+// usb-hid.c
+struct usb_interface_descriptor;
+int usb_keyboard_init(u32 endp, struct usb_interface_descriptor *iface
+                      , int imax);
+void usb_keyboard_setup();
+void usb_check_key();
+
+
+/****************************************************************
+ * hid flags
+ ****************************************************************/
+
+#define USB_INTERFACE_SUBCLASS_BOOT     1
+#define USB_INTERFACE_PROTOCOL_KEYBOARD 1
+#define USB_INTERFACE_PROTOCOL_MOUSE    2
+
+#define HID_REQ_GET_REPORT              0x01
+#define HID_REQ_GET_IDLE                0x02
+#define HID_REQ_GET_PROTOCOL            0x03
+#define HID_REQ_SET_REPORT              0x09
+#define HID_REQ_SET_IDLE                0x0A
+#define HID_REQ_SET_PROTOCOL            0x0B
+
+#endif // ush-hid.h
diff --git a/src/usb-uhci.c b/src/usb-uhci.c
new file mode 100644
index 0000000..814e3ec
--- /dev/null
+++ b/src/usb-uhci.c
@@ -0,0 +1,299 @@
+// Code for handling UHCI USB controllers.
+//
+// Copyright (C) 2009  Kevin O'Connor <kevin@koconnor.net>
+//
+// 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; i<ARRAY_SIZE(fl->links); 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; i<count-1; i++) {
+        tds[i].link = (u32)&tds[i+1] | UHCI_PTR_DEPTH;
+        tds[i].status = (uhci_maxerr(3) | (lowspeed ? TD_CTRL_LS : 0)
+                         | TD_CTRL_ACTIVE);
+        int len = (i == count-2 ? (datasize - (i-1)*maxpacket) : maxpacket);
+        tds[i].token = (uhci_explen(len) | toggle
+                        | (devaddr << TD_TOKEN_DEVADDR_SHIFT)
+                        | (dir ? USB_PID_IN : USB_PID_OUT));
+        tds[i].buffer = data + (i-1) * maxpacket;
+        toggle ^= TD_TOKEN_TOGGLE;
+    }
+    tds[i].link = UHCI_PTR_TERM;
+    tds[i].status = (uhci_maxerr(0) | (lowspeed ? TD_CTRL_LS : 0)
+                     | TD_CTRL_ACTIVE);
+    tds[i].token = (uhci_explen(0) | TD_TOKEN_TOGGLE
+                    | (devaddr << TD_TOKEN_DEVADDR_SHIFT)
+                    | (dir ? USB_PID_OUT : USB_PID_IN));
+    tds[i].buffer = 0;
+
+    // Transfer data
+    struct uhci_qh *data_qh = cntl->qh;
+    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; i<count; i++) {
+        tds[i].link = (i==count-1 ? (u32)&tds[0] : (u32)&tds[i+1]);
+        tds[i].status = (uhci_maxerr(3) | (lowspeed ? TD_CTRL_LS : 0)
+                         | TD_CTRL_ACTIVE);
+        tds[i].token = (uhci_explen(maxpacket) | toggle
+                        | (devaddr << TD_TOKEN_DEVADDR_SHIFT)
+                        | USB_PID_IN);
+        tds[i].buffer = &tds[i].data;
+        toggle ^= TD_TOKEN_TOGGLE;
+    }
+
+    qh->next_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 <kevin@koconnor.net>
+//
+// 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 @@
     } 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_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);
 }