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( (tVAddr)cont->CapRegs );
262 if( cont->PeriodicQueue )
263 MM_Deallocate( (tVAddr)cont->PeriodicQueue );
265 MM_Deallocate( (tVAddr)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);
385 tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
386 qh->EndpointExt |= 1; // TODO: uFrame load balancing (8 entry bitfield)
388 // Allocate TD for the data
389 tEHCI_qTD *td = EHCI_int_AllocateTD(Cont, (bOutbound ? PID_OUT : PID_IN), Buf, Length);
390 qh->CurrentTD = MM_GetPhysAddr(td);
391 qh->Impl.FirstTD = td;
392 qh->Impl.LastTD = td;
394 // - Insert into period queue
395 EHCI_int_AddToPeriodic(Cont, qh, period_pow, Length);
400 void *EHCI_InitIsoch(void *Ptr, int Endpoint, size_t MaxPacketSize)
402 ENTER("pPtr iEndpoint iMaxPacketSize",
403 Ptr, Endpoint, MaxPacketSize);
404 LEAVE_RET('p', (void*)(tVAddr)(Endpoint + 1));
406 void *EHCI_InitControl(void *Ptr, int Endpoint, size_t MaxPacketSize)
408 return EHCI_int_AllocateEndpt(Ptr, Endpoint, MaxPacketSize, -1);
410 void *EHCI_InitBulk(void *Ptr, int Endpoint, size_t MaxPacketSize)
412 return EHCI_int_AllocateEndpt(Ptr, Endpoint, MaxPacketSize, -2);
414 void EHCI_RemEndpoint(void *Ptr, void *Handle)
418 else if( (tVAddr)Handle <= 256*16 )
421 tEHCI_Endpoint *endpt = Ptr;
423 // Remove QH from list
424 // - If it's a polling endpoint, need to remove from all periodic lists
425 if( endpt->PeriodPow2 >= 0) {
432 // Deallocate endpoint
433 // TODO: Write EHCI_DeallocateEndpoint
438 void *EHCI_SendControl(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData,
440 const void *SetupData, size_t SetupLength,
441 const void *OutData, size_t OutLength,
442 void *InData, size_t InLength
445 tEHCI_Controller *Cont = Ptr;
446 tEHCI_Endpoint *endpt = Dest;
448 tEHCI_qTD *td_setup, *td_data, *td_status;
451 if( (tVAddr)Dest <= 256*16 )
453 if( endpt->PeriodPow2 != -1 ) {
454 Log_Notice("EHCI", "Non-control endpoint passed to SendControl");
457 if( SetupLength > endpt->MaxPacketSize )
459 // TODO: Check size of status
462 LOG("Dest=%p, isOutbound=%i, Lengths(Setup:%i,Out:%i,In:%i)",
463 Dest, isOutbound, SetupLength, OutLength, InLength);
465 // Allocate a QH to work with
466 qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
469 td_setup = EHCI_int_AllocateTD(Cont, PID_SETUP, (void*)SetupData, SetupLength);
472 td_data = OutData ? EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength) : NULL;
473 td_status = EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength);
477 td_data = InData ? EHCI_int_AllocateTD(Cont, PID_IN, InData, InLength) : NULL;
478 td_status = EHCI_int_AllocateTD(Cont, PID_OUT, (void*)OutData, OutLength);
480 td_status->Token |= QTD_TOKEN_IOC; // IOC
484 td_setup->Link = MM_GetPhysAddr(td_data);
485 td_data->Link = MM_GetPhysAddr(td_status);
486 td_data->Token |= QTD_TOKEN_STS_ACTIVE; // Active
489 td_setup->Link = MM_GetPhysAddr(td_status);
491 td_setup->Token |= QTD_TOKEN_STS_ACTIVE; // Active
492 td_status->Token |= QTD_TOKEN_STS_ACTIVE;
494 td_status->Link2 = 1;
496 // Set QH's current pointer
497 qh->CurrentTD = MM_GetPhysAddr(td_setup);
498 qh->Impl.FirstTD = td_setup;
499 qh->Impl.LastTD = td_status;
500 EHCI_int_AppendQHToAsync(Cont, qh);
505 void *EHCI_SendBulk(void *Ptr, void *Dest, tUSBHostCb Cb, void *CbData, int Dir, void *Data, size_t Length)
507 tEHCI_Controller *Cont = Ptr;
508 tEHCI_Endpoint *endpt = Dest;
510 // Sanity check the pointer
511 // - Can't be NULL or an isoch
512 if( (tVAddr)Dest <= 256*16 )
514 if( endpt->PeriodPow2 != -2 ) {
517 LOG("Ptr=%p,Dest=%p(%x),Cb=%p(%p),DirIsOut=%i,Data=%p+0x%x)",
518 Ptr, Dest, endpt->EndpointID, Cb, CbData, Dir, Data, Length);
520 // Allocate a QH to work with
521 tEHCI_QH *qh = EHCI_int_AllocateQH(Cont, endpt, Cb, CbData);
523 // Allocate single TD
524 tEHCI_qTD *td = EHCI_int_AllocateTD(Cont, (Dir ? PID_OUT : PID_IN), Data, Length);
525 td->Token |= QTD_TOKEN_IOC | QTD_TOKEN_STS_ACTIVE;
526 qh->CurrentTD = MM_GetPhysAddr(td);
527 qh->Impl.FirstTD = td;
528 qh->Impl.LastTD = td;
530 EHCI_int_AppendQHToAsync(Cont, qh);
535 void EHCI_FreeOp(void *Ptr, void *Handle)
537 tEHCI_Controller *Cont = Ptr;
539 EHCI_int_DeallocateQH(Cont, Handle);
542 Uint32 EHCI_int_RootHub_FeatToMask(int Feat)
546 case PORT_RESET: return PORTSC_PortReset;
547 case PORT_ENABLE: return PORTSC_PortEnabled;
549 Log_Warning("EHCI", "Unknown root hub port feature %i", Feat);
554 void EHCI_RootHub_SetPortFeature(void *Ptr, int Port, int Feat)
556 tEHCI_Controller *Cont = Ptr;
557 if(Port >= Cont->nPorts) return;
559 Cont->OpRegs->PortSC[Port] |= EHCI_int_RootHub_FeatToMask(Feat);
562 void EHCI_RootHub_ClearPortFeature(void *Ptr, int Port, int Feat)
564 tEHCI_Controller *Cont = Ptr;
565 if(Port >= Cont->nPorts) return;
567 Cont->OpRegs->PortSC[Port] &= ~EHCI_int_RootHub_FeatToMask(Feat);
570 int EHCI_RootHub_GetPortStatus(void *Ptr, int Port, int Flag)
572 tEHCI_Controller *Cont = Ptr;
573 if(Port >= Cont->nPorts) return 0;
575 return !!(Cont->OpRegs->PortSC[Port] & EHCI_int_RootHub_FeatToMask(Flag));
578 // --------------------------------------------------------------------
580 // --------------------------------------------------------------------
581 tEHCI_Endpoint *EHCI_int_AllocateEndpt(tEHCI_Controller *Cont, int Endpoint, size_t MaxPacketSize, int PeriodPow2)
583 ENTER("pCont iEndpoint iMaxPacketSize",
584 Cont, Endpoint, MaxPacketSize);
586 tEHCI_Endpoint *endpt = malloc( sizeof(*endpt) );
587 endpt->NextToggle = FALSE;
588 endpt->EndpointID = Endpoint;
589 endpt->MaxPacketSize = MaxPacketSize; // TODO: Sanity Check
590 endpt->PeriodPow2 = PeriodPow2;
591 endpt->InterruptOfs = 0;
592 endpt->ActiveQHs = NULL;
593 // TODO: store endpoints on controller
595 LOG("Created %p for %P Ep 0x%x - %i bytes MPS",
596 endpt, Cont->PhysBase, Endpoint, MaxPacketSize);
602 tEHCI_qTD *EHCI_int_GetTDFromPhys(tEHCI_Controller *Cont, Uint32 Addr)
604 if( Addr == 0 ) return NULL;
605 LOG("%p + (%x - %x)", Cont->TDPool, Addr, MM_GetPhysAddr(Cont->TDPool));
606 return Cont->TDPool + (Addr - MM_GetPhysAddr(Cont->TDPool))/sizeof(tEHCI_qTD);
609 tEHCI_qTD *EHCI_int_AllocateTD(tEHCI_Controller *Cont, int PID, void *Data, size_t Length)
611 // Semaphore_Wait(&Cont->TDSemaphore, 1);
612 Mutex_Acquire(&Cont->TDPoolMutex);
613 for( int i = 0; i < TD_POOL_SIZE; i ++ )
615 // PID code == 3: Avaliable for use
616 if( ((Cont->TDPool[i].Token >> 8) & 3) != 3 )
619 Cont->TDPool[i].Token = (PID << 8) | (Length << 16);
620 Mutex_Release(&Cont->TDPoolMutex);
622 tEHCI_qTD *td = &Cont->TDPool[i];
624 td->Impl.Length = Length;
628 // TODO: Support breaking across multiple pages
629 ASSERTC(Length, <, PAGE_SIZE);
630 // TODO: Handle bouncing >32-bit pages
632 ASSERT( MM_GetPhysAddr(Data) >> 32 == 0 );
634 td->Pages[0] = MM_GetPhysAddr(Data);
635 if( (td->Pages[0] & (PAGE_SIZE-1)) + Length - 1 > PAGE_SIZE )
636 td->Pages[1] = MM_GetPhysAddr((char*)Data + Length - 1) & ~(PAGE_SIZE-1);
637 LOG("Allocated %p(%P) PID%i on %P (Token=0x%x)",
638 td, MM_GetPhysAddr(td), PID, Cont->PhysBase,
643 Mutex_Release(&Cont->TDPoolMutex);
647 void EHCI_int_DeallocateTD(tEHCI_Controller *Cont, tEHCI_qTD *TD)
652 tEHCI_QH *EHCI_int_AllocateQH(tEHCI_Controller *Cont, tEHCI_Endpoint *Endpoint, tUSBHostCb Cb, void *CbData)
655 Mutex_Acquire(&Cont->QHPoolMutex);
656 for( int i = 0; i < QH_POOL_SIZE; i ++ )
658 // If page not yet allocated, allocate it
659 if( !MM_GetPhysAddr( Cont->QHPools[i/QH_POOL_NPERPAGE] ) ) {
661 Cont->QHPools[i/QH_POOL_NPERPAGE] = (void*)MM_AllocDMA(1, 32, &tmp);
662 memset(Cont->QHPools[i/QH_POOL_NPERPAGE], 0, PAGE_SIZE);
665 ret = &Cont->QHPools[i/QH_POOL_NPERPAGE][i%QH_POOL_NPERPAGE];
666 if( ret->HLink != 0 )
669 memset(ret, 0, sizeof(*ret));
671 Mutex_Release(&Cont->QHPoolMutex);
675 ret->Endpoint = (Endpoint->EndpointID >> 4) | 0x80 | ((Endpoint->EndpointID & 0xF) << 8)
676 | (Endpoint->MaxPacketSize << 16);
678 ret->EndpointExt = (1<<30);
679 ret->Impl.Next = NULL;
680 ret->Impl.Callback = Cb;
681 ret->Impl.CallbackData = CbData;
682 ret->Impl.Endpt = Endpoint;
683 // TODO: Endpoint speed (13:12) 0:Full, 1:Low, 2:High
684 // TODO: Control Endpoint Flag (27) 0:*, 1:Full/Low Control
687 Mutex_Release(&Cont->QHPoolMutex);
691 void EHCI_int_DeallocateQH(tEHCI_Controller *Cont, tEHCI_QH *QH)
694 // TODO: Ensure it's unused and clean up associated TDs
698 void EHCI_int_AppendQHToAsync(tEHCI_Controller *Cont, tEHCI_QH *QH)
700 QH->Overlay.Token = QTD_TOKEN_STS_ACTIVE;
702 Mutex_Acquire(&Cont->lAsyncSchedule);
704 QH->HLink = Cont->DeadQH->HLink;
705 QH->Impl.Next = Cont->DeadQH->Impl.Next;
706 Cont->DeadQH->HLink = MM_GetPhysAddr(QH)|2;
707 Cont->DeadQH->Impl.Next = QH;
708 // Start async schedule
710 // - Clear 'H' in dead QH
711 Mutex_Release(&Cont->lAsyncSchedule);
712 LOG("Appended %P to %p(%P)", MM_GetPhysAddr(QH), Cont, Cont->PhysBase);
715 // --------------------------------------------------------------------
717 // --------------------------------------------------------------------
718 void EHCI_InterruptHandler(int IRQ, void *Ptr)
720 tEHCI_Controller *Cont = Ptr;
721 Uint32 sts = Cont->OpRegs->USBSts;
722 Uint32 orig_sts = sts;
723 const Uint32 reserved_bits = 0xFFFF0FC0;
725 if( sts & reserved_bits ) {
726 LOG("Oops, reserved bits set (%08x), funny hardware?", sts);
727 sts &= ~reserved_bits;
730 // Unmask read-only bits
733 if( sts & USBINTR_IOC ) {
735 LOG("%P IOC", Cont->PhysBase);
736 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IOC);
740 if( sts & USBINTR_IntrAsyncAdvance ) {
741 LOG("%P IAAD", Cont->PhysBase);
742 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IAAD);
743 sts &= ~USBINTR_IntrAsyncAdvance;
746 if( sts & USBINTR_PortChange ) {
747 // Port change, determine what port and poke helper thread
748 LOG("%P Port status change", Cont->PhysBase);
749 Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_PORTSC);
750 sts &= ~USBINTR_PortChange;
753 if( sts & USBINTR_FrameRollover ) {
754 // Frame rollover, used to aid timing (trigger per-second operations)
755 //LOG("%p Frame rollover", Ptr);
756 sts &= ~USBINTR_FrameRollover;
760 // Unhandled interupt bits
762 LOG("WARN - Bitmask %x unhandled", sts);
767 Cont->OpRegs->USBSts = orig_sts;
770 void EHCI_int_HandlePortConnectChange(tEHCI_Controller *Cont, int Port)
773 if( Cont->OpRegs->PortSC[Port] & PORTSC_CurrentConnectStatus )
775 // Is the device low-speed?
776 if( (Cont->OpRegs->PortSC[Port] & PORTSC_LineStatus_MASK) == PORTSC_LineStatus_Kstate )
778 LOG("Low speed device on %P Port %i, giving to companion", Cont->PhysBase, Port);
779 Cont->OpRegs->PortSC[Port] |= PORTSC_PortOwner;
781 // not a low-speed device, EHCI reset
784 LOG("Device connected on %P Port %i", Cont->PhysBase, Port);
786 USB_PortCtl_BeginReset(Cont->RootHub, Port);
792 if( Cont->OpRegs->PortSC[Port] & PORTSC_PortOwner ) {
793 LOG("Device disconnected on %P Port %i (companion), taking it back",
794 Cont->PhysBase, Port);
795 Cont->OpRegs->PortSC[Port] &= ~PORTSC_PortOwner;
798 LOG("Device disconnected on %P Port %i", Cont->PhysBase, Port);
799 USB_DeviceDisconnected(Cont->RootHub, Port);
804 void EHCI_int_RetireQHs(tEHCI_Controller *Cont)
806 tEHCI_QH *prev = Cont->DeadQH;
807 Mutex_Acquire(&Cont->lAsyncSchedule);
808 for( tEHCI_QH *qh = prev->Impl.Next; qh != Cont->DeadQH; qh = prev->Impl.Next )
811 ASSERT(qh != qh->Impl.Next);
812 if( qh->Overlay.Token & QTD_TOKEN_STS_ACTIVE ) {
817 // Remove from async list
818 prev->HLink = qh->HLink;
819 prev->Impl.Next = qh->Impl.Next;
821 // Add to reclaim list
822 qh->Impl.Next = Cont->ReclaimList;
823 Cont->ReclaimList = qh;
826 Cont->OpRegs->USBCmd |= USBCMD_IAAD;
828 Mutex_Release(&Cont->lAsyncSchedule);
832 * Fire callbacks on QHs and mark them as completed
834 * TODO: Possible bug with items being added to reclaim list before
835 * the last doorbell fires.
837 void EHCI_int_ReclaimQHs(tEHCI_Controller *Cont)
839 // Doorbell was rung, so reclaim QHs
840 // - Actually just fires callbacks, now that we know that the QHs can be cleared
842 Mutex_Acquire(&Cont->lReclaimList);
843 while( (qh = Cont->ReclaimList) )
845 Cont->ReclaimList = qh->Impl.Next;
846 Mutex_Release(&Cont->lReclaimList);
848 if( qh->Impl.Callback )
850 tEHCI_qTD *last = qh->Impl.LastTD;
851 size_t remaining_len = (last->Token >> 16) & 0x7FFF;
852 if( remaining_len > last->Impl.Length )
853 remaining_len = last->Impl.Length;
854 size_t transferred_len = last->Impl.Length - remaining_len;
856 LOG("Calling %p(%p) for EndPt %x (%p+0x%x)",
857 qh->Impl.Callback, qh->Impl.CallbackData,
858 qh->Impl.Endpt->EndpointID, last->Impl.Ptr, transferred_len);
859 qh->Impl.Callback(qh->Impl.CallbackData, last->Impl.Ptr, transferred_len);
862 Mutex_Acquire(&Cont->lReclaimList);
864 Mutex_Release(&Cont->lReclaimList);
867 void EHCI_int_InterruptThread(void *ControllerPtr)
869 tEHCI_Controller *Cont = ControllerPtr;
874 LOG("sleeping for events");
875 events = Threads_WaitEvents(EHCI_THREADEVENT_IOC|EHCI_THREADEVENT_PORTSC|EHCI_THREADEVENT_IAAD);
877 // TODO: Should this cause a termination?
879 LOG("events = 0x%x", events);
881 if( events & EHCI_THREADEVENT_IAAD )
883 EHCI_int_ReclaimQHs(Cont);
886 if( events & EHCI_THREADEVENT_IOC )
888 // IOC, handle completed requests
889 Log_Warning("EHCI", "%P IOC - TODO: Call registered callbacks and reclaim",
891 // Search periodic lists for one that fired
893 // - Remove them from the queue and ask the controller to bell when they're removable
894 EHCI_int_RetireQHs(Cont);
897 // Port status change interrupt
898 if( events & EHCI_THREADEVENT_PORTSC )
900 // Check for port status changes
901 for(int i = 0; i < Cont->nPorts; i ++ )
903 Uint32 sts = Cont->OpRegs->PortSC[i];
904 //LOG("Port %i: sts = %x", i, sts);
905 Cont->OpRegs->PortSC[i] = sts;
906 if( sts & PORTSC_ConnectStatusChange )
908 LOG("Port %i: Connect Change", i);
909 EHCI_int_HandlePortConnectChange(Cont, i);
912 if( sts & PORTSC_PortEnableChange )
914 // Handle enable/disable
915 LOG("Port %i: Enable Change", i);
918 if( sts & PORTSC_OvercurrentChange )
920 // Handle over-current change
921 LOG("Port %i: Over-Current Change", i);