| /* |
| * Copyright (C) 2008-2010 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. |
| */ |
| |
| //#define USB_DEBUG |
| |
| #include <usb/usb.h> |
| #include "uhci.h" |
| #include "uhci_private.h" |
| |
| #include "base/die.h" |
| #include "base/io.h" |
| #include "base/time.h" |
| #include "base/xalloc.h" |
| |
| static void uhci_start (UsbDevHc *controller); |
| static void uhci_stop (UsbDevHc *controller); |
| static void uhci_reset (UsbDevHc *controller); |
| static void uhci_shutdown (UsbDevHc *controller); |
| static int uhci_bulk (UsbEndpoint *ep, int size, uint8_t *data, int finalize); |
| static int uhci_control (UsbDev *dev, UsbDirection dir, int drlen, void *devreq, |
| int dalen, uint8_t *data); |
| static void* uhci_create_intr_queue (UsbEndpoint *ep, int reqsize, int reqcount, int reqtiming); |
| static void uhci_destroy_intr_queue (UsbEndpoint *ep, void *queue); |
| static uint8_t* uhci_poll_intr_queue (void *queue); |
| |
| static void td_dump(td_t *td) |
| { |
| usb_debug("+---------------------------------------------------+\n"); |
| if ((td->token & TD_PID_MASK) == UHCI_SETUP) |
| usb_debug("|..[SETUP]..........................................|\n"); |
| else if ((td->token & TD_PID_MASK) == UHCI_IN) |
| usb_debug("|..[IN].............................................|\n"); |
| else if ((td->token & TD_PID_MASK) == UHCI_OUT) |
| usb_debug("|..[OUT]............................................|\n"); |
| else |
| usb_debug("|..[]...............................................|\n"); |
| usb_debug("|:|============ UHCI TD at [0x%08lx] ==========|:|\n", (uintptr_t)td); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Next TD/QH [0x%08lx] |:|\n", td->ptr & ~0xFUL); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Depth/Breath [%lx] | QH/TD [%lx] | TERMINATE [%lx] |:|\n", |
| (td->ptr & (1UL << 2)) >> 2, (td->ptr & (1UL << 1)) >> 1, td->ptr & 1UL); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| T | Maximum Length | [%04lx] |:|\n", (td->token & (0x7FFUL << 21)) >> 21); |
| usb_debug("|:| O | PID CODE | [%04lx] |:|\n", td->token & 0xFF); |
| usb_debug("|:| K | Endpoint | [%04lx] |:|\n", (td->token & TD_EP_MASK) >> TD_EP_SHIFT); |
| usb_debug("|:| E | Device Address | [%04lx] |:|\n", (td->token & (0x7FUL << 8)) >> 8); |
| usb_debug("|:| N | Data Toggle | [%lx] |:|\n", (td->token & (1UL << 19)) >> 19); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| C | Short Packet Detector | [%lx] |:|\n", (td->ctrlsts & (1UL << 29)) >> 29); |
| usb_debug("|:| O | Error Counter | [%lx] |:|\n", |
| (td->ctrlsts & (3UL << TD_COUNTER_SHIFT)) >> TD_COUNTER_SHIFT); |
| usb_debug("|:| N | Low Speed Device | [%lx] |:|\n", (td->ctrlsts & (1UL << 26)) >> 26); |
| usb_debug("|:| T | Isochronous Select | [%lx] |:|\n", (td->ctrlsts & (1UL << 25)) >> 25); |
| usb_debug("|:| R | Interrupt on Complete (IOC) | [%lx] |:|\n", (td->ctrlsts & (1UL << 24)) >> 24); |
| usb_debug("|:+ O ----------------------------------------+:|\n"); |
| usb_debug("|:| L | Active | [%lx] |:|\n", (td->ctrlsts & (1UL << 23)) >> 23); |
| usb_debug("|:| & | Stalled | [%lx] |:|\n", (td->ctrlsts & (1UL << 22)) >> 22); |
| usb_debug("|:| S | Data Buffer Error | [%lx] |:|\n", (td->ctrlsts & (1UL << 21)) >> 21); |
| usb_debug("|:| T | Bubble Detected | [%lx] |:|\n", (td->ctrlsts & (1UL << 20)) >> 20); |
| usb_debug("|:| A | NAK Received | [%lx] |:|\n", (td->ctrlsts & (1UL << 19)) >> 19); |
| usb_debug("|:| T | CRC/Timeout Error | [%lx] |:|\n", (td->ctrlsts & (1UL << 18)) >> 18); |
| usb_debug("|:| U | Bitstuff Error | [%lx] |:|\n", (td->ctrlsts & (1UL << 17)) >> 17); |
| usb_debug("|:| S ----------------------------------------|:|\n"); |
| usb_debug("|:| | Actual Length | [%04lx] |:|\n", td->ctrlsts & 0x7FFUL); |
| usb_debug("|:+-----------------------------------------------+:|\n"); |
| usb_debug("|:| Buffer pointer [0x%08lx] |:|\n", td->bufptr); |
| usb_debug("|:|-----------------------------------------------|:|\n"); |
| usb_debug("|...................................................|\n"); |
| usb_debug("+---------------------------------------------------+\n"); |
| } |
| |
| static void |
| uhci_reset (UsbDevHc *controller) |
| { |
| /* reset */ |
| uhci_reg_write16 (controller, USBCMD, 4); /* Global Reset */ |
| mdelay (50); /* uhci spec 2.1.1: at least 10ms */ |
| uhci_reg_write16 (controller, USBCMD, 0); |
| mdelay (10); |
| uhci_reg_write16 (controller, USBCMD, 2); /* Host Controller Reset */ |
| /* wait for controller to finish reset */ |
| /* TOTEST: how long to wait? 100ms for now */ |
| int timeout = 200; /* time out after 200 * 500us == 100ms */ |
| while (((uhci_reg_read16 (controller, USBCMD) & 2) != 0) && timeout--) |
| udelay (500); |
| if (timeout < 0) |
| usb_debug ("Warning: uhci: host controller reset timed out.\n"); |
| } |
| |
| static void |
| uhci_reinit (UsbDevHc *controller) |
| { |
| uhci_reg_write32(controller, FLBASEADD, |
| (uint32_t)(uintptr_t)UHCI_INST(controller)->framelistptr); |
| |
| /* disable irqs */ |
| uhci_reg_write16 (controller, USBINTR, 0); |
| |
| /* reset framelist index */ |
| uhci_reg_write16 (controller, FRNUM, 0); |
| |
| uhci_reg_write16(controller, USBCMD, |
| uhci_reg_read16(controller, USBCMD) | 0xc0); // max packets, configure flag |
| |
| uhci_start (controller); |
| } |
| |
| UsbDevHc * |
| uhci_pci_init (pcidev_t addr) |
| { |
| int i; |
| uint16_t reg16; |
| |
| UsbDevHc *controller = new_controller (); |
| controller->instance = xzalloc(sizeof (uhci_t)); |
| controller->type = UsbUhci; |
| controller->start = uhci_start; |
| controller->stop = uhci_stop; |
| controller->reset = uhci_reset; |
| controller->init = uhci_reinit; |
| controller->shutdown = uhci_shutdown; |
| controller->bulk = uhci_bulk; |
| controller->control = uhci_control; |
| controller->set_address = usb_generic_set_address; |
| controller->finish_device_config = NULL; |
| controller->destroy_device = NULL; |
| controller->create_intr_queue = uhci_create_intr_queue; |
| controller->destroy_intr_queue = uhci_destroy_intr_queue; |
| controller->poll_intr_queue = uhci_poll_intr_queue; |
| init_device_entry (controller, 0); |
| UHCI_INST (controller)->roothub = controller->devices[0]; |
| |
| /* ~1 clears the register type indicator that is set to 1 |
| * for IO space */ |
| controller->reg_base = pci_read_config32 (addr, 0x20) & ~1; |
| |
| /* kill legacy support handler */ |
| uhci_stop (controller); |
| mdelay (1); |
| uhci_reg_write16 (controller, USBSTS, 0x3f); |
| reg16 = pci_read_config16(addr, 0xc0); |
| reg16 &= 0xdf80; |
| pci_write_config16 (addr, 0xc0, reg16); |
| |
| UHCI_INST (controller)->framelistptr = memalign (0x1000, 1024 * sizeof (flistp_t)); /* 4kb aligned to 4kb */ |
| if (! UHCI_INST (controller)->framelistptr) |
| die("Not enough memory for USB frame list pointer.\n"); |
| |
| memset (UHCI_INST (controller)->framelistptr, 0, |
| 1024 * sizeof (flistp_t)); |
| |
| /* According to the *BSD UHCI code, this one is needed on some |
| PIIX chips, because otherwise they misbehave. It must be |
| added to the last chain. |
| |
| FIXME: this leaks, if the driver should ever be reinited |
| for some reason. Not a problem now. |
| */ |
| td_t *antiberserk = memalign(16, sizeof(td_t)); |
| if (!antiberserk) |
| die("Not enough memory for chipset workaround.\n"); |
| memset(antiberserk, 0, sizeof(td_t)); |
| |
| UHCI_INST (controller)->qh_prei = memalign (16, sizeof (qh_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)); |
| |
| if (! UHCI_INST (controller)->qh_prei || |
| ! UHCI_INST (controller)->qh_intr || |
| ! UHCI_INST (controller)->qh_data || |
| ! UHCI_INST (controller)->qh_last) |
| die("Not enough memory for USB controller queues.\n"); |
| |
| UHCI_INST (controller)->qh_prei->headlinkptr = |
| (uintptr_t)UHCI_INST (controller)->qh_intr | FLISTP_QH; |
| UHCI_INST (controller)->qh_prei->elementlinkptr = 0 | FLISTP_TERMINATE; |
| |
| UHCI_INST (controller)->qh_intr->headlinkptr = |
| (uintptr_t)UHCI_INST (controller)->qh_data | FLISTP_QH; |
| UHCI_INST (controller)->qh_intr->elementlinkptr = 0 | FLISTP_TERMINATE; |
| |
| UHCI_INST (controller)->qh_data->headlinkptr = |
| (uintptr_t)UHCI_INST (controller)->qh_last | FLISTP_QH; |
| UHCI_INST (controller)->qh_data->elementlinkptr = 0 | FLISTP_TERMINATE; |
| |
| UHCI_INST (controller)->qh_last->headlinkptr = (uintptr_t)UHCI_INST (controller)->qh_data | FLISTP_TERMINATE; |
| UHCI_INST (controller)->qh_last->elementlinkptr = (uintptr_t)antiberserk | FLISTP_TERMINATE; |
| |
| for (i = 0; i < 1024; i++) { |
| UHCI_INST (controller)->framelistptr[i] = |
| (uintptr_t)UHCI_INST (controller)->qh_prei | FLISTP_QH; |
| } |
| controller->devices[0]->controller = controller; |
| controller->devices[0]->init = uhci_rh_init; |
| controller->devices[0]->init (controller->devices[0]); |
| uhci_reset (controller); |
| uhci_reinit (controller); |
| return controller; |
| } |
| |
| static void |
| uhci_shutdown (UsbDevHc *controller) |
| { |
| if (controller == 0) |
| return; |
| detach_controller (controller); |
| uhci_reg_write16(controller, USBCMD, |
| uhci_reg_read16(controller, USBCMD) & 0); // stop work |
| free (UHCI_INST (controller)->framelistptr); |
| free (UHCI_INST (controller)->qh_prei); |
| 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 (UsbDevHc *controller) |
| { |
| uhci_reg_write16(controller, USBCMD, |
| uhci_reg_read16(controller, USBCMD) | 1); // start work on schedule |
| } |
| |
| static void |
| uhci_stop (UsbDevHc *controller) |
| { |
| uhci_reg_write16(controller, USBCMD, |
| uhci_reg_read16(controller, USBCMD) & ~1); // stop work on schedule |
| } |
| |
| #define GET_TD(x) ((void*)(((unsigned int)(x))&~0xf)) |
| |
| static td_t * |
| wait_for_completed_qh (UsbDevHc *controller, qh_t *qh) |
| { |
| int timeout = 1000; /* max 30 ms. */ |
| void *current = GET_TD (qh->elementlinkptr); |
| while (((qh->elementlinkptr & FLISTP_TERMINATE) == 0) && (timeout-- > 0)) { |
| if (current != GET_TD (qh->elementlinkptr)) { |
| current = GET_TD (qh->elementlinkptr); |
| timeout = 1000; |
| } |
| uhci_reg_write16(controller, USBSTS, |
| uhci_reg_read16(controller, USBSTS) | 0); // clear resettable registers |
| udelay (30); |
| } |
| return (GET_TD (qh->elementlinkptr) == |
| 0) ? 0 : GET_TD ((void *)(uintptr_t)qh->elementlinkptr); |
| } |
| |
| 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 *dev, UsbDirection 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 = (uintptr_t)&tds[i + 1] | TD_DEPTH_FIRST; |
| } |
| tds[count].ptr = 0 | TD_DEPTH_FIRST | TD_TERMINATE; |
| |
| tds[0].token = UHCI_SETUP | |
| dev->address << TD_DEVADDR_SHIFT | |
| endp << TD_EP_SHIFT | |
| TD_TOGGLE_DATA0 | |
| maxlen(drlen) << TD_MAXLEN_SHIFT; |
| tds[0].bufptr = (uintptr_t)devreq; |
| tds[0].ctrlsts = (3 << TD_COUNTER_SHIFT) | |
| (dev->speed?TD_LOWSPEED:0) | |
| TD_STATUS_ACTIVE; |
| |
| int toggle = 1; |
| for (i = 1; i < count; i++) { |
| switch (dir) { |
| case UsbDirSetup: tds[i].token = UHCI_SETUP; break; |
| case UsbDirIn: tds[i].token = UHCI_IN; break; |
| case UsbDirOut: tds[i].token = UHCI_OUT; break; |
| } |
| tds[i].token |= dev->address << TD_DEVADDR_SHIFT | |
| endp << TD_EP_SHIFT | |
| maxlen (min (mlen, dalen)) << TD_MAXLEN_SHIFT | |
| toggle << TD_TOGGLE_SHIFT; |
| tds[i].bufptr = (uintptr_t)data; |
| tds[i].ctrlsts = (3 << TD_COUNTER_SHIFT) | |
| (dev->speed?TD_LOWSPEED:0) | |
| TD_STATUS_ACTIVE; |
| toggle ^= 1; |
| dalen -= mlen; |
| data += mlen; |
| } |
| |
| tds[count].token = ((dir == UsbDirOut) ? UHCI_IN : UHCI_OUT) | |
| dev->address << TD_DEVADDR_SHIFT | |
| endp << TD_EP_SHIFT | |
| maxlen(0) << TD_MAXLEN_SHIFT | |
| TD_TOGGLE_DATA1; |
| tds[count].bufptr = 0; |
| tds[count].ctrlsts = (0 << TD_COUNTER_SHIFT) | /* as Linux 2.4.10 does */ |
| (dev->speed?TD_LOWSPEED:0) | |
| TD_STATUS_ACTIVE; |
| UHCI_INST (dev->controller)->qh_data->elementlinkptr = |
| (uintptr_t)tds & ~(FLISTP_QH | FLISTP_TERMINATE); |
| td_t *td = wait_for_completed_qh (dev->controller, |
| UHCI_INST (dev->controller)-> |
| qh_data); |
| int result; |
| if (td == 0) { |
| result = 0; |
| } else { |
| usb_debug ("control packet, req %x\n", req); |
| td_dump (td); |
| result = -1; |
| } |
| free (tds); |
| return result; |
| } |
| |
| 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 = (uintptr_t)&tds[i + 1] | TD_DEPTH_FIRST; |
| } |
| tds[numpackets - 1].ptr = 0 | TD_TERMINATE; |
| return tds; |
| } |
| |
| static void |
| fill_schedule (td_t *td, UsbEndpoint *ep, int length, unsigned char *data, |
| int *toggle) |
| { |
| switch (ep->direction) { |
| case UsbDirIn: td->token = UHCI_IN; break; |
| case UsbDirOut: td->token = UHCI_OUT; break; |
| case UsbDirSetup: td->token = UHCI_SETUP; break; |
| } |
| td->token |= ep->dev->address << TD_DEVADDR_SHIFT | |
| (ep->endpoint & 0xf) << TD_EP_SHIFT | |
| maxlen (length) << TD_MAXLEN_SHIFT | |
| (*toggle & 1) << TD_TOGGLE_SHIFT; |
| td->bufptr = (uintptr_t)data; |
| td->ctrlsts = ((ep->direction == UsbDirSetup ? 3 : 0) << |
| TD_COUNTER_SHIFT) | |
| (ep->dev->speed?TD_LOWSPEED:0) | |
| TD_STATUS_ACTIVE; |
| *toggle ^= 1; |
| } |
| |
| static int |
| run_schedule (UsbDev *dev, td_t *td) |
| { |
| UHCI_INST (dev->controller)->qh_data->elementlinkptr = |
| (uintptr_t)td & ~(FLISTP_QH | FLISTP_TERMINATE); |
| 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 (UsbEndpoint *ep, int size, uint8_t *data, int finalize) |
| { |
| int maxpsize = ep->maxpacketsize; |
| if (maxpsize == 0) |
| die("MaxPacketSize == 0!!!"); |
| int numpackets = (size + maxpsize - 1) / maxpsize; |
| if (finalize && ((size % maxpsize) == 0)) { |
| numpackets++; |
| } |
| 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) { |
| free (tds); |
| return -1; |
| } |
| ep->toggle = toggle; |
| free (tds); |
| return 0; |
| } |
| |
| typedef struct { |
| qh_t *qh; |
| td_t *tds; |
| td_t *last_td; |
| uint8_t *data; |
| int lastread; |
| int total; |
| int reqsize; |
| } intr_q; |
| |
| /* create and hook-up an intr queue into device schedule */ |
| static void* |
| uhci_create_intr_queue (UsbEndpoint *ep, int reqsize, int reqcount, int reqtiming) |
| { |
| uint8_t *data = malloc(reqsize*reqcount); |
| td_t *tds = memalign(16, sizeof(td_t) * reqcount); |
| qh_t *qh = memalign(16, sizeof(qh_t)); |
| |
| if (!data || !tds || !qh) |
| die("Not enough memory to create USB intr queue prerequisites.\n"); |
| |
| qh->elementlinkptr = (uintptr_t)tds; |
| |
| intr_q *q = malloc(sizeof(intr_q)); |
| if (!q) |
| die("Not enough memory to create USB intr queue.\n"); |
| q->qh = qh; |
| q->tds = tds; |
| q->data = data; |
| q->lastread = 0; |
| q->total = reqcount; |
| q->reqsize = reqsize; |
| q->last_td = &tds[reqcount - 1]; |
| |
| memset (tds, 0, sizeof (td_t) * reqcount); |
| int i; |
| for (i = 0; i < reqcount; i++) { |
| tds[i].ptr = (uintptr_t)&tds[i + 1]; |
| |
| switch (ep->direction) { |
| case UsbDirIn: tds[i].token = UHCI_IN; break; |
| case UsbDirOut: tds[i].token = UHCI_OUT; break; |
| case UsbDirSetup: tds[i].token = UHCI_SETUP; break; |
| } |
| tds[i].token |= ep->dev->address << TD_DEVADDR_SHIFT | |
| (ep->endpoint & 0xf) << TD_EP_SHIFT | |
| maxlen (reqsize) << TD_MAXLEN_SHIFT | |
| (ep->toggle & 1) << TD_TOGGLE_SHIFT; |
| tds[i].bufptr = (uintptr_t)data; |
| tds[i].ctrlsts = (0 << TD_COUNTER_SHIFT) | |
| (ep->dev->speed?TD_LOWSPEED:0) | |
| TD_STATUS_ACTIVE; |
| ep->toggle ^= 1; |
| data += reqsize; |
| } |
| tds[reqcount - 1].ptr = 0 | TD_TERMINATE; |
| |
| /* insert QH into framelist */ |
| uhci_t *const uhcic = UHCI_INST(ep->dev->controller); |
| const uint32_t def_ptr = (uintptr_t)uhcic->qh_prei | FLISTP_QH; |
| int nothing_placed = 1; |
| qh->headlinkptr = def_ptr; |
| for (i = 0; i < 1024; i += reqtiming) { |
| /* advance to the next free position */ |
| while ((i < 1024) && (uhcic->framelistptr[i] != def_ptr)) ++i; |
| if (i < 1024) { |
| uhcic->framelistptr[i] = (uintptr_t)qh | FLISTP_QH; |
| nothing_placed = 0; |
| } |
| } |
| if (nothing_placed) { |
| usb_debug("Error: Failed to place UHCI interrupt queue " |
| "head into framelist: no space left\n"); |
| uhci_destroy_intr_queue(ep, q); |
| return NULL; |
| } |
| |
| return q; |
| } |
| |
| /* remove queue from device schedule, dropping all data that came in */ |
| static void |
| uhci_destroy_intr_queue (UsbEndpoint *ep, void *q_) |
| { |
| intr_q *const q = (intr_q*)q_; |
| |
| /* remove QH from framelist */ |
| uhci_t *const uhcic = UHCI_INST(ep->dev->controller); |
| const uint32_t qh_ptr = (uintptr_t)q->qh | FLISTP_QH; |
| const uint32_t def_ptr = (uintptr_t)uhcic->qh_prei | FLISTP_QH; |
| int i; |
| for (i = 0; i < 1024; ++i) { |
| if (uhcic->framelistptr[i] == qh_ptr) |
| uhcic->framelistptr[i] = def_ptr; |
| } |
| |
| free(q->data); |
| free(q->tds); |
| free(q->qh); |
| 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 uint8_t* |
| uhci_poll_intr_queue (void *q_) |
| { |
| intr_q *q = (intr_q*)q_; |
| if ((q->tds[q->lastread].ctrlsts & TD_STATUS_ACTIVE) == 0) { |
| int current = q->lastread; |
| int previous; |
| if (q->lastread == 0) { |
| previous = q->total - 1; |
| } else { |
| previous = q->lastread - 1; |
| } |
| q->tds[previous].ctrlsts &= ~TD_STATUS_MASK; |
| q->tds[previous].ptr = 0 | TD_TERMINATE; |
| if (q->last_td != &q->tds[previous]) { |
| q->last_td->ptr = (uintptr_t)&q->tds[previous] & ~TD_TERMINATE; |
| q->last_td = &q->tds[previous]; |
| } |
| q->tds[previous].ctrlsts |= TD_STATUS_ACTIVE; |
| q->lastread = (q->lastread + 1) % q->total; |
| if (!(q->tds[current].ctrlsts & TD_STATUS_MASK)) |
| return &q->data[current*q->reqsize]; |
| } |
| /* reset queue if we fully processed it after underrun */ |
| else if (q->qh->elementlinkptr & FLISTP_TERMINATE) { |
| usb_debug("resetting underrun uhci interrupt queue.\n"); |
| q->qh->elementlinkptr = (uintptr_t)(q->tds + q->lastread); |
| } |
| return NULL; |
| } |
| |
| void |
| uhci_reg_write32 (UsbDevHc *ctrl, usbreg reg, uint32_t value) |
| { |
| outl (value, ctrl->reg_base + reg); |
| } |
| |
| uint32_t |
| uhci_reg_read32 (UsbDevHc *ctrl, usbreg reg) |
| { |
| return inl (ctrl->reg_base + reg); |
| } |
| |
| void |
| uhci_reg_write16 (UsbDevHc *ctrl, usbreg reg, uint16_t value) |
| { |
| outw (value, ctrl->reg_base + reg); |
| } |
| |
| uint16_t |
| uhci_reg_read16 (UsbDevHc *ctrl, usbreg reg) |
| { |
| return inw (ctrl->reg_base + reg); |
| } |
| |
| void |
| uhci_reg_write8 (UsbDevHc *ctrl, usbreg reg, uint8_t value) |
| { |
| outb (value, ctrl->reg_base + reg); |
| } |
| |
| uint8_t |
| uhci_reg_read8 (UsbDevHc *ctrl, usbreg reg) |
| { |
| return inb (ctrl->reg_base + reg); |
| } |