Subversion Repositories Kolibri OS

Compare Revisions

Regard whitespace Rev 1028 → Rev 1029

/drivers/usb/uhci/hcd.inc
0,0 → 1,604
 
#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");
};
};