| /* |
| * 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) |
| { |
| } |
| |
| static void |
| xhci_reinit (hci_t *controller) |
| { |
| } |
| |
| hci_t * |
| xhci_init (pcidev_t addr) |
| { |
| int i; |
| |
| hci_t *controller = new_controller (); |
| |
| if (!controller) |
| fatal("Could not create USB controller instance.\n"); |
| |
| controller->instance = malloc (sizeof (xhci_t)); |
| if(!controller->instance) |
| fatal("Not enough memory creating USB controller instance.\n"); |
| |
| controller->type = XHCI; |
| |
| controller->start = xhci_start; |
| controller->stop = xhci_stop; |
| controller->reset = xhci_reset; |
| controller->init = xhci_reinit; |
| 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) { |
| fatal("We don't do 64bit addressing.\n"); |
| } |
| usb_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); |
| usb_debug("caplen: %lx\nrtsoff: %lx\ndboff: %lx\n", XHCI_INST (controller)->capreg->caplength, XHCI_INST (controller)->capreg->rtsoff, XHCI_INST (controller)->capreg->dboff); |
| usb_debug("caplength: %x\n", XHCI_INST (controller)->capreg->caplength); |
| usb_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)) { |
| fatal("Unsupported xHCI version\n"); |
| } |
| usb_debug("maxslots: %x\n", XHCI_INST (controller)->capreg->MaxSlots); |
| usb_debug("maxports: %x\n", XHCI_INST (controller)->capreg->MaxPorts); |
| int pagesize = XHCI_INST (controller)->opreg->pagesize << 12; |
| usb_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)); |
| |
| usb_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; |
| |
| usb_debug("waiting for controller to be ready - "); |
| while ((XHCI_INST (controller)->opreg->usbsts & USBSTS_CNR) != 0) mdelay(1); |
| usb_debug("ok.\n"); |
| |
| usb_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 |
| usb_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) { |
| usb_debug("CRCR: %lx, USBSTS: %lx\n", XHCI_INST (controller)->opreg->crcr_lo, XHCI_INST (controller)->opreg->usbsts); |
| usb_debug("ev0.C %x, ev1.C %x\n", ev->event_cmd_cmpl.C, ev1->event_cmd_cmpl.C); |
| mdelay(100); |
| } |
| usb_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: |
| usb_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: |
| usb_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: |
| usb_debug("Unknown event: %d, Completion Code: %d\n", ev->event_cmd_cmpl.TRB_Type, ev->event_cmd_cmpl.Completion_Code); |
| break; |
| } |
| usb_debug("CRCR: %lx, USBSTS: %lx\n", XHCI_INST (controller)->opreg->crcr_lo, XHCI_INST (controller)->opreg->usbsts); |
| usb_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]); |
| |
| 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) |
| 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; |
| } |