diff options
author | Patrick Georgi <patrick.georgi@coresystems.de> | 2008-09-02 16:06:22 +0000 |
---|---|---|
committer | Patrick Georgi <patrick.georgi@coresystems.de> | 2008-09-02 16:06:22 +0000 |
commit | d21f68bbd588f46c23066eb8d227b51e4823de41 (patch) | |
tree | 1c9c724d292b8de59599d98e8549177329953a12 | |
parent | 5ccfa1ac7994888f57c60b80fff80e350ab3f16c (diff) | |
download | coreboot-d21f68bbd588f46c23066eb8d227b51e4823de41.tar.xz |
This patch adds USB capabilities to libpayload. It requires some
memalign implementation (eg. the one I sent yesterday).
Features:
- UHCI controller driver
- UHCI root hub driver
- USB MSC (Mass Storage Class) driver
- skeleton of a USB HID driver
(requires better interrupt transfer handling, which is TODO)
- skeleton of a USB hub driver
(needs several blank spots filled in, eg. power management.
Again: TODO)
OHCI and EHCI are not supported, though OHCI support should be rather
easy as the stack provides reasonable abstractions (or so I hope). EHCI
will probably be more complicated.
Isochronous transfers (eg. webcams, audio stuff, ...) are not supported.
They can be, but I doubt we'll have a reason for that in the boot
environment.
The MSC driver was tested against a couple of USB flash drives, and
should be reasonably tolerant by now. But I probably underestimate
the amount of bugs present in USB flash drives, so feedback is welcome.
Signed-off-by: Patrick Georgi <patrick.georgi@coresystems.de>
Acked-by: Jordan Crouse <jordan.crouse@amd.com>
git-svn-id: svn://svn.coreboot.org/coreboot/trunk@3560 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1
-rw-r--r-- | payloads/libpayload/Config.in | 41 | ||||
-rw-r--r-- | payloads/libpayload/Makefile | 1 | ||||
-rw-r--r-- | payloads/libpayload/drivers/Makefile.inc | 11 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/TODO | 22 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/uhci.c | 507 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/uhci.h | 124 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/uhci_rh.c | 182 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usb.c | 350 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usb.h | 224 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usb_dev.c | 55 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbdisk.h | 51 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbhid.c | 137 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbhub.c | 158 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbinit.c | 120 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbmsc.c | 397 | ||||
-rw-r--r-- | payloads/libpayload/drivers/usb/usbmsc.h | 47 |
16 files changed, 2427 insertions, 0 deletions
diff --git a/payloads/libpayload/Config.in b/payloads/libpayload/Config.in index 97bf78d649..61567003f0 100644 --- a/payloads/libpayload/Config.in +++ b/payloads/libpayload/Config.in @@ -132,5 +132,46 @@ config SPEAKER bool "Support for PC speaker" default y +config USB + bool "USB Support" + default n + +config USB_UHCI + bool "Support for USB UHCI controllers" + depends on USB + help + Select this option if you are going to use USB 1.1 on an Intel based + system. + +config USB_OHCI + bool "Support for USB OHCI controllers" + depends on USB + help + Select this option if you are going to use USB 1.1 on an AMD based + system. + NOTE: This option is not (fully) implemented yet + +config USB_EHCI + bool "Support for USB EHCI controllers" + depends on USB + help + Select this option if you want to use USB 2.0 + NOTE: This option is not (fully) implemented yet + +config USB_HID + bool "Support for USB keyboards (broken)" + depends on USB + default n + +config USB_HUB + bool "Support for USB hubs (broken)" + depends on USB + default n + +config USB_MSC + bool "Support for USB storage" + depends on USB + + endmenu diff --git a/payloads/libpayload/Makefile b/payloads/libpayload/Makefile index 65d7d3d5a6..9254de7ad1 100644 --- a/payloads/libpayload/Makefile +++ b/payloads/libpayload/Makefile @@ -137,6 +137,7 @@ install: lib prepare: $(Q)mkdir -p $(obj)/util/kconfig/lxdialog $(Q)mkdir -p $(obj)/crypto $(obj)/curses $(obj)/drivers/video + $(Q)mkdir -p $(obj)/drivers/usb $(Q)mkdir -p $(obj)/i386 $(obj)/lib/$(ARCHDIR-y) $(obj)/libc $(Q)mkdir -p $(src)/lib/$(ARCHDIR-y) diff --git a/payloads/libpayload/drivers/Makefile.inc b/payloads/libpayload/drivers/Makefile.inc index 854082c511..cad2080216 100644 --- a/payloads/libpayload/drivers/Makefile.inc +++ b/payloads/libpayload/drivers/Makefile.inc @@ -47,3 +47,14 @@ TARGETS-$(CONFIG_VGA_VIDEO_CONSOLE) += drivers/video/vga.o # Geode console drivers TARGETS-$(CONFIG_GEODE_VIDEO_CONSOLE) += drivers/video/geode.o TARGETS-$(CONFIG_GEODE_VIDEO_CONSOLE) += drivers/video/font8x16.o + +# USB stack +TARGETS-$(CONFIG_USB) += drivers/usb/usbinit.o +TARGETS-$(CONFIG_USB) += drivers/usb/usb.o +TARGETS-$(CONFIG_USB) += drivers/usb/usb_dev.o +TARGETS-$(CONFIG_USB_HUB) += drivers/usb/usbhub.o +TARGETS-$(CONFIG_USB_UHCI) += drivers/usb/uhci.o +TARGETS-$(CONFIG_USB_UHCI) += drivers/usb/uhci_rh.o +TARGETS-$(CONFIG_USB_HID) += drivers/usb/usbhid.o +TARGETS-$(CONFIG_USB_MSC) += drivers/usb/usbmsc.o + diff --git a/payloads/libpayload/drivers/usb/TODO b/payloads/libpayload/drivers/usb/TODO new file mode 100644 index 0000000000..1dfc9d40ab --- /dev/null +++ b/payloads/libpayload/drivers/usb/TODO @@ -0,0 +1,22 @@ +- handle error conditions +- handle disconnect more gracefully (ie. make calling layer aware that the device doesn't exist somehow) +- usbhub: + - proper client enumeration (esp. detach) + - change detection + - power management +- handle interrupts more cleverly: + create a new queue for the interrupt with a couple of TD sequences, + - each ending with "breadth first" flag + - linked as a chain + add that queue at the appropriate times in front of the default structure so the max latency is honored + - only one intr chain per framelist item, so it must be arranged appropriately + reads from usb device just look at "invalidated" tds and the results they got + handled tds get reactivated as a ring structure + - added as child of the oldest td + - queue header already dropped the td, so no issue there + + this setup ensures that: + - the max latency of the device is honored + - the client knows the right order of the data + - there is no need for an interrupt handler + - but must be polled at least max latency * num tds times -> more tds = less time pressure diff --git a/payloads/libpayload/drivers/usb/uhci.c b/payloads/libpayload/drivers/usb/uhci.c new file mode 100644 index 0000000000..a558db842c --- /dev/null +++ b/payloads/libpayload/drivers/usb/uhci.c @@ -0,0 +1,507 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "usb.h" +#include "uhci.h" +#include <arch/virtual.h> + +static void uhci_start (hci_t *controller); +static void uhci_stop (hci_t *controller); +static void uhci_reset (hci_t *controller); +static void uhci_shutdown (hci_t *controller); +static int uhci_packet (usbdev_t *dev, int endp, int pid, int toggle, + int length, u8 *data); +static int uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize); +static int uhci_control (usbdev_t *dev, pid_t dir, int drlen, void *devreq, + int dalen, u8 *data); + +#if 0 +/* dump uhci */ +static void +uhci_dump (hci_t *controller) +{ + printf ("dump:\nUSBCMD: %x\n", uhci_reg_read16 (controller, USBCMD)); + printf ("USBSTS: %x\n", uhci_reg_read16 (controller, USBSTS)); + printf ("USBINTR: %x\n", uhci_reg_read16 (controller, USBINTR)); + printf ("FRNUM: %x\n", uhci_reg_read16 (controller, FRNUM)); + printf ("FLBASEADD: %x\n", uhci_reg_read32 (controller, FLBASEADD)); + printf ("SOFMOD: %x\n", uhci_reg_read8 (controller, SOFMOD)); + printf ("PORTSC1: %x\n", uhci_reg_read16 (controller, PORTSC1)); + printf ("PORTSC2: %x\n", uhci_reg_read16 (controller, PORTSC2)); +} +#endif + +static void +td_dump (td_t *td) +{ + printf ("%x packet (at %lx) to %x.%x failed\n", td->pid, + virt_to_phys (td), td->dev_addr, td->endp); + printf ("td (counter at %x) returns: ", td->counter); + printf (" bitstuff err: %x, ", td->status_bitstuff_err); + printf (" CRC err: %x, ", td->status_crc_err); + printf (" NAK rcvd: %x, ", td->status_nakrcvd); + printf (" Babble: %x, ", td->status_babble); + printf (" Data Buffer err: %x, ", td->status_databuf_err); + printf (" Stalled: %x, ", td->status_stalled); + printf (" Active: %x\n", td->status_active); + if (td->status_babble) + printf (" Babble because of %s\n", + td->status_bitstuff_err ? "host" : "device"); + if (td->status_active) + printf (" still active - timeout?\n"); +} + +static void +uhci_reset (hci_t *controller) +{ + /* reset */ + uhci_reg_write16 (controller, USBCMD, 4); + mdelay (50); + uhci_reg_write16 (controller, USBCMD, 0); + mdelay (10); + uhci_reg_write16 (controller, USBCMD, 2); + while ((uhci_reg_read16 (controller, USBCMD) & 2) != 0) + mdelay (1); + + uhci_reg_write32 (controller, FLBASEADD, + (u32) virt_to_phys (UHCI_INST (controller)-> + framelistptr)); + //printf ("framelist at %p\n",UHCI_INST(controller)->framelistptr); + + /* disable irqs */ + uhci_reg_write16 (controller, USBINTR, 0); + + /* reset framelist index */ + uhci_reg_write16 (controller, FRNUM, 0); + + uhci_reg_mask16 (controller, USBCMD, ~0, 0xc0); // max packets, configure flag + + uhci_start (controller); +} + +hci_t * +uhci_init (pcidev_t addr) +{ + int i; + hci_t *controller = new_controller (); + + controller->instance = malloc (sizeof (uhci_t)); + controller->start = uhci_start; + controller->stop = uhci_stop; + controller->reset = uhci_reset; + controller->shutdown = uhci_shutdown; + controller->packet = uhci_packet; + controller->bulk = uhci_bulk; + controller->control = uhci_control; + UHCI_INST (controller)->roothub = &(controller->devices[0]); + + controller->bus_address = addr; + controller->reg_base = pci_read_config32 (controller->bus_address, 0x20) & ~1; /* ~1 clears the register type indicator that is set to 1 for IO space */ + + /* kill legacy support handler */ + uhci_stop (controller); + mdelay (1); + uhci_reg_write16 (controller, USBSTS, 0x3f); + pci_write_config32 (controller->bus_address, 0xc0, 0x8f00); + + UHCI_INST (controller)->framelistptr = memalign (0x1000, 1024 * sizeof (flistp_t *)); /* 4kb aligned to 4kb */ + memset (UHCI_INST (controller)->framelistptr, 0, + 1024 * sizeof (flistp_t)); + + UHCI_INST (controller)->qh_intr = memalign (16, sizeof (qh_t)); + UHCI_INST (controller)->qh_data = memalign (16, sizeof (qh_t)); + UHCI_INST (controller)->qh_last = memalign (16, sizeof (qh_t)); + + UHCI_INST (controller)->qh_intr->headlinkptr.ptr = + virt_to_phys (UHCI_INST (controller)->qh_data); + UHCI_INST (controller)->qh_intr->headlinkptr.queue_head = 1; + UHCI_INST (controller)->qh_intr->elementlinkptr.ptr = 0; + UHCI_INST (controller)->qh_intr->elementlinkptr.terminate = 1; + + UHCI_INST (controller)->qh_data->headlinkptr.ptr = + virt_to_phys (UHCI_INST (controller)->qh_last); + UHCI_INST (controller)->qh_data->headlinkptr.queue_head = 1; + UHCI_INST (controller)->qh_data->elementlinkptr.ptr = 0; + UHCI_INST (controller)->qh_data->elementlinkptr.terminate = 1; + + UHCI_INST (controller)->qh_last->headlinkptr.ptr = 0; + UHCI_INST (controller)->qh_last->headlinkptr.terminate = 1; + UHCI_INST (controller)->qh_last->elementlinkptr.ptr = 0; + UHCI_INST (controller)->qh_last->elementlinkptr.terminate = 1; + + for (i = 0; i < 1024; i++) { + UHCI_INST (controller)->framelistptr[i].ptr = + virt_to_phys (UHCI_INST (controller)->qh_intr); + UHCI_INST (controller)->framelistptr[i].terminate = 0; + UHCI_INST (controller)->framelistptr[i].queue_head = 1; + } + for (i = 1; i < 128; i++) { + init_device_entry (controller, i); + } + controller->devices[0].controller = controller; + controller->devices[0].init = uhci_rh_init; + controller->devices[0].init (&controller->devices[0]); + uhci_reset (controller); + return controller; +} + +static void +uhci_shutdown (hci_t *controller) +{ + if (controller == 0) + return; + detach_controller (controller); + UHCI_INST (controller)->roothub->destroy (UHCI_INST (controller)-> + roothub); + uhci_reg_mask16 (controller, USBCMD, 0, 0); // stop work + free (UHCI_INST (controller)->framelistptr); + free (UHCI_INST (controller)->qh_intr); + free (UHCI_INST (controller)->qh_data); + free (UHCI_INST (controller)->qh_last); + free (UHCI_INST (controller)); + free (controller); +} + +static void +uhci_start (hci_t *controller) +{ + uhci_reg_mask16 (controller, USBCMD, ~0, 1); // start work on schedule +} + +static void +uhci_stop (hci_t *controller) +{ + uhci_reg_mask16 (controller, USBCMD, ~1, 0); // stop work on schedule +} + +#define GET_TD(x) ((void*)(((unsigned int)(x))&~0xf)) + +static td_t * +wait_for_completed_qh (hci_t *controller, qh_t *qh) +{ + int timeout = 1000; /* max 30 ms. */ + void *current = GET_TD (qh->elementlinkptr.ptr); + while ((qh->elementlinkptr.terminate == 0) && (timeout-- > 0)) { + if (current != GET_TD (qh->elementlinkptr.ptr)) { + current = GET_TD (qh->elementlinkptr.ptr); + timeout = 1000; + } + uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers + udelay (30); + } + return (GET_TD (qh->elementlinkptr.ptr) == + 0) ? 0 : GET_TD (phys_to_virt (qh->elementlinkptr.ptr)); +} + +static void +wait_for_completed_td (hci_t *controller, td_t *td) +{ + int timeout = 10000; + while ((td->status_active == 1) + && ((uhci_reg_read16 (controller, USBSTS) & 2) == 0) + && (timeout-- > 0)) { + uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers + udelay (10); + } +} + +static int +maxlen (int size) +{ + return (size - 1) & 0x7ff; +} + +static int +min (int a, int b) +{ + if (a < b) + return a; + else + return b; +} + +static int +uhci_control (usbdev_t *dev, pid_t dir, int drlen, void *devreq, int dalen, + unsigned char *data) +{ + int endp = 0; /* this is control: always 0 */ + int mlen = dev->endpoints[0].maxpacketsize; + int count = (2 + (dalen + mlen - 1) / mlen); + unsigned short req = ((unsigned short *) devreq)[0]; + int i; + td_t *tds = memalign (16, sizeof (td_t) * count); + memset (tds, 0, sizeof (td_t) * count); + count--; /* to compensate for 0-indexed array */ + for (i = 0; i < count; i++) { + tds[i].ptr = virt_to_phys (&tds[i + 1]); + tds[i].depth_first = 1; + tds[i].terminate = 0; + } + tds[count].ptr = 0; + tds[count].depth_first = 1; + tds[count].terminate = 1; + + tds[0].pid = SETUP; + tds[0].dev_addr = dev->address; + tds[0].endp = endp; + tds[0].maxlen = maxlen (drlen); + tds[0].counter = 3; + tds[0].data_toggle = 0; + tds[0].lowspeed = dev->lowspeed; + tds[0].bufptr = virt_to_phys (devreq); + tds[0].status_active = 1; + + int toggle = 1; + for (i = 1; i < count; i++) { + tds[i].pid = dir; + tds[i].dev_addr = dev->address; + tds[i].endp = endp; + tds[i].maxlen = maxlen (min (mlen, dalen)); + tds[i].counter = 3; + tds[i].data_toggle = toggle; + tds[i].lowspeed = dev->lowspeed; + tds[i].bufptr = virt_to_phys (data); + tds[i].status_active = 1; + toggle ^= 1; + dalen -= mlen; + data += mlen; + } + + tds[count].pid = (dir == OUT) ? IN : OUT; + tds[count].dev_addr = dev->address; + tds[count].endp = endp; + tds[count].maxlen = maxlen (0); + tds[count].counter = 0; /* as per linux 2.4.10 */ + tds[count].data_toggle = 1; + tds[count].lowspeed = dev->lowspeed, tds[count].bufptr = 0; + tds[count].status_active = 1; + UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr = + virt_to_phys (tds); + UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0; + UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0; + td_t *td = wait_for_completed_qh (dev->controller, + UHCI_INST (dev->controller)-> + qh_data); + int result; + if (td == 0) { + result = 0; + } else { + printf ("control packet, req %x\n", req); + td_dump (td); + result = 1; + } + free (tds); + return result; +} + +static int +uhci_packet (usbdev_t *dev, int endp, int pid, int toggle, int length, + unsigned char *data) +{ + static td_t *td = 0; + if (td == 0) + td = memalign (16, sizeof (td_t)); + + memset (td, 0, sizeof (td_t)); + td->ptr = 0; + td->terminate = 1; + td->queue_head = 0; + + td->pid = pid; + td->dev_addr = dev->address; + td->endp = endp & 0xf; + td->maxlen = maxlen (length); + if (pid == SETUP) + td->counter = 3; + else + td->counter = 0; + td->data_toggle = toggle & 1; + td->lowspeed = dev->lowspeed; + td->bufptr = virt_to_phys (data); + + td->status_active = 1; + + UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr = + virt_to_phys (td); + UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0; + UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0; + wait_for_completed_td (dev->controller, td); + if ((td->status & 0x7f) == 0) { + //printf("successfully sent a %x packet to %x.%x\n",pid, dev->address,endp); + // success + return 0; + } else { + td_dump (td); + return 1; + } +} + +static td_t * +create_schedule (int numpackets) +{ + if (numpackets == 0) + return 0; + td_t *tds = memalign (16, sizeof (td_t) * numpackets); + memset (tds, 0, sizeof (td_t) * numpackets); + int i; + for (i = 0; i < numpackets; i++) { + tds[i].ptr = virt_to_phys (&tds[i + 1]); + tds[i].terminate = 0; + tds[i].queue_head = 0; + tds[i].depth_first = 1; + } + tds[numpackets - 1].ptr = 0; + tds[numpackets - 1].terminate = 1; + tds[numpackets - 1].queue_head = 0; + tds[numpackets - 1].depth_first = 0; + return tds; +} + +static void +fill_schedule (td_t *td, endpoint_t *ep, int length, unsigned char *data, + int *toggle) +{ + td->pid = ep->direction; + td->dev_addr = ep->dev->address; + td->endp = ep->endpoint & 0xf; + td->maxlen = maxlen (length); + if (ep->direction == SETUP) + td->counter = 3; + else + td->counter = 0; + td->data_toggle = *toggle & 1; + td->lowspeed = ep->dev->lowspeed; + td->bufptr = virt_to_phys (data); + + td->status_active = 1; + *toggle ^= 1; +} + +static int +run_schedule (usbdev_t *dev, td_t *td) +{ + UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr = + virt_to_phys (td); + UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0; + UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0; + td = wait_for_completed_qh (dev->controller, + UHCI_INST (dev->controller)->qh_data); + if (td == 0) { + return 0; + } else { + td_dump (td); + return 1; + } +} + +/* finalize == 1: if data is of packet aligned size, add a zero length packet */ +static int +uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) +{ + int maxpsize = ep->maxpacketsize; + if (maxpsize == 0) + fatal ("MaxPacketSize == 0!!!"); + int numpackets = (size + maxpsize - 1 + finalize) / maxpsize; + if (numpackets == 0) + return 0; + td_t *tds = create_schedule (numpackets); + int i = 0, toggle = ep->toggle; + while ((size > 0) || ((size == 0) && (finalize != 0))) { + fill_schedule (&tds[i], ep, min (size, maxpsize), data, + &toggle); + i++; + data += maxpsize; + size -= maxpsize; + } + if (run_schedule (ep->dev, tds) == 1) { + clear_stall (ep); + free (tds); + return 1; + } + ep->toggle = toggle; + free (tds); + return 0; +} + +void +uhci_reg_write32 (hci_t *ctrl, usbreg reg, u32 value) +{ + outl (value, ctrl->reg_base + reg); +} + +u32 +uhci_reg_read32 (hci_t *ctrl, usbreg reg) +{ + return inl (ctrl->reg_base + reg); +} + +void +uhci_reg_write16 (hci_t *ctrl, usbreg reg, u16 value) +{ + outw (value, ctrl->reg_base + reg); +} + +u16 +uhci_reg_read16 (hci_t *ctrl, usbreg reg) +{ + return inw (ctrl->reg_base + reg); +} + +void +uhci_reg_write8 (hci_t *ctrl, usbreg reg, u8 value) +{ + outb (value, ctrl->reg_base + reg); +} + +u8 +uhci_reg_read8 (hci_t *ctrl, usbreg reg) +{ + return inb (ctrl->reg_base + reg); +} + +void +uhci_reg_mask32 (hci_t *ctrl, usbreg reg, u32 andmask, u32 ormask) +{ + uhci_reg_write32 (ctrl, reg, + (uhci_reg_read32 (ctrl, reg) & andmask) | ormask); +} + +void +uhci_reg_mask16 (hci_t *ctrl, usbreg reg, u16 andmask, u16 ormask) +{ + uhci_reg_write16 (ctrl, reg, + (uhci_reg_read16 (ctrl, reg) & andmask) | ormask); +} + +void +uhci_reg_mask8 (hci_t *ctrl, usbreg reg, u8 andmask, u8 ormask) +{ + uhci_reg_write8 (ctrl, reg, + (uhci_reg_read8 (ctrl, reg) & andmask) | ormask); +} diff --git a/payloads/libpayload/drivers/usb/uhci.h b/payloads/libpayload/drivers/usb/uhci.h new file mode 100644 index 0000000000..f5727c4e61 --- /dev/null +++ b/payloads/libpayload/drivers/usb/uhci.h @@ -0,0 +1,124 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __UHCI_H +#define __UHCI_H + +#include <pci.h> +#include "usb.h" + +typedef union { + struct { + unsigned long terminate:1; + unsigned long queue_head:1; + unsigned long:2; + unsigned long ptr_part:28; + }; + u32 ptr; +} __attribute__ ((packed)) flistp_t; + +typedef struct { + union { + struct { + unsigned long terminate:1; + unsigned long queue_head:1; + unsigned long depth_first:1; + unsigned long:29; + } __attribute__ ((packed)); + u32 ptr; + } __attribute__ ((packed)); + + volatile unsigned long actlen:11; + volatile unsigned long:5; + union { + struct { + unsigned long:1; // bit 0 + unsigned long status_bitstuff_err:1; + unsigned long status_crc_err:1; + unsigned long status_nakrcvd:1; + unsigned long status_babble:1; + unsigned long status_databuf_err:1; + unsigned long status_stalled:1; + unsigned long status_active:1; // bit 7 + } __attribute__ ((packed)); + unsigned char status; + } __attribute__ ((packed)); + volatile unsigned long ioc:1; /* interrupt on complete */ + volatile unsigned long isochronous:1; + volatile unsigned long lowspeed:1; + volatile unsigned long counter:2; + volatile unsigned long shortpck:1; + volatile unsigned long:2; + + unsigned long pid:8; + unsigned long dev_addr:7; + unsigned long endp:4; + unsigned long data_toggle:1; + unsigned long:1; + unsigned long maxlen:11; + + u32 bufptr; + +} __attribute__ ((packed)) + td_t; + + typedef struct { + flistp_t headlinkptr; + volatile flistp_t elementlinkptr; + } __attribute__ ((packed)) + qh_t; + + typedef enum { USBCMD = 0, USBSTS = 2, USBINTR = 4, FRNUM = + 6, FLBASEADD = 8, SOFMOD = 0xc, PORTSC1 = 0x10, PORTSC2 = + 0x12 + } usbreg; + + void uhci_reg_write32 (hci_t *ctrl, usbreg reg, u32 value); + u32 uhci_reg_read32 (hci_t *ctrl, usbreg reg); + void uhci_reg_write16 (hci_t *ctrl, usbreg reg, u16 value); + u16 uhci_reg_read16 (hci_t *ctrl, usbreg reg); + void uhci_reg_write8 (hci_t *ctrl, usbreg reg, u8 value); + u8 uhci_reg_read8 (hci_t *ctrl, usbreg reg); + void uhci_reg_mask32 (hci_t *ctrl, usbreg reg, u32 andmask, u32 ormask); + void uhci_reg_mask16 (hci_t *ctrl, usbreg reg, u16 andmask, u16 ormask); + void uhci_reg_mask8 (hci_t *ctrl, usbreg reg, u8 andmask, u8 ormask); + + typedef struct uhci { + flistp_t *framelistptr; + qh_t *qh_intr, *qh_data, *qh_last; + usbdev_t *roothub; + } uhci_t; + +#define UHCI_INST(controller) ((uhci_t*)((controller)->instance)) + + hci_t *uhci_init (pcidev_t addr); + + void uhci_rh_init (usbdev_t *dev); + +#endif diff --git a/payloads/libpayload/drivers/usb/uhci_rh.c b/payloads/libpayload/drivers/usb/uhci_rh.c new file mode 100644 index 0000000000..e703e53f21 --- /dev/null +++ b/payloads/libpayload/drivers/usb/uhci_rh.c @@ -0,0 +1,182 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <libpayload.h> +#include "uhci.h" + +typedef struct { + int port[2]; +} rh_inst_t; + +#define RH_INST(dev) ((rh_inst_t*)(dev)->data) + +static void +uhci_rh_enable_port (usbdev_t *dev, int port) +{ + u16 value; + hci_t *controller = dev->controller; + if (port == 1) + port = PORTSC1; + else + port = PORTSC2; + uhci_reg_mask16 (controller, port, ~(1 << 12), 0); /* wakeup */ + + uhci_reg_mask16 (controller, port, ~0, 1 << 9); /* reset */ + mdelay (30); // >10ms + uhci_reg_mask16 (controller, port, ~(1 << 9), 0); + mdelay (1); // >5.3us per spec, <3ms because some devices make trouble + + uhci_reg_mask16 (controller, port, ~0, 1 << 2); /* enable */ + do { + value = uhci_reg_read16 (controller, port); + mdelay (1); + } while (((value & (1 << 2)) == 0) && (value & 0x01)); +} + +/* disable root hub */ +static void +uhci_rh_disable_port (usbdev_t *dev, int port) +{ + hci_t *controller = dev->controller; + port = PORTSC2; + if (port == 1) + port = PORTSC1; + uhci_reg_mask16 (controller, port, ~4, 0); + int value; + do { + value = uhci_reg_read16 (controller, port); + mdelay (1); + } while ((value & (1 << 2)) != 0); +} + +static void +uhci_rh_scanport (usbdev_t *dev, int port) +{ + int portsc, offset; + if (port == 1) { + portsc = PORTSC1; + offset = 0; + } else if (port == 2) { + portsc = PORTSC2; + offset = 1; + } else + return; + int devno = RH_INST (dev)->port[offset]; + if (devno != -1) { + dev->controller->devices[devno].destroy (&dev->controller-> + devices[devno]); + init_device_entry (dev->controller, devno); + RH_INST (dev)->port[offset] = -1; + } + uhci_reg_mask16 (dev->controller, portsc, ~0, (1 << 3) | (1 << 2)); // clear port state change, enable port + + if ((uhci_reg_read16 (dev->controller, portsc) & 1) != 0) { + int newdev; + usbdev_t *newdev_t; + // device attached + + uhci_rh_disable_port (dev, port); + uhci_rh_enable_port (dev, port); + + int lowspeed = + (uhci_reg_read16 (dev->controller, portsc) >> 8) & 1; + printf ("%sspeed device\n", (lowspeed == 1) ? "low" : "full"); + + newdev = set_address (dev->controller, lowspeed); + if (newdev == -1) + return; + newdev_t = &dev->controller->devices[newdev]; + RH_INST (dev)->port[offset] = newdev; + newdev_t->address = newdev; + newdev_t->hub = dev->address; + newdev_t->port = portsc; + // determine responsible driver + newdev_t->init (newdev_t); + } +} + +static int +uhci_rh_report_port_changes (usbdev_t *dev) +{ + int stored, real; + + stored = (RH_INST (dev)->port[0] == -1); + real = ((uhci_reg_read16 (dev->controller, PORTSC1) & 1) == 0); + if (stored != real) + return 1; + + stored = (RH_INST (dev)->port[1] == -1); + real = ((uhci_reg_read16 (dev->controller, PORTSC2) & 1) == 0); + if (stored != real) + return 2; + +// maybe detach+attach happened between two scans? + if ((uhci_reg_read16 (dev->controller, PORTSC1) & 2) > 0) + return 1; + if ((uhci_reg_read16 (dev->controller, PORTSC2) & 2) > 0) + return 2; + +// no change + return -1; +} + +static void +uhci_rh_destroy (usbdev_t *dev) +{ + uhci_rh_disable_port (dev, 1); + uhci_rh_disable_port (dev, 2); + free (RH_INST (dev)); +} + +static void +uhci_rh_poll (usbdev_t *dev) +{ + int port; + while ((port = uhci_rh_report_port_changes (dev)) != -1) + uhci_rh_scanport (dev, port); +} + +void +uhci_rh_init (usbdev_t *dev) +{ + dev->destroy = uhci_rh_destroy; + dev->poll = uhci_rh_poll; + + uhci_rh_enable_port (dev, 1); + uhci_rh_enable_port (dev, 2); + dev->data = malloc (sizeof (rh_inst_t)); + RH_INST (dev)->port[0] = -1; + RH_INST (dev)->port[1] = -1; + + /* we can set them here because a root hub _really_ shouldn't + appear elsewhere */ + dev->address = 0; + dev->hub = -1; + dev->port = -1; +} diff --git a/payloads/libpayload/drivers/usb/usb.c b/payloads/libpayload/drivers/usb/usb.c new file mode 100644 index 0000000000..fc4de87dd7 --- /dev/null +++ b/payloads/libpayload/drivers/usb/usb.c @@ -0,0 +1,350 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> +#include "usb.h" + +hci_t *usb_hcs = 0; + +hci_t * +new_controller () +{ + hci_t *controller = malloc (sizeof (hci_t)); + + /* atomic */ + controller->next = usb_hcs; + usb_hcs = controller; + /* atomic end */ + + return controller; +} + +void +detach_controller (hci_t *controller) +{ + if (controller == 0) + return; + if (usb_hcs == controller) { + usb_hcs = controller->next; + } else { + hci_t *it = usb_hcs; + while (it != 0) { + if (it->next == controller) { + it->next = controller->next; + return; + } + } + } +} + +/** + * Polls all hubs on all USB controllers, to find out about device changes + */ +void +usb_poll () +{ + if (usb_hcs == 0) + return; + hci_t *controller = usb_hcs; + while (controller != 0) { + int i; + for (i = 0; i < 128; i++) { + if (controller->devices[i].address != -1) { + controller->devices[i].poll (&controller-> + devices[i]); + } + } + controller = controller->next; + } +} + +void +init_device_entry (hci_t *controller, int i) +{ + controller->devices[i].controller = controller; + controller->devices[i].address = -1; + controller->devices[i].hub = -1; + controller->devices[i].port = -1; + controller->devices[i].init = usb_nop_init; + controller->devices[i].init (&controller->devices[i]); +} + +void +set_feature (usbdev_t *dev, int endp, int feature, int rtype) +{ + dev_req_t dr; + + dr.bmRequestType = rtype; + dr.data_dir = host_to_device; + dr.bRequest = SET_FEATURE; + dr.wValue = feature; + dr.wIndex = endp; + dr.wLength = 0; + dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); +} + +void +get_status (usbdev_t *dev, int intf, int rtype, int len, void *data) +{ + dev_req_t dr; + + dr.bmRequestType = rtype; + dr.data_dir = device_to_host; + dr.bRequest = GET_STATUS; + dr.wValue = 0; + dr.wIndex = intf; + dr.wLength = len; + dev->controller->control (dev, IN, sizeof (dr), &dr, len, data); +} + +u8 * +get_descriptor (usbdev_t *dev, unsigned char bmRequestType, int descType, + int descIdx, int langID) +{ + u8 buf[8]; + u8 *result; + dev_req_t dr; + int size; + + dr.bmRequestType = bmRequestType; + dr.data_dir = device_to_host; // always like this for descriptors + dr.bRequest = GET_DESCRIPTOR; + dr.wValue = (descType << 8) | descIdx; + dr.wIndex = langID; + dr.wLength = 8; + if (dev->controller->control (dev, IN, sizeof (dr), &dr, 8, buf)) { + printf ("getting descriptor size (type %x) failed\n", + descType); + } + + if (descType == 1) { + device_descriptor_t *dd = (device_descriptor_t *) buf; + printf ("maxPacketSize0: %x\n", dd->bMaxPacketSize0); + if (dd->bMaxPacketSize0 != 0) + dev->endpoints[0].maxpacketsize = dd->bMaxPacketSize0; + } + + /* special case for configuration descriptors: they carry all their + subsequent descriptors with them, and keep the entire size at a + different location */ + size = buf[0]; + if (buf[1] == 2) { + int realsize = ((unsigned short *) (buf + 2))[0]; + size = realsize; + } + result = malloc (size); + memset (result, 0, size); + dr.wLength = size; + if (dev->controller-> + control (dev, IN, sizeof (dr), &dr, size, result)) { + printf ("getting descriptor (type %x, size %x) failed\n", + descType, size); + } + + return result; +} + +void +set_configuration (usbdev_t *dev) +{ + dev_req_t dr; + + dr.bmRequestType = 0; + dr.bRequest = SET_CONFIGURATION; + dr.wValue = dev->configuration[5]; + dr.wIndex = 0; + dr.wLength = 0; + dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); +} + +int +clear_stall (endpoint_t *ep) +{ + usbdev_t *dev = ep->dev; + int endp = ep->endpoint; + dev_req_t dr; + + dr.bmRequestType = 0; + if (endp != 0) { + dr.req_recp = endp_recp; + } + dr.bRequest = CLEAR_FEATURE; + dr.wValue = ENDPOINT_HALT; + dr.wIndex = endp; + dr.wLength = 0; + dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + return 0; +} + +/* returns free address or -1 */ +static int +get_free_address (hci_t *controller) +{ + int i; + for (i = 1; i < 128; i++) { + if (controller->devices[i].address != i) + return i; + } + printf ("no free address found\n"); + return -1; // no free address +} + +int +set_address (hci_t *controller, int lowspeed) +{ + int adr = get_free_address (controller); // address to set + dev_req_t dr; + configuration_descriptor_t *cd; + device_descriptor_t *dd; + + memset (&dr, 0, sizeof (dr)); + dr.data_dir = host_to_device; + dr.req_type = standard_type; + dr.req_recp = dev_recp; + dr.bRequest = SET_ADDRESS; + dr.wValue = adr; + dr.wIndex = 0; + dr.wLength = 0; + + usbdev_t *dev = &controller->devices[adr]; + // dummy values for registering the address + dev->address = 0; + dev->lowspeed = lowspeed; + dev->endpoints[0].dev = dev; + dev->endpoints[0].endpoint = 0; + dev->endpoints[0].maxpacketsize = 8; + dev->endpoints[0].toggle = 0; + dev->endpoints[0].direction = SETUP; + mdelay (50); + if (dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0)) { + printf ("set_address failed\n"); + return -1; + } + mdelay (50); + dev->address = adr; + dev->descriptor = + get_descriptor (dev, + gen_bmRequestType (device_to_host, + standard_type, dev_recp), + 1, 0, 0); + dd = (device_descriptor_t *) dev->descriptor; + printf ("device version: %x.%x\n", dd->bcdUSB >> 8, + dd->bcdUSB & 0xff); + printf ("device has %x configurations\n", dd->bNumConfigurations); + if (dd->bNumConfigurations == 0) { + /* device isn't usable */ + printf ("no usable configuration!\n"); + dev->address = 0; + return -1; + } + dev->configuration = + get_descriptor (dev, + gen_bmRequestType (device_to_host, + standard_type, dev_recp), + 2, 0, 0); + cd = (configuration_descriptor_t *) dev->configuration; + set_configuration (dev); + interface_descriptor_t *interface = + (interface_descriptor_t *) (((char *) cd) + cd->bLength); + { + int i; + int num = cd->bNumInterfaces; + interface_descriptor_t *current = interface; + printf ("device has %x interfaces\n", num); + num = (num > 5) ? 5 : num; + for (i = 0; i < num; i++) { + int j; + printf (" #%x has %x endpoints, interface %x:%x, protocol %x\n", current->bInterfaceNumber, current->bNumEndpoints, current->bInterfaceClass, current->bInterfaceSubClass, current->bInterfaceProtocol); + endpoint_descriptor_t *endp = + (endpoint_descriptor_t *) (((char *) current) + + + current->bLength); + if (interface->bInterfaceClass == 0x3) + endp = (endpoint_descriptor_t *) (((char *) endp) + ((char *) endp)[0]); // ignore HID descriptor + memset (dev->endpoints, 0, sizeof (dev->endpoints)); + dev->num_endp = 1; // 0 always exists + dev->endpoints[0].dev = dev; + dev->endpoints[0].maxpacketsize = dd->bMaxPacketSize0; + dev->endpoints[0].direction = SETUP; + dev->endpoints[0].type = CONTROL; + for (j = 1; j <= current->bNumEndpoints; j++) { + static const char *transfertypes[4] = + { "control", "isochronous", "bulk", + "interrupt" + }; + printf (" #%x: Endpoint %x (%s), max packet size %x, type %s\n", j, endp->bEndpointAddress & 0x7f, ((endp->bEndpointAddress & 0x80) != 0) ? "in" : "out", endp->wMaxPacketSize, transfertypes[endp->bmAttributes]); + endpoint_t *ep = + &dev->endpoints[dev->num_endp++]; + ep->dev = dev; + ep->endpoint = endp->bEndpointAddress; + ep->toggle = 0; + ep->maxpacketsize = endp->wMaxPacketSize; + ep->direction = + ((endp->bEndpointAddress & 0x80) == + 0) ? OUT : IN; + ep->type = endp->bmAttributes; + endp = (endpoint_descriptor_t + *) (((char *) endp) + endp->bLength); + } + current = (interface_descriptor_t *) endp; + } + } + int class = dd->bDeviceClass; + if (class == 0) + class = interface->bInterfaceClass; + + enum { hid_device = 0x3, msc_device = 0x8, hub_device = 0x9 }; + + printf ("device of class %x found\n", class); + if (class == hub_device) { + printf ("hub found\n"); +#ifdef CONFIG_USB_HUB + controller->devices[adr].init = usb_hub_init; +#else + printf ("support not compiled in\n"); +#endif + } + if (class == hid_device) { + printf ("HID found\n"); +#ifdef CONFIG_USB_HID + controller->devices[adr].init = usb_hid_init; +#else + printf ("support not compiled in\n"); +#endif + } + if (class == msc_device) { + printf ("MSC found\n"); +#ifdef CONFIG_USB_MSC + controller->devices[adr].init = usb_msc_init; +#else + printf ("support not compiled in\n"); +#endif + } + return adr; +} diff --git a/payloads/libpayload/drivers/usb/usb.h b/payloads/libpayload/drivers/usb/usb.h new file mode 100644 index 0000000000..1500b75a1e --- /dev/null +++ b/payloads/libpayload/drivers/usb/usb.h @@ -0,0 +1,224 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __USB_H +#define __USB_H +#include <libpayload.h> +#include <pci.h> + +typedef enum { host_to_device = 0, device_to_host = 1 } dev_req_dir; +typedef enum { standard_type = 0, class_type = 1, vendor_type = + 2, reserved_type = 3 +} dev_req_type; +typedef enum { dev_recp = 0, iface_recp = 1, endp_recp = 2, other_recp = 3 +} dev_req_recp; + +typedef enum { + GET_STATUS = 0, + CLEAR_FEATURE = 1, + SET_FEATURE = 3, + SET_ADDRESS = 5, + GET_DESCRIPTOR = 6, + SET_DESCRIPTOR = 7, + GET_CONFIGURATION = 8, + SET_CONFIGURATION = 9, + GET_INTERFACE = 10, + SET_INTERFACE = 11, + SYNCH_FRAME = 12 +} bRequest_Codes; + +typedef enum { + ENDPOINT_HALT = 0, + DEVICE_REMOTE_WAKEUP = 1, + TEST_MODE = 2 +} feature_selectors; + +typedef struct { + union { + struct { + dev_req_recp req_recp:5; + dev_req_type req_type:2; + dev_req_dir data_dir:1; + } __attribute__ ((packed)); + unsigned char bmRequestType; + } __attribute__ ((packed)); + unsigned char bRequest; + unsigned short wValue; + unsigned short wIndex; + unsigned short wLength; +} __attribute__ ((packed)) dev_req_t; + +struct usbdev_hc; +typedef struct usbdev_hc hci_t; + +struct usbdev; +typedef struct usbdev usbdev_t; + +typedef enum { SETUP = 0x2d, IN = 0x69, OUT = 0xe1 } pid_t; +typedef enum { CONTROL = 0, ISOCHRONOUS = 1, BULK = 2, INTERRUPT = 3 +} endpoint_type; + +typedef struct { + usbdev_t *dev; + int endpoint; + pid_t direction; + int toggle; + int maxpacketsize; + endpoint_type type; +} endpoint_t; + + +struct usbdev { + hci_t *controller; + endpoint_t endpoints[32]; + int num_endp; + int address; // usb address + int hub; // hub, device is attached to + int port; // port where device is attached + int lowspeed; // 1 if lowspeed device + void *data; + u8 *descriptor; + u8 *configuration; + void (*init) (usbdev_t *dev); + void (*destroy) (usbdev_t *dev); + void (*poll) (usbdev_t *dev); +}; + +struct usbdev_hc { + struct usbdev_hc *next; + pcidev_t bus_address; + u32 reg_base; + usbdev_t devices[128]; // dev 0 is root hub, 127 is last addressable + void (*start) (hci_t *controller); + void (*stop) (hci_t *controller); + void (*reset) (hci_t *controller); + void (*shutdown) (hci_t *controller); + int (*packet) (usbdev_t *dev, int endp, int pid, int toggle, + int length, u8 *data); + int (*bulk) (endpoint_t *ep, int size, u8 *data, int finalize); + int (*control) (usbdev_t *dev, pid_t pid, int dr_length, + void *devreq, int data_length, u8 *data); + void *instance; +}; + +typedef struct { + unsigned char bDescLength; + unsigned char bDescriptorType; + unsigned char bNbrPorts; + union { + struct { + unsigned long logicalPowerSwitchingMode:2; + unsigned long isCompoundDevice:1; + unsigned long overcurrentProtectionMode:2; + unsigned long ttThinkTime:2; + unsigned long arePortIndicatorsSupported:1; + unsigned long:8; + } __attribute__ ((packed)); + unsigned short wHubCharacteristics; + } __attribute__ ((packed)); + unsigned char bPowerOn2PwrGood; + unsigned char bHubContrCurrent; + char DeviceRemovable[]; +} __attribute__ ((packed)) hub_descriptor_t; + +typedef struct { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned short bcdUSB; + unsigned char bDeviceClass; + unsigned char bDeviceSubClass; + unsigned char bDeviceProtocol; + unsigned char bMaxPacketSize0; + unsigned short idVendor; + unsigned short idProduct; + unsigned short bcdDevice; + unsigned char iManufacturer; + unsigned char iProduct; + unsigned char iSerialNumber; + unsigned char bNumConfigurations; +} __attribute__ ((packed)) device_descriptor_t; + +typedef struct { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned short wTotalLength; + unsigned char bNumInterfaces; + unsigned char bConfigurationValue; + unsigned char iConfiguration; + unsigned char bmAttributes; + unsigned char bMaxPower; +} __attribute__ ((packed)) configuration_descriptor_t; + +typedef struct { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned char bInterfaceNumber; + unsigned char bAlternateSetting; + unsigned char bNumEndpoints; + unsigned char bInterfaceClass; + unsigned char bInterfaceSubClass; + unsigned char bInterfaceProtocol; + unsigned char iInterface; +} __attribute__ ((packed)) interface_descriptor_t; + +typedef struct { + unsigned char bLength; + unsigned char bDescriptorType; + unsigned char bEndpointAddress; + unsigned char bmAttributes; + unsigned short wMaxPacketSize; + unsigned char bInterval; +} __attribute__ ((packed)) endpoint_descriptor_t; + +hci_t *new_controller (void); +void detach_controller (hci_t *controller); +void usb_poll (void); +void init_device_entry (hci_t *controller, int num); + +void set_feature (usbdev_t *dev, int endp, int feature, int rtype); +void get_status (usbdev_t *dev, int endp, int rtype, int len, void *data); +int clear_stall (endpoint_t *ep); + +void usb_nop_init (usbdev_t *dev); +void usb_hub_init (usbdev_t *dev); +void usb_hid_init (usbdev_t *dev); +void usb_msc_init (usbdev_t *dev); + +int set_address (hci_t *controller, int lowspeed); + +u8 *get_descriptor (usbdev_t *dev, unsigned char bmRequestType, + int descType, int descIdx, int langID); + +static inline unsigned char +gen_bmRequestType (dev_req_dir dir, dev_req_type type, dev_req_recp recp) +{ + return (dir << 7) | (type << 5) | recp; +} + +#endif diff --git a/payloads/libpayload/drivers/usb/usb_dev.c b/payloads/libpayload/drivers/usb/usb_dev.c new file mode 100644 index 0000000000..0c6a34ca10 --- /dev/null +++ b/payloads/libpayload/drivers/usb/usb_dev.c @@ -0,0 +1,55 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "usb.h" + +static void +usb_nop_destroy (usbdev_t *dev) +{ + if (dev->descriptor != 0) + free (dev->descriptor); + usb_nop_init (dev); + dev->address = -1; + dev->hub = -1; + dev->port = -1; +} + +static void +usb_nop_poll (usbdev_t *dev) +{ + return; +} + +void +usb_nop_init (usbdev_t *dev) +{ + dev->descriptor = 0; + dev->destroy = usb_nop_destroy; + dev->poll = usb_nop_poll; +} diff --git a/payloads/libpayload/drivers/usb/usbdisk.h b/payloads/libpayload/drivers/usb/usbdisk.h new file mode 100644 index 0000000000..3ac24631ac --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbdisk.h @@ -0,0 +1,51 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __USBDISK_H +#define __USBDISK_H +#include "usb.h" + +/** + * To be implemented by libpayload-client. It's called by the USB stack + * when a new USB storage device is found, so the client has the chance + * to know about it. + * + * @param dev descriptor for the USB storage device + */ +void usbdisk_create (usbdev_t *dev); + +/** + * To be implemented by libpayload-client. It's called by the USB stack + * when it finds out that a USB storage device is removed. + * + * @param dev descriptor for the USB storage device + */ +void usbdisk_remove (usbdev_t *dev); + +#endif diff --git a/payloads/libpayload/drivers/usb/usbhid.c b/payloads/libpayload/drivers/usb/usbhid.c new file mode 100644 index 0000000000..414cc07cdb --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbhid.c @@ -0,0 +1,137 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "usb.h" + +enum { hid_subclass_none = 0, hid_subclass_boot = 1 }; +enum { hid_proto_boot = 0, hid_proto_report = 1 }; +enum { hid_boot_proto_none = 0, hid_boot_proto_keyboard = + 1, hid_boot_proto_mouse = 2 +}; +static const char *boot_protos[3] = { "(none)", "keyboard", "mouse" }; +enum { GET_REPORT = 0x1, GET_IDLE = 0x2, GET_PROTOCOL = 0x3, SET_REPORT = + 0x9, SET_IDLE = 0xa, SET_PROTOCOL = 0xb +}; + +static void +usb_hid_destroy (usbdev_t *dev) +{ +} + +int keypress; +char keymap[256] = { + -1, -1, -1, -1, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '1', '2', + '3', '4', '5', '6', '7', '8', '9', '0', '\n', TERM_ESC, + TERM_BACKSPACE, TERM_TAB, ' ', '-', '=', '[', + ']', '\\', -1, ';', '\'', '`', ',', '.', '/', -1, -1, -1, -1, -1, -1, + -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, TERM_HOME, TERM_PPAGE, -1, + TERM_END, TERM_NPAGE, TERM_RIGHT, + TERM_LEFT, TERM_DOWN, TERM_UP, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + + +static void +usb_hid_poll (usbdev_t *dev) +{ + char buf[8]; + static int toggle = 0; + // hardcode to endpoint 1, 8 bytes + dev->controller->packet (dev, 1, IN, toggle, 8, buf); + toggle ^= 1; + // FIXME: manage buf[0]=special keys, too + keypress = keymap[buf[2]]; + if ((keypress == -1) && (buf[2] != 0)) { + printf ("%x %x %x %x %x %x %x %x\n", buf[0], buf[1], buf[2], + buf[3], buf[4], buf[5], buf[6], buf[7]); + } +} + +int (*oldhook) (void); + +int +hookfunc (void) +{ + int key; + if (oldhook != 0) + key = oldhook (); + if (key == -1) + key = keypress; + return key; +} + +void +usb_hid_init (usbdev_t *dev) +{ + + configuration_descriptor_t *cd = dev->configuration; + interface_descriptor_t *interface = ((char *) cd) + cd->bLength; + + if (interface->bInterfaceSubClass == hid_subclass_boot) { + printf (" supports boot interface..\n"); + printf (" it's a %s\n", + boot_protos[interface->bInterfaceProtocol]); + if (interface->bInterfaceProtocol == hid_boot_proto_keyboard) { + printf (" activating...\n"); + dev_req_t dr; + // set_protocol(hid_proto_boot) + dr.data_dir = host_to_device; + dr.req_type = class_type; + dr.req_recp = iface_recp; + dr.bRequest = SET_PROTOCOL; + dr.wValue = hid_proto_boot; + dr.wIndex = interface->bInterfaceNumber; + dr.wLength = 0; + dev->controller->control (dev, OUT, + sizeof (dev_req_t), &dr, 0, + 0); + + // only add here, because we only support boot-keyboard HID devices + // FIXME: make this a real console input driver instead, once the API is there + dev->destroy = usb_hid_destroy; + dev->poll = usb_hid_poll; + oldhook = getkey_hook; + getkey_hook = hookfunc; + } + } +} diff --git a/payloads/libpayload/drivers/usb/usbhub.c b/payloads/libpayload/drivers/usb/usbhub.c new file mode 100644 index 0000000000..04c04c383a --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbhub.c @@ -0,0 +1,158 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "usb.h" + +// assume that host_to_device is overwritten if necessary +#define DR_PORT gen_bmRequestType(host_to_device, class_type, other_recp) +#define PORT_RESET 0x4 +#define PORT_POWER 0x8 + +typedef struct { + int num_ports; + int *ports; + hub_descriptor_t *descriptor; +} usbhub_inst_t; + +#define HUB_INST(dev) ((usbhub_inst_t*)(dev)->data) + +static void +usb_hub_destroy (usbdev_t *dev) +{ + free (HUB_INST (dev)->ports); + free (HUB_INST (dev)->descriptor); + free (HUB_INST (dev)); +} + +static void +usb_hub_scanport (usbdev_t *dev, int port) +{ + int newdev; + unsigned short buf[2]; + usbdev_t *newdev_t; + + get_status (dev, port, DR_PORT, 4, buf); + int portstatus = ((buf[0] & 1) == 0); + int datastatus = (HUB_INST (dev)->ports[port] == -1); + if (portstatus == datastatus) + return; // no change - FIXME: read right fields for that test + + if (!datastatus) { + int devno = HUB_INST (dev)->ports[port]; + if (devno == -1) + fatal ("FATAL: illegal devno!\n"); + dev->controller->devices[devno].destroy (&dev->controller-> + devices[devno]); + init_device_entry (dev->controller, devno); + HUB_INST (dev)->ports[port] = -1; + return; + } + + set_feature (dev, port, PORT_RESET, DR_PORT); + mdelay (20); + + get_status (dev, port, DR_PORT, 4, buf); + int lowspeed = (buf[0] >> 9) & 1; + + newdev = set_address (dev->controller, lowspeed); + if (newdev == -1) + return; + newdev_t = &dev->controller->devices[newdev]; + + HUB_INST (dev)->ports[port] = newdev; + newdev_t->address = newdev; + newdev_t->hub = dev->address; + newdev_t->port = port; + // determine responsible driver + newdev_t->init (newdev_t); +} + +static int +usb_hub_report_port_changes (usbdev_t *dev) +{ + int port; + unsigned short buf[2]; + for (port = 1; port <= HUB_INST (dev)->num_ports; port++) { + get_status (dev, port, DR_PORT, 4, buf); + // FIXME: proper change detection + int portstatus = ((buf[0] & 1) == 0); + int datastatus = (HUB_INST (dev)->ports[port] == -1); + if (portstatus != datastatus) + return port; + } + +// no change + return -1; +} + +static void +usb_hub_enable_port (usbdev_t *dev, int port) +{ + set_feature (dev, port, PORT_POWER, DR_PORT); + mdelay (20); +} + +#if 0 +static void +usb_hub_disable_port (usbdev_t *dev, int port) +{ +} +#endif + +static void +usb_hub_poll (usbdev_t *dev) +{ + int port; + while ((port = usb_hub_report_port_changes (dev)) != -1) + usb_hub_scanport (dev, port); +} + +void +usb_hub_init (usbdev_t *dev) +{ + int i; + dev->destroy = usb_hub_destroy; + dev->poll = usb_hub_poll; + + dev->data = malloc (sizeof (usbhub_inst_t)); + + HUB_INST (dev)->descriptor = + (hub_descriptor_t *) get_descriptor (dev, + gen_bmRequestType + (device_to_host, + class_type, dev_recp), + 0x29, 0, 0); + HUB_INST (dev)->num_ports = HUB_INST (dev)->descriptor->bNbrPorts; + HUB_INST (dev)->ports = + malloc (sizeof (int) * (HUB_INST (dev)->num_ports + 1)); + for (i = 1; i <= HUB_INST (dev)->num_ports; i++) + HUB_INST (dev)->ports[i] = -1; + for (i = 1; i <= HUB_INST (dev)->num_ports; i++) + usb_hub_enable_port (dev, i); +} diff --git a/payloads/libpayload/drivers/usb/usbinit.c b/payloads/libpayload/drivers/usb/usbinit.c new file mode 100644 index 0000000000..fe13837c51 --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbinit.c @@ -0,0 +1,120 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <config.h> +#include "usb.h" +#include "uhci.h" +#include "usbdisk.h" + +/** + * Initializes USB controller attached to PCI + * + * @param bus PCI bus number + * @param dev PCI device id at bus + * @param func function id of the controller + */ +int +usb_controller_initialize (int bus, int dev, int func) +{ + u32 class; + u32 devclass; + u32 prog_if; + pcidev_t addr; + u32 pciid; + + addr = PCI_DEV (bus, dev, func); + class = pci_read_config32 (addr, 8); + pciid = pci_read_config32 (addr, 0); + + devclass = class >> 16; + prog_if = (class >> 8) & 0xff; + + /* enable busmaster */ +#define PCI_COMMAND 4 +#define PCI_COMMAND_MASTER 4 + pci_write_config32 (addr, PCI_COMMAND, + pci_read_config32 (addr, + PCI_COMMAND) | + PCI_COMMAND_MASTER); + + if (devclass == 0xc03) { + printf ("%02x:%02x.%x %04x:%04x.%d ", 0, dev, func, + pciid >> 16, pciid & 0xFFFF, func); + if (prog_if == 0) { + printf ("UHCI controller\n"); +#ifdef CONFIG_USB_UHCI + uhci_init (addr); + usb_poll (); + usb_poll (); +#else + printf ("Not supported.\n"); +#endif + } + if (prog_if == 0x10) { + printf ("OHCI controller\n"); +#ifdef CONFIG_USB_OHCI + // ohci_init(addr); +#else + printf ("Not supported.\n"); +#endif + + } + if (prog_if == 0x20) { + printf ("EHCI controller\n"); +#ifdef CONFIG_USB_EHCI + // ehci_init(addr); +#else + printf ("Not supported.\n"); +#endif + + } + } + + return 0; +} + +/** + * Initialize all USB controllers attached to PCI. + */ +int +usb_initialize (void) +{ + int bus, dev, func; + for (bus = 0; bus < 256; bus++) + for (dev = 0; dev < 32; dev++) + for (func = 0; func < 8; func++) + usb_controller_initialize (bus, dev, func); + return 0; +} + +int +usb_exit (void) +{ + return 0; +} diff --git a/payloads/libpayload/drivers/usb/usbmsc.c b/payloads/libpayload/drivers/usb/usbmsc.c new file mode 100644 index 0000000000..cbef585e24 --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbmsc.c @@ -0,0 +1,397 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <arch/endian.h> +#include "usb.h" +#include "usbmsc.h" +#include "usbdisk.h" + +enum { + msc_subclass_rbc = 0x1, + msc_subclass_mmc2 = 0x2, + msc_subclass_qic157 = 0x3, + msc_subclass_ufi = 0x4, + msc_subclass_sff8070i = 0x5, + msc_subclass_scsitrans = 0x6 +}; +static const char *msc_subclass_strings[7] = { + "(none)", + "RBC", + "MMC-2", + "QIC-157", + "UFI", + "SFF-8070i", + "SCSI transparent" +}; +enum { + msc_proto_cbi_wcomp = 0x0, + msc_proto_cbi_wocomp = 0x1, + msc_proto_bulk_only = 0x50 +}; +static const char *msc_protocol_strings[0x51] = { + "Control/Bulk/Interrupt protocol (with command completion interrupt)", + "Control/Bulk/Interrupt protocol (with no command completion interrupt)", + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + "Bulk-Only Transport" +}; + + +static void +usb_msc_destroy (usbdev_t *dev) +{ + usbdisk_remove (dev); + free (dev->data); + dev->data = 0; +} + +static void +usb_msc_poll (usbdev_t *dev) +{ +} + +const int DEV_RESET = 0xff; +const int GET_MAX_LUN = 0xfe; + +const unsigned int cbw_signature = 0x43425355; +const unsigned int csw_signature = 0x53425355; + +typedef struct { + unsigned int dCBWSignature; + unsigned int dCBWTag; + unsigned int dCBWDataTransferLength; + unsigned char bmCBWFlags; + unsigned long bCBWLUN:4; + unsigned long:4; + unsigned long bCBWCBLength:5; + unsigned long:3; + unsigned char CBWCB[31 - 15]; +} __attribute__ ((packed)) + cbw_t; + + typedef struct { + unsigned int dCSWSignature; + unsigned int dCSWTag; + unsigned int dCSWDataResidue; + unsigned char bCSWStatus; + } __attribute__ ((packed)) + csw_t; + + static void + reset_transport (usbdev_t *dev) +{ + dev_req_t dr; + memset (&dr, 0, sizeof (dr)); + dr.bmRequestType = 0; + dr.data_dir = host_to_device; +#ifndef QEMU + dr.req_type = class_type; + dr.req_recp = iface_recp; +#endif + dr.bRequest = DEV_RESET; + dr.wValue = 0; + dr.wIndex = 0; + dr.wLength = 0; + dev->controller->control (dev, OUT, sizeof (dr), &dr, 0, 0); + clear_stall (MSC_INST (dev)->bulk_in); + clear_stall (MSC_INST (dev)->bulk_out); +} + +/* device may stall this command, so beware! */ +static int +get_max_luns (usbdev_t *dev) +{ + unsigned char luns = 75; + dev_req_t dr; + dr.bmRequestType = 0; + dr.data_dir = device_to_host; +#ifndef QEMU + dr.req_type = class_type; + dr.req_recp = iface_recp; +#endif + dr.bRequest = GET_MAX_LUN; + dr.wValue = 0; + dr.wIndex = 0; + dr.wLength = 1; + if (dev->controller->control (dev, IN, sizeof (dr), &dr, 1, &luns)) { + luns = 0; // assume only 1 lun if req fails + } + return luns; +} + +int tag; +int lun = 0; + +static void +wrap_cbw (cbw_t *cbw, int datalen, cbw_direction dir, const u8 *cmd, + int cmdlen) +{ + memset (cbw, 0, sizeof (cbw_t)); + + cbw->dCBWSignature = cbw_signature; + cbw->dCBWTag = tag++; + cbw->bCBWLUN = lun; // static value per device + + cbw->dCBWDataTransferLength = datalen; + cbw->bmCBWFlags = dir; + memcpy (cbw->CBWCB, cmd, sizeof (cbw->CBWCB)); + cbw->bCBWCBLength = cmdlen; +} + +static void +get_csw (endpoint_t *ep, csw_t *csw) +{ + ep->dev->controller->bulk (ep, sizeof (csw_t), (u8 *) csw, 1); +} + +static int +execute_command (usbdev_t *dev, cbw_direction dir, const u8 *cb, int cblen, + u8 *buf, int buflen) +{ + cbw_t cbw; + csw_t csw; + + int always_succeed = 0; + if ((cb[0] == 0x1b) && (cb[4] == 1)) { //start command, always succeed + always_succeed = 1; + } + wrap_cbw (&cbw, buflen, dir, cb, cblen); + if (dev->controller-> + bulk (MSC_INST (dev)->bulk_out, sizeof (cbw), (u8 *) &cbw, 0)) { + clear_stall (MSC_INST (dev)->bulk_out); + return 1; + } + mdelay (10); + if (dir == cbw_direction_data_in) { + if (dev->controller-> + bulk (MSC_INST (dev)->bulk_in, buflen, buf, 0)) { + clear_stall (MSC_INST (dev)->bulk_in); + return 1; + } + } else { + if (dev->controller-> + bulk (MSC_INST (dev)->bulk_out, buflen, buf, 0)) { + clear_stall (MSC_INST (dev)->bulk_out); + return 1; + } + } + get_csw (MSC_INST (dev)->bulk_in, &csw); + if (always_succeed == 1) { + // return success, regardless of message + return 0; + } + if (csw.bCSWStatus == 2) { + // phase error, reset transport + reset_transport (dev); + return 1; + } + if (csw.bCSWStatus == 0) { + // no error, exit + return 0; + } + // error "check condition" or reserved error + return 1; +} + +typedef struct { + unsigned char command; //0 + unsigned char res1; //1 + unsigned int block; //2-5 + unsigned char res2; //6 + unsigned short numblocks; //7-8 + unsigned char res3; //9 - the block is 10 bytes long +} __attribute__ ((packed)) cmdblock_t; + +typedef struct { + unsigned char command; //0 + unsigned char res1; //1 + unsigned char res2; //2 + unsigned char res3; //3 + unsigned char lun; //4 + unsigned char res4; //5 +} __attribute__ ((packed)) cmdblock6_t; + + +/** + * Reads or writes a number of sequential blocks on a USB storage device. + * As it uses the READ(10) SCSI-2 command, it's limited to storage devices + * of at most 2TB. It assumes sectors of 512 bytes. + * + * @param dev device to access + * @param start first sector to access + * @param n number of sectors to access + * @param dir direction of access: cbw_direction_data_in == read, cbw_direction_data_out == write + * @param buf buffer to read into or write from. Must be at least n*512 bytes + * @return 0 on success, 1 on failure + */ +int +readwrite_blocks (usbdev_t *dev, int start, int n, cbw_direction dir, u8 *buf) +{ + cmdblock_t cb; + memset (&cb, 0, sizeof (cb)); + if (dir == cbw_direction_data_in) { + // read + cb.command = 0x28; + } else { + // write + cb.command = 0x2a; + } + cb.block = ntohl (start); + cb.numblocks = ntohw (n); + return execute_command (dev, dir, (u8 *) &cb, sizeof (cb), buf, + n * 512); +} + +static int +test_unit_ready (usbdev_t *dev) +{ + cmdblock6_t cb; + memset (&cb, 0, sizeof (cb)); // full initialization for T-U-R + return execute_command (dev, cbw_direction_data_out, (u8 *) &cb, + sizeof (cb), 0, 0); +} + +static int +spin_up (usbdev_t *dev) +{ + cmdblock6_t cb; + memset (&cb, 0, sizeof (cb)); + cb.command = 0x1b; + cb.lun = 1; + return execute_command (dev, cbw_direction_data_out, (u8 *) &cb, + sizeof (cb), 0, 0); +} + +static void +read_capacity (usbdev_t *dev) +{ + cmdblock_t cb; + memset (&cb, 0, sizeof (cb)); + cb.command = 0x25; // read capacity + u8 buf[8]; + int count = 0; + while ((count++ < 20) + && + (execute_command + (dev, cbw_direction_data_in, (u8 *) &cb, sizeof (cb), buf, + 8) == 1)); + if (count >= 20) { + // still not successful, assume 2tb in 512byte sectors, which is just the same garbage as any other number, but probably reasonable. + printf ("assuming 2TB in 512byte sectors as READ CAPACITY didn't answer.\n"); + MSC_INST (dev)->numblocks = 0xffffffff; + MSC_INST (dev)->blocksize = 512; + } else { + MSC_INST (dev)->numblocks = ntohl (*(u32 *) buf) + 1; + MSC_INST (dev)->blocksize = ntohl (*(u32 *) (buf + 4)); + } + printf (" has %d blocks sized %db\n", MSC_INST (dev)->numblocks, + MSC_INST (dev)->blocksize); +} + +void +usb_msc_init (usbdev_t *dev) +{ + int i, timeout; + + dev->destroy = usb_msc_destroy; + dev->poll = usb_msc_poll; + + configuration_descriptor_t *cd = + (configuration_descriptor_t *) dev->configuration; + interface_descriptor_t *interface = + (interface_descriptor_t *) (((char *) cd) + cd->bLength); + + printf (" it uses %s command set\n", + msc_subclass_strings[interface->bInterfaceSubClass]); + printf (" it uses %s protocol\n", + msc_protocol_strings[interface->bInterfaceProtocol]); + + if ((interface->bInterfaceProtocol != 0x50) + || (interface->bInterfaceSubClass != 6)) { + /* Other protocols, such as ATAPI don't seem to be very popular. looks like ATAPI would be really easy to add, if necessary. */ + printf (" Only SCSI over Bulk is supported.\n"); + return; + } + + dev->data = malloc (sizeof (usbmsc_inst_t)); + MSC_INST (dev)->bulk_in = 0; + MSC_INST (dev)->bulk_out = 0; + + for (i = 1; i <= dev->num_endp; i++) { + if (dev->endpoints[i].endpoint == 0) + continue; + if (dev->endpoints[i].type != BULK) + continue; + if ((dev->endpoints[i].direction == IN) + && (MSC_INST (dev)->bulk_in == 0)) + MSC_INST (dev)->bulk_in = &dev->endpoints[i]; + if ((dev->endpoints[i].direction == OUT) + && (MSC_INST (dev)->bulk_out == 0)) + MSC_INST (dev)->bulk_out = &dev->endpoints[i]; + } + + if (MSC_INST (dev)->bulk_in == 0) + fatal ("couldn't find bulk-in endpoint"); + if (MSC_INST (dev)->bulk_out == 0) + fatal ("couldn't find bulk-out endpoint"); + printf (" using endpoint %x as in, %x as out\n", + MSC_INST (dev)->bulk_in->endpoint, + MSC_INST (dev)->bulk_out->endpoint); + + printf (" has %d luns\n", get_max_luns (dev) + 1); + + printf (" Waiting for device to become ready... "); + timeout = 10; + while (test_unit_ready (dev) && --timeout) { + mdelay (100); + printf ("."); + } + if (test_unit_ready (dev)) { + printf ("timeout. Device not ready. Still trying...\n"); + } else { + printf ("ok.\n"); + } + + printf (" spin up"); + for (i = 0; i < 30; i++) { + printf ("."); + if (!spin_up (dev)) { + printf (" OK."); + break; + } + mdelay (100); + } + printf ("\n"); + + read_capacity (dev); + usbdisk_create (dev); +} diff --git a/payloads/libpayload/drivers/usb/usbmsc.h b/payloads/libpayload/drivers/usb/usbmsc.h new file mode 100644 index 0000000000..e180d3e64a --- /dev/null +++ b/payloads/libpayload/drivers/usb/usbmsc.h @@ -0,0 +1,47 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2008 coresystems GmbH + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef __USBMSC_H +#define __USBMSC_H +typedef struct { + unsigned int blocksize; + unsigned int numblocks; + endpoint_t *bulk_in; + endpoint_t *bulk_out; +} usbmsc_inst_t; + +#define MSC_INST(dev) ((usbmsc_inst_t*)(dev)->data) + +typedef enum { cbw_direction_data_in = 0x80, cbw_direction_data_out = 0 +} cbw_direction; + +int readwrite_blocks (usbdev_t *dev, int start, int n, cbw_direction dir, + u8 *buf); + +#endif |