diff options
Diffstat (limited to 'payloads/libpayload/drivers/usb/xhci.c')
-rw-r--r-- | payloads/libpayload/drivers/usb/xhci.c | 262 |
1 files changed, 262 insertions, 0 deletions
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 <arch/virtual.h> +#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; +} |