[Solved] xHCI Interrupt Troubles
Posted: Fri Sep 25, 2020 10:14 am
Hello,
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
Any other code can be found at https://github.com/foliagecanine/tritiu ... 386/xhci.c or in that repository.
What am I doing wrong?
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?