508 lines
14 KiB
C
508 lines
14 KiB
C
/*
|
|
* This file is part of the libpayload project.
|
|
*
|
|
* Copyright (C) 2008 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.
|
|
*/
|
|
|
|
#include "usb.h"
|
|
#include "uhci.h"
|
|
#include <arch/virtual.h>
|
|
|
|
static void uhci_start (hci_t *controller);
|
|
static void uhci_stop (hci_t *controller);
|
|
static void uhci_reset (hci_t *controller);
|
|
static void uhci_shutdown (hci_t *controller);
|
|
static int uhci_packet (usbdev_t *dev, int endp, int pid, int toggle,
|
|
int length, u8 *data);
|
|
static int uhci_bulk (endpoint_t *ep, int size, u8 *data, int finalize);
|
|
static int uhci_control (usbdev_t *dev, pid_t dir, int drlen, void *devreq,
|
|
int dalen, u8 *data);
|
|
|
|
#if 0
|
|
/* dump uhci */
|
|
static void
|
|
uhci_dump (hci_t *controller)
|
|
{
|
|
printf ("dump:\nUSBCMD: %x\n", uhci_reg_read16 (controller, USBCMD));
|
|
printf ("USBSTS: %x\n", uhci_reg_read16 (controller, USBSTS));
|
|
printf ("USBINTR: %x\n", uhci_reg_read16 (controller, USBINTR));
|
|
printf ("FRNUM: %x\n", uhci_reg_read16 (controller, FRNUM));
|
|
printf ("FLBASEADD: %x\n", uhci_reg_read32 (controller, FLBASEADD));
|
|
printf ("SOFMOD: %x\n", uhci_reg_read8 (controller, SOFMOD));
|
|
printf ("PORTSC1: %x\n", uhci_reg_read16 (controller, PORTSC1));
|
|
printf ("PORTSC2: %x\n", uhci_reg_read16 (controller, PORTSC2));
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
td_dump (td_t *td)
|
|
{
|
|
printf ("%x packet (at %lx) to %x.%x failed\n", td->pid,
|
|
virt_to_phys (td), td->dev_addr, td->endp);
|
|
printf ("td (counter at %x) returns: ", td->counter);
|
|
printf (" bitstuff err: %x, ", td->status_bitstuff_err);
|
|
printf (" CRC err: %x, ", td->status_crc_err);
|
|
printf (" NAK rcvd: %x, ", td->status_nakrcvd);
|
|
printf (" Babble: %x, ", td->status_babble);
|
|
printf (" Data Buffer err: %x, ", td->status_databuf_err);
|
|
printf (" Stalled: %x, ", td->status_stalled);
|
|
printf (" Active: %x\n", td->status_active);
|
|
if (td->status_babble)
|
|
printf (" Babble because of %s\n",
|
|
td->status_bitstuff_err ? "host" : "device");
|
|
if (td->status_active)
|
|
printf (" still active - timeout?\n");
|
|
}
|
|
|
|
static void
|
|
uhci_reset (hci_t *controller)
|
|
{
|
|
/* reset */
|
|
uhci_reg_write16 (controller, USBCMD, 4);
|
|
mdelay (50);
|
|
uhci_reg_write16 (controller, USBCMD, 0);
|
|
mdelay (10);
|
|
uhci_reg_write16 (controller, USBCMD, 2);
|
|
while ((uhci_reg_read16 (controller, USBCMD) & 2) != 0)
|
|
mdelay (1);
|
|
|
|
uhci_reg_write32 (controller, FLBASEADD,
|
|
(u32) virt_to_phys (UHCI_INST (controller)->
|
|
framelistptr));
|
|
//printf ("framelist at %p\n",UHCI_INST(controller)->framelistptr);
|
|
|
|
/* disable irqs */
|
|
uhci_reg_write16 (controller, USBINTR, 0);
|
|
|
|
/* reset framelist index */
|
|
uhci_reg_write16 (controller, FRNUM, 0);
|
|
|
|
uhci_reg_mask16 (controller, USBCMD, ~0, 0xc0); // max packets, configure flag
|
|
|
|
uhci_start (controller);
|
|
}
|
|
|
|
hci_t *
|
|
uhci_init (pcidev_t addr)
|
|
{
|
|
int i;
|
|
hci_t *controller = new_controller ();
|
|
|
|
controller->instance = malloc (sizeof (uhci_t));
|
|
controller->start = uhci_start;
|
|
controller->stop = uhci_stop;
|
|
controller->reset = uhci_reset;
|
|
controller->shutdown = uhci_shutdown;
|
|
controller->packet = uhci_packet;
|
|
controller->bulk = uhci_bulk;
|
|
controller->control = uhci_control;
|
|
UHCI_INST (controller)->roothub = &(controller->devices[0]);
|
|
|
|
controller->bus_address = addr;
|
|
controller->reg_base = pci_read_config32 (controller->bus_address, 0x20) & ~1; /* ~1 clears the register type indicator that is set to 1 for IO space */
|
|
|
|
/* kill legacy support handler */
|
|
uhci_stop (controller);
|
|
mdelay (1);
|
|
uhci_reg_write16 (controller, USBSTS, 0x3f);
|
|
pci_write_config32 (controller->bus_address, 0xc0, 0x8f00);
|
|
|
|
UHCI_INST (controller)->framelistptr = memalign (0x1000, 1024 * sizeof (flistp_t *)); /* 4kb aligned to 4kb */
|
|
memset (UHCI_INST (controller)->framelistptr, 0,
|
|
1024 * sizeof (flistp_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));
|
|
|
|
UHCI_INST (controller)->qh_intr->headlinkptr.ptr =
|
|
virt_to_phys (UHCI_INST (controller)->qh_data);
|
|
UHCI_INST (controller)->qh_intr->headlinkptr.queue_head = 1;
|
|
UHCI_INST (controller)->qh_intr->elementlinkptr.ptr = 0;
|
|
UHCI_INST (controller)->qh_intr->elementlinkptr.terminate = 1;
|
|
|
|
UHCI_INST (controller)->qh_data->headlinkptr.ptr =
|
|
virt_to_phys (UHCI_INST (controller)->qh_last);
|
|
UHCI_INST (controller)->qh_data->headlinkptr.queue_head = 1;
|
|
UHCI_INST (controller)->qh_data->elementlinkptr.ptr = 0;
|
|
UHCI_INST (controller)->qh_data->elementlinkptr.terminate = 1;
|
|
|
|
UHCI_INST (controller)->qh_last->headlinkptr.ptr = 0;
|
|
UHCI_INST (controller)->qh_last->headlinkptr.terminate = 1;
|
|
UHCI_INST (controller)->qh_last->elementlinkptr.ptr = 0;
|
|
UHCI_INST (controller)->qh_last->elementlinkptr.terminate = 1;
|
|
|
|
for (i = 0; i < 1024; i++) {
|
|
UHCI_INST (controller)->framelistptr[i].ptr =
|
|
virt_to_phys (UHCI_INST (controller)->qh_intr);
|
|
UHCI_INST (controller)->framelistptr[i].terminate = 0;
|
|
UHCI_INST (controller)->framelistptr[i].queue_head = 1;
|
|
}
|
|
for (i = 1; i < 128; i++) {
|
|
init_device_entry (controller, i);
|
|
}
|
|
controller->devices[0].controller = controller;
|
|
controller->devices[0].init = uhci_rh_init;
|
|
controller->devices[0].init (&controller->devices[0]);
|
|
uhci_reset (controller);
|
|
return controller;
|
|
}
|
|
|
|
static void
|
|
uhci_shutdown (hci_t *controller)
|
|
{
|
|
if (controller == 0)
|
|
return;
|
|
detach_controller (controller);
|
|
UHCI_INST (controller)->roothub->destroy (UHCI_INST (controller)->
|
|
roothub);
|
|
uhci_reg_mask16 (controller, USBCMD, 0, 0); // stop work
|
|
free (UHCI_INST (controller)->framelistptr);
|
|
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 (hci_t *controller)
|
|
{
|
|
uhci_reg_mask16 (controller, USBCMD, ~0, 1); // start work on schedule
|
|
}
|
|
|
|
static void
|
|
uhci_stop (hci_t *controller)
|
|
{
|
|
uhci_reg_mask16 (controller, USBCMD, ~1, 0); // stop work on schedule
|
|
}
|
|
|
|
#define GET_TD(x) ((void*)(((unsigned int)(x))&~0xf))
|
|
|
|
static td_t *
|
|
wait_for_completed_qh (hci_t *controller, qh_t *qh)
|
|
{
|
|
int timeout = 1000; /* max 30 ms. */
|
|
void *current = GET_TD (qh->elementlinkptr.ptr);
|
|
while ((qh->elementlinkptr.terminate == 0) && (timeout-- > 0)) {
|
|
if (current != GET_TD (qh->elementlinkptr.ptr)) {
|
|
current = GET_TD (qh->elementlinkptr.ptr);
|
|
timeout = 1000;
|
|
}
|
|
uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers
|
|
udelay (30);
|
|
}
|
|
return (GET_TD (qh->elementlinkptr.ptr) ==
|
|
0) ? 0 : GET_TD (phys_to_virt (qh->elementlinkptr.ptr));
|
|
}
|
|
|
|
static void
|
|
wait_for_completed_td (hci_t *controller, td_t *td)
|
|
{
|
|
int timeout = 10000;
|
|
while ((td->status_active == 1)
|
|
&& ((uhci_reg_read16 (controller, USBSTS) & 2) == 0)
|
|
&& (timeout-- > 0)) {
|
|
uhci_reg_mask16 (controller, USBSTS, ~0, 0); // clear resettable registers
|
|
udelay (10);
|
|
}
|
|
}
|
|
|
|
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_t *dev, pid_t 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 = virt_to_phys (&tds[i + 1]);
|
|
tds[i].depth_first = 1;
|
|
tds[i].terminate = 0;
|
|
}
|
|
tds[count].ptr = 0;
|
|
tds[count].depth_first = 1;
|
|
tds[count].terminate = 1;
|
|
|
|
tds[0].pid = SETUP;
|
|
tds[0].dev_addr = dev->address;
|
|
tds[0].endp = endp;
|
|
tds[0].maxlen = maxlen (drlen);
|
|
tds[0].counter = 3;
|
|
tds[0].data_toggle = 0;
|
|
tds[0].lowspeed = dev->lowspeed;
|
|
tds[0].bufptr = virt_to_phys (devreq);
|
|
tds[0].status_active = 1;
|
|
|
|
int toggle = 1;
|
|
for (i = 1; i < count; i++) {
|
|
tds[i].pid = dir;
|
|
tds[i].dev_addr = dev->address;
|
|
tds[i].endp = endp;
|
|
tds[i].maxlen = maxlen (min (mlen, dalen));
|
|
tds[i].counter = 3;
|
|
tds[i].data_toggle = toggle;
|
|
tds[i].lowspeed = dev->lowspeed;
|
|
tds[i].bufptr = virt_to_phys (data);
|
|
tds[i].status_active = 1;
|
|
toggle ^= 1;
|
|
dalen -= mlen;
|
|
data += mlen;
|
|
}
|
|
|
|
tds[count].pid = (dir == OUT) ? IN : OUT;
|
|
tds[count].dev_addr = dev->address;
|
|
tds[count].endp = endp;
|
|
tds[count].maxlen = maxlen (0);
|
|
tds[count].counter = 0; /* as per linux 2.4.10 */
|
|
tds[count].data_toggle = 1;
|
|
tds[count].lowspeed = dev->lowspeed, tds[count].bufptr = 0;
|
|
tds[count].status_active = 1;
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
|
|
virt_to_phys (tds);
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
|
|
td_t *td = wait_for_completed_qh (dev->controller,
|
|
UHCI_INST (dev->controller)->
|
|
qh_data);
|
|
int result;
|
|
if (td == 0) {
|
|
result = 0;
|
|
} else {
|
|
printf ("control packet, req %x\n", req);
|
|
td_dump (td);
|
|
result = 1;
|
|
}
|
|
free (tds);
|
|
return result;
|
|
}
|
|
|
|
static int
|
|
uhci_packet (usbdev_t *dev, int endp, int pid, int toggle, int length,
|
|
unsigned char *data)
|
|
{
|
|
static td_t *td = 0;
|
|
if (td == 0)
|
|
td = memalign (16, sizeof (td_t));
|
|
|
|
memset (td, 0, sizeof (td_t));
|
|
td->ptr = 0;
|
|
td->terminate = 1;
|
|
td->queue_head = 0;
|
|
|
|
td->pid = pid;
|
|
td->dev_addr = dev->address;
|
|
td->endp = endp & 0xf;
|
|
td->maxlen = maxlen (length);
|
|
if (pid == SETUP)
|
|
td->counter = 3;
|
|
else
|
|
td->counter = 0;
|
|
td->data_toggle = toggle & 1;
|
|
td->lowspeed = dev->lowspeed;
|
|
td->bufptr = virt_to_phys (data);
|
|
|
|
td->status_active = 1;
|
|
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
|
|
virt_to_phys (td);
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
|
|
wait_for_completed_td (dev->controller, td);
|
|
if ((td->status & 0x7f) == 0) {
|
|
//printf("successfully sent a %x packet to %x.%x\n",pid, dev->address,endp);
|
|
// success
|
|
return 0;
|
|
} else {
|
|
td_dump (td);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
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 = virt_to_phys (&tds[i + 1]);
|
|
tds[i].terminate = 0;
|
|
tds[i].queue_head = 0;
|
|
tds[i].depth_first = 1;
|
|
}
|
|
tds[numpackets - 1].ptr = 0;
|
|
tds[numpackets - 1].terminate = 1;
|
|
tds[numpackets - 1].queue_head = 0;
|
|
tds[numpackets - 1].depth_first = 0;
|
|
return tds;
|
|
}
|
|
|
|
static void
|
|
fill_schedule (td_t *td, endpoint_t *ep, int length, unsigned char *data,
|
|
int *toggle)
|
|
{
|
|
td->pid = ep->direction;
|
|
td->dev_addr = ep->dev->address;
|
|
td->endp = ep->endpoint & 0xf;
|
|
td->maxlen = maxlen (length);
|
|
if (ep->direction == SETUP)
|
|
td->counter = 3;
|
|
else
|
|
td->counter = 0;
|
|
td->data_toggle = *toggle & 1;
|
|
td->lowspeed = ep->dev->lowspeed;
|
|
td->bufptr = virt_to_phys (data);
|
|
|
|
td->status_active = 1;
|
|
*toggle ^= 1;
|
|
}
|
|
|
|
static int
|
|
run_schedule (usbdev_t *dev, td_t *td)
|
|
{
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.ptr =
|
|
virt_to_phys (td);
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.queue_head = 0;
|
|
UHCI_INST (dev->controller)->qh_data->elementlinkptr.terminate = 0;
|
|
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 (endpoint_t *ep, int size, u8 *data, int finalize)
|
|
{
|
|
int maxpsize = ep->maxpacketsize;
|
|
if (maxpsize == 0)
|
|
fatal ("MaxPacketSize == 0!!!");
|
|
int numpackets = (size + maxpsize - 1 + finalize) / maxpsize;
|
|
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) {
|
|
clear_stall (ep);
|
|
free (tds);
|
|
return 1;
|
|
}
|
|
ep->toggle = toggle;
|
|
free (tds);
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
uhci_reg_write32 (hci_t *ctrl, usbreg reg, u32 value)
|
|
{
|
|
outl (value, ctrl->reg_base + reg);
|
|
}
|
|
|
|
u32
|
|
uhci_reg_read32 (hci_t *ctrl, usbreg reg)
|
|
{
|
|
return inl (ctrl->reg_base + reg);
|
|
}
|
|
|
|
void
|
|
uhci_reg_write16 (hci_t *ctrl, usbreg reg, u16 value)
|
|
{
|
|
outw (value, ctrl->reg_base + reg);
|
|
}
|
|
|
|
u16
|
|
uhci_reg_read16 (hci_t *ctrl, usbreg reg)
|
|
{
|
|
return inw (ctrl->reg_base + reg);
|
|
}
|
|
|
|
void
|
|
uhci_reg_write8 (hci_t *ctrl, usbreg reg, u8 value)
|
|
{
|
|
outb (value, ctrl->reg_base + reg);
|
|
}
|
|
|
|
u8
|
|
uhci_reg_read8 (hci_t *ctrl, usbreg reg)
|
|
{
|
|
return inb (ctrl->reg_base + reg);
|
|
}
|
|
|
|
void
|
|
uhci_reg_mask32 (hci_t *ctrl, usbreg reg, u32 andmask, u32 ormask)
|
|
{
|
|
uhci_reg_write32 (ctrl, reg,
|
|
(uhci_reg_read32 (ctrl, reg) & andmask) | ormask);
|
|
}
|
|
|
|
void
|
|
uhci_reg_mask16 (hci_t *ctrl, usbreg reg, u16 andmask, u16 ormask)
|
|
{
|
|
uhci_reg_write16 (ctrl, reg,
|
|
(uhci_reg_read16 (ctrl, reg) & andmask) | ormask);
|
|
}
|
|
|
|
void
|
|
uhci_reg_mask8 (hci_t *ctrl, usbreg reg, u8 andmask, u8 ormask)
|
|
{
|
|
uhci_reg_write8 (ctrl, reg,
|
|
(uhci_reg_read8 (ctrl, reg) & andmask) | ormask);
|
|
}
|