3 * - By John Hodge (thePowersGang)
9 #define VERSION VER2(0,1)
20 #define EHCI_MAX_CONTROLLERS 4
21 #define EHCI_THREADEVENT_IOC THREAD_EVENT_USER1
22 #define EHCI_THREADEVENT_PORTSC THREAD_EVENT_USER2
23 #define EHCI_THREADEVENT_IAAD THREAD_EVENT_USER3
26 int EHCI_Initialise(char **Arguments);
27 int EHCI_Cleanup(void);
28 int EHCI_InitController(tPAddr BaseAddress, Uint8 InterruptNum);
30 void *EHCI_InitInterrupt(void *Ptr, int Endpoint, int bInput, int Period,
31 tUSBHostCb Cb, void *CbData, void *Buf, size_t Length);
32 void *EHCI_InitIsoch (void *Ptr, int Endpoint, size_t MaxPacketSize);
33 void *EHCI_InitControl(void *Ptr, int Endpoint, size_t MaxPacketSize);
34 void *EHCI_InitBulk (void *Ptr, int Endpoint, size_t MaxPacketSize);
35 void EHCI_RemEndpoint(void *Ptr, void *Handle);
36 void *EHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
38 const void *SetupData, size_t SetupLength,
39 const void *OutData, size_t OutLength,
40 void *InData, size_t InLength
42 void *EHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length);
43 void EHCI_FreeOp(void *Ptr, void *Handle);
44 Uint32 EHCI_int_RootHub_FeatToMask(int Feat);
45 void EHCI_RootHub_SetPortFeature(void *Ptr, int Port, int Feat);
46 void EHCI_RootHub_ClearPortFeature(void *Ptr, int Port, int Feat);
47 int EHCI_RootHub_GetPortStatus(void *Ptr, int Port, int Flag);
49 tEHCI_Endpoint *EHCI_int_AllocateEndpt(tEHCI_Controller *Ctrlr, int Endpoint, size_t MPS, int PeriodPow2);
50 tEHCI_qTD *EHCI_int_AllocateTD(tEHCI_Controller *Cont, int PID, void *Data, size_t Length);
51 void EHCI_int_DeallocateTD(tEHCI_Controller *Cont, tEHCI_qTD *TD);
52 tEHCI_QH *EHCI_int_AllocateQH(tEHCI_Controller *Cont, tEHCI_Endpoint *Endpoint, tUSBHostCb Cb, void *CbPtr);
53 void EHCI_int_DeallocateQH(tEHCI_Controller *Cont, tEHCI_QH *QH);
54 void EHCI_InterruptHandler(int IRQ, void *Ptr);
56 void EHCI_int_AppendQHToAsync(tEHCI_Controller *Cont, tEHCI_QH *QH);
57 void EHCI_int_InterruptThread(void *ControllerPtr);
60 MODULE_DEFINE(0, VERSION, USB_EHCI, EHCI_Initialise, NULL, "USB_Core", NULL);
61 tEHCI_Controller gaEHCI_Controllers[EHCI_MAX_CONTROLLERS];
62 tUSBHostDef gEHCI_HostDef = {
63 .InitInterrupt = EHCI_InitInterrupt,
64 .InitIsoch = EHCI_InitIsoch,
65 .InitControl = EHCI_InitControl,
66 .InitBulk = EHCI_InitBulk,
67 .RemEndpoint = EHCI_RemEndpoint,
69 .SendControl = EHCI_SendControl,
70 .SendBulk = EHCI_SendBulk,
71 .FreeOp = EHCI_FreeOp,
73 .CheckPorts = NULL, // No need
74 .SetPortFeature = EHCI_RootHub_SetPortFeature,
75 .ClearPortFeature = EHCI_RootHub_ClearPortFeature,
76 .GetPortStatus = EHCI_RootHub_GetPortStatus,
80 int EHCI_Initialise(char **Arguments)
82 for( int id = -1; (id = PCI_GetDeviceByClass(0x0C0320, 0xFFFFFF, id)) >= 0; )
84 Uint32 addr = PCI_GetValidBAR(id, 0, PCI_BARTYPE_MEM);
86 Log_Error("EHCI", "PCI%i BAR0 is not memory", id);
89 Uint8 irq = PCI_GetIRQ(id);
91 Log_Error("EHCI", "PCI%i has no IRQ", id);
95 Log_Log("ECHI", "Controller at PCI %i 0x%x IRQ 0x%x", id, addr, irq);
97 if( EHCI_InitController(addr, irq) )
99 // TODO: Detect other forms of failure than "out of slots"
104 for( int i = 0; Arguments && Arguments[i]; i ++ )
106 char *pos = Arguments[i], *next;
107 LOG("pos = '%s'", pos);
108 tPAddr base = strtoull(pos, &next, 16);
112 LOG("pos = '%s'", pos);
115 LOG("pos = '%s'", pos);
116 int irq = strtol(pos, &next, 16);
121 LOG("base=%x, irq=%i", base, irq);
122 if( EHCI_InitController(base, irq) )
131 int EHCI_Cleanup(void)
136 // --- Driver Init ---
137 int EHCI_InitController(tPAddr BaseAddress, Uint8 InterruptNum)
139 tEHCI_Controller *cont = NULL;
141 ENTER("PBaseAddress iInterruptNum",
142 BaseAddress, InterruptNum);
144 // Allocate a controller structure
145 for( int i = 0; i < EHCI_MAX_CONTROLLERS; i ++ )
147 if( gaEHCI_Controllers[i].PhysBase == 0 ) {
148 cont = &gaEHCI_Controllers[i];
149 cont->PhysBase = BaseAddress;
154 Log_Notice("EHCI", "Too many controllers (EHCI_MAX_CONTROLLERS=%i)",
155 EHCI_MAX_CONTROLLERS);
160 // - Nuke a couple of fields so error handling code doesn't derp
161 cont->CapRegs = NULL;
162 cont->PeriodicQueue = NULL;
165 // -- Build up structure --
166 cont->CapRegs = (void*)( MM_MapHWPages(BaseAddress, 1) + (BaseAddress % PAGE_SIZE) );
167 if( !cont->CapRegs ) {
168 Log_Warning("EHCI", "Can't map 1 page at %P into kernel space", BaseAddress);
171 LOG("cont->CapRegs = %p", cont->CapRegs);
172 if( (cont->CapRegs->CapLength & 3) ) {
173 Log_Warning("EHCI", "Controller at %P non-aligned op regs (%x)",
174 BaseAddress, cont->CapRegs->CapLength);
177 if( BaseAddress % PAGE_SIZE + cont->CapRegs->CapLength + sizeof(tEHCI_CapRegs) > PAGE_SIZE ) {
178 Log_Warning("EHCI", "%P: Cap regs over page boundary (+0x%x bytes)",
179 BaseAddress % PAGE_SIZE + cont->CapRegs->CapLength + sizeof(tEHCI_CapRegs)
183 cont->OpRegs = (void*)( (Uint32*)cont->CapRegs + cont->CapRegs->CapLength / 4 );
184 LOG("cont->OpRegs = %p", cont->OpRegs);
185 // - Allocate periodic queue
187 cont->PeriodicQueue = (void*)MM_AllocDMA(1, 32, &unused);
188 if( !cont->PeriodicQueue ) {
189 Log_Warning("ECHI", "Can't allocate 1 32-bit page for periodic queue");
192 // > Populate queue (with non-present entries)
193 for( int i = 0; i < 1024; i ++ )
194 cont->PeriodicQueue[i] = 1;
195 // TODO: For 64-bit, QH pool and periodic queue need to be in the same 32-bit segment
197 // - Allocate TD pool
198 cont->TDPool = (void*)MM_AllocDMA(1, 32, &unused);
199 if( !cont->TDPool ) {
200 Log_Warning("ECHI", "Can't allocate 1 32-bit page for qTD pool");
203 for( int i = 0; i < TD_POOL_SIZE; i ++ ) {
204 cont->TDPool[i].Token = 3 << 8; // TODO: what is this value?
208 cont->nPorts = cont->CapRegs->HCSParams & 0xF;
209 LOG("cont->nPorts = %i", cont->nPorts);
212 IRQ_AddHandler(InterruptNum, EHCI_InterruptHandler, cont);
213 cont->InterruptThread = Proc_SpawnWorker(EHCI_int_InterruptThread, cont);
214 if( !cont->InterruptThread ) {
215 Log_Warning("EHCI", "Can't spawn interrupt worker thread");
218 LOG("cont->InterruptThread = %p", cont->InterruptThread);
221 cont->DeadTD = EHCI_int_AllocateTD(cont, 0, NULL, 0);
222 memset(cont->DeadTD, 0, sizeof(tEHCI_qTD));
223 cont->DeadTD->Link = 1;
224 cont->DeadTD->Link2 = 1;
225 cont->DeadTD->Token = QTD_TOKEN_STS_HALT;
228 // - HLink is set to itself, initing a circular list
229 cont->DeadQH = EHCI_int_AllocateQH(cont, NULL, NULL, NULL);
230 memset(cont->DeadQH, 0, sizeof(tEHCI_QH));
231 cont->DeadQH->HLink = MM_GetPhysAddr(cont->DeadQH)|2;
232 cont->DeadQH->Impl.Next = cont->DeadQH;
233 cont->DeadQH->Endpoint = QH_ENDPT_H; // H - Head of Reclamation List
234 cont->DeadQH->CurrentTD = MM_GetPhysAddr(cont->DeadTD);
235 cont->DeadQH->Overlay.Link = MM_GetPhysAddr(cont->DeadTD);
237 // -- Initialisation procedure (from ehci-r10) --
238 // - Reset controller
239 cont->OpRegs->USBCmd = USBCMD_HCReset;
240 // - Set CTRLDSSEGMENT (TODO: 64-bit support)
242 cont->OpRegs->USBIntr = USBINTR_IOC|USBINTR_PortChange|USBINTR_FrameRollover|USBINTR_IntrAsyncAdvance;
243 // - Set PERIODICLIST BASE
244 cont->OpRegs->PeridocListBase = MM_GetPhysAddr( cont->PeriodicQueue );
245 // - Set ASYNCLISTADDR
246 cont->OpRegs->AsyncListAddr = MM_GetPhysAddr(cont->DeadQH);
247 // - Enable controller
248 cont->OpRegs->USBCmd = (0x40 << 16) | USBCMD_PeriodicEnable | USBCMD_AsyncEnable | USBCMD_Run;
250 cont->OpRegs->ConfigFlag = 1;
252 // -- Register with USB Core --
253 cont->RootHub = USB_RegisterHost(&gEHCI_HostDef, cont, cont->nPorts);
254 LOG("cont->RootHub = %p", cont->RootHub);
261 MM_Deallocate( cont->CapRegs );
262 if( cont->PeriodicQueue )
263 MM_Deallocate( cont->PeriodicQueue );
265 MM_Deallocate( cont->TDPool );
271 static inline int _GetClosestPower2(int Period)
273 // Round the period to the closest power of two
276 // - Find the first power above the period
277 while( pow2period < Period )
282 // - Check which is closest
283 if( Period - pow2period / 2 > pow2period - Period ) {
289 LOG("period_pow = %i (%ims) from %ims", period_pow, 1 << period_pow, Period);
293 void EHCI_int_AddToPeriodic(tEHCI_Controller *Cont, tEHCI_QH *qh, int PeriodPow2, size_t Load)
295 int Period = 1 << PeriodPow2;
296 Mutex_Acquire(&Cont->PeriodicListLock);
298 // Choose an interrupt slot to use
299 int minslot = 0, minslot_load = INT_MAX;
300 for( int slot = 0; slot < Period; slot ++ )
303 for( int i = 0; i < PERIODIC_SIZE; i += Period )
304 load += Cont->InterruptLoad[i+slot];
305 if( load == 0 ) break;
306 if( load < minslot_load ) {
311 // Increase loading on the selected slot
312 for( int i = minslot; i < PERIODIC_SIZE; i += Period )
313 Cont->InterruptLoad[i] += Load;
314 qh->Impl.Endpt->InterruptOfs = minslot;
315 LOG("Using slot %i", minslot);
317 // Insert into the periodic list
318 for( int i = 0; i < PERIODIC_SIZE; i += Period )
321 // - the end is reached
322 // - this QH is found
323 // - A QH with a lower period is encountered
324 tEHCI_QH *pqh = NULL;
326 for( nqh = Cont->PeriodicQueueV[i]; nqh; pqh = nqh, nqh = nqh->Impl.Next )
330 if( nqh->Impl.Endpt->PeriodPow2 < PeriodPow2 )
334 // Somehow, we've already been added to this queue.
335 if( nqh && nqh == qh )
338 if( qh->Impl.Next && qh->Impl.Next != nqh ) {
339 Log_Warning("EHCI", "Suspected bookkeeping error on %p - int list %i+%i overlap",
340 Cont, PeriodPow2, minslot);
346 qh->HLink = MM_GetPhysAddr(nqh) | 2;
349 qh->Impl.Next = NULL;
350 qh->HLink = 2|1; // QH, Terminate
355 pqh->HLink = MM_GetPhysAddr(qh) | 2;
358 Cont->PeriodicQueueV[i] = qh;
359 Cont->PeriodicQueue[i] = MM_GetPhysAddr(qh) | 2;
362 Mutex_Release(&Cont->PeriodicListLock);
365 // --------------------------------------------------------------------
367 // --------------------------------------------------------------------
368 void *EHCI_InitInterrupt(void *Ptr, int Endpoint, int bOutbound, int Period,
369 tUSBHostCb Cb, void *CbData, void *Buf, size_t Length)
371 tEHCI_Controller *Cont = Ptr;
373 ASSERTCR(Endpoint, <, 256*16, NULL);
374 ASSERTCR(Period, >, 0, NULL);
378 LOG("Endpoint=%x, bOutbound=%i, Period=%i, Length=%i", Endpoint, bOutbound, Period, Length);
380 int period_pow = _GetClosestPower2(Period);
382 tEHCI_Endpoint *endpt = EHCI_int_AllocateEndpt(Cont, Endpoint, Length, period_pow);
383 endpt->Next = Cont->FirstInterruptEndpt;
384 Cont->FirstInterruptEndpt = endpt;
387 tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
388 qh->EndpointExt |= 1; // TODO: uFrame load balancing (8 entry bitfield)
390 // Allocate TD for the data
391 tEHCI_qTD *td = EHCI_int_AllocateTD(Cont, (bOutbound ? PID_OUT : PID_IN), Buf, Length);
392 qh->CurrentTD = MM_GetPhysAddr(td);
393 qh->Impl.FirstTD = td;
394 qh->Impl.LastTD = td;
396 // - Insert into period queue
397 EHCI_int_AddToPeriodic(Cont, qh, period_pow, Length);
402 void *EHCI_InitIsoch(void *Ptr, int Endpoint, size_t MaxPacketSize)
404 ENTER("pPtr iEndpoint iMaxPacketSize",
405 Ptr, Endpoint, MaxPacketSize);
406 LEAVE_RET('p', (void*)(tVAddr)(Endpoint + 1));
408 void *EHCI_InitControl(void *Ptr, int Endpoint, size_t MaxPacketSize)
410 return EHCI_int_AllocateEndpt(Ptr, Endpoint, MaxPacketSize, -1);
412 void *EHCI_InitBulk(void *Ptr, int Endpoint, size_t MaxPacketSize)
414 return EHCI_int_AllocateEndpt(Ptr, Endpoint, MaxPacketSize, -2);
416 void EHCI_RemEndpoint(void *Ptr, void *Handle)
420 else if( (tVAddr)Handle <= 256*16 )
423 tEHCI_Endpoint *endpt = Ptr;
425 // Remove QH from list
426 // - If it's a polling endpoint, need to remove from all periodic lists
427 if( endpt->PeriodPow2 >= 0) {
434 // Deallocate endpoint
435 // TODO: Write EHCI_DeallocateEndpoint
440 void *EHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
442 const void *SetupData, size_t SetupLength,
443 const void *OutData, size_t OutLength,
444 void *InData, size_t InLength
447 tEHCI_Controller *Cont = Ptr;
448 tEHCI_Endpoint *endpt = Dest;
450 tEHCI_qTD *td_setup, *td_data, *td_status;
453 if( (tVAddr)Dest <= 256*16 )
455 if( endpt->PeriodPow2 != -1 ) {
456 Log_Notice("EHCI", "Non-control endpoint passed to SendControl");
459 if( SetupLength > endpt->MaxPacketSize )
461 // TODO: Check size of status
464 LOG("Dest=%p, isOutbound=%i, Lengths(Setup:%i,Out:%i,In:%i)",
465 Dest, isOutbound, SetupLength, OutLength, InLength);
467 // Allocate a QH to work with
468 qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
471 td_setup = EHCI_int_AllocateTD(Cont, PID_SETUP, (void*)SetupData, SetupLength);
474 td_data = OutData ? EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength) : NULL;
475 td_status = EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength);
479 td_data = InData ? EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength) : NULL;
480 td_status = EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength);
482 td_status->Token |= QTD_TOKEN_IOC; // IOC
486 td_setup->Link = MM_GetPhysAddr(td_data);
487 td_data->Link = MM_GetPhysAddr(td_status);
488 td_data->Token |= QTD_TOKEN_STS_ACTIVE; // Active
491 td_setup->Link = MM_GetPhysAddr(td_status);
493 td_setup->Token |= QTD_TOKEN_STS_ACTIVE; // Active
494 td_status->Token |= QTD_TOKEN_STS_ACTIVE;
496 td_status->Link2 = 1;
498 // Set QH's current pointer
499 qh->CurrentTD = MM_GetPhysAddr(td_setup);
500 qh->Impl.FirstTD = td_setup;
501 qh->Impl.LastTD = td_status;
502 EHCI_int_AppendQHToAsync(Cont, qh);
507 void *EHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length)
509 tEHCI_Controller *Cont = Ptr;
510 tEHCI_Endpoint *endpt = Dest;
512 // Sanity check the pointer
513 // - Can't be NULL or an isoch
514 if( (tVAddr)Dest <= 256*16 )
516 if( endpt->PeriodPow2 != -2 ) {
519 LOG("Ptr=%p,Dest=%p(%x),Cb=%p(%p),DirIsOut=%i,Data=%p+0x%x)",
520 Ptr, Dest, endpt->EndpointID, Cb, CbData, Dir, Data, Length);
522 // Allocate a QH to work with
523 tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
525 // Allocate single TD
526 tEHCI_qTD *td = EHCI_int_AllocateTD(Cont, (Dir ? PID_OUT : PID_IN), Data, Length);
527 td->Token |= QTD_TOKEN_IOC | QTD_TOKEN_STS_ACTIVE;
528 qh->CurrentTD = MM_GetPhysAddr(td);
529 qh->Impl.FirstTD = td;
530 qh->Impl.LastTD = td;
532 EHCI_int_AppendQHToAsync(Cont, qh);
537 void EHCI_FreeOp(void *Ptr, void *Handle)
539 tEHCI_Controller *Cont = Ptr;
541 EHCI_int_DeallocateQH(Cont, Handle);
544 Uint32 EHCI_int_RootHub_FeatToMask(int Feat)
548 case PORT_RESET: return PORTSC_PortReset;
549 case PORT_ENABLE: return PORTSC_PortEnabled;
551 Log_Warning("EHCI", "Unknown root hub port feature %i", Feat);
556 void EHCI_RootHub_SetPortFeature(void *Ptr, int Port, int Feat)
558 tEHCI_Controller *Cont = Ptr;
559 if(Port >= Cont->nPorts) return;
561 Cont->OpRegs->PortSC[Port] |= EHCI_int_RootHub_FeatToMask(Feat);
564 void EHCI_RootHub_ClearPortFeature(void *Ptr, int Port, int Feat)
566 tEHCI_Controller *Cont = Ptr;
567 if(Port >= Cont->nPorts) return;
569 Cont->OpRegs->PortSC[Port] &= ~EHCI_int_RootHub_FeatToMask(Feat);
572 int EHCI_RootHub_GetPortStatus(void *Ptr, int Port, int Flag)
574 tEHCI_Controller *Cont = Ptr;
575 if(Port >= Cont->nPorts) return 0;
577 return !!(Cont->OpRegs->PortSC[Port] & EHCI_int_RootHub_FeatToMask(Flag));
580 // --------------------------------------------------------------------
582 // --------------------------------------------------------------------
583 tEHCI_Endpoint *EHCI_int_AllocateEndpt(tEHCI_Controller *Cont, int Endpoint, size_t MaxPacketSize, int PeriodPow2)
585 ENTER("pCont iEndpoint iMaxPacketSize",
586 Cont, Endpoint, MaxPacketSize);
588 tEHCI_Endpoint *endpt = malloc( sizeof(*endpt) );
589 endpt->NextToggle = FALSE;
590 endpt->EndpointID = Endpoint;
591 endpt->MaxPacketSize = MaxPacketSize; // TODO: Sanity Check
592 endpt->PeriodPow2 = PeriodPow2;
593 endpt->InterruptOfs = 0;
594 endpt->ActiveQHs = NULL;
595 // TODO: store endpoints on controller
597 LOG("Created %p for %P Ep 0x%x - %i bytes MPS",
598 endpt, Cont->PhysBase, Endpoint, MaxPacketSize);
604 tEHCI_qTD *EHCI_int_GetTDFromPhys(tEHCI_Controller *Cont, Uint32 Addr)
606 if( Addr == 0 ) return NULL;
607 LOG("%p + (%x - %x)", Cont->TDPool, Addr, MM_GetPhysAddr(Cont->TDPool));
608 return Cont->TDPool + (Addr - MM_GetPhysAddr(Cont->TDPool))/sizeof(tEHCI_qTD);
611 tEHCI_qTD *EHCI_int_AllocateTD(tEHCI_Controller *Cont, int PID, void *Data, size_t Length)
613 // Semaphore_Wait(&Cont->TDSemaphore, 1);
614 Mutex_Acquire(&Cont->TDPoolMutex);
615 for( int i = 0; i < TD_POOL_SIZE; i ++ )
617 // PID code == 3: Avaliable for use
618 if( ((Cont->TDPool[i].Token >> 8) & 3) != 3 )
621 Cont->TDPool[i].Token = (PID << 8) | (Length << 16);
622 Mutex_Release(&Cont->TDPoolMutex);
624 tEHCI_qTD *td = &Cont->TDPool[i];
626 td->Impl.Length = Length;
630 // TODO: Support breaking across multiple pages
631 ASSERTC(Length, <, PAGE_SIZE);
632 // TODO: Handle bouncing >32-bit pages
634 ASSERT( MM_GetPhysAddr(Data) >> 32 == 0 );
636 td->Pages[0] = MM_GetPhysAddr(Data);
637 if( (td->Pages[0] & (PAGE_SIZE-1)) + Length - 1 > PAGE_SIZE )
638 td->Pages[1] = MM_GetPhysAddr((char*)Data + Length - 1) & ~(PAGE_SIZE-1);
639 LOG("Allocated %p(%P) PID%i on %P (Token=0x%x)",
640 td, MM_GetPhysAddr(td), PID, Cont->PhysBase,
645 Mutex_Release(&Cont->TDPoolMutex);
649 void EHCI_int_DeallocateTD(tEHCI_Controller *Cont, tEHCI_qTD *TD)
654 tEHCI_QH *EHCI_int_AllocateQH(tEHCI_Controller *Cont, tEHCI_Endpoint *Endpoint, tUSBHostCb Cb, void *CbData)
657 Mutex_Acquire(&Cont->QHPoolMutex);
658 for( int i = 0; i < QH_POOL_SIZE; i ++ )
660 // If page not yet allocated, allocate it
661 if( !MM_GetPhysAddr( Cont->QHPools[i/QH_POOL_NPERPAGE] ) ) {
663 Cont->QHPools[i/QH_POOL_NPERPAGE] = (void*)MM_AllocDMA(1, 32, &tmp);
664 memset(Cont->QHPools[i/QH_POOL_NPERPAGE], 0, PAGE_SIZE);
667 ret = &Cont->QHPools[i/QH_POOL_NPERPAGE][i%QH_POOL_NPERPAGE];
668 if( ret->HLink != 0 )
671 memset(ret, 0, sizeof(*ret));
673 Mutex_Release(&Cont->QHPoolMutex);
677 ret->Endpoint = (Endpoint->EndpointID >> 4) | 0x80 | ((Endpoint->EndpointID & 0xF) << 8)
678 | (Endpoint->MaxPacketSize << 16);
680 ret->EndpointExt = (1<<30);
681 ret->Impl.Next = NULL;
682 ret->Impl.Callback = Cb;
683 ret->Impl.CallbackData = CbData;
684 ret->Impl.Endpt = Endpoint;
685 // TODO: Endpoint speed (13:12) 0:Full, 1:Low, 2:High
686 // TODO: Control Endpoint Flag (27) 0:*, 1:Full/Low Control
689 Mutex_Release(&Cont->QHPoolMutex);
693 void EHCI_int_DeallocateQH(tEHCI_Controller *Cont, tEHCI_QH *QH)
696 // TODO: Ensure it's unused and clean up associated TDs
700 void EHCI_int_AppendQHToAsync(tEHCI_Controller *Cont, tEHCI_QH *QH)
702 QH->Overlay.Token = QTD_TOKEN_STS_ACTIVE;
704 Mutex_Acquire(&Cont->lAsyncSchedule);
706 QH->HLink = Cont->DeadQH->HLink;
707 QH->Impl.Next = Cont->DeadQH->Impl.Next;
708 Cont->DeadQH->HLink = MM_GetPhysAddr(QH)|2;
709 Cont->DeadQH->Impl.Next = QH;
710 // Start async schedule
712 // - Clear 'H' in dead QH
713 Mutex_Release(&Cont->lAsyncSchedule);
714 LOG("Appended %P to %p(%P)", MM_GetPhysAddr(QH), Cont, Cont->PhysBase);
717 // --------------------------------------------------------------------
719 // --------------------------------------------------------------------
720 void EHCI_InterruptHandler(int IRQ, void *Ptr)
722 tEHCI_Controller *Cont = Ptr;
723 Uint32 sts = Cont->OpRegs->USBSts;
724 Uint32 orig_sts = sts;
725 const Uint32 reserved_bits = 0xFFFF0FC0;
727 if( sts & reserved_bits ) {
728 LOG("Oops, reserved bits set (%08x), funny hardware?", sts);
729 sts &= ~reserved_bits;
732 // Unmask read-only bits
735 if( sts & USBINTR_IOC ) {
737 LOG("%P IOC", Cont->PhysBase);
738 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IOC);
742 if( sts & USBINTR_IntrAsyncAdvance ) {
743 LOG("%P IAAD", Cont->PhysBase);
744 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IAAD);
745 sts &= ~USBINTR_IntrAsyncAdvance;
748 if( sts & USBINTR_PortChange ) {
749 // Port change, determine what port and poke helper thread
750 LOG("%P Port status change", Cont->PhysBase);
751 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_PORTSC);
752 sts &= ~USBINTR_PortChange;
755 if( sts & USBINTR_FrameRollover ) {
756 // Frame rollover, used to aid timing (trigger per-second operations)
757 //LOG("%p Frame rollover", Ptr);
758 sts &= ~USBINTR_FrameRollover;
762 // Unhandled interupt bits
764 LOG("WARN - Bitmask %x unhandled", sts);
769 Cont->OpRegs->USBSts = orig_sts;
772 void EHCI_int_HandlePortConnectChange(tEHCI_Controller *Cont, int Port)
775 if( Cont->OpRegs->PortSC[Port] & PORTSC_CurrentConnectStatus )
777 // Is the device low-speed?
778 if( (Cont->OpRegs->PortSC[Port] & PORTSC_LineStatus_MASK) == PORTSC_LineStatus_Kstate )
780 LOG("Low speed device on %P Port %i, giving to companion", Cont->PhysBase, Port);
781 Cont->OpRegs->PortSC[Port] |= PORTSC_PortOwner;
783 // not a low-speed device, EHCI reset
786 LOG("Device connected on %P Port %i", Cont->PhysBase, Port);
788 USB_PortCtl_BeginReset(Cont->RootHub, Port);
794 if( Cont->OpRegs->PortSC[Port] & PORTSC_PortOwner ) {
795 LOG("Device disconnected on %P Port %i (companion), taking it back",
796 Cont->PhysBase, Port);
797 Cont->OpRegs->PortSC[Port] &= ~PORTSC_PortOwner;
800 LOG("Device disconnected on %P Port %i", Cont->PhysBase, Port);
801 USB_DeviceDisconnected(Cont->RootHub, Port);
806 void EHCI_int_CheckInterruptQHs(tEHCI_Controller *Cont)
808 for( tEHCI_Endpoint *endpt = Cont->FirstInterruptEndpt; endpt; endpt = endpt->Next )
810 tEHCI_QH *qh = endpt->ActiveQHs;
811 // Check if the TD of the first QH is active
812 if( qh->Overlay.Token & QTD_TOKEN_STS_ACTIVE )
814 // Inactive, fire interrupt and re-trigger
815 if( !qh->Impl.Callback ) {
820 tEHCI_qTD *last = qh->Impl.LastTD;
821 size_t remaining_len = (last->Token >> 16) & 0x7FFF;
822 if( remaining_len > last->Impl.Length )
823 remaining_len = last->Impl.Length;
824 size_t transferred_len = last->Impl.Length - remaining_len;
826 LOG("Calling %p(%p) for Intr EndPt %x (%p+0x%x)",
827 qh->Impl.Callback, qh->Impl.CallbackData,
828 qh->Impl.Endpt->EndpointID, last->Impl.Ptr, transferred_len);
829 qh->Impl.Callback(qh->Impl.CallbackData, last->Impl.Ptr, transferred_len);
831 qh->Impl.FirstTD->Token |= QTD_TOKEN_STS_ACTIVE;
832 qh->Overlay.Token |= QTD_TOKEN_STS_ACTIVE;
836 void EHCI_int_RetireQHs(tEHCI_Controller *Cont)
838 tEHCI_QH *prev = Cont->DeadQH;
839 Mutex_Acquire(&Cont->lAsyncSchedule);
840 for( tEHCI_QH *qh = prev->Impl.Next; qh != Cont->DeadQH; qh = prev->Impl.Next )
843 ASSERT(qh != qh->Impl.Next);
844 if( qh->Overlay.Token & QTD_TOKEN_STS_ACTIVE ) {
849 // Remove from async list
850 prev->HLink = qh->HLink;
851 prev->Impl.Next = qh->Impl.Next;
853 // Add to reclaim list
854 qh->Impl.Next = Cont->ReclaimList;
855 Cont->ReclaimList = qh;
858 Cont->OpRegs->USBCmd |= USBCMD_IAAD;
860 Mutex_Release(&Cont->lAsyncSchedule);
864 * Fire callbacks on QHs and mark them as completed
866 * TODO: Possible bug with items being added to reclaim list before
867 * the last doorbell fires.
869 void EHCI_int_ReclaimQHs(tEHCI_Controller *Cont)
871 // Doorbell was rung, so reclaim QHs
872 // - Actually just fires callbacks, now that we know that the QHs can be cleared
874 Mutex_Acquire(&Cont->lReclaimList);
875 while( (qh = Cont->ReclaimList) )
877 Cont->ReclaimList = qh->Impl.Next;
878 Mutex_Release(&Cont->lReclaimList);
880 if( qh->Impl.Callback )
882 tEHCI_qTD *last = qh->Impl.LastTD;
883 size_t remaining_len = (last->Token >> 16) & 0x7FFF;
884 if( remaining_len > last->Impl.Length )
885 remaining_len = last->Impl.Length;
886 size_t transferred_len = last->Impl.Length - remaining_len;
888 LOG("Calling %p(%p) for EndPt %x (%p+0x%x)",
889 qh->Impl.Callback, qh->Impl.CallbackData,
890 qh->Impl.Endpt->EndpointID, last->Impl.Ptr, transferred_len);
891 //if( last->Impl.Ptr )
892 // Debug_HexDump("EHCI Callback Data", last->Impl.Ptr, transferred_len);
893 qh->Impl.Callback(qh->Impl.CallbackData, last->Impl.Ptr, transferred_len);
896 Mutex_Acquire(&Cont->lReclaimList);
898 Mutex_Release(&Cont->lReclaimList);
901 void EHCI_int_InterruptThread(void *ControllerPtr)
903 tEHCI_Controller *Cont = ControllerPtr;
904 Threads_SetName("EHCI Interrupt Worker");
909 LOG("sleeping for events");
910 events = Threads_WaitEvents(EHCI_THREADEVENT_IOC|EHCI_THREADEVENT_PORTSC|EHCI_THREADEVENT_IAAD);
912 // TODO: Should this cause a termination?
914 LOG("events = 0x%x", events);
916 if( events & EHCI_THREADEVENT_IAAD )
918 EHCI_int_ReclaimQHs(Cont);
921 if( events & EHCI_THREADEVENT_IOC )
923 // IOC, handle completed requests
924 // Search periodic lists for one that fired
925 EHCI_int_CheckInterruptQHs(Cont);
927 // - Remove them from the queue and ask the controller to bell when they're removable
928 EHCI_int_RetireQHs(Cont);
931 // Port status change interrupt
932 if( events & EHCI_THREADEVENT_PORTSC )
934 // Check for port status changes
935 for(int i = 0; i < Cont->nPorts; i ++ )
937 Uint32 sts = Cont->OpRegs->PortSC[i];
938 //LOG("Port %i: sts = %x", i, sts);
939 Cont->OpRegs->PortSC[i] = sts;
940 if( sts & PORTSC_ConnectStatusChange )
942 LOG("Port %i: Connect Change", i);
943 EHCI_int_HandlePortConnectChange(Cont, i);
946 if( sts & PORTSC_PortEnableChange )
948 // Handle enable/disable
949 LOG("Port %i: Enable Change", i);
952 if( sts & PORTSC_OvercurrentChange )
954 // Handle over-current change
955 LOG("Port %i: Over-Current Change", i);