fb8dc89b4d
git-svn-id: svn://kolibrios.org@1029 a494cfbc-eb01-0410-851d-a64ba20cac60
605 lines
17 KiB
C++
605 lines
17 KiB
C++
|
|
#define UHCI_USBLEGSUP 0x00c0 /* legacy support */
|
|
#define UHCI_USBCMD 0 /* command register */
|
|
#define UHCI_USBINTR 4 /* interrupt register */
|
|
#define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */
|
|
#define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */
|
|
#define UHCI_USBCMD_RUN 0x0001 /* RUN/STOP bit */
|
|
#define UHCI_USBCMD_HCRESET 0x0002 /* Host Controller reset */
|
|
#define UHCI_USBCMD_EGSM 0x0008 /* Global Suspend Mode */
|
|
#define UHCI_USBCMD_CONFIGURE 0x0040 /* Config Flag */
|
|
#define UHCI_USBINTR_RESUME 0x0002 /* Resume interrupt enable */
|
|
|
|
|
|
#define USBCMD 0
|
|
#define USBCMD_RS 0x0001 /* Run/Stop */
|
|
#define USBCMD_HCRESET 0x0002 /* Host reset */
|
|
#define USBCMD_GRESET 0x0004 /* Global reset */
|
|
#define USBCMD_EGSM 0x0008 /* Global Suspend Mode */
|
|
#define USBCMD_FGR 0x0010 /* Force Global Resume */
|
|
#define USBCMD_SWDBG 0x0020 /* SW Debug mode */
|
|
#define USBCMD_CF 0x0040 /* Config Flag (sw only) */
|
|
#define USBCMD_MAXP 0x0080 /* Max Packet (0 = 32, 1 = 64) */
|
|
|
|
#define USBSTS 2
|
|
#define USBSTS_USBINT 0x0001 /* Interrupt due to IOC */
|
|
#define USBSTS_ERROR 0x0002 /* Interrupt due to error */
|
|
#define USBSTS_RD 0x0004 /* Resume Detect */
|
|
#define USBSTS_HSE 0x0008 /* Host System Error: PCI problems */
|
|
#define USBSTS_HCPE 0x0010 /* Host Controller Process Error:
|
|
* the schedule is buggy */
|
|
#define USBSTS_HCH 0x0020 /* HC Halted */
|
|
|
|
|
|
#define USBFRNUM 6
|
|
#define USBFLBASEADD 8
|
|
#define USBSOF 12
|
|
#define USBSOF_DEFAULT 64 /* Frame length is exactly 1 ms */
|
|
|
|
#define USBPORTSC1 16
|
|
#define USBPORTSC2 18
|
|
|
|
#define UHCI_RH_MAXCHILD 7
|
|
|
|
|
|
/*
|
|
* Make sure the controller is completely inactive, unable to
|
|
* generate interrupts or do DMA.
|
|
*/
|
|
void uhci_reset_hc(hc_t *hc)
|
|
{
|
|
/* Turn off PIRQ enable and SMI enable. (This also turns off the
|
|
* BIOS's USB Legacy Support.) Turn off all the R/WC bits too.
|
|
*/
|
|
pciWriteWord(hc->PciTag, UHCI_USBLEGSUP, UHCI_USBLEGSUP_RWC);
|
|
|
|
/* Reset the HC - this will force us to get a
|
|
* new notification of any already connected
|
|
* ports due to the virtual disconnect that it
|
|
* implies.
|
|
*/
|
|
out16(hc->iobase + UHCI_USBCMD, UHCI_USBCMD_HCRESET);
|
|
__asm__ __volatile__ ("":::"memory");
|
|
|
|
delay(20/10);
|
|
|
|
if (in16(hc->iobase + UHCI_USBCMD) & UHCI_USBCMD_HCRESET)
|
|
dbgprintf("HCRESET not completed yet!\n");
|
|
|
|
/* Just to be safe, disable interrupt requests and
|
|
* make sure the controller is stopped.
|
|
*/
|
|
out16(hc->iobase + UHCI_USBINTR, 0);
|
|
out16(hc->iobase + UHCI_USBCMD, 0);
|
|
};
|
|
|
|
int uhci_check_and_reset_hc(hc_t *hc)
|
|
{
|
|
u16_t legsup;
|
|
unsigned int cmd, intr;
|
|
|
|
/*
|
|
* When restarting a suspended controller, we expect all the
|
|
* settings to be the same as we left them:
|
|
*
|
|
* PIRQ and SMI disabled, no R/W bits set in USBLEGSUP;
|
|
* Controller is stopped and configured with EGSM set;
|
|
* No interrupts enabled except possibly Resume Detect.
|
|
*
|
|
* If any of these conditions are violated we do a complete reset.
|
|
*/
|
|
legsup = pciReadWord(hc->PciTag, UHCI_USBLEGSUP);
|
|
if (legsup & ~(UHCI_USBLEGSUP_RO | UHCI_USBLEGSUP_RWC)) {
|
|
dbgprintf("%s: legsup = 0x%04x\n",__FUNCTION__, legsup);
|
|
goto reset_needed;
|
|
}
|
|
|
|
cmd = in16(hc->iobase + UHCI_USBCMD);
|
|
if ( (cmd & UHCI_USBCMD_RUN) ||
|
|
!(cmd & UHCI_USBCMD_CONFIGURE) ||
|
|
!(cmd & UHCI_USBCMD_EGSM))
|
|
{
|
|
dbgprintf("%s: cmd = 0x%04x\n", __FUNCTION__, cmd);
|
|
goto reset_needed;
|
|
}
|
|
|
|
intr = in16(hc->iobase + UHCI_USBINTR);
|
|
if (intr & (~UHCI_USBINTR_RESUME))
|
|
{
|
|
dbgprintf("%s: intr = 0x%04x\n", __FUNCTION__, intr);
|
|
goto reset_needed;
|
|
}
|
|
return 0;
|
|
|
|
reset_needed:
|
|
dbgprintf("Performing full reset\n");
|
|
uhci_reset_hc(hc);
|
|
return 1;
|
|
}
|
|
|
|
|
|
Bool init_hc(hc_t *hc)
|
|
{
|
|
int port;
|
|
u32_t ifl;
|
|
u16_t dev_status;
|
|
int i;
|
|
|
|
dbgprintf("\n\ninit uhci %x\n\n", hc->pciId);
|
|
|
|
for(i=0;i<6;i++)
|
|
{
|
|
if(hc->ioBase[i]){
|
|
hc->iobase = hc->ioBase[i];
|
|
// dbgprintf("Io base_%d 0x%x\n", i,hc->ioBase[i]);
|
|
break;
|
|
};
|
|
};
|
|
|
|
/* The UHCI spec says devices must have 2 ports, and goes on to say
|
|
* they may have more but gives no way to determine how many there
|
|
* are. However according to the UHCI spec, Bit 7 of the port
|
|
* status and control register is always set to 1. So we try to
|
|
* use this to our advantage. Another common failure mode when
|
|
* a nonexistent register is addressed is to return all ones, so
|
|
* we test for that also.
|
|
*/
|
|
for (port = 0; port < 2; port++)
|
|
{
|
|
u32_t status;
|
|
|
|
status = in16(hc->iobase + USBPORTSC1 + (port * 2));
|
|
dbgprintf("port%d status %x\n", port, status);
|
|
if (!(status & 0x0080) || status == 0xffff)
|
|
break;
|
|
}
|
|
dbgprintf("detected %d ports\n\n", port);
|
|
|
|
hc->numports = port;
|
|
|
|
/* Kick BIOS off this hardware and reset if the controller
|
|
* isn't already safely quiescent.
|
|
*/
|
|
uhci_check_and_reset_hc(hc);
|
|
|
|
hc->frame_base = (u32_t*)KernelAlloc(4096);
|
|
hc->frame_dma = GetPgAddr(hc->frame_base);
|
|
hc->frame_number = 0;
|
|
|
|
qh_t *qh = alloc_qh();
|
|
|
|
qh->qlink = 1;
|
|
qh->qelem = 1;
|
|
|
|
hc->qh1 = qh;
|
|
|
|
// dbgprintf("alloc qh %x dma %x\n", qh, qh->dma);
|
|
|
|
for(i=0; i<1024; i++)
|
|
hc->frame_base[i] = qh->dma | 2;
|
|
|
|
|
|
/* Set the frame length to the default: 1 ms exactly */
|
|
out8(hc->iobase + USBSOF, USBSOF_DEFAULT);
|
|
|
|
/* Store the frame list base address */
|
|
out32(hc->iobase + USBFLBASEADD, hc->frame_dma);
|
|
|
|
/* Set the current frame number */
|
|
out16(hc->iobase + USBFRNUM, 0);
|
|
|
|
out16(hc->iobase + USBSTS, 0x3F);
|
|
out16(hc->iobase + USBCMD, USBCMD_RS | USBCMD_CF |
|
|
USBCMD_MAXP);
|
|
|
|
for (port = 0; port < hc->numports; ++port)
|
|
out16(hc->iobase + USBPORTSC1 + (port * 2), 0x200);
|
|
delay(100/10);
|
|
|
|
for (port = 0; port < 2; ++port)
|
|
{
|
|
time_t timeout;
|
|
|
|
u32_t status = in16(hc->iobase + USBPORTSC1 + (port * 2));
|
|
dbgprintf("port%d status %x\n", port, status);
|
|
|
|
out16(hc->iobase + USBPORTSC1 + (port * 2), 0);
|
|
|
|
timeout = 100/10;
|
|
while(timeout--)
|
|
{
|
|
delay(10/10);
|
|
status = in16(hc->iobase + USBPORTSC1 + (port * 2));
|
|
if(status & 1)
|
|
{
|
|
udev_t *dev = malloc(sizeof(udev_t));
|
|
|
|
out16(hc->iobase + USBPORTSC1 + (port * 2), 0x0E);
|
|
|
|
delay(20/10);
|
|
|
|
dbgprintf("enable port\n");
|
|
status = in16(hc->iobase + USBPORTSC1 + (port * 2));
|
|
dbgprintf("port%d status %x\n", port, status);
|
|
|
|
link_initialize(&dev->link);
|
|
dev->id = 0;
|
|
dev->host = hc;
|
|
dev->addr = 0;
|
|
dev->port = port;
|
|
dev->ep0_size = 8;
|
|
dev->status = status;
|
|
|
|
dbgprintf("port%d connected", port);
|
|
if(status & 4)
|
|
dbgprintf(" enabled");
|
|
else
|
|
dbgprintf(" disabled");
|
|
if(status & 0x100){
|
|
dev->speed = 0x4000000;
|
|
dbgprintf(" low speed\n");
|
|
} else {
|
|
dev->speed = 0;
|
|
dbgprintf(" full speed\n");
|
|
};
|
|
|
|
if(set_address(dev)) {
|
|
list_prepend(&dev->link, &newdev_list);
|
|
hc->port_map |= 1<<port;
|
|
}
|
|
else {
|
|
free(dev);
|
|
out16(hc->iobase + USBPORTSC1 + (port * 2), 0);
|
|
}
|
|
break;
|
|
};
|
|
};
|
|
};
|
|
return TRUE;
|
|
};
|
|
|
|
u16_t __attribute__((aligned(16)))
|
|
req_descr[4] = {0x0680,0x0100,0x0000,8};
|
|
|
|
/*
|
|
IN(69) OUT(E1) SETUP(2D)
|
|
SETUP(0) IN(1)
|
|
SETUP(0) OUT(1) OUT(0) OUT(1)...IN(1)
|
|
SETUP(0) IN(1) IN(0) IN(1)...OUT(0)
|
|
*/
|
|
|
|
|
|
Bool set_address(udev_t *dev)
|
|
{
|
|
static udev_id = 0;
|
|
static udev_addr = 0;
|
|
static u16_t __attribute__((aligned(16)))
|
|
req_addr[4] = {0x0500,0x0001,0x0000,0x0000};
|
|
|
|
static u16_t __attribute__((aligned(16)))
|
|
req_descr[4] = {0x0680,0x0100,0x0000,8};
|
|
|
|
static u32_t data[2] __attribute__((aligned(16)));
|
|
|
|
qh_t *qh;
|
|
td_t *td0, *td1, *td2;
|
|
u32_t dev_status;
|
|
count_t timeout;
|
|
int address;
|
|
|
|
address = ++udev_addr;
|
|
|
|
req_addr[1] = address;
|
|
|
|
if( !ctrl_request(dev, &req_addr, DOUT, NULL, 0))
|
|
return FALSE;
|
|
|
|
dev->addr = address;
|
|
dev->id = (++udev_id << 8) | address;
|
|
|
|
dbgprintf("set address %d\n", address);
|
|
|
|
data[0] = 0;
|
|
data[1] = 0;
|
|
|
|
if( !ctrl_request(dev, &req_descr, DIN, data, 8))
|
|
return FALSE;
|
|
|
|
dev_descr_t *descr = (dev_descr_t*)&data;
|
|
dev->ep0_size = descr->bMaxPacketSize0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
request_t *create_request(udev_t *dev, endp_t *enp, u32_t dir,
|
|
void *data, size_t req_size)
|
|
{
|
|
td_t *td, *td_prev;
|
|
addr_t data_dma;
|
|
|
|
request_t *rq = (request_t*)malloc(sizeof(request_t));
|
|
|
|
link_initialize(&rq->link);
|
|
|
|
rq->td_head = 0;
|
|
rq->td_tail = 0;
|
|
|
|
rq->data = (addr_t)data;
|
|
rq->size = req_size;
|
|
rq->dev = dev;
|
|
|
|
if(data)
|
|
data_dma = DMA(data);
|
|
|
|
td_prev = NULL;
|
|
|
|
while(req_size >= enp->size)
|
|
{
|
|
td = alloc_td();
|
|
td->link = 1;
|
|
|
|
if(rq->td_head == NULL)
|
|
rq->td_head = td;
|
|
|
|
if( td_prev )
|
|
td_prev->link = td->dma | 4;
|
|
td->status = 0x00800000 | dev->speed;
|
|
td->token = TOKEN(enp->size,enp->toggle,enp->address,
|
|
dev->addr,dir);
|
|
td->buffer = data_dma;
|
|
td->bk = td_prev;
|
|
|
|
td_prev = td;
|
|
|
|
data_dma+= enp->size;
|
|
req_size-= enp->size;
|
|
enp->toggle ^= DATA1;
|
|
}
|
|
if(req_size)
|
|
{
|
|
td = alloc_td();
|
|
td->link = 1;
|
|
|
|
if(rq->td_head == NULL)
|
|
rq->td_head = td;
|
|
|
|
if( td_prev )
|
|
td_prev->link = td->dma | 4;
|
|
|
|
td->status = 0x00800000 | dev->speed;
|
|
td->token = TOKEN( req_size, enp->toggle, enp->address,
|
|
dev->addr, dir);
|
|
td->buffer = data_dma;
|
|
td->bk = td_prev;
|
|
|
|
enp->toggle ^= DATA1;
|
|
}
|
|
rq->td_tail = td;
|
|
/*
|
|
dbgprintf("create request %x\n"
|
|
"head %x\n"
|
|
"tail %x\n"
|
|
"data %x\n"
|
|
"size %x\n",
|
|
rq, rq->td_head, rq->td_tail,
|
|
rq->data, rq->size);
|
|
*/
|
|
return rq;
|
|
}
|
|
|
|
Bool ctrl_request(udev_t *dev, void *req, u32_t pid,
|
|
void *data, size_t req_size)
|
|
{
|
|
size_t packet_size = dev->ep0_size;
|
|
size_t size = req_size;
|
|
u32_t toggle = DATA1;
|
|
|
|
td_t *td0, *td, *td_prev;
|
|
qh_t *qh;
|
|
addr_t data_dma = 0;
|
|
Bool retval;
|
|
|
|
td0 = alloc_td();
|
|
|
|
td0->status = 0x00800000 | dev->speed;
|
|
td0->token = TOKEN( 8, DATA0, 0, dev->addr, 0x2D);
|
|
td0->buffer = DMA(req);
|
|
td0->bk = NULL;
|
|
|
|
if(data)
|
|
data_dma = DMA(data);
|
|
|
|
td_prev = td0;
|
|
|
|
while(size >= packet_size)
|
|
{
|
|
td = alloc_td();
|
|
td_prev->link = td->dma | 4;
|
|
td->status = 0x00800000 | dev->speed;
|
|
td->token = TOKEN(packet_size, toggle, 0,dev->addr, pid);
|
|
td->buffer = data_dma;
|
|
td->bk = td_prev;
|
|
|
|
td_prev = td;
|
|
|
|
data_dma+= packet_size;
|
|
size-= packet_size;
|
|
toggle ^= DATA1;
|
|
}
|
|
if(size)
|
|
{
|
|
td = alloc_td();
|
|
td_prev->link = td->dma | 4;
|
|
td->status = 0x00800000 | dev->speed;
|
|
td->token = ((size-1)<<21)|toggle|(dev->addr<<8)|pid;
|
|
td->buffer = data_dma;
|
|
td->bk = td_prev;
|
|
|
|
td_prev = td;
|
|
|
|
data_dma+= packet_size;
|
|
size-= packet_size;
|
|
toggle ^= DATA1;
|
|
}
|
|
|
|
td = alloc_td();
|
|
td_prev->link = td->dma | 4;
|
|
|
|
pid = (pid == DIN) ? DOUT : DIN;
|
|
|
|
td->link = 1;
|
|
td->status = 0x00800000 | dev->speed;
|
|
td->token = (0x7FF<<21)|DATA1|(dev->addr<<8)|pid;
|
|
td->buffer = 0;
|
|
td->bk = td_prev;
|
|
|
|
qh = dev->host->qh1;
|
|
|
|
qh->qelem = td0->dma;
|
|
__asm__ __volatile__ ("":::"memory");
|
|
|
|
count_t timeout = 25;
|
|
while(timeout--){
|
|
delay(10/10);
|
|
if( !(td->status & TD_CTRL_ACTIVE))
|
|
break;
|
|
}
|
|
|
|
if( (td0->status & TD_ANY_ERROR) ||
|
|
(td_prev->status & TD_ANY_ERROR) ||
|
|
(td->status & TD_ANY_ERROR))
|
|
{
|
|
u32_t dev_status = in16(dev->host->iobase + USBSTS);
|
|
|
|
dbgprintf("\nframe %x, cmd %x status %x\n",
|
|
in16(dev->host->iobase + USBFRNUM),
|
|
in16(dev->host->iobase + USBCMD),
|
|
dev_status);
|
|
dbgprintf("td0 status %x\n",td0->status);
|
|
dbgprintf("td_prev status %x\n",td_prev->status);
|
|
dbgprintf("td status %x\n",td->status);
|
|
dbgprintf("qh %x \n", qh->qelem);
|
|
|
|
retval = FALSE;
|
|
} else retval = TRUE;
|
|
|
|
do
|
|
{
|
|
td_prev = td->bk;
|
|
free_td(td);
|
|
td = td_prev;
|
|
}while( td != NULL);
|
|
|
|
return retval;
|
|
};
|
|
|
|
|
|
Bool init_device(udev_t *dev)
|
|
{
|
|
static u16_t __attribute__((aligned(16)))
|
|
req_descr[4] = {0x0680,0x0100,0x0000,18};
|
|
|
|
static u16_t __attribute__((aligned(16)))
|
|
req_conf[4] = {0x0680,0x0200,0x0000,9};
|
|
|
|
static dev_descr_t __attribute__((aligned(16))) descr;
|
|
|
|
interface_descr_t *interface;
|
|
|
|
u32_t data[8];
|
|
|
|
u8_t *dptr;
|
|
conf_descr_t *conf;
|
|
|
|
dbgprintf("\ninit device %x, host %x, port %d\n\n",
|
|
dev->id, dev->host->pciId, dev->port);
|
|
|
|
if( !ctrl_request(dev, req_descr, DIN, &descr, 18))
|
|
return;
|
|
|
|
dev->dev_descr = descr;
|
|
|
|
dbgprintf("device descriptor:\n\n"
|
|
"bLength %d\n"
|
|
"bDescriptorType %d\n"
|
|
"bcdUSB %x\n"
|
|
"bDeviceClass %x\n"
|
|
"bDeviceSubClass %x\n"
|
|
"bDeviceProtocol %x\n"
|
|
"bMaxPacketSize0 %d\n"
|
|
"idVendor %x\n"
|
|
"idProduct %x\n"
|
|
"bcdDevice %x\n"
|
|
"iManufacturer %x\n"
|
|
"iProduct %x\n"
|
|
"iSerialNumber %x\n"
|
|
"bNumConfigurations %d\n\n",
|
|
descr.bLength, descr.bDescriptorType,
|
|
descr.bcdUSB, descr.bDeviceClass,
|
|
descr.bDeviceSubClass, descr.bDeviceProtocol,
|
|
descr.bMaxPacketSize0, descr.idVendor,
|
|
descr.idProduct, descr.bcdDevice,
|
|
descr.iManufacturer, descr.iProduct,
|
|
descr.iSerialNumber, descr.bNumConfigurations);
|
|
|
|
req_conf[3] = 8;
|
|
if( !ctrl_request(dev, req_conf, DIN, &data, 8))
|
|
return;
|
|
|
|
conf = (conf_descr_t*)&data;
|
|
|
|
size_t conf_size = conf->wTotalLength;
|
|
|
|
req_conf[3] = conf_size;
|
|
conf = malloc(conf_size);
|
|
|
|
if( !ctrl_request(dev, req_conf, DIN, conf, conf_size))
|
|
return;
|
|
|
|
dptr = (u8_t*)conf;
|
|
dptr+= conf->bLength;
|
|
|
|
dbgprintf("configuration descriptor\n\n"
|
|
"bLength %d\n"
|
|
"bDescriptorType %d\n"
|
|
"wTotalLength %d\n"
|
|
"bNumInterfaces %d\n"
|
|
"bConfigurationValue %x\n"
|
|
"iConfiguration %d\n"
|
|
"bmAttributes %x\n"
|
|
"bMaxPower %dmA\n\n",
|
|
conf->bLength,
|
|
conf->bDescriptorType,
|
|
conf->wTotalLength,
|
|
conf->bNumInterfaces,
|
|
conf->bConfigurationValue,
|
|
conf->iConfiguration,
|
|
conf->bmAttributes,
|
|
conf->bMaxPower*2);
|
|
|
|
interface = (interface_descr_t*)dptr;
|
|
|
|
switch(interface->bInterfaceClass)
|
|
{
|
|
case USB_CLASS_AUDIO:
|
|
dbgprintf( "audio device\n");
|
|
break;
|
|
case USB_CLASS_HID:
|
|
dev->conf = conf;
|
|
list_remove(&dev->link);
|
|
return init_hid(dev);
|
|
|
|
case USB_CLASS_PRINTER:
|
|
dbgprintf("printer\n");
|
|
break;
|
|
case USB_CLASS_MASS_STORAGE:
|
|
dbgprintf("mass storage device\n");
|
|
break;
|
|
case USB_CLASS_HUB:
|
|
dbgprintf("hub device\n");
|
|
break;
|
|
default:
|
|
dbgprintf("unknown device\n");
|
|
};
|
|
};
|