#define UHCI_USBLEGSUP 0x00c0 /* legacy support */ #define UHCI_USBLEGSUP_DEFAULT 0x2000 /* only PIRQ enable set */ #define UHCI_USBLEGSUP_RWC 0x8f00 /* the R/WC bits */ #define UHCI_USBLEGSUP_RO 0x5040 /* R/O and reserved bits */ #define UHCI_USBCMD 0 /* command register */ #define UHCI_USBINTR 4 /* interrupt register */ #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. */ out16(hc->iobase + UHCI_USBCMD, 0); out16(hc->iobase + UHCI_USBINTR, 0); 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; } int hc_interrupt(void *data) { hc_t *hc = (hc_t*)data; // printf("USB interrupt\n"); request_t *rq; u16_t status; status = in16(hc->iobase + USBSTS); if (!(status & ~USBSTS_HCH)) /* shared interrupt, not mine */ return 0; out16(hc->iobase + USBSTS, status); /* Clear it */ rq = (request_t*)hc->rq_list.next; while( &rq->list != &hc->rq_list) { request_t *rtmp; td_t *td; rtmp = rq; rq = (request_t*)rq->list.next; td = rtmp->td_tail; if( td->status & TD_CTRL_ACTIVE) continue; list_del(&rtmp->list); RaiseEvent(rtmp->evh, 0, &rtmp->event); }; return 1; }; bool init_hc(hc_t *hc) { int port; u32_t ifl; u16_t dev_status; td_t *td; 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; hc->td_pool = dma_pool_create("uhci_td", NULL, sizeof(td_t), 16, 0); if (!hc->td_pool) { dbgprintf("unable to create td dma_pool\n"); goto err_create_td_pool; } for (i = 0; i < UHCI_NUM_SKELQH; i++) { qh_t *qh = alloc_qh(); qh->qlink = 1; qh->qelem = 1; hc->qh[i] = qh; } for (i = SKEL_ISO + 1; i < SKEL_ASYNC; ++i) hc->qh[i]->qlink = hc->qh[SKEL_ASYNC]->dma | 2; for (i = 0; i < 1024; i++) { int qnum; qnum = 8 - (int) __bsf( i | 1024); if (qnum <= 1) qnum = 9; hc->frame_base[i] = hc->qh[qnum]->dma | 2; } mb(); /* 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 + UHCI_USBINTR, 4); printf("set handler %d ", hc->irq_line); delay(100/10); AttachIntHandler(hc->irq_line, hc_interrupt, hc); printf("done\n"); delay(100/10); pciWriteWord(hc->PciTag, UHCI_USBLEGSUP, UHCI_USBLEGSUP_DEFAULT); out16(hc->iobase + USBCMD, USBCMD_RS | USBCMD_CF | USBCMD_MAXP); for (port = 0; port < hc->numports; ++port) out16(hc->iobase + USBPORTSC1 + (port * 2), 0x200); for (port = 0; port < 2; ++port) { time_t timeout; delay(100/10); 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 = kmalloc(sizeof(udev_t),0); 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); INIT_LIST_HEAD(&dev->list); dev->host = hc; 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_add_tail(&dev->list, &newdev_list); hc->port_map |= 1<iobase + USBPORTSC1 + (port * 2), 0); } break; }; }; }; return true; err_create_td_pool: KernelFree(hc->frame_base); return false; }; 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; } #define ALIGN16(x) (((x)+15)&~15) #define MakePtr( cast, ptr, addValue ) (cast)((addr_t)(ptr)+(addr_t)(addValue)) request_t *alloc_rq_buffer(udev_t *dev, endp_t *enp, u32_t dir, size_t data_size) { size_t packet_size = dev->ep0_size; int dsize = data_size; size_t buf_size; addr_t buf_dma; addr_t td_dma; addr_t data_dma; request_t *rq; td_t *td, *td_prev; int td_count = 0; while(dsize > 0) { td_count++; dsize-= packet_size; }; buf_size = ALIGN16(sizeof(request_t)) + ALIGN16(data_size) + td_count*sizeof(td_t); rq = (request_t*)hcd_buffer_alloc(buf_size, &buf_dma); memset(rq, 0, buf_size); data_dma = buf_dma + ALIGN16(sizeof(request_t)); td_dma = data_dma + ALIGN16(data_size); INIT_LIST_HEAD(&rq->list); rq->data = MakePtr(addr_t, rq, ALIGN16(sizeof(request_t))); td = MakePtr(td_t*, rq->data, ALIGN16(data_size)); rq->td_head = td; rq->size = data_size; rq->dev = dev; td_prev = NULL; dsize = data_size; while(dsize != 0) { if ( dsize < packet_size) { packet_size = dsize; }; td->dma = td_dma; td->link = 1; if( td_prev ) td_prev->link = td->dma | 4; td->status = TD_CTRL_ACTIVE | dev->speed; td->token = TOKEN(packet_size,enp->toggle,enp->address, dev->addr,dir); td->buffer = data_dma; td->bk = td_prev; td_prev = td; td++; td_dma+= sizeof(td_t); data_dma+= packet_size; dsize-= packet_size; enp->toggle ^= DATA1; }; td_prev->status |= TD_CTRL_IOC; rq->td_tail = td_prev; rq->evh = CreateEvent(NULL, MANUAL_DESTROY); if(rq->evh.handle == 0) printf("%s: epic fail\n", __FUNCTION__); rq->event.code = 0xFF000001; rq->event.data[0] = (addr_t)rq; 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; hc_t *hc = dev->host; addr_t td_dma = 0; bool retval; request_t *rq = (request_t*)kmalloc(sizeof(request_t),0); INIT_LIST_HEAD(&rq->list); rq->data = (addr_t)data; rq->size = req_size; rq->dev = dev; td0 = dma_pool_alloc(hc->td_pool, 0, &td_dma); td0->dma = td_dma; // dbgprintf("alloc td0 %x dma %x\n", td0, td_dma); 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 > 0) { if ( size < packet_size) { packet_size = size; }; td = dma_pool_alloc(hc->td_pool, 0, &td_dma); td->dma = td_dma; // dbgprintf("alloc td %x dma %x\n", td, td->dma); td_prev->link = td->dma | 4; td->status = TD_CTRL_ACTIVE | 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; } td = dma_pool_alloc(hc->td_pool, 0, &td_dma); td->dma = td_dma; // dbgprintf("alloc td %x dma %x\n", td, td->dma); td_prev->link = td->dma | 4; pid = (pid == DIN) ? DOUT : DIN; td->link = 1; td->status = TD_CTRL_ACTIVE | TD_CTRL_IOC | dev->speed ; td->token = (0x7FF<<21)|DATA1|(dev->addr<<8)|pid; td->buffer = 0; td->bk = td_prev; rq->td_head = td0; rq->td_tail = td; rq->evh = CreateEvent(NULL, MANUAL_DESTROY); if(rq->evh.handle == 0) printf("%s: epic fail\n", __FUNCTION__); rq->event.code = 0xFF000001; rq->event.data[0] = (addr_t)rq; u32_t efl = safe_cli(); list_add_tail(&rq->list, &dev->host->rq_list); qh = dev->host->qh[SKEL_ASYNC]; qh->qelem = td0->dma; mb(); safe_sti(efl); WaitEvent(rq->evh); dbgprintf("td0 status 0x%0x\n", td0->status); dbgprintf("td status 0x%0x\n", td->status); 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; qh->qelem = 1; mb(); do { td_prev = td->bk; dma_pool_free(hc->td_pool, td, td->dma); td = td_prev; }while( td != NULL); /* delete event; */ kfree(rq); 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)) { dbgprintf("%s epic fail\n",__FUNCTION__); 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_del(&dev->list); 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"); }; };