+void EHCI_int_AppendQHToAsync(tEHCI_Controller *Cont, tEHCI_QH *QH)
+{
+ QH->Overlay.Token = QTD_TOKEN_STS_ACTIVE;
+
+ Mutex_Acquire(&Cont->lAsyncSchedule);
+ // Insert into list
+ QH->HLink = Cont->DeadQH->HLink;
+ QH->Impl.Next = Cont->DeadQH->Impl.Next;
+ Cont->DeadQH->HLink = MM_GetPhysAddr(QH)|2;
+ Cont->DeadQH->Impl.Next = QH;
+ // Start async schedule
+ // - Set ASYNCENABLE
+ // - Clear 'H' in dead QH
+ Mutex_Release(&Cont->lAsyncSchedule);
+ LOG("Appended %P to %p(%P)", MM_GetPhysAddr(QH), Cont, Cont->PhysBase);
+}
+
+// --------------------------------------------------------------------
+// Interrupt actions
+// --------------------------------------------------------------------
+void EHCI_InterruptHandler(int IRQ, void *Ptr)
+{
+ tEHCI_Controller *Cont = Ptr;
+ Uint32 sts = Cont->OpRegs->USBSts;
+ Uint32 orig_sts = sts;
+ const Uint32 reserved_bits = 0xFFFF0FC0;
+
+ if( sts & reserved_bits ) {
+ LOG("Oops, reserved bits set (%08x), funny hardware?", sts);
+ sts &= ~reserved_bits;
+ }
+
+ // Unmask read-only bits
+ sts &= ~(0xF000);
+
+ if( sts & USBINTR_IOC ) {
+ // IOC
+ LOG("%P IOC", Cont->PhysBase);
+ Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IOC);
+ sts &= ~USBINTR_IOC;
+ }
+
+ if( sts & USBINTR_IntrAsyncAdvance ) {
+ LOG("%P IAAD", Cont->PhysBase);
+ Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_IAAD);
+ sts &= ~USBINTR_IntrAsyncAdvance;
+ }
+
+ if( sts & USBINTR_PortChange ) {
+ // Port change, determine what port and poke helper thread
+ LOG("%P Port status change", Cont->PhysBase);
+ Threads_PostEvent(Cont->InterruptThread, EHCI_THREADEVENT_PORTSC);
+ sts &= ~USBINTR_PortChange;
+ }
+
+ if( sts & USBINTR_FrameRollover ) {
+ // Frame rollover, used to aid timing (trigger per-second operations)
+ //LOG("%p Frame rollover", Ptr);
+ sts &= ~USBINTR_FrameRollover;
+ }
+
+ if( sts ) {
+ // Unhandled interupt bits
+ // TODO: Warn
+ LOG("WARN - Bitmask %x unhandled", sts);
+ }
+
+
+ // Clear interrupts
+ Cont->OpRegs->USBSts = orig_sts;
+}
+
+void EHCI_int_HandlePortConnectChange(tEHCI_Controller *Cont, int Port)
+{
+ // Connect Event
+ if( Cont->OpRegs->PortSC[Port] & PORTSC_CurrentConnectStatus )
+ {
+ // Is the device low-speed?
+ if( (Cont->OpRegs->PortSC[Port] & PORTSC_LineStatus_MASK) == PORTSC_LineStatus_Kstate )
+ {
+ LOG("Low speed device on %P Port %i, giving to companion", Cont->PhysBase, Port);
+ Cont->OpRegs->PortSC[Port] |= PORTSC_PortOwner;
+ }
+ // not a low-speed device, EHCI reset
+ else
+ {
+ LOG("Device connected on %P Port %i", Cont->PhysBase, Port);
+ // Reset procedure.
+ USB_PortCtl_BeginReset(Cont->RootHub, Port);
+ }
+ }
+ // Disconnect Event
+ else
+ {
+ if( Cont->OpRegs->PortSC[Port] & PORTSC_PortOwner ) {
+ LOG("Device disconnected on %P Port %i (companion), taking it back",
+ Cont->PhysBase, Port);
+ Cont->OpRegs->PortSC[Port] &= ~PORTSC_PortOwner;
+ }
+ else {
+ LOG("Device disconnected on %P Port %i", Cont->PhysBase, Port);
+ USB_DeviceDisconnected(Cont->RootHub, Port);
+ }
+ }
+}
+
+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);
+ }
+ }
+ }
+ }
+}