I am finally starting to get back to this hobby of ours, and I decided to check my code with QEMU. I use Bochs mostly, simply because I have been using it for so long and have gotten quite intimate with its code several times :-)
I checked UHCI, OHCI, and EHCI and all my tests and code worked as expected. However, when I checked xHCI, it failed in a few places. After spending some time looking, I found three errors with QEMU. Therefore, I thought I would post here just to let you know that if you have been experiencing these same issues with your code, you can relax, it is a QEMU thing, not your code.
The first isn't really an error, more of a hard coded item. QEMU hard codes the Event Ring Segment Limit to one. i.e.: It does not have code to have more than one segment in an Event Ring. This isn't really an error, but more of a feature that I think should be added. I only found this out looking through other code.
The second is a bug. When your code has processed an Event TRB, it is to write to the xHC_INTERRUPTER_DEQUEUE register the address of this processed TRB, clearing the EHB bit and (optionally) writing the DESI value to the bottom three bits. This tells the controller that you have processed this Event. In my tests, QEMU was sending two interrupts, one for the initial Event TRB and then a spurious interrupt. Come to find out, QEMU checks to see that this value you write to the xHC_INTERRUPTER_DEQUEUE register matches where it thinks that TRB should be. However, QEMU has already incremented this value and the test is false, telling QEMU to send the interrupt again. I have reported the error at https://bugs.launchpad.net/qemu/+bug/1859359. Therefore, if you have been receiving two interrupts for each one Event, this is the problem.
The third item isn't really a bug, but is a certain problem that caused my code to not work. For those of you whom are familiar with USB, when you send a CONTROL transfer, you send a SETUP packet, send/receive zero or more DATA packets, then send a STATUS packet. Since the STATUS packet is used to indicate a successful transfer, it really isn't to be sent until the transfer is actually successful. Therefore, on an xHCI transfer ring, you really should only insert the SETUP TRB and zero or more DATA TRBs waiting for each one to successfully be transferred (setting aside short packets, etc). Once you have successfully received and/or sent all of the packets, you then send the STATUS TRB.
An example:
Code: Select all
Insert SETUP
Insert DATA(s)
Ring Doorbell
Wait for Success/fail
IF Success THEN
Insert STATUS
Ring Doorbell
ELSE
Clear STALL/ERROR
ENDIF
Once I ignored the spurious interrupt, which actually causes the xHC_INTERRUPTER_DEQUEUE to advance (in error by the way), and now do not check for a success/fail before sending the STATUS TRB, my code works just fine with QEMU.
I post this information here simply to show that if you are experiencing the same things, you now know why. I do not post trying to bash QEMU in any way. I use QEMU and have enjoyed using it for some time.
Anyway, just for your information. Hopefully someone can add these features/bug fixes to QEMU soon.
Thanks,
Ben
- http://www.fysnet.net/osdesign_book_series.htm