From 6615ef3bfc3ff20643a31f01e40473e174460b46 Mon Sep 17 00:00:00 2001 From: Patrick Georgi Date: Fri, 13 Aug 2010 09:18:58 +0000 Subject: [PATCH] Add support for OHCI controllers and prelimiary support for xHCI (USB3) controllers. Improve scanning for USB controllers. Limitations: - OHCI doesn't support interrupt transfers yet (ie. no keyboards) - xHCI just does initialization and device attach/detach so far Signed-off-by: Patrick Georgi Acked-by: Peter Stuge git-svn-id: svn://svn.coreboot.org/coreboot/trunk@5691 2b7e53f0-3cfb-0310-b3e9-8179ed1497e1 --- payloads/libpayload/Config.in | 8 +- payloads/libpayload/drivers/Makefile.inc | 4 + payloads/libpayload/drivers/usb/ohci.c | 472 ++++++++++++++++++ payloads/libpayload/drivers/usb/ohci.h | 40 ++ .../libpayload/drivers/usb/ohci_private.h | 255 ++++++++++ payloads/libpayload/drivers/usb/ohci_rh.c | 164 ++++++ payloads/libpayload/drivers/usb/usbinit.c | 19 +- payloads/libpayload/drivers/usb/xhci.c | 262 ++++++++++ payloads/libpayload/drivers/usb/xhci.h | 40 ++ .../libpayload/drivers/usb/xhci_private.h | 350 +++++++++++++ payloads/libpayload/drivers/usb/xhci_rh.c | 133 +++++ 11 files changed, 1736 insertions(+), 11 deletions(-) create mode 100644 payloads/libpayload/drivers/usb/ohci.c create mode 100644 payloads/libpayload/drivers/usb/ohci.h create mode 100644 payloads/libpayload/drivers/usb/ohci_private.h create mode 100644 payloads/libpayload/drivers/usb/ohci_rh.c create mode 100644 payloads/libpayload/drivers/usb/xhci.c create mode 100644 payloads/libpayload/drivers/usb/xhci.h create mode 100644 payloads/libpayload/drivers/usb/xhci_private.h create mode 100644 payloads/libpayload/drivers/usb/xhci_rh.c diff --git a/payloads/libpayload/Config.in b/payloads/libpayload/Config.in index 0cd0438fe4..5ac35faeca 100644 --- a/payloads/libpayload/Config.in +++ b/payloads/libpayload/Config.in @@ -224,7 +224,6 @@ config USB_OHCI 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" @@ -233,6 +232,13 @@ config USB_EHCI Select this option if you want to use USB 2.0 NOTE: This option is not (fully) implemented yet +config USB_XHCI + bool "Support for USB xHCI controllers" + depends on USB + help + Select this option if you want to use USB 3.0 + NOTE: This option is not (fully) implemented yet + config USB_HID bool "Support for USB keyboards" depends on USB diff --git a/payloads/libpayload/drivers/Makefile.inc b/payloads/libpayload/drivers/Makefile.inc index 0c7a4bc905..134119fce4 100644 --- a/payloads/libpayload/drivers/Makefile.inc +++ b/payloads/libpayload/drivers/Makefile.inc @@ -60,6 +60,10 @@ TARGETS-$(CONFIG_USB) += drivers/usb/quirks.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_OHCI) += drivers/usb/ohci.o +TARGETS-$(CONFIG_USB_OHCI) += drivers/usb/ohci_rh.o +TARGETS-$(CONFIG_USB_XHCI) += drivers/usb/xhci.o +TARGETS-$(CONFIG_USB_XHCI) += drivers/usb/xhci_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/ohci.c b/payloads/libpayload/drivers/usb/ohci.c new file mode 100644 index 0000000000..94c19454cb --- /dev/null +++ b/payloads/libpayload/drivers/usb/ohci.c @@ -0,0 +1,472 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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. + */ + +#define USB_DEBUG + +#include +#include +#include "ohci_private.h" +#include "ohci.h" + +static void ohci_start (hci_t *controller); +static void ohci_stop (hci_t *controller); +static void ohci_reset (hci_t *controller); +static void ohci_shutdown (hci_t *controller); +static int ohci_bulk (endpoint_t *ep, int size, u8 *data, int finalize); +static int ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, + int dalen, u8 *data); +static void* ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); +static void ohci_destroy_intr_queue (endpoint_t *ep, void *queue); +static u8* ohci_poll_intr_queue (void *queue); + +static void +ohci_reset (hci_t *controller) +{ +} + +#ifdef USB_DEBUG +/* Section 4.3.3 */ +static const char *completion_codes[] = { + "No error", + "CRC", + "Bit stuffing", + "Data toggle mismatch", + "Stall", + "Device not responding", + "PID check failure", + "Unexpected PID", + "Data overrun", + "Data underrun", + "--- (10)", + "--- (11)", + "Buffer overrun", + "Buffer underrun", + "Not accessed (14)", + "Not accessed (15)" +}; + +/* Section 4.3.1.2 */ +static const char *direction[] = { + "SETUP", + "OUT", + "IN", + "reserved / from TD" +}; +#endif + +hci_t * +ohci_init (pcidev_t addr) +{ + int i; + + hci_t *controller = new_controller (); + + if (!controller) + usb_fatal("Could not create USB controller instance.\n"); + + controller->instance = malloc (sizeof (ohci_t)); + if(!controller->instance) + usb_fatal("Not enough memory creating USB controller instance.\n"); + + controller->start = ohci_start; + controller->stop = ohci_stop; + controller->reset = ohci_reset; + controller->shutdown = ohci_shutdown; + controller->bulk = ohci_bulk; + controller->control = ohci_control; + controller->create_intr_queue = ohci_create_intr_queue; + controller->destroy_intr_queue = ohci_destroy_intr_queue; + controller->poll_intr_queue = ohci_poll_intr_queue; + for (i = 0; i < 128; i++) { + controller->devices[i] = 0; + } + init_device_entry (controller, 0); + OHCI_INST (controller)->roothub = controller->devices[0]; + + controller->bus_address = addr; + controller->reg_base = pci_read_config32 (controller->bus_address, 0x10); // OHCI mandates MMIO, so bit 0 is clear + OHCI_INST (controller)->opreg = (opreg_t*)phys_to_virt(controller->reg_base); + printf("OHCI Version %x.%x\n", (OHCI_INST (controller)->opreg->HcRevision >> 4) & 0xf, OHCI_INST (controller)->opreg->HcRevision & 0xf); + + if ((OHCI_INST (controller)->opreg->HcControl & HostControllerFunctionalStateMask) == USBReset) { + /* cold boot */ + OHCI_INST (controller)->opreg->HcControl &= ~RemoteWakeupConnected; + OHCI_INST (controller)->opreg->HcFmInterval = (11999 * FrameInterval) | ((((11999 - 210)*6)/7) * FSLargestDataPacket); + /* TODO: right value for PowerOnToPowerGoodTime ? */ + OHCI_INST (controller)->opreg->HcRhDescriptorA = NoPowerSwitching | NoOverCurrentProtection | (10 * PowerOnToPowerGoodTime); + OHCI_INST (controller)->opreg->HcRhDescriptorB = (0 * DeviceRemovable); + udelay(100); /* TODO: reset asserting according to USB spec */ + } else if ((OHCI_INST (controller)->opreg->HcControl & HostControllerFunctionalStateMask) != USBOperational) { + OHCI_INST (controller)->opreg->HcControl = (OHCI_INST (controller)->opreg->HcControl & ~HostControllerFunctionalStateMask) | USBResume; + udelay(100); /* TODO: resume time according to USB spec */ + } + int interval = OHCI_INST (controller)->opreg->HcFmInterval; + + td_t *periodic_td = memalign(sizeof(*periodic_td), sizeof(*periodic_td)); + memset((void*)periodic_td, 0, sizeof(*periodic_td)); + for (i=0; i<32; i++) OHCI_INST (controller)->hcca->HccaInterruptTable[i] = virt_to_phys(periodic_td); + /* TODO: build HCCA data structures */ + + OHCI_INST (controller)->opreg->HcCommandStatus = HostControllerReset; + udelay (10); /* at most 10us for reset to complete. State must be set to Operational within 2ms (5.1.1.4) */ + OHCI_INST (controller)->opreg->HcFmInterval = interval; + OHCI_INST (controller)->hcca = memalign(256, 256); + memset((void*)OHCI_INST (controller)->hcca, 0, 256); + + OHCI_INST (controller)->opreg->HcHCCA = virt_to_phys(OHCI_INST (controller)->hcca); + OHCI_INST (controller)->opreg->HcControl &= ~IsochronousEnable; // unused by this driver + OHCI_INST (controller)->opreg->HcControl |= BulkListEnable; // always enabled. OHCI still sleeps on BulkListFilled + OHCI_INST (controller)->opreg->HcControl |= ControlListEnable; // dito + OHCI_INST (controller)->opreg->HcControl |= PeriodicListEnable; // FIXME: setup interrupt data structures and enable all the time + // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs + OHCI_INST (controller)->opreg->HcInterruptEnable = 1<<31; + OHCI_INST (controller)->opreg->HcInterruptDisable = ~(1<<31); + OHCI_INST (controller)->opreg->HcInterruptStatus = ~0; + OHCI_INST (controller)->opreg->HcPeriodicStart = (((OHCI_INST (controller)->opreg->HcFmInterval & FrameIntervalMask) / 10) * 9); + OHCI_INST (controller)->opreg->HcControl = (OHCI_INST (controller)->opreg->HcControl & ~HostControllerFunctionalStateMask) | USBOperational; + + mdelay(100); + + controller->devices[0]->controller = controller; + controller->devices[0]->init = ohci_rh_init; + controller->devices[0]->init (controller->devices[0]); + ohci_reset (controller); + return controller; +} + +static void +ohci_shutdown (hci_t *controller) +{ + if (controller == 0) + return; + detach_controller (controller); + ohci_stop(controller); + OHCI_INST (controller)->roothub->destroy (OHCI_INST (controller)-> + roothub); + free (OHCI_INST (controller)); + free (controller); +} + +static void +ohci_start (hci_t *controller) +{ +// TODO: turn on all operation of OHCI, but assume that it's initialized. +} + +static void +ohci_stop (hci_t *controller) +{ +// TODO: turn off all operation of OHCI +} + +static void +dump_td(td_t *cur, int level) +{ +#ifdef USB_DEBUG + static const char *spaces=" "; + const char *spc=spaces+(10-level); +#endif + debug("%std at %x (%s), condition code: %s\n", spc, cur, direction[cur->direction], completion_codes[cur->condition_code & 0xf]); + debug("%s toggle: %x\n", spc, cur->toggle); +} + +static int +wait_for_ed(usbdev_t *dev, ed_t *head) +{ + td_t *cur; + + /* wait for results */ + while (((head->head_pointer & ~3) != head->tail_pointer) && + !(head->head_pointer & 1) && + ((((td_t*)phys_to_virt(head->head_pointer & ~3))->condition_code & 0xf)>=0xe)) { + debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n", + OHCI_INST(dev->controller)->opreg->HcInterruptStatus, + OHCI_INST(dev->controller)->opreg->HcControl, + OHCI_INST(dev->controller)->opreg->HcCommandStatus, + head->head_pointer, + ((td_t*)phys_to_virt(head->head_pointer & ~3))->next_td, + head->tail_pointer, + ((td_t*)phys_to_virt(head->head_pointer & ~3))->condition_code); + mdelay(1); + } + if (OHCI_INST(dev->controller)->opreg->HcInterruptStatus & WritebackDoneHead) { + debug("done queue:\n"); + debug("%x, %x\n", OHCI_INST(dev->controller)->hcca->HccaDoneHead, phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead)); + if ((OHCI_INST(dev->controller)->hcca->HccaDoneHead & ~1) == 0) { + debug("HcInterruptStatus %x\n", OHCI_INST(dev->controller)->opreg->HcInterruptStatus); + } + td_t *done_queue = NULL; + td_t *done_head = (td_t*)phys_to_virt(OHCI_INST(dev->controller)->hcca->HccaDoneHead); + OHCI_INST(dev->controller)->opreg->HcInterruptStatus = WritebackDoneHead; + while (1) { + td_t *oldnext = (td_t*)phys_to_virt(done_head->next_td); + if (oldnext == done_queue) break; /* last element refers to second to last, ie. endless loop */ + if (oldnext == phys_to_virt(0)) break; /* last element of done list == first element of real list */ + debug("head is %x, pointing to %x. requeueing to %x\n", done_head, oldnext, done_queue); + done_head->next_td = (u32)done_queue; + done_queue = done_head; + done_head = oldnext; + } + for (cur = done_queue; cur != 0; cur = (td_t*)cur->next_td) { + dump_td(cur, 1); + } + } + + if (head->head_pointer & 1) { + debug("HALTED!\n"); + return 1; + } + return 0; +} + +static int +ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, + unsigned char *data) +{ + int i; + + td_t *cur; + + // pages are specified as 4K in OHCI, so don't use getpagesize() + int first_page = (unsigned long)data / 4096; + int last_page = (unsigned long)(data+dalen-1)/4096; + if (last_page < first_page) last_page = first_page; + int pages = (dalen==0)?0:(last_page - first_page + 1); + int td_count = (pages+1)/2; + + td_t *tds = memalign(sizeof(td_t), (td_count+3)*sizeof(td_t)); + memset((void*)tds, 0, (td_count+3)*sizeof(td_t)); + + for (i=0; i < td_count + 3; i++) { + tds[i].next_td = virt_to_phys(&tds[i+1]); + } + tds[td_count + 3].next_td = 0; + + tds[0].direction = OHCI_SETUP; + tds[0].toggle_from_td = 1; + tds[0].toggle = 0; + tds[0].error_count = 0; + tds[0].delay_interrupt = 7; + tds[0].condition_code = 0xf; + tds[0].current_buffer_pointer = virt_to_phys(devreq); + tds[0].buffer_end = virt_to_phys(devreq + drlen - 1); + + cur = &tds[0]; + + while (pages > 0) { + cur++; + cur->direction = (dir==IN)?OHCI_IN:OHCI_OUT; + cur->toggle_from_td = 0; + cur->toggle = 1; + cur->error_count = 0; + cur->delay_interrupt = 7; + cur->condition_code = 0xf; + cur->current_buffer_pointer = virt_to_phys(data); + pages--; + int consumed = (4096 - ((unsigned long)data % 4096)); + if (consumed >= dalen) { + // end of data is within same page + cur->buffer_end = virt_to_phys(data + dalen - 1); + dalen = 0; + /* assert(pages == 0); */ + } else { + dalen -= consumed; + data += consumed; + pages--; + int second_page_size = dalen; + if (dalen > 4096) { + second_page_size = 4096; + } + cur->buffer_end = virt_to_phys(data + second_page_size - 1); + dalen -= second_page_size; + data += second_page_size; + } + } + + cur++; + cur->direction = (dir==IN)?OHCI_OUT:OHCI_IN; + cur->toggle_from_td = 1; + cur->toggle = 1; + cur->error_count = 0; + cur->delay_interrupt = 7; + cur->condition_code = 0xf; + cur->current_buffer_pointer = 0; + cur->buffer_end = 0; + + /* final dummy TD */ + cur++; + + /* Data structures */ + ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t)); + memset((void*)head, 0, sizeof(*head)); + head->function_address = dev->address; + head->endpoint_number = 0; + head->direction = OHCI_FROM_TD; + head->lowspeed = dev->speed; + head->format = 0; + head->maximum_packet_size = dev->endpoints[0].maxpacketsize; + head->tail_pointer = virt_to_phys(cur); + head->head_pointer = virt_to_phys(tds); + head->halted = 0; + head->toggle = 0; + + debug("doing control transfer with %x. first_td at %x\n", head->function_address, virt_to_phys(tds)); + + /* activate schedule */ + OHCI_INST(dev->controller)->opreg->HcControlHeadED = virt_to_phys(head); + OHCI_INST(dev->controller)->opreg->HcCommandStatus = ControlListFilled; + + int failure = wait_for_ed(dev, head); + + /* free memory */ + free((void*)tds); + free((void*)head); + + return failure; +} + +/* finalize == 1: if data is of packet aligned size, add a zero length packet */ +static int +ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) +{ + int i; + debug("bulk: %x bytes from %x, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize); + + td_t *cur; + + // pages are specified as 4K in OHCI, so don't use getpagesize() + int first_page = (unsigned long)data / 4096; + int last_page = (unsigned long)(data+dalen-1)/4096; + if (last_page < first_page) last_page = first_page; + int pages = (dalen==0)?0:(last_page - first_page + 1); + int td_count = (pages+1)/2; + + if (finalize && ((dalen % ep->maxpacketsize) == 0)) { + td_count++; + } + + td_t *tds = memalign(sizeof(td_t), (td_count+1)*sizeof(td_t)); + memset((void*)tds, 0, (td_count+1)*sizeof(td_t)); + + for (i=0; i < td_count; i++) { + tds[i].next_td = virt_to_phys(&tds[i+1]); + } + + for (cur = tds; cur->next_td != 0; cur++) { + cur->toggle_from_td = 0; + cur->error_count = 0; + cur->delay_interrupt = 7; + cur->condition_code = 0xf; + cur->direction = (ep->direction==IN)?OHCI_IN:OHCI_OUT; + pages--; + if (dalen == 0) { + /* magic TD for empty packet transfer */ + cur->current_buffer_pointer = 0; + cur->buffer_end = 0; + /* assert((pages == 0) && finalize); */ + } + int consumed = (4096 - ((unsigned long)data % 4096)); + if (consumed >= dalen) { + // end of data is within same page + cur->buffer_end = virt_to_phys(data + dalen - 1); + dalen = 0; + /* assert(pages == finalize); */ + } else { + dalen -= consumed; + data += consumed; + pages--; + int second_page_size = dalen; + if (dalen > 4096) { + second_page_size = 4096; + } + cur->buffer_end = virt_to_phys(data + second_page_size - 1); + dalen -= second_page_size; + data += second_page_size; + } + } + + /* Data structures */ + ed_t *head = memalign(sizeof(ed_t), sizeof(ed_t)); + memset((void*)head, 0, sizeof(*head)); + head->function_address = ep->dev->address; + head->endpoint_number = ep->endpoint & 0xf; + head->direction = (ep->direction==IN)?OHCI_IN:OHCI_OUT; + head->lowspeed = ep->dev->speed; + head->format = 0; + head->maximum_packet_size = ep->maxpacketsize; + head->tail_pointer = virt_to_phys(cur); + head->head_pointer = virt_to_phys(tds); + head->halted = 0; + head->toggle = ep->toggle; + + debug("doing bulk transfer with %x(%x). first_td at %x, last %x\n", head->function_address, head->endpoint_number, virt_to_phys(tds), virt_to_phys(cur)); + + /* activate schedule */ + OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = virt_to_phys(head); + OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = BulkListFilled; + + int failure = wait_for_ed(ep->dev, head); + + ep->toggle = head->toggle; + + /* free memory */ + free((void*)tds); + free((void*)head); + + if (failure) { + /* try cleanup */ + clear_stall(ep); + } + + return failure; +} + +/* create and hook-up an intr queue into device schedule */ +static void* +ohci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming) +{ + return NULL; +} + +/* remove queue from device schedule, dropping all data that came in */ +static void +ohci_destroy_intr_queue (endpoint_t *ep, void *q_) +{ +} + +/* read one intr-packet from queue, if available. extend the queue for new input. + return NULL if nothing new available. + Recommended use: while (data=poll_intr_queue(q)) process(data); + */ +static u8* +ohci_poll_intr_queue (void *q_) +{ + return NULL; +} + diff --git a/payloads/libpayload/drivers/usb/ohci.h b/payloads/libpayload/drivers/usb/ohci.h new file mode 100644 index 0000000000..f5011676a1 --- /dev/null +++ b/payloads/libpayload/drivers/usb/ohci.h @@ -0,0 +1,40 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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 __OHCI_H +#define __OHCI_H + +#include +#include + + hci_t *ohci_init (pcidev_t addr); + + void ohci_rh_init (usbdev_t *dev); + +#endif diff --git a/payloads/libpayload/drivers/usb/ohci_private.h b/payloads/libpayload/drivers/usb/ohci_private.h new file mode 100644 index 0000000000..a340be1f3a --- /dev/null +++ b/payloads/libpayload/drivers/usb/ohci_private.h @@ -0,0 +1,255 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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 __OHCI_PRIVATE_H +#define __OHCI_PRIVATE_H + +#include +#include + +#define MASK(startbit, lenbit) (((1<<(lenbit))-1)<<(startbit)) + + // FIXME: fake + typedef enum { CMD} reg; + + enum { + NumberDownstreamPorts = 1<<0, + PowerSwitchingMode = 1<<8, + NoPowerSwitching = 1<<9, + DeviceType = 1<<10, + OverCurrentProtectionMode = 1<<11, + NoOverCurrentProtection = 1<<12, + PowerOnToPowerGoodTime = 1<<24 + } HcRhDescriptorAReg; + + enum { + NumberDownstreamPortsMask = MASK(0, 8), + PowerOnToPowerGoodTimeMask = MASK(24, 8) + } HcRhDescriptorAMask; + + enum { + DeviceRemovable = 1<<0, + PortPowerControlMask = 1<<16 + } HcRhDescriptorBReg; + + enum { + CurrentConnectStatus = 1<<0, + PortEnableStatus = 1<<1, + PortSuspendStatus = 1<<2, + PortOverCurrentIndicator = 1<<3, + PortResetStatus = 1<<4, + PortPowerStatus = 1<<8, + LowSpeedDeviceAttached = 1<<9, + ConnectStatusChange = 1<<16, + PortEnableStatusChange = 1<<17, + PortSuspendStatusChange = 1<<18, + PortOverCurrentIndicatorChange = 1<<19, + PortResetStatusChange = 1<<20 + } HcRhPortStatusRead; + enum { + ClearPortEnable = 1<<0, + SetPortEnable = 1<<1, + SetPortSuspend = 1<<2, + ClearSuspendStatus = 1<<3, + SetPortReset = 1<<4, + SetPortPower = 1<<8, + ClearPortPower = 1<<9, + } HcRhPortStatusSet; + + enum { + LocalPowerStatus = 1<<0, + OverCurrentIndicator = 1<<1, + DeviceRemoteWakeupEnable = 1<<15, + LocalPowerStatusChange = 1<<16, + OverCurrentIndicatorChange = 1<<17, + ClearRemoteWakeupEnable = 1<<31 + } HcRhStatusReg; + + enum { + FrameInterval = 1<<0, + FSLargestDataPacket = 1<<16, + FrameIntervalToggle = 1<<31 + } HcFmIntervalOffset; + enum { + FrameIntervalMask = MASK(0, 14), + FSLargestDataPacketMask = MASK(16, 15), + FrameIntervalToggleMask = MASK(31, 1) + } HcFmIntervalMask; + + enum { + ControlBulkServiceRatio = 1<<0, + PeriodicListEnable = 1<<2, + IsochronousEnable = 1<<3, + ControlListEnable = 1<<4, + BulkListEnable = 1<<5, + HostControllerFunctionalState = 1<<6, + InterruptRouting = 1<<8, + RemoteWakeupConnected = 1<<9, + RemoteWakeupEnable = 1<<10 + } HcControlReg; + + enum { + ControlBulkServiceRatioMask = MASK(0, 2), + HostControllerFunctionalStateMask = MASK(6, 2) + } HcControlMask; + + enum { + USBReset = 0*HostControllerFunctionalState, + USBResume = 1*HostControllerFunctionalState, + USBOperational = 2*HostControllerFunctionalState, + USBSuspend = 3*HostControllerFunctionalState + }; + + enum { + HostControllerReset = 1<<0, + ControlListFilled = 1<<1, + BulkListFilled = 1<<2, + OwnershipChangeRequest = 1<<3, + SchedulingOverrunCount = 1<<16 + } HcCommandStatusReg; + + enum { + SchedulingOverrunCountMask = MASK(16, 2) + } HcCommandStatusMask; + + enum { + FrameRemaining = 1<<0, + FrameRemainingToggle = 1<<31 + } HcFmRemainingReg; + + enum { + SchedulingOverrung = 1<<0, + WritebackDoneHead = 1<<1, + StartofFrame = 1<<2, + ResumeDetected = 1<<3, + UnrecoverableError = 1<<4, + FrameNumberOverflow = 1<<5, + RootHubStatusChange = 1<<6, + OwnershipChange = 1<<30 + } HcInterruptStatusReg; + + typedef struct { + // Control and Status Partition + volatile u32 HcRevision; + volatile u32 HcControl; + volatile u32 HcCommandStatus; + volatile u32 HcInterruptStatus; + volatile u32 HcInterruptEnable; + volatile u32 HcInterruptDisable; + + // Memory Pointer Partition + volatile u32 HcHCCA; + volatile u32 HcPeriodCurrentED; + volatile u32 HcControlHeadED; + volatile u32 HcControlCurrentED; + volatile u32 HcBulkHeadED; + volatile u32 HcBulkCurrentED; + volatile u32 HcDoneHead; + + // Frame Counter Partition + volatile u32 HcFmInterval; + volatile u32 HcFmRemaining; + volatile u32 HcFmNumber; + volatile u32 HcPeriodicStart; + volatile u32 HcLSThreshold; + + // Root Hub Partition + volatile u32 HcRhDescriptorA; + volatile u32 HcRhDescriptorB; + volatile u32 HcRhStatus; + /* all bits in HcRhPortStatus registers are R/WC, so + _DO NOT_ use |= to set the bits, + this clears the entire state */ + volatile u32 HcRhPortStatus[]; + } __attribute__ ((packed)) opreg_t; + + typedef struct { + u32 HccaInterruptTable[32]; + u16 HccaFrameNumber; + u16 HccaPad1; + u32 HccaDoneHead; + u8 reserved[116]; // pad to 256 byte + } __attribute__ ((packed)) hcca_t; + + typedef struct ohci { + opreg_t *opreg; + hcca_t *hcca; + usbdev_t *roothub; + } ohci_t; + + typedef enum { OHCI_SETUP=0, OHCI_OUT=1, OHCI_IN=2, OHCI_FROM_TD=3 } ohci_pid_t; + + typedef volatile struct { + union { + u32 dword0; + struct { + unsigned long function_address:7; + unsigned long endpoint_number:4; + unsigned long direction:2; + unsigned long lowspeed:1; + unsigned long skip:1; + unsigned long format:1; + unsigned long maximum_packet_size:11; + unsigned long:5; + } __attribute__ ((packed)); + }; + u32 tail_pointer; + union { + u32 head_pointer; + struct { + unsigned long halted:1; + unsigned long toggle:1; + unsigned long:30; + } __attribute__ ((packed)); + }; + u32 next_ed; + } __attribute__ ((packed)) ed_t; + + typedef volatile struct { + union { + u32 dword0; + struct { + unsigned long:18; + unsigned long buffer_rounding:1; + unsigned long direction:2; + unsigned long delay_interrupt:3; + unsigned long toggle:1; + unsigned long toggle_from_td:1; + unsigned long error_count:2; + unsigned long condition_code:4; + } __attribute__ ((packed)); + }; + u32 current_buffer_pointer; + u32 next_td; + u32 buffer_end; + } __attribute__ ((packed)) td_t; + +#define OHCI_INST(controller) ((ohci_t*)((controller)->instance)) + +#endif diff --git a/payloads/libpayload/drivers/usb/ohci_rh.c b/payloads/libpayload/drivers/usb/ohci_rh.c new file mode 100644 index 0000000000..da74340abd --- /dev/null +++ b/payloads/libpayload/drivers/usb/ohci_rh.c @@ -0,0 +1,164 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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. + */ + +//#define USB_DEBUG + +#include +#include "ohci_private.h" +#include "ohci.h" + +typedef struct { + int numports; + int *port; +} rh_inst_t; + +#define RH_INST(dev) ((rh_inst_t*)(dev)->data) + +static void +ohci_rh_enable_port (usbdev_t *dev, int port) +{ + if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & CurrentConnectStatus)) + return; + + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = SetPortEnable; // enable port + mdelay(10); + while (!(OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) mdelay(1); + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = SetPortReset; // reset port + while (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortResetStatus) mdelay(1); + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = PortResetStatusChange; +} + +/* disable root hub */ +static void +ohci_rh_disable_port (usbdev_t *dev, int port) +{ + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ClearPortEnable; // disable port + while (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus) mdelay(1); +} + +static void +ohci_rh_scanport (usbdev_t *dev, int port) +{ + if (port >= RH_INST(dev)->numports) { + debug("Invalid port %d\n", port); + return; + } + + /* device registered, and device change logged, so something must have happened */ + if (RH_INST (dev)->port[port] != -1) { + usb_detach_device(dev->controller, RH_INST (dev)->port[port]); + RH_INST (dev)->port[port] = -1; + } + + /* no device attached + previously registered devices are detached, nothing left to do */ + if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & CurrentConnectStatus)) + return; + + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ConnectStatusChange; // clear port state change + ohci_rh_enable_port (dev, port); + + mdelay(100); // wait for signal to stabilize + + if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) { + debug ("port enable failed\n"); + return; + } + + int speed = (OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & LowSpeedDeviceAttached) != 0; + RH_INST (dev)->port[port] = usb_attach_device(dev->controller, dev->address, port, speed); +} + +static int +ohci_rh_report_port_changes (usbdev_t *dev) +{ + int i; + + if (!(OHCI_INST (dev->controller)->opreg->HcInterruptStatus & RootHubStatusChange)) return -1; + OHCI_INST (dev->controller)->opreg->HcInterruptStatus = RootHubStatusChange; + debug("port change\n"); + + for (i = 0; i < RH_INST(dev)->numports; i++) { + // maybe detach+attach happened between two scans? + if (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[i] & ConnectStatusChange) { + debug("attachment change on port %d\n", i); + return i; + } + } + + // no change + return -1; +} + +static void +ohci_rh_destroy (usbdev_t *dev) +{ + int i; + for (i = 0; i < RH_INST (dev)->numports; i++) + ohci_rh_disable_port (dev, i); + free (RH_INST (dev)); +} + +static void +ohci_rh_poll (usbdev_t *dev) +{ + int port; + while ((port = ohci_rh_report_port_changes (dev)) != -1) + ohci_rh_scanport (dev, port); +} + +void +ohci_rh_init (usbdev_t *dev) +{ + int i; + + dev->destroy = ohci_rh_destroy; + dev->poll = ohci_rh_poll; + + dev->data = malloc (sizeof (rh_inst_t)); + if (!dev->data) + usb_fatal ("Not enough memory for OHCI RH.\n"); + + RH_INST (dev)->numports = OHCI_INST (dev->controller)->opreg->HcRhDescriptorA & NumberDownstreamPortsMask; + RH_INST (dev)->port = malloc(sizeof(int) * RH_INST (dev)->numports); + debug("%d ports registered\n", RH_INST (dev)->numports); + + for (i = 0; i < RH_INST (dev)->numports; i++) { + ohci_rh_enable_port (dev, i); + RH_INST (dev)->port[i] = -1; + } + + /* we can set them here because a root hub _really_ shouldn't + appear elsewhere */ + dev->address = 0; + dev->hub = -1; + dev->port = -1; + + debug("rh init done\n"); +} diff --git a/payloads/libpayload/drivers/usb/usbinit.c b/payloads/libpayload/drivers/usb/usbinit.c index adaba34602..9c8063a3cb 100644 --- a/payloads/libpayload/drivers/usb/usbinit.c +++ b/payloads/libpayload/drivers/usb/usbinit.c @@ -30,9 +30,9 @@ #include #include #include "uhci.h" -//#include "ohci.h" +#include "ohci.h" //#include "ehci.h" -//#include "xhci.h" +#include "xhci.h" #include /** @@ -68,7 +68,7 @@ usb_controller_initialize (int bus, int dev, int func) pci_command |= PCI_COMMAND_MASTER; pci_write_config32(addr, PCI_COMMAND, pci_command); - printf ("%02x:%02x.%x %04x:%04x.%d ", 0, dev, func, + printf ("%02x:%02x.%x %04x:%04x.%d ", bus, dev, func, pciid >> 16, pciid & 0xFFFF, func); if (prog_if == 0) { printf ("UHCI controller\n"); @@ -81,8 +81,7 @@ usb_controller_initialize (int bus, int dev, int func) if (prog_if == 0x10) { printf ("OHCI controller\n"); #ifdef CONFIG_USB_OHCI - //ohci_init(addr); - printf ("Not supported.\n"); + ohci_init(addr); #else printf ("Not supported.\n"); #endif @@ -99,10 +98,9 @@ usb_controller_initialize (int bus, int dev, int func) } if (prog_if == 0x30) { - printf ("XHCI controller\n"); + printf ("xHCI controller\n"); #ifdef CONFIG_USB_XHCI - //xhci_init(addr); - printf ("Not supported.\n"); + xhci_init(addr); #else printf ("Not supported.\n"); #endif @@ -128,8 +126,9 @@ usb_initialize (void) */ for (bus = 0; bus < 256; bus++) for (dev = 0; dev < 32; dev++) - for (func = 7; func >= 0 ; func--) - usb_controller_initialize (bus, dev, func); + if (pci_read_config32 (PCI_DEV(bus, dev, 0), 8) >> 16 == 0x0c03) + for (func = 7; func >= 0 ; func--) + usb_controller_initialize (bus, dev, func); usb_poll(); return 0; } diff --git a/payloads/libpayload/drivers/usb/xhci.c b/payloads/libpayload/drivers/usb/xhci.c new file mode 100644 index 0000000000..0aca63de9d --- /dev/null +++ b/payloads/libpayload/drivers/usb/xhci.c @@ -0,0 +1,262 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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. + */ + +#define USB_DEBUG + +#include +#include "xhci.h" +#include "xhci_private.h" + +static void xhci_start (hci_t *controller); +static void xhci_stop (hci_t *controller); +static void xhci_reset (hci_t *controller); +static void xhci_shutdown (hci_t *controller); +static int xhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize); +static int xhci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, + int dalen, u8 *data); +static void* xhci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming); +static void xhci_destroy_intr_queue (endpoint_t *ep, void *queue); +static u8* xhci_poll_intr_queue (void *queue); + +static void +xhci_reset (hci_t *controller) +{ +} + +hci_t * +xhci_init (pcidev_t addr) +{ + int i; + + hci_t *controller = new_controller (); + + if (!controller) + usb_fatal("Could not create USB controller instance.\n"); + + controller->instance = malloc (sizeof (xhci_t)); + if(!controller->instance) + usb_fatal("Not enough memory creating USB controller instance.\n"); + + controller->start = xhci_start; + controller->stop = xhci_stop; + controller->reset = xhci_reset; + controller->shutdown = xhci_shutdown; + controller->bulk = xhci_bulk; + controller->control = xhci_control; + controller->create_intr_queue = xhci_create_intr_queue; + controller->destroy_intr_queue = xhci_destroy_intr_queue; + controller->poll_intr_queue = xhci_poll_intr_queue; + for (i = 0; i < 128; i++) { + controller->devices[i] = 0; + } + init_device_entry (controller, 0); + XHCI_INST (controller)->roothub = controller->devices[0]; + + controller->bus_address = addr; + controller->reg_base = (u32)phys_to_virt(pci_read_config32 (controller->bus_address, 0x10) & ~0xf); + //controller->reg_base = pci_read_config32 (controller->bus_address, 0x14) & ~0xf; + if (pci_read_config32 (controller->bus_address, 0x14) > 0) { + usb_fatal("We don't do 64bit addressing.\n"); + } + debug("regbase: %lx\n", controller->reg_base); + + XHCI_INST (controller)->capreg = (void*)controller->reg_base; + XHCI_INST (controller)->opreg = (void*)(controller->reg_base + XHCI_INST (controller)->capreg->caplength); + XHCI_INST (controller)->hcrreg = (void*)(controller->reg_base + XHCI_INST (controller)->capreg->rtsoff); + XHCI_INST (controller)->dbreg = (void*)(controller->reg_base + XHCI_INST (controller)->capreg->dboff); + debug("caplen: %lx\nrtsoff: %lx\ndboff: %lx\n", XHCI_INST (controller)->capreg->caplength, XHCI_INST (controller)->capreg->rtsoff, XHCI_INST (controller)->capreg->dboff); + debug("caplength: %x\n", XHCI_INST (controller)->capreg->caplength); + debug("hciversion: %x.%x\n", XHCI_INST (controller)->capreg->hciver_hi, XHCI_INST (controller)->capreg->hciver_lo); + if ((XHCI_INST (controller)->capreg->hciversion < 0x96) || (XHCI_INST (controller)->capreg->hciversion > 0x100)) { + usb_fatal("Unsupported xHCI version\n"); + } + debug("maxslots: %x\n", XHCI_INST (controller)->capreg->MaxSlots); + debug("maxports: %x\n", XHCI_INST (controller)->capreg->MaxPorts); + int pagesize = XHCI_INST (controller)->opreg->pagesize << 12; + debug("pagesize: %x\n", pagesize); + + XHCI_INST (controller)->dcbaa = memalign(64, (XHCI_INST (controller)->capreg->MaxSlots+1)*sizeof(devctxp_t)); + memset((void*)XHCI_INST (controller)->dcbaa, 0, (XHCI_INST (controller)->capreg->MaxSlots+1)*sizeof(devctxp_t)); + + debug("max scratchpad bufs: %x\n", XHCI_INST (controller)->capreg->Max_Scratchpad_Bufs); + if (XHCI_INST (controller)->capreg->Max_Scratchpad_Bufs > 0) { + XHCI_INST (controller)->dcbaa->ptr = memalign(64, XHCI_INST (controller)->capreg->Max_Scratchpad_Bufs * 8); + } + + XHCI_INST (controller)->opreg->dcbaap_lo = virt_to_phys(XHCI_INST (controller)->dcbaa); + XHCI_INST (controller)->opreg->dcbaap_hi = 0; + + printf("waiting for controller to be ready - "); + while ((XHCI_INST (controller)->opreg->usbsts & USBSTS_CNR) != 0) mdelay(1); + printf("ok.\n"); + + debug("ERST Max: %lx -> %lx entries\n", XHCI_INST (controller)->capreg->ERST_Max, 1<<(XHCI_INST (controller)->capreg->ERST_Max)); + + // enable all available slots + XHCI_INST (controller)->opreg->config = XHCI_INST (controller)->capreg->MaxSlots & CONFIG_MASK_MaxSlotsEn; + + XHCI_INST (controller)->cmd_ring = memalign(64, 16*sizeof(trb_t)); /* TODO: make sure not to cross 64k page boundary */ + memset((void*)XHCI_INST (controller)->cmd_ring, 0, 16*sizeof(trb_t)); + + XHCI_INST (controller)->ev_ring = memalign(64, 16*sizeof(trb_t)); /* TODO: make sure not to cross 64k page boundary */ + memset((void*)XHCI_INST (controller)->ev_ring, 0, 16*sizeof(trb_t)); + + XHCI_INST (controller)->ev_ring_table = memalign(64, sizeof(erst_entry_t)); + memset((void*)XHCI_INST (controller)->ev_ring_table, 0, sizeof(erst_entry_t)); + XHCI_INST (controller)->ev_ring_table[0].seg_base_lo = virt_to_phys(XHCI_INST (controller)->ev_ring); + XHCI_INST (controller)->ev_ring_table[0].seg_base_hi = 0; + XHCI_INST (controller)->ev_ring_table[0].seg_size = 16; + + // init command ring + XHCI_INST (controller)->opreg->crcr_lo = virt_to_phys(XHCI_INST (controller)->cmd_ring) | CRCR_RCS; + XHCI_INST (controller)->opreg->crcr_hi = 0; + XHCI_INST (controller)->cmd_ccs = 1; + XHCI_INST (controller)->ev_ccs = 1; + + // init primary interrupter + XHCI_INST (controller)->hcrreg->intrrs[0].erstsz = 1; + XHCI_INST (controller)->hcrreg->intrrs[0].erdp_lo = virt_to_phys(XHCI_INST (controller)->ev_ring); + XHCI_INST (controller)->hcrreg->intrrs[0].erdp_hi = 0; + XHCI_INST (controller)->hcrreg->intrrs[0].erstba_lo = virt_to_phys(XHCI_INST (controller)->ev_ring_table); + XHCI_INST (controller)->hcrreg->intrrs[0].erstba_hi = 0; + + XHCI_INST (controller)->opreg->usbcmd |= USBCMD_RS; /* start USB controller */ + XHCI_INST (controller)->dbreg[0] = 0; // and tell controller to consume commands + + /* TODO: TEST */ + // setup noop command + trb_t *cmd = &XHCI_INST (controller)->cmd_ring[0]; + ((u32*)cmd)[3] = 1-XHCI_INST (controller)->cmd_ccs; // disable command descriptor + ((u32*)cmd)[0] = 0; + ((u32*)cmd)[1] = 0; + ((u32*)cmd)[2] = 0; + cmd->cmd_No_Op.TRB_Type = TRB_CMD_NOOP; + + // ring the HC doorbell + debug("Posting command at %lx\n", virt_to_phys(cmd)); + cmd->cmd_No_Op.C = XHCI_INST (controller)->cmd_ccs; // enable command + XHCI_INST (controller)->dbreg[0] = 0; // and tell controller to consume commands + + // wait for result in event ring + trb_t *ev = &XHCI_INST (controller)->ev_ring[0]; + trb_t *ev1 = &XHCI_INST (controller)->ev_ring[1]; + while (ev->event_cmd_cmpl.C != XHCI_INST (controller)->ev_ccs) { + debug("CRCR: %lx, USBSTS: %lx\n", XHCI_INST (controller)->opreg->crcr_lo, XHCI_INST (controller)->opreg->usbsts); + debug("ev0.C %x, ev1.C %x\n", ev->event_cmd_cmpl.C, ev1->event_cmd_cmpl.C); + mdelay(100); + } + debug("command ring is %srunning\n", (XHCI_INST (controller)->opreg->crcr_lo & CRCR_CRR)?"":"not "); + switch (ev->event_cmd_cmpl.TRB_Type) { + case TRB_EV_CMD_CMPL: + debug("Completed command TRB at %lx. Code: %d\n", + ev->event_cmd_cmpl.Cmd_TRB_Pointer_lo, ev->event_cmd_cmpl.Completion_Code); + break; + case TRB_EV_PORTSC: + debug("Port Status Change Event. Completion Code: %d\n Port: %d. Ignoring.\n", + ev->event_cmd_cmpl.Completion_Code, ev->event_portsc.Port); + // we ignore the event as we look for the PORTSC registers instead, at a time when it suits _us_ + break; + default: + debug("Unknown event: %d, Completion Code: %d\n", ev->event_cmd_cmpl.TRB_Type, ev->event_cmd_cmpl.Completion_Code); + break; + } + debug("CRCR: %lx, USBSTS: %lx\n", XHCI_INST (controller)->opreg->crcr_lo, XHCI_INST (controller)->opreg->usbsts); + debug("ev0.C %x, ev1.C %x, ev1.CC %d\n", ev->event_cmd_cmpl.C, ev1->event_cmd_cmpl.C, ev1->event_cmd_cmpl.Completion_Code); + + controller->devices[0]->controller = controller; + controller->devices[0]->init = xhci_rh_init; + controller->devices[0]->init (controller->devices[0]); + + xhci_reset (controller); + return controller; +} + +static void +xhci_shutdown (hci_t *controller) +{ + if (controller == 0) + return; + detach_controller (controller); + XHCI_INST (controller)->roothub->destroy (XHCI_INST (controller)-> + roothub); + /* TODO: stop hardware, kill data structures */ + free (XHCI_INST (controller)); + free (controller); +} + +static void +xhci_start (hci_t *controller) +{ +} + +static void +xhci_stop (hci_t *controller) +{ +} + +static int +xhci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, + unsigned char *data) +{ + return 1; +} + +/* finalize == 1: if data is of packet aligned size, add a zero length packet */ +static int +xhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize) +{ + int maxpsize = ep->maxpacketsize; + if (maxpsize == 0) + usb_fatal ("MaxPacketSize == 0!!!"); + return 1; +} + +/* create and hook-up an intr queue into device schedule */ +static void* +xhci_create_intr_queue (endpoint_t *ep, int reqsize, int reqcount, int reqtiming) +{ + return NULL; +} + +/* remove queue from device schedule, dropping all data that came in */ +static void +xhci_destroy_intr_queue (endpoint_t *ep, void *q_) +{ + //free(q); +} + +/* read one intr-packet from queue, if available. extend the queue for new input. + return NULL if nothing new available. + Recommended use: while (data=poll_intr_queue(q)) process(data); + */ +static u8* +xhci_poll_intr_queue (void *q_) +{ + return NULL; +} diff --git a/payloads/libpayload/drivers/usb/xhci.h b/payloads/libpayload/drivers/usb/xhci.h new file mode 100644 index 0000000000..b900b12200 --- /dev/null +++ b/payloads/libpayload/drivers/usb/xhci.h @@ -0,0 +1,40 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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 __XHCI_H +#define __XHCI_H + +#include +#include + + hci_t *xhci_init (pcidev_t addr); + + void xhci_rh_init (usbdev_t *dev); + +#endif diff --git a/payloads/libpayload/drivers/usb/xhci_private.h b/payloads/libpayload/drivers/usb/xhci_private.h new file mode 100644 index 0000000000..16834f77cc --- /dev/null +++ b/payloads/libpayload/drivers/usb/xhci_private.h @@ -0,0 +1,350 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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 __XHCI_PRIVATE_H +#define __XHCI_PRIVATE_H + +#include + +#define MASK(startbit, lenbit) (((1<<(lenbit))-1)<<(startbit)) + +typedef volatile union trb { + // transfer + + // events +#define TRB_EV_CMD_CMPL 33 + struct { + u32 Cmd_TRB_Pointer_lo; + u32 Cmd_TRB_Pointer_hi; + struct { + unsigned long:24; + unsigned long Completion_Code:8; + } __attribute__ ((packed)); + struct { + unsigned long C:1; + unsigned long:9; + unsigned long TRB_Type:6; + unsigned long VF_ID:8; + unsigned long Slot_ID:8; + } __attribute__ ((packed)); + } __attribute__ ((packed)) event_cmd_cmpl; + +#define TRB_EV_PORTSC 34 + struct { + struct { + unsigned long:24; + unsigned long Port:8; + } __attribute__ ((packed)); + u32 rsvd; + struct { + unsigned long:24; + unsigned long Completion_Code:8; + } __attribute__ ((packed)); + struct { + unsigned long C:1; + unsigned long:9; + unsigned long TRB_Type:6; + unsigned long:16; + } __attribute__ ((packed)); + } __attribute__ ((packed)) event_portsc; + + // commands +#define TRB_CMD_NOOP 23 + struct { + u32 rsvd[3]; + struct { + unsigned long C:1; + unsigned long:9; + unsigned long TRB_Type:6; + unsigned long:16; + } __attribute__ ((packed)); + } __attribute__ ((packed)) cmd_No_Op; + + // "others" + struct { + u32 Ring_Segment_Ptr_lo; + u32 Ring_Segment_Ptr_hi; + struct { + unsigned long:22; + unsigned long Interrupter_Target; + } __attribute__ ((packed)); + struct { + unsigned long C:1; + unsigned long TC:1; + unsigned long:2; + unsigned long CH:1; + unsigned long IOC:1; + unsigned long:4; + unsigned long TRB_Type:6; + unsigned long:16; + } __attribute__ ((packed)); + } __attribute__ ((packed)) link; +} trb_t; + +typedef struct slotctx { + struct { + unsigned long Route_String:20; + unsigned long Speed:4; + unsigned long:1; + unsigned long MTT:1; + unsigned long Hub:1; + unsigned long Context_Entries:5; + } __attribute__ ((packed)); + struct { + unsigned long Max_Exit_Latency:16; + unsigned long Root_Hub_Port_Number:8; + unsigned long Number_of_Ports:8; + } __attribute__ ((packed)); + struct { + unsigned long TT_Hub_Slot_ID:8; + unsigned long TT_Port_Number:8; + unsigned long TTT:2; + unsigned long:4; + unsigned long Interrupter_Target:10; + } __attribute__ ((packed)); + struct { + unsigned long USB_Device_Address:8; + unsigned long:19; + unsigned long Slot_State:5; + } __attribute__ ((packed)); + u32 rsvd[4]; +} slotctx_t; + +typedef struct epctx { + struct { + unsigned long EP_State:3; + unsigned long:5; + unsigned long Mult:2; + unsigned long MaxPStreams:5; + unsigned long LSA:1; + unsigned long Interval:8; + unsigned long:8; + } __attribute__ ((packed)); + struct { + unsigned long:1; + unsigned long CErr:2; + unsigned long EP_Type:3; + unsigned long:1; + unsigned long HID:1; + unsigned long Max_Burst_Size:8; + unsigned long Max_Packet_Size:16; + } __attribute__ ((packed)); + union { + u32 TR_Dequeue_Pointer_lo; + struct { + unsigned long DCS:1; + unsigned long:3; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + u32 TR_Dequeue_Pointer_hi; + struct { + unsigned long Average_TRB_Length:16; + unsigned long Max_ESIT_Payload:16; + } __attribute__ ((packed)); + u32 rsvd[3]; +} epctx_t; + +typedef struct devctx { + slotctx_t slot; + epctx_t ep0; + struct { + epctx_t out; + epctx_t in; + } eps[15]; +} devctx_t; + +typedef struct devctxp { + devctx_t *ptr; + void *upper; +} devctxp_t; + +typedef struct erst_entry { + u32 seg_base_lo; + u32 seg_base_hi; + u32 seg_size; + u32 rsvd; +} erst_entry_t; + +typedef struct xhci { + /* capreg is read-only, so no need for volatile, + and thus 32bit accesses can be assumed. */ + struct capreg { + u8 caplength; + u8 res1; + union { + u16 hciversion; + struct { + u8 hciver_lo; + u8 hciver_hi; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + union { + u32 hcsparams1; + struct { + unsigned long MaxSlots:7; + unsigned long MaxIntrs:11; + unsigned long:6; + unsigned long MaxPorts:8; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + union { + u32 hcsparams2; + struct { + unsigned long IST:4; + unsigned long ERST_Max:4; + unsigned long:18; + unsigned long SPR:1; + unsigned long Max_Scratchpad_Bufs:5; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + union { + u32 hcsparams3; + struct { + unsigned long u1latency:8; + unsigned long:8; + unsigned long u2latency:16; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + union { + u32 hccparams; + struct { + unsigned long ac64:1; + unsigned long bnc:1; + unsigned long csz:1; + unsigned long ppc:1; + unsigned long pind:1; + unsigned long lhrc:1; + unsigned long ltc:1; + unsigned long nss:1; + unsigned long:4; + unsigned long MaxPSASize:4; + unsigned long xECP:16; + } __attribute__ ((packed)); + } __attribute__ ((packed)); + u32 dboff; + u32 rtsoff; + } __attribute__ ((packed)) *capreg; + + /* opreg is R/W is most places, so volatile access is necessary. + volatile means that the compiler seeks byte writes if possible, + making bitfields unusable for MMIO register blocks. Yay C :-( */ + volatile struct opreg { + u32 usbcmd; +#define USBCMD_RS 1<<0 +#define USBCMD_HCRST 1<<1 + u32 usbsts; +#define USBSTS_HCH 1<<0 +#define USBSTS_HSE 1<<2 +#define USBSTS_EINT 1<<3 +#define USBSTS_PCD 1<<4 +#define USBSTS_CNR 1<<11 + u32 pagesize; + u8 res1[0x13-0x0c+1]; + u32 dnctrl; + u32 crcr_lo; + u32 crcr_hi; +#define CRCR_RCS 1<<0 +#define CRCR_CS 1<<1 +#define CRCR_CA 1<<2 +#define CRCR_CRR 1<<3 + u8 res2[0x2f-0x20+1]; + u32 dcbaap_lo; + u32 dcbaap_hi; + u32 config; +#define CONFIG_MASK_MaxSlotsEn 0xff + u8 res3[0x3ff-0x3c+1]; + struct { + u32 portsc; +#define PORTSC_CCS 1<<0 +#define PORTSC_PED 1<<1 + // BIT 2 rsvdZ +#define PORTSC_OCA 1<<3 +#define PORTSC_PR 1<<4 +#define PORTSC_PLS 1<<5 +#define PORTSC_PLS_MASK MASK(5, 4) +#define PORTSC_PP 1<<9 +#define PORTSC_PORT_SPEED 1<<10 +#define PORTSC_PORT_SPEED_MASK MASK(10, 4) +#define PORTSC_PIC 1<<14 +#define PORTSC_PIC_MASK MASK(14, 2) +#define PORTSC_LWS 1<<16 +#define PORTSC_CSC 1<<17 +#define PORTSC_PEC 1<<18 +#define PORTSC_WRC 1<<19 +#define PORTSC_OCC 1<<20 +#define PORTSC_PRC 1<<21 +#define PORTSC_PLC 1<<22 +#define PORTSC_CEC 1<<23 +#define PORTSC_CAS 1<<24 +#define PORTSC_WCE 1<<25 +#define PORTSC_WDE 1<<26 +#define PORTSC_WOE 1<<27 + // BIT 29:28 rsvdZ +#define PORTSC_DR 1<<30 +#define PORTSC_WPR 1<<31 +#define PORTSC_RW_MASK PORTSC_PR | PORTSC_PLS_MASK | PORTSC_PP | PORTSC_PIC_MASK | PORTSC_LWS | PORTSC_WCE | PORTSC_WDE | PORTSC_WOE + u32 portpmsc; + u32 portli; + u32 res; + } __attribute__ ((packed)) prs[]; + } __attribute__ ((packed)) *opreg; + + /* R/W, volatile, MMIO -> no bitfields */ + volatile struct hcrreg { + u32 mfindex; + u8 res1[0x20-0x4]; + struct { + u32 iman; + u32 imod; + u32 erstsz; + u32 res; + u32 erstba_lo; + u32 erstba_hi; + u32 erdp_lo; + u32 erdp_hi; + } __attribute__ ((packed)) intrrs[]; // up to 1024, but maximum host specific, given in capreg->MaxIntrs + } __attribute__ ((packed)) *hcrreg; + + /* R/W, volatile, MMIO -> no bitfields */ + volatile u32 *dbreg; + + /* R/W, volatile, Memory -> bitfields allowed */ + volatile devctxp_t *dcbaa; + + trb_t *cmd_ring; + trb_t *ev_ring; + volatile erst_entry_t *ev_ring_table; + int cmd_ccs, ev_ccs; + + usbdev_t *roothub; +} xhci_t; + +#define XHCI_INST(controller) ((xhci_t*)((controller)->instance)) + +#endif diff --git a/payloads/libpayload/drivers/usb/xhci_rh.c b/payloads/libpayload/drivers/usb/xhci_rh.c new file mode 100644 index 0000000000..2817f04299 --- /dev/null +++ b/payloads/libpayload/drivers/usb/xhci_rh.c @@ -0,0 +1,133 @@ +/* + * This file is part of the libpayload project. + * + * Copyright (C) 2010 Patrick Georgi + * + * 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. + */ + +#define USB_DEBUG + +#include +#include "xhci_private.h" +#include "xhci.h" + +typedef struct { + int numports; + int *port; +} rh_inst_t; + +#define RH_INST(dev) ((rh_inst_t*)(dev)->data) + +static void +xhci_rh_enable_port (usbdev_t *dev, int port) +{ + // FIXME: check power situation? + // enable slot + // attach device context to slot + // address device +} + +/* disable root hub */ +static void +xhci_rh_disable_port (usbdev_t *dev, int port) +{ +} + +static void +xhci_rh_scanport (usbdev_t *dev, int port) +{ + // clear CSC + int val = XHCI_INST (dev->controller)->opreg->prs[port].portsc; + val &= PORTSC_RW_MASK; + val |= PORTSC_CSC; + XHCI_INST (dev->controller)->opreg->prs[port].portsc = val; + + debug("device attach status on port %x: %x\n", port, XHCI_INST (dev->controller)->opreg->prs[port].portsc & PORTSC_CCS); +} + +static int +xhci_rh_report_port_changes (usbdev_t *dev) +{ + int i; + // no change + if (!(XHCI_INST (dev->controller)->opreg->usbsts & USBSTS_PCD)) + return -1; + + for (i = 0; i < RH_INST (dev)->numports; i++) { + if (XHCI_INST (dev->controller)->opreg->prs[i].portsc & PORTSC_CSC) { + debug("found connect status change on port %d\n", i); + return i; + } + } + + return -1; // shouldn't ever happen +} + +static void +xhci_rh_destroy (usbdev_t *dev) +{ + int i; + for (i = 0; i < RH_INST (dev)->numports; i++) + xhci_rh_disable_port (dev, i); + free (RH_INST (dev)); +} + +static void +xhci_rh_poll (usbdev_t *dev) +{ + int port; + while ((port = xhci_rh_report_port_changes (dev)) != -1) + xhci_rh_scanport (dev, port); +} + +void +xhci_rh_init (usbdev_t *dev) +{ + int i; + + dev->destroy = xhci_rh_destroy; + dev->poll = xhci_rh_poll; + + dev->data = malloc (sizeof (rh_inst_t)); + if (!dev->data) + usb_fatal ("Not enough memory for XHCI RH.\n"); + + RH_INST (dev)->numports = XHCI_INST (dev->controller)->capreg->MaxPorts; + RH_INST (dev)->port = malloc(sizeof(int) * RH_INST (dev)->numports); + debug("%d ports registered\n", RH_INST (dev)->numports); + + for (i = 0; i < RH_INST (dev)->numports; i++) { + xhci_rh_enable_port (dev, i); + RH_INST (dev)->port[i] = -1; + } + + /* we can set them here because a root hub _really_ shouldn't + appear elsewhere */ + dev->address = 0; + dev->hub = -1; + dev->port = -1; + + debug("rh init done\n"); +}