+void EHCI_int_CheckInterruptQHs(tEHCI_Controller *Cont)
+{
+ for( tEHCI_Endpoint *endpt = Cont->FirstInterruptEndpt; endpt; endpt = endpt->Next )
+ {
+ tEHCI_QH *qh = endpt->ActiveQHs;
+ // Check if the TD of the first QH is active
+ if( qh->Overlay.Token & QTD_TOKEN_STS_ACTIVE )
+ continue ;
+ // Inactive, fire interrupt and re-trigger
+ if( !qh->Impl.Callback ) {
+ // Umm... ?
+ }
+ else
+ {
+ tEHCI_qTD *last = qh->Impl.LastTD;
+ size_t remaining_len = (last->Token >> 16) & 0x7FFF;
+ if( remaining_len > last->Impl.Length )
+ remaining_len = last->Impl.Length;
+ size_t transferred_len = last->Impl.Length - remaining_len;
+
+ LOG("Calling %p(%p) for Intr EndPt %x (%p+0x%x)",
+ qh->Impl.Callback, qh->Impl.CallbackData,
+ qh->Impl.Endpt->EndpointID, last->Impl.Ptr, transferred_len);
+ qh->Impl.Callback(qh->Impl.CallbackData, last->Impl.Ptr, transferred_len);
+ }
+ qh->Impl.FirstTD->Token |= QTD_TOKEN_STS_ACTIVE;
+ qh->Overlay.Token |= QTD_TOKEN_STS_ACTIVE;
+ }
+}
+
+void EHCI_int_RetireQHs(tEHCI_Controller *Cont)
+{
+ tEHCI_QH *prev = Cont->DeadQH;
+ Mutex_Acquire(&Cont->lAsyncSchedule);
+ for( tEHCI_QH *qh = prev->Impl.Next; qh != Cont->DeadQH; qh = prev->Impl.Next )
+ {
+ ASSERT(qh);
+ ASSERT(qh != qh->Impl.Next);
+ if( qh->Overlay.Token & QTD_TOKEN_STS_ACTIVE ) {
+ prev = qh;
+ continue ;
+ }
+
+ // Remove from async list
+ prev->HLink = qh->HLink;
+ prev->Impl.Next = qh->Impl.Next;
+
+ // Add to reclaim list
+ qh->Impl.Next = Cont->ReclaimList;
+ Cont->ReclaimList = qh;
+
+ // Ring doorbell!
+ Cont->OpRegs->USBCmd |= USBCMD_IAAD;
+ }
+ Mutex_Release(&Cont->lAsyncSchedule);
+}
+
+/*
+ * Fire callbacks on QHs and mark them as completed
+ *
+ * TODO: Possible bug with items being added to reclaim list before
+ * the last doorbell fires.
+ */
+void EHCI_int_ReclaimQHs(tEHCI_Controller *Cont)
+{
+ // Doorbell was rung, so reclaim QHs
+ // - Actually just fires callbacks, now that we know that the QHs can be cleared
+ tEHCI_QH *qh;
+ Mutex_Acquire(&Cont->lReclaimList);
+ while( (qh = Cont->ReclaimList) )
+ {
+ Cont->ReclaimList = qh->Impl.Next;
+ Mutex_Release(&Cont->lReclaimList);
+
+ if( qh->Impl.Callback )
+ {
+ tEHCI_qTD *last = qh->Impl.LastTD;
+ size_t remaining_len = (last->Token >> 16) & 0x7FFF;
+ if( remaining_len > last->Impl.Length )
+ remaining_len = last->Impl.Length;
+ size_t transferred_len = last->Impl.Length - remaining_len;
+
+ LOG("Calling %p(%p) for EndPt %x (%p+0x%x)",
+ qh->Impl.Callback, qh->Impl.CallbackData,
+ qh->Impl.Endpt->EndpointID, last->Impl.Ptr, transferred_len);
+ //if( last->Impl.Ptr )
+ // Debug_HexDump("EHCI Callback Data", last->Impl.Ptr, transferred_len);
+ qh->Impl.Callback(qh->Impl.CallbackData, last->Impl.Ptr, transferred_len);
+ }
+
+ Mutex_Acquire(&Cont->lReclaimList);
+ }
+ Mutex_Release(&Cont->lReclaimList);
+}
+
+void EHCI_int_InterruptThread(void *ControllerPtr)
+{
+ tEHCI_Controller *Cont = ControllerPtr;
+ Threads_SetName("EHCI Interrupt Worker");
+ while(Cont->OpRegs)
+ {
+ Uint32 events;
+
+ LOG("sleeping for events");
+ events = Threads_WaitEvents(EHCI_THREADEVENT_IOC|EHCI_THREADEVENT_PORTSC|EHCI_THREADEVENT_IAAD);
+ if( !events ) {
+ // TODO: Should this cause a termination?
+ }
+ LOG("events = 0x%x", events);
+
+ if( events & EHCI_THREADEVENT_IAAD )
+ {
+ EHCI_int_ReclaimQHs(Cont);
+ }
+
+ if( events & EHCI_THREADEVENT_IOC )
+ {
+ // IOC, handle completed requests
+ // Search periodic lists for one that fired
+ EHCI_int_CheckInterruptQHs(Cont);
+ // Retire QHs
+ // - Remove them from the queue and ask the controller to bell when they're removable
+ EHCI_int_RetireQHs(Cont);
+ }
+
+ // Port status change interrupt
+ if( events & EHCI_THREADEVENT_PORTSC )
+ {
+ // Check for port status changes
+ for(int i = 0; i < Cont->nPorts; i ++ )
+ {
+ Uint32 sts = Cont->OpRegs->PortSC[i];
+ //LOG("Port %i: sts = %x", i, sts);
+ Cont->OpRegs->PortSC[i] = sts;
+ if( sts & PORTSC_ConnectStatusChange )
+ {
+ LOG("Port %i: Connect Change", i);
+ EHCI_int_HandlePortConnectChange(Cont, i);
+ }
+
+ if( sts & PORTSC_PortEnableChange )
+ {
+ // Handle enable/disable
+ LOG("Port %i: Enable Change", i);
+ }
+
+ if( sts & PORTSC_OvercurrentChange )
+ {
+ // Handle over-current change
+ LOG("Port %i: Over-Current Change", i);
+ }
+ }
+ }
+ }
+}