So recently I finished a UHCI driver (at least as far as I need for now).
I asked around what people thought would be the next easiest *HCI driver to implement, and I got xHCI.
So I've been working on an xHCI driver and am having a lot of trouble understanding it (it is a LOT more complicated than UHCI).
Still, I've managed to get to a point where I can reset ports and have them become active.
However, I'm really getting stuck on how command and event rings work, especially cycles, when they change, etc.
I recently found a bug where I was putting virtual addresses instead of physical addresses into the Interrupter 0 registers (for the dequeue pointer and segment table pointer registers).
In fixing this bug I now get interrupts. That is a good thing. The bad thing is i KEEP getting interrupts.
Before I even try to put anything on the command ring, I'm getting interrupts.
I think I've pinpointed where the interrupts start to when the port gets reset.
I added a debug line to check whether there was an event TRB on the event ring, and based off the cycle number there was exactly one.
From what I can tell, to acknowledge an interrupt I need to clear any bits in the USB Status register and the Interrupt Pending bit of the interrupter that fired. At this point I should process any event TRBs. Then I need to increment the interrupter's dequeue pointer to the last event TRB processed.
Should the EHB (Event Handler Busy) bit be set for the interrupter's dequeue pointer? Right now I write the address or-ed with the EHB bit, which I believe is Write-Clear.
Also, what happens when the dequeue pointer is incremented past the end of the segment? Is the software supposed to reset it somehow?
For reference, here's my xHCI interrupt function
Code: Select all
void xhci_interrupt() {
dbgprintf("[xHCI] USB INTERRUPT\n");
for (uint8_t i = 0; i < USB_MAX_CTRLRS; i++) {
xhci_controller *xc = get_xhci_controller(i);
if (!xc->hcops)
continue;
_wr32(xc->hcops+XHCI_HCOPS_USBSTS,_rd32(xc->hcops+XHCI_HCOPS_USBSTS));
if (_rd32(xc->runtime+XHCI_RUNTIME_IR0+XHCI_INTREG_IMR)&(XHCI_INTREG_IMR_EN|XHCI_INTREG_IMR_IP)==(XHCI_INTREG_IMR_EN|XHCI_INTREG_IMR_IP)) {
_wr32(xc->runtime+XHCI_RUNTIME_IR0+XHCI_INTREG_IMR,_rd32(xc->runtime+XHCI_RUNTIME_IR0+XHCI_INTREG_IMR));
while (xc->cevttrb->command&XHCI_TRB_CYCLE==xc->evtcycle) {
xc->cevttrb += sizeof(xhci_trb);
dbgprintf("Processed TRB\n");
}
_wr64(xc->runtime+XHCI_RUNTIME_IR0+XHCI_INTREG_ERDQPTR,((uint64_t)(uint32_t)get_phys_addr(xc->cevttrb-sizeof(xhci_trb)))|XHCI_INTREG_ERDQPTR_EHBSY,xc);
}
}
}
What am I doing wrong?