+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;
+}
+