diff options
author | 2015-08-28 09:58:54 +0800 | |
---|---|---|
committer | 2015-09-01 12:44:00 +0800 | |
commit | e44e3482bdb4d0ebde2d8b41830ac2cdb07948fb (patch) | |
tree | 66b09f592c55df2878107a468a91d21506104d3f /qemu/roms/openbios/drivers/usbohci.c | |
parent | 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 (diff) |
Add qemu 2.4.0
Change-Id: Ic99cbad4b61f8b127b7dc74d04576c0bcbaaf4f5
Signed-off-by: Yang Zhang <yang.z.zhang@intel.com>
Diffstat (limited to 'qemu/roms/openbios/drivers/usbohci.c')
-rw-r--r-- | qemu/roms/openbios/drivers/usbohci.c | 926 |
1 files changed, 926 insertions, 0 deletions
diff --git a/qemu/roms/openbios/drivers/usbohci.c b/qemu/roms/openbios/drivers/usbohci.c new file mode 100644 index 000000000..774164b0b --- /dev/null +++ b/qemu/roms/openbios/drivers/usbohci.c @@ -0,0 +1,926 @@ +/* + * Driver for USB OHCI ported from CoreBoot + * + * Copyright (C) 2014 BALATON Zoltan + * + * This file was 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_ED + +#include "config.h" +#include <asm/io.h> +#include <libopenbios/ofmem.h> +#include "timer.h" +#include "drivers/pci.h" +#include "pci.h" +#include <drivers/usb.h> +#include "usbohci_private.h" +#include "usbohci.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_process_done_queue(ohci_t *ohci, int spew_debug); + +#ifdef USB_DEBUG_ED +static void +dump_td (td_t *cur) +{ + usb_debug("+---------------------------------------------------+\n"); + if (((__le32_to_cpu(cur->config) & (3UL << 19)) >> 19) == 0) + usb_debug("|..[SETUP]..........................................|\n"); + else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 2) + usb_debug("|..[IN].............................................|\n"); + else if (((__le32_to_cpu(cur->config) & (3UL << 8)) >> 8) == 1) + usb_debug("|..[OUT]............................................|\n"); + else + usb_debug("|..[]...............................................|\n"); + usb_debug("|:|============ OHCI TD at [0x%08lx] ==========|:|\n", virt_to_phys(cur)); + usb_debug("|:| ERRORS = [%ld] | CONFIG = [0x%08x] | |:|\n", + 3 - ((__le32_to_cpu(cur->config) & (3UL << 26)) >> 26), __le32_to_cpu(cur->config)); + usb_debug("|:+-----------------------------------------------+:|\n"); + usb_debug("|:| C | Condition Code | [%02ld] |:|\n", + (__le32_to_cpu(cur->config) & (0xFUL << 28)) >> 28); + usb_debug("|:| O | Direction/PID | [%ld] |:|\n", + (__le32_to_cpu(cur->config) & (3UL << 19)) >> 19); + usb_debug("|:| N | Buffer Rounding | [%ld] |:|\n", + (__le32_to_cpu(cur->config) & (1UL << 18)) >> 18); + usb_debug("|:| F | Delay Intterrupt | [%ld] |:|\n", + (__le32_to_cpu(cur->config) & (7UL << 21)) >> 21); + usb_debug("|:| I | Data Toggle | [%ld] |:|\n", + (__le32_to_cpu(cur->config) & (3UL << 24)) >> 24); + usb_debug("|:| G | Error Count | [%ld] |:|\n", + (__le32_to_cpu(cur->config) & (3UL << 26)) >> 26); + usb_debug("|:+-----------------------------------------------+:|\n"); + usb_debug("|:| Current Buffer Pointer [0x%08x] |:|\n", __le32_to_cpu(cur->current_buffer_pointer)); + usb_debug("|:+-----------------------------------------------+:|\n"); + usb_debug("|:| Next TD [0x%08x] |:|\n", __le32_to_cpu(cur->next_td)); + usb_debug("|:+-----------------------------------------------+:|\n"); + usb_debug("|:| Current Buffer End [0x%08x] |:|\n", __le32_to_cpu(cur->buffer_end)); + usb_debug("|:|-----------------------------------------------|:|\n"); + usb_debug("|...................................................|\n"); + usb_debug("+---------------------------------------------------+\n"); +} + +static void +dump_ed (ed_t *cur) +{ + td_t *tmp_td = NULL; + usb_debug("+===================================================+\n"); + usb_debug("| ############# OHCI ED at [0x%08lx] ########### |\n", virt_to_phys(cur)); + usb_debug("+---------------------------------------------------+\n"); + usb_debug("| Next Endpoint Descriptor [0x%08lx] |\n", __le32_to_cpu(cur->next_ed) & ~0xFUL); + usb_debug("+---------------------------------------------------+\n"); + usb_debug("| | @ 0x%08x : |\n", __le32_to_cpu(cur->config)); + usb_debug("| C | Maximum Packet Length | [%04ld] |\n", + ((__le32_to_cpu(cur->config) & (0x3fffUL << 16)) >> 16)); + usb_debug("| O | Function Address | [%04d] |\n", + __le32_to_cpu(cur->config) & 0x7F); + usb_debug("| N | Endpoint Number | [%02ld] |\n", + (__le32_to_cpu(cur->config) & (0xFUL << 7)) >> 7); + usb_debug("| F | Endpoint Direction | [%ld] |\n", + ((__le32_to_cpu(cur->config) & (3UL << 11)) >> 11)); + usb_debug("| I | Endpoint Speed | [%ld] |\n", + ((__le32_to_cpu(cur->config) & (1UL << 13)) >> 13)); + usb_debug("| G | Skip | [%ld] |\n", + ((__le32_to_cpu(cur->config) & (1UL << 14)) >> 14)); + usb_debug("| | Format | [%ld] |\n", + ((__le32_to_cpu(cur->config) & (1UL << 15)) >> 15)); + usb_debug("+---------------------------------------------------+\n"); + usb_debug("| TD Queue Tail Pointer [0x%08lx] |\n", + __le32_to_cpu(cur->tail_pointer) & ~0xFUL); + usb_debug("+---------------------------------------------------+\n"); + usb_debug("| TD Queue Head Pointer [0x%08lx] |\n", + __le32_to_cpu(cur->head_pointer) & ~0xFUL); + usb_debug("| CarryToggleBit [%d] Halted [%d] |\n", + (u16)(__le32_to_cpu(cur->head_pointer) & 0x2UL)>>1, (u16)(__le32_to_cpu(cur->head_pointer) & 0x1UL)); + + tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(cur->head_pointer) & ~0xFUL)); + if ((__le32_to_cpu(cur->head_pointer) & ~0xFUL) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL)) { + usb_debug("|:::::::::::::::::: OHCI TD CHAIN ::::::::::::::::::|\n"); + while (virt_to_phys(tmp_td) != (__le32_to_cpu(cur->tail_pointer) & ~0xFUL)) + { + dump_td(tmp_td); + tmp_td = (td_t *)phys_to_virt((__le32_to_cpu(tmp_td->next_td) & ~0xFUL)); + } + usb_debug("|:::::::::::::::: EOF OHCI TD CHAIN ::::::::::::::::|\n"); + usb_debug("+---------------------------------------------------+\n"); + } else { + usb_debug("+---------------------------------------------------+\n"); + } +} +#endif + +static void +ohci_reset (hci_t *controller) +{ + if (controller == NULL) + return; + + OHCI_INST(controller)->opreg->HcCommandStatus = __cpu_to_le32(HostControllerReset); + mdelay(2); /* wait 2ms */ + OHCI_INST(controller)->opreg->HcControl = 0; + mdelay(10); /* wait 10ms */ +} + +static void +ohci_reinit (hci_t *controller) +{ +} + +hci_t * +ohci_init (void *bar) +{ + int i; + + hci_t *controller = new_controller (); + + if (!controller) { + printk("Could not create USB controller instance.\n"); + return NULL; + } + + controller->instance = malloc (sizeof (ohci_t)); + if(!controller->instance) { + printk("Not enough memory creating USB controller instance.\n"); + return NULL; + } + + controller->type = OHCI; + + controller->start = ohci_start; + controller->stop = ohci_stop; + controller->reset = ohci_reset; + controller->init = ohci_reinit; + controller->shutdown = ohci_shutdown; + controller->bulk = ohci_bulk; + controller->control = ohci_control; + controller->set_address = generic_set_address; + controller->finish_device_config = NULL; + controller->destroy_device = NULL; + 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->reg_base = (u32)(unsigned long)bar; + OHCI_INST (controller)->opreg = (opreg_t*)phys_to_virt(controller->reg_base); + usb_debug("OHCI Version %x.%x\n", + (READ_OPREG(OHCI_INST(controller), HcRevision) >> 4) & 0xf, + READ_OPREG(OHCI_INST(controller), HcRevision) & 0xf); + + if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) == USBReset) { + /* cold boot */ + OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~RemoteWakeupConnected); + OHCI_INST (controller)->opreg->HcFmInterval = + __cpu_to_le32((11999 * FrameInterval) | ((((11999 - 210)*6)/7) * FSLargestDataPacket)); + /* TODO: right value for PowerOnToPowerGoodTime ? */ + OHCI_INST (controller)->opreg->HcRhDescriptorA = + __cpu_to_le32(NoPowerSwitching | NoOverCurrentProtection | (10 * PowerOnToPowerGoodTime)); + OHCI_INST (controller)->opreg->HcRhDescriptorB = __cpu_to_le32(0 * DeviceRemovable); + udelay(100); /* TODO: reset asserting according to USB spec */ + } else if ((READ_OPREG(OHCI_INST(controller), HcControl) & HostControllerFunctionalStateMask) != USBOperational) { + OHCI_INST (controller)->opreg->HcControl = + __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl) & ~HostControllerFunctionalStateMask) + | USBResume); + udelay(100); /* TODO: resume time according to USB spec */ + } + int interval = OHCI_INST (controller)->opreg->HcFmInterval; + + OHCI_INST (controller)->opreg->HcCommandStatus = __cpu_to_le32(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; + ofmem_posix_memalign((void **)&(OHCI_INST (controller)->hcca), 256, 256); + memset((void*)OHCI_INST (controller)->hcca, 0, 256); + + usb_debug("HCCA addr %p\n", OHCI_INST(controller)->hcca); + /* Initialize interrupt table. */ + u32 *const intr_table = OHCI_INST(controller)->hcca->HccaInterruptTable; + ed_t *const periodic_ed; + ofmem_posix_memalign((void **)&periodic_ed, sizeof(ed_t), sizeof(ed_t)); + memset((void *)periodic_ed, 0, sizeof(*periodic_ed)); + for (i = 0; i < 32; ++i) + intr_table[i] = __cpu_to_le32(virt_to_phys(periodic_ed)); + OHCI_INST (controller)->periodic_ed = periodic_ed; + + OHCI_INST (controller)->opreg->HcHCCA = __cpu_to_le32(virt_to_phys(OHCI_INST(controller)->hcca)); + /* Make sure periodic schedule is enabled. */ + OHCI_INST (controller)->opreg->HcControl |= __cpu_to_le32(PeriodicListEnable); + OHCI_INST (controller)->opreg->HcControl &= __cpu_to_le32(~IsochronousEnable); // unused by this driver + // disable everything, contrary to what OHCI spec says in 5.1.1.4, as we don't need IRQs + OHCI_INST (controller)->opreg->HcInterruptEnable = __cpu_to_le32(1<<31); + OHCI_INST (controller)->opreg->HcInterruptDisable = __cpu_to_le32(~(1<<31)); + OHCI_INST (controller)->opreg->HcInterruptStatus = __cpu_to_le32(~0); + OHCI_INST (controller)->opreg->HcPeriodicStart = + __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcFmInterval) & FrameIntervalMask) / 10 * 9); + OHCI_INST (controller)->opreg->HcControl = __cpu_to_le32((READ_OPREG(OHCI_INST(controller), HcControl) + & ~HostControllerFunctionalStateMask) | USBOperational); + + mdelay(100); + + controller->devices[0]->controller = controller; + controller->devices[0]->init = ohci_rh_init; + controller->devices[0]->init (controller->devices[0]); + return controller; +} + +hci_t * +ohci_pci_init (pci_addr addr) +{ + u32 reg_base; + uint16_t cmd; + + cmd = pci_config_read16(addr, PCI_COMMAND); + cmd |= PCI_COMMAND_BUS_MASTER; + pci_config_write16(addr, PCI_COMMAND, cmd); + + /* regarding OHCI spec, Appendix A, BAR_OHCI register description, Table A-4 + * BASE ADDRESS only [31-12] bits. All other usually 0, but not all. + * OHCI mandates MMIO, so bit 0 is clear */ + reg_base = pci_config_read32 (addr, PCI_BASE_ADDR_0) & 0xfffff000; + + return ohci_init((void *)(unsigned long)reg_base); +} + +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); + controller->reset (controller); + free ((void *)OHCI_INST (controller)->periodic_ed); + 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 int +wait_for_ed(usbdev_t *dev, ed_t *head, int pages) +{ + usb_debug("Waiting for %d pages on dev %p with head %p\n", pages, dev, head); + /* wait for results */ + /* TOTEST: how long to wait? + * give 2s per TD (2 pages) plus another 2s for now + */ + int timeout = pages*1000 + 2000; + while (((__le32_to_cpu(head->head_pointer) & ~3) != __le32_to_cpu(head->tail_pointer)) && + !(__le32_to_cpu(head->head_pointer) & 1) && + ((__le32_to_cpu((((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3)))->config) + & TD_CC_MASK) >= TD_CC_NOACCESS) && timeout--) { + /* don't log every ms */ + if (!(timeout % 100)) + usb_debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n", + READ_OPREG(OHCI_INST(dev->controller), HcInterruptStatus), + READ_OPREG(OHCI_INST(dev->controller), HcControl), + READ_OPREG(OHCI_INST(dev->controller), HcCommandStatus), + __le32_to_cpu(head->head_pointer), + __le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->next_td), + __le32_to_cpu(head->tail_pointer), + (__le32_to_cpu(((td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~3))->config) & TD_CC_MASK) >> TD_CC_SHIFT); + mdelay(1); + } + if (timeout < 0) + usb_debug("Error: ohci: endpoint " + "descriptor processing timed out.\n"); + /* Clear the done queue. */ + ohci_process_done_queue(OHCI_INST(dev->controller), 1); + + if (__le32_to_cpu(head->head_pointer) & 1) { + usb_debug("HALTED!\n"); + return 1; + } + return 0; +} + +static void +ohci_free_ed (ed_t *const head) +{ + /* In case the transfer canceled, we have to free unprocessed TDs. */ + while ((__le32_to_cpu(head->head_pointer) & ~0x3) != __le32_to_cpu(head->tail_pointer)) { + /* Save current TD pointer. */ + td_t *const cur_td = + (td_t*)phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3); + /* Advance head pointer. */ + head->head_pointer = cur_td->next_td; + /* Free current TD. */ + free((void *)cur_td); + } + + /* Always free the dummy TD */ + if ((__le32_to_cpu(head->head_pointer) & ~0x3) == __le32_to_cpu(head->tail_pointer)) + free(phys_to_virt(__le32_to_cpu(head->head_pointer) & ~0x3)); + /* and the ED. */ + free((void *)head); +} + +static int +ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen, + unsigned char *data) +{ + 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); + + /* First TD. */ + td_t *const first_td; + ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t)); + memset((void *)first_td, 0, sizeof(*first_td)); + cur = first_td; + + cur->config = __cpu_to_le32(TD_DIRECTION_SETUP | + TD_DELAY_INTERRUPT_NOINTR | + TD_TOGGLE_FROM_TD | + TD_TOGGLE_DATA0 | + TD_CC_NOACCESS); + cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(devreq)); + cur->buffer_end = __cpu_to_le32(virt_to_phys((char *)devreq + drlen - 1)); + + while (pages > 0) { + /* One more TD. */ + td_t *const next; + ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t)); + memset((void *)next, 0, sizeof(*next)); + /* Linked to the previous. */ + cur->next_td = __cpu_to_le32(virt_to_phys(next)); + /* Advance to the new TD. */ + cur = next; + + cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | + TD_DELAY_INTERRUPT_NOINTR | + TD_TOGGLE_FROM_ED | + TD_CC_NOACCESS); + cur->current_buffer_pointer = __cpu_to_le32(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 = __cpu_to_le32(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 = __cpu_to_le32(virt_to_phys(data + second_page_size - 1)); + dalen -= second_page_size; + data += second_page_size; + } + } + + /* One more TD. */ + td_t *const next_td; + ofmem_posix_memalign((void **)&next_td, sizeof(td_t), sizeof(td_t)); + memset((void *)next_td, 0, sizeof(*next_td)); + /* Linked to the previous. */ + cur->next_td = __cpu_to_le32(virt_to_phys(next_td)); + /* Advance to the new TD. */ + cur = next_td; + cur->config = __cpu_to_le32((dir == IN ? TD_DIRECTION_OUT : TD_DIRECTION_IN) | + TD_DELAY_INTERRUPT_ZERO | /* Write done head after this TD. */ + TD_TOGGLE_FROM_TD | + TD_TOGGLE_DATA1 | + TD_CC_NOACCESS); + cur->current_buffer_pointer = 0; + cur->buffer_end = 0; + + /* Final dummy TD. */ + td_t *const final_td; + ofmem_posix_memalign((void **)&final_td, sizeof(td_t), sizeof(td_t)); + memset((void *)final_td, 0, sizeof(*final_td)); + /* Linked to the previous. */ + cur->next_td = __cpu_to_le32(virt_to_phys(final_td)); + + /* Data structures */ + ed_t *head; + ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t)); + memset((void*)head, 0, sizeof(*head)); + head->config = __cpu_to_le32((dev->address << ED_FUNC_SHIFT) | + (0 << ED_EP_SHIFT) | + (OHCI_FROM_TD << ED_DIR_SHIFT) | + (dev->speed?ED_LOWSPEED:0) | + (dev->endpoints[0].maxpacketsize << ED_MPS_SHIFT)); + head->tail_pointer = __cpu_to_le32(virt_to_phys(final_td)); + head->head_pointer = __cpu_to_le32(virt_to_phys(first_td)); + + usb_debug("ohci_control(): doing transfer with %x. first_td at %x\n", + __le32_to_cpu(head->config) & ED_FUNC_MASK, __le32_to_cpu(head->head_pointer)); +#ifdef USB_DEBUG_ED + dump_ed(head); +#endif + + /* activate schedule */ + OHCI_INST(dev->controller)->opreg->HcControlHeadED = __cpu_to_le32(virt_to_phys(head)); + OHCI_INST(dev->controller)->opreg->HcControl |= __cpu_to_le32(ControlListEnable); + OHCI_INST(dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(ControlListFilled); + + int failure = wait_for_ed(dev, head, + (dalen==0)?0:(last_page - first_page + 1)); + /* Wait some frames before and one after disabling list access. */ + mdelay(4); + OHCI_INST(dev->controller)->opreg->HcControl &= __cpu_to_le32(~ControlListEnable); + mdelay(1); + + /* free memory */ + ohci_free_ed(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; + usb_debug("bulk: %x bytes from %p, finalize: %x, maxpacketsize: %x\n", dalen, data, finalize, ep->maxpacketsize); + + td_t *cur, *next; + + // 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++; + } + + /* First TD. */ + td_t *const first_td; + ofmem_posix_memalign((void **)&first_td, sizeof(td_t), sizeof(td_t)); + memset((void *)first_td, 0, sizeof(*first_td)); + cur = next = first_td; + + for (i = 0; i < td_count; ++i) { + /* Advance to next TD. */ + cur = next; + cur->config = __cpu_to_le32((ep->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | + TD_DELAY_INTERRUPT_NOINTR | + TD_TOGGLE_FROM_ED | + TD_CC_NOACCESS); + cur->current_buffer_pointer = __cpu_to_le32(virt_to_phys(data)); + 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 = __cpu_to_le32(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 = __cpu_to_le32(virt_to_phys(data + second_page_size - 1)); + dalen -= second_page_size; + data += second_page_size; + } + /* One more TD. */ + ofmem_posix_memalign((void **)&next, sizeof(td_t), sizeof(td_t)); + memset((void *)next, 0, sizeof(*next)); + /* Linked to the previous. */ + cur->next_td = __cpu_to_le32(virt_to_phys(next)); + } + + /* Write done head after last TD. */ + cur->config &= __cpu_to_le32(~TD_DELAY_INTERRUPT_MASK); + /* Advance to final, dummy TD. */ + cur = next; + + /* Data structures */ + ed_t *head; + ofmem_posix_memalign((void **)&head, sizeof(ed_t), sizeof(ed_t)); + memset((void*)head, 0, sizeof(*head)); + head->config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) | + ((ep->endpoint & 0xf) << ED_EP_SHIFT) | + (((ep->direction==IN)?OHCI_IN:OHCI_OUT) << ED_DIR_SHIFT) | + (ep->dev->speed?ED_LOWSPEED:0) | + (ep->maxpacketsize << ED_MPS_SHIFT)); + head->tail_pointer = __cpu_to_le32(virt_to_phys(cur)); + head->head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle?ED_TOGGLE:0)); + + usb_debug("doing bulk transfer with %x(%x). first_td at %lx, last %lx\n", + __le32_to_cpu(head->config) & ED_FUNC_MASK, + (__le32_to_cpu(head->config) & ED_EP_MASK) >> ED_EP_SHIFT, + virt_to_phys(first_td), virt_to_phys(cur)); + + /* activate schedule */ + OHCI_INST(ep->dev->controller)->opreg->HcBulkHeadED = __cpu_to_le32(virt_to_phys(head)); + OHCI_INST(ep->dev->controller)->opreg->HcControl |= __cpu_to_le32(BulkListEnable); + OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = __cpu_to_le32(BulkListFilled); + + int failure = wait_for_ed(ep->dev, head, + (dalen==0)?0:(last_page - first_page + 1)); + /* Wait some frames before and one after disabling list access. */ + mdelay(4); + OHCI_INST(ep->dev->controller)->opreg->HcControl &= __cpu_to_le32(~BulkListEnable); + mdelay(1); + + ep->toggle = __le32_to_cpu(head->head_pointer) & ED_TOGGLE; + + /* free memory */ + ohci_free_ed(head); + + if (failure) { + /* try cleanup */ + clear_stall(ep); + } + + return failure; +} + + +struct _intr_queue; + +struct _intrq_td { + volatile td_t td; + u8 *data; + struct _intrq_td *next; + struct _intr_queue *intrq; +}; + +struct _intr_queue { + volatile ed_t ed; + struct _intrq_td *head; + struct _intrq_td *tail; + u8 *data; + int reqsize; + endpoint_t *endp; + unsigned int remaining_tds; + int destroy; +}; + +typedef struct _intrq_td intrq_td_t; +typedef struct _intr_queue intr_queue_t; + +#define INTRQ_TD_FROM_TD(x) ((intrq_td_t *)x) + +static void +ohci_fill_intrq_td(intrq_td_t *const td, intr_queue_t *const intrq, + u8 *const data) +{ + memset(td, 0, sizeof(*td)); + td->td.config = __cpu_to_le32(TD_QUEUETYPE_INTR | + (intrq->endp->direction == IN ? TD_DIRECTION_IN : TD_DIRECTION_OUT) | + TD_DELAY_INTERRUPT_ZERO | + TD_TOGGLE_FROM_ED | + TD_CC_NOACCESS); + td->td.current_buffer_pointer = __cpu_to_le32(virt_to_phys(data)); + td->td.buffer_end = __cpu_to_le32(virt_to_phys(data) + intrq->reqsize - 1); + td->intrq = intrq; + td->data = data; +} + +/* create and hook-up an intr queue into device schedule */ +static void * +ohci_create_intr_queue(endpoint_t *const ep, const int reqsize, + const int reqcount, const int reqtiming) +{ + int i; + intrq_td_t *first_td = NULL, *last_td = NULL; + + if (reqsize > 4096) + return NULL; + + intr_queue_t *const intrq; + ofmem_posix_memalign((void **)&intrq, sizeof(intrq->ed), sizeof(*intrq)); + memset(intrq, 0, sizeof(*intrq)); + intrq->data = (u8 *)malloc(reqcount * reqsize); + intrq->reqsize = reqsize; + intrq->endp = ep; + + /* Create #reqcount TDs. */ + u8 *cur_data = intrq->data; + for (i = 0; i < reqcount; ++i) { + intrq_td_t *const td; + ofmem_posix_memalign((void **)&td, sizeof(td->td), sizeof(*td)); + ++intrq->remaining_tds; + ohci_fill_intrq_td(td, intrq, cur_data); + cur_data += reqsize; + if (!first_td) + first_td = td; + else + last_td->td.next_td = __cpu_to_le32(virt_to_phys(&td->td)); + last_td = td; + } + + /* Create last, dummy TD. */ + intrq_td_t *dummy_td; + ofmem_posix_memalign((void **)&dummy_td, sizeof(dummy_td->td), sizeof(*dummy_td)); + memset(dummy_td, 0, sizeof(*dummy_td)); + dummy_td->intrq = intrq; + if (last_td) + last_td->td.next_td = __cpu_to_le32(virt_to_phys(&dummy_td->td)); + last_td = dummy_td; + + /* Initialize ED. */ + intrq->ed.config = __cpu_to_le32((ep->dev->address << ED_FUNC_SHIFT) | + ((ep->endpoint & 0xf) << ED_EP_SHIFT) | + (((ep->direction == IN) ? OHCI_IN : OHCI_OUT) << ED_DIR_SHIFT) | + (ep->dev->speed ? ED_LOWSPEED : 0) | + (ep->maxpacketsize << ED_MPS_SHIFT)); + intrq->ed.tail_pointer = __cpu_to_le32(virt_to_phys(last_td)); + intrq->ed.head_pointer = __cpu_to_le32(virt_to_phys(first_td) | (ep->toggle ? ED_TOGGLE : 0)); + +#ifdef USB_DEBUG_ED + dump_ed(&intrq->ed); +#endif + /* Insert ED into periodic table. */ + int nothing_placed = 1; + ohci_t *const ohci = OHCI_INST(ep->dev->controller); + u32 *const intr_table = ohci->hcca->HccaInterruptTable; + const u32 dummy_ptr = __cpu_to_le32(virt_to_phys(ohci->periodic_ed)); + for (i = 0; i < 32; i += reqtiming) { + /* Advance to the next free position. */ + while ((i < 32) && (intr_table[i] != dummy_ptr)) ++i; + if (i < 32) { + usb_debug("Placed endpoint %lx to %d\n", virt_to_phys(&intrq->ed), i); + intr_table[i] = __cpu_to_le32(virt_to_phys(&intrq->ed)); + nothing_placed = 0; + } + } + if (nothing_placed) { + usb_debug("Error: Failed to place ohci interrupt endpoint " + "descriptor into periodic table: no space left\n"); + ohci_destroy_intr_queue(ep, intrq); + return NULL; + } + + return intrq; +} + +/* remove queue from device schedule, dropping all data that came in */ +static void +ohci_destroy_intr_queue(endpoint_t *const ep, void *const q_) +{ + intr_queue_t *const intrq = (intr_queue_t *)q_; + + int i; + + /* Remove interrupt queue from periodic table. */ + ohci_t *const ohci = OHCI_INST(ep->dev->controller); + u32 *const intr_table = ohci->hcca->HccaInterruptTable; + for (i=0; i < 32; ++i) { + if (intr_table[i] == __cpu_to_le32(virt_to_phys(intrq))) + intr_table[i] = __cpu_to_le32(virt_to_phys(ohci->periodic_ed)); + } + /* Wait for frame to finish. */ + mdelay(1); + + /* Free unprocessed TDs. */ + while ((__le32_to_cpu(intrq->ed.head_pointer) & ~0x3) != __le32_to_cpu(intrq->ed.tail_pointer)) { + td_t *const cur_td = (td_t *)phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3); + intrq->ed.head_pointer = cur_td->next_td; + free(INTRQ_TD_FROM_TD(cur_td)); + --intrq->remaining_tds; + } + /* Free final, dummy TD. */ + free(phys_to_virt(__le32_to_cpu(intrq->ed.head_pointer) & ~0x3)); + /* Free data buffer. */ + free(intrq->data); + + /* Free TDs already fetched from the done queue. */ + ohci_process_done_queue(ohci, 1); + while (intrq->head) { + intrq_td_t *const cur_td = (intrq_td_t *const )__le32_to_cpu(intrq->head); + intrq->head = intrq->head->next; + free(cur_td); + --intrq->remaining_tds; + } + + /* Mark interrupt queue to be destroyed. + ohci_process_done_queue() will free the remaining TDs + and finish the interrupt queue off once all TDs are gone. */ + intrq->destroy = 1; + + /* Save data toggle. */ + ep->toggle = __le32_to_cpu(intrq->ed.head_pointer) & ED_TOGGLE; +} + +/* 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 *const q_) +{ + intr_queue_t *const intrq = (intr_queue_t *)q_; + + u8 *data = NULL; + + /* Process done queue first, then check if we have work to do. */ + ohci_process_done_queue(OHCI_INST(intrq->endp->dev->controller), 0); + + if (intrq->head) { + /* Save pointer to processed TD and advance. */ + intrq_td_t *const cur_td = intrq->head; + intrq->head = cur_td->next; + + /* Return data buffer of this TD. */ + data = cur_td->data; + + /* Requeue this TD (i.e. copy to dummy and requeue as dummy). */ + intrq_td_t *const dummy_td = + INTRQ_TD_FROM_TD(phys_to_virt(__le32_to_cpu(intrq->ed.tail_pointer))); + ohci_fill_intrq_td(dummy_td, intrq, data); + /* Reset all but intrq pointer (i.e. init as dummy). */ + memset(cur_td, 0, sizeof(*cur_td)); + cur_td->intrq = intrq; + /* Insert into interrupt queue as dummy. */ + dummy_td->td.next_td = __le32_to_cpu(virt_to_phys(&cur_td->td)); + intrq->ed.tail_pointer = __le32_to_cpu(virt_to_phys(&cur_td->td)); + } + + return data; +} + +static void +ohci_process_done_queue(ohci_t *const ohci, const int spew_debug) +{ + int i, j; + + /* Temporary queue of interrupt queue TDs (to reverse order). */ + intrq_td_t *temp_tdq = NULL; + + /* Check if done head has been written. */ + if (!(READ_OPREG(ohci, HcInterruptStatus) & WritebackDoneHead)) + return; + /* Fetch current done head. + Lsb is only interesting for hw interrupts. */ + u32 phys_done_queue = __le32_to_cpu(ohci->hcca->HccaDoneHead) & ~1; + /* Tell host controller, he may overwrite the done head pointer. */ + ohci->opreg->HcInterruptStatus = __cpu_to_le32(WritebackDoneHead); + + i = 0; + /* Process done queue (it's in reversed order). */ + while (phys_done_queue) { + td_t *const done_td = (td_t *)phys_to_virt(phys_done_queue); + + /* Advance pointer to next TD. */ + phys_done_queue = __le32_to_cpu(done_td->next_td); + + switch (__le32_to_cpu(done_td->config) & TD_QUEUETYPE_MASK) { + case TD_QUEUETYPE_ASYNC: + /* Free processed async TDs. */ + free((void *)done_td); + break; + case TD_QUEUETYPE_INTR: { + intrq_td_t *const td = INTRQ_TD_FROM_TD(done_td); + intr_queue_t *const intrq = td->intrq; + /* Check if the corresponding interrupt + queue is still beeing processed. */ + if (intrq->destroy) { + /* Free this TD, and */ + free(td); + --intrq->remaining_tds; + /* the interrupt queue if it has no more TDs. */ + if (!intrq->remaining_tds) + free(intrq); + usb_debug("Freed TD from orphaned interrupt " + "queue, %d TDs remain.\n", + intrq->remaining_tds); + } else { + /* Save done TD to be processed. */ + td->next = temp_tdq; + temp_tdq = td; + } + break; + } + default: + break; + } + ++i; + } + if (spew_debug) + usb_debug("Processed %d done TDs.\n", i); + + j = 0; + /* Process interrupt queue TDs in right order. */ + while (temp_tdq) { + /* Save pointer of current TD and advance. */ + intrq_td_t *const cur_td = temp_tdq; + temp_tdq = temp_tdq->next; + + /* The interrupt queue for the current TD. */ + intr_queue_t *const intrq = cur_td->intrq; + /* Append to interrupt queue. */ + if (!intrq->head) { + /* First element. */ + intrq->head = intrq->tail = cur_td; + } else { + /* Insert at tail. */ + intrq->tail->next = cur_td; + intrq->tail = cur_td; + } + /* It's always the last element. */ + cur_td->next = NULL; + ++j; + } + if (spew_debug) + usb_debug("processed %d done tds, %d intr tds thereof.\n", i, j); +} + +int ob_usb_ohci_init (const char *path, uint32_t addr) +{ + hci_t *ctrl; + int i; + + usb_debug("ohci_init: %s addr = %x\n", path, addr); + ctrl = ohci_pci_init(addr); + if (!ctrl) + return 0; + + /* Init ports */ + usb_poll(); + + /* Look for a keyboard */ + for (i = 0; i < 128; i++) { + if (ctrl->devices[i] && ctrl->devices[i]->configuration) { + configuration_descriptor_t *cd; + interface_descriptor_t *intf; + + cd = (configuration_descriptor_t *)ctrl->devices[i]->configuration; + intf = (interface_descriptor_t *)(ctrl->devices[i]->configuration + cd->bLength); + usb_debug("Device at port %d is class %d\n", i, intf->bInterfaceClass); + if (intf->bInterfaceClass == hid_device && + intf->bInterfaceSubClass == hid_subclass_boot && + intf->bInterfaceProtocol == hid_boot_proto_keyboard ) { + break; + } + } + } + if ( i < 128 ) + ob_usb_hid_add_keyboard(path); + + return 1; +} |