Initial support for USB hubs. Note, I've only tested this on qemu - I don't have a real USB hub to test on. Help with testing would be appreciated.
-Kevin
Initial support for USB hubs.
Add support for detecting, initializing, and enumerating USB hubs.
diff --git a/Makefile b/Makefile index 5110267..17d4e1f 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ OUT=out/ SRCBOTH=misc.c pmm.c stacks.c output.c util.c block.c floppy.c ata.c mouse.c \ kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \ pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c \ - usb.c usb-uhci.c usb-ohci.c usb-hid.c paravirt.c + usb.c usb-uhci.c usb-ohci.c usb-hid.c usb-hub.c paravirt.c SRC16=$(SRCBOTH) system.c disk.c apm.c font.c SRC32FLAT=$(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 \ diff --git a/src/config.h b/src/config.h index d83d6ea..c02d496 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,8 @@ #define CONFIG_USB_UHCI 1 // Support USB OHCI controllers #define CONFIG_USB_OHCI 1 +// Support USB hubs +#define CONFIG_USB_HUB 1 // Support USB keyboards #define CONFIG_USB_KEYBOARD 1 // Support PS2 ports (keyboard and mouse) diff --git a/src/usb-hub.c b/src/usb-hub.c new file mode 100644 index 0000000..4ad783c --- /dev/null +++ b/src/usb-hub.c @@ -0,0 +1,136 @@ +// Code for handling standard USB hubs. +// +// Copyright (C) 2010 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.h" // struct usb_s + +static int +get_hub_desc(struct usb_hub_descriptor *desc, u32 endp) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_DEVICE; + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.wValue = USB_DT_HUB<<8; + req.wIndex = 0; + req.wLength = sizeof(*desc); + return send_default_control(endp, &req, desc); +} + +static int +set_port_feature(int port, int feature, u32 endp) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_SET_FEATURE; + req.wValue = feature; + req.wIndex = port; + req.wLength = 0; + return send_default_control(endp, &req, NULL); +} + +static int +clear_port_feature(int port, int feature, u32 endp) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_CLEAR_FEATURE; + req.wValue = feature; + req.wIndex = port; + req.wLength = 0; + return send_default_control(endp, &req, NULL); +} + +static int +get_port_status(int port, struct usb_port_status *sts, u32 endp) +{ + struct usb_ctrlrequest req; + req.bRequestType = USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_OTHER; + req.bRequest = USB_REQ_GET_STATUS; + req.wValue = 0; + req.wIndex = port; + req.wLength = sizeof(*sts); + return send_default_control(endp, &req, sts); +} + +// Find any devices connected to the root hub. +int +usb_hub_init(u32 endp) +{ + if (!CONFIG_USB_HUB) + return 0; + + struct usb_hub_descriptor desc; + int ret = get_hub_desc(&desc, endp); + if (ret) + return ret; + + // Turn on power to all ports. + int i; + for (i=1; i<=desc.bNbrPorts; i++) { + ret = set_port_feature(i, USB_PORT_FEAT_POWER, endp); + if (ret) + goto fail; + } + + // Wait for port detection. + msleep(desc.bPwrOn2PwrGood * 2 + USB_TIME_SIGATT); + // XXX - should poll for ports becoming active sooner and then + // possibly wait USB_TIME_ATTDB. + + // Detect down stream devices. + struct usb_s *cntl = endp2cntl(endp); + int totalcount = 0; + for (i=1; i<=desc.bNbrPorts; i++) { + struct usb_port_status sts; + ret = get_port_status(i, &sts, endp); + if (ret) + goto fail; + if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION)) + // XXX - power down port? + continue; + + // Reset port. + ret = set_port_feature(i, USB_PORT_FEAT_RESET, endp); + if (ret) + goto fail; + + // Wait for reset to complete. + u64 end = calc_future_tsc(USB_TIME_DRST * 2); + for (;;) { + ret = get_port_status(i, &sts, endp); + if (ret) + goto fail; + if (!(sts.wPortStatus & USB_PORT_STAT_RESET)) + break; + if (check_time(end)) { + // Timeout. + warn_timeout(); + goto fail; + } + yield(); + } + if (!(sts.wPortStatus & USB_PORT_STAT_CONNECTION)) + // Device no longer present. XXX - power down port? + continue; + + // XXX - should try to parallelize configuration. + int count = configure_usb_device( + cntl, !!(sts.wPortStatus & USB_PORT_STAT_LOW_SPEED)); + if (! count) { + // Shutdown port + ret = clear_port_feature(i, USB_PORT_FEAT_ENABLE, endp); + if (ret) + goto fail; + } + totalcount += count; + } + + return totalcount; + +fail: + dprintf(1, "Failure on hub setup\n"); + return 0; +} diff --git a/src/usb-hub.h b/src/usb-hub.h new file mode 100644 index 0000000..24d8f1f --- /dev/null +++ b/src/usb-hub.h @@ -0,0 +1,57 @@ +#ifndef __USB_HUB_H +#define __USB_HUB_H + +// usb-hub.c +int usb_hub_init(u32 endp); + + +/**************************************************************** + * hub flags + ****************************************************************/ + +#define USB_DT_HUB (USB_TYPE_CLASS | 0x09) + +struct usb_hub_descriptor { + u8 bDescLength; + u8 bDescriptorType; + u8 bNbrPorts; + u16 wHubCharacteristics; + u8 bPwrOn2PwrGood; + u8 bHubContrCurrent; + // Variable length fields for DeviceRemovable[], PortPwrCtrlMask[] follow. +} PACKED; + +#define USB_PORT_FEAT_CONNECTION 0 +#define USB_PORT_FEAT_ENABLE 1 +#define USB_PORT_FEAT_SUSPEND 2 +#define USB_PORT_FEAT_OVER_CURRENT 3 +#define USB_PORT_FEAT_RESET 4 +#define USB_PORT_FEAT_POWER 8 +#define USB_PORT_FEAT_LOWSPEED 9 +#define USB_PORT_FEAT_C_CONNECTION 16 +#define USB_PORT_FEAT_C_ENABLE 17 +#define USB_PORT_FEAT_C_SUSPEND 18 +#define USB_PORT_FEAT_C_OVER_CURRENT 19 +#define USB_PORT_FEAT_C_RESET 20 +#define USB_PORT_FEAT_TEST 21 +#define USB_PORT_FEAT_INDICATOR 22 +#define USB_PORT_FEAT_C_PORT_L1 23 + +struct usb_port_status { + u16 wPortStatus; + u16 wPortChange; +} PACKED; + +#define USB_PORT_STAT_CONNECTION 0x0001 +#define USB_PORT_STAT_ENABLE 0x0002 +#define USB_PORT_STAT_SUSPEND 0x0004 +#define USB_PORT_STAT_OVERCURRENT 0x0008 +#define USB_PORT_STAT_RESET 0x0010 +#define USB_PORT_STAT_L1 0x0020 +#define USB_PORT_STAT_POWER 0x0100 +#define USB_PORT_STAT_LOW_SPEED 0x0200 +#define USB_PORT_STAT_HIGH_SPEED 0x0400 +#define USB_PORT_STAT_TEST 0x0800 +#define USB_PORT_STAT_INDICATOR 0x1000 + +#endif // ush-hid.h diff --git a/src/usb.c b/src/usb.c index 7108991..cb6f391 100644 --- a/src/usb.c +++ b/src/usb.c @@ -12,6 +12,7 @@ #include "usb-uhci.h" // uhci_init #include "usb-ohci.h" // ohci_init #include "usb-hid.h" // usb_keyboard_setup +#include "usb-hub.h" // usb_hub_init #include "usb.h" // struct usb_s #include "biosvar.h" // GET_GLOBAL
@@ -171,10 +172,11 @@ configure_usb_device(struct usb_s *cntl, int lowspeed) // 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 + if ((iface->bInterfaceClass != USB_CLASS_HID + || iface->bInterfaceSubClass != USB_INTERFACE_SUBCLASS_BOOT + || iface->bInterfaceProtocol != USB_INTERFACE_PROTOCOL_KEYBOARD) + && (iface->bInterfaceClass != USB_CLASS_HUB)) + // Not a supported device. goto fail;
// Set the address and configure device. @@ -186,6 +188,10 @@ configure_usb_device(struct usb_s *cntl, int lowspeed) goto fail;
// Configure driver. + if (iface->bInterfaceClass == USB_CLASS_HUB) { + free(config); + return usb_hub_init(endp); + } ret = usb_keyboard_init(endp, iface, ((void*)config + config->wTotalLength - (void*)iface)); if (ret)