#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<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"); }; };