QEMU and xHCI

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

QEMU and xHCI

Post by BenLunt »

Hi guys,

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
The issue here is that the current QEMU code checks to see that there is a STATUS TRB on the ring *before* it starts the CONTROL transfer. In my opinion, this is in error, though reading the specifications, this is acceptable, though if the controller does do this, it *must* send an EVENT TRB if found in error. QEMU is not doing this. I have reported this as well: https://bugs.launchpad.net/qemu/+bug/1859378

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
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: QEMU and xHCI

Post by BenLunt »

BenLunt wrote: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.
After some research and communication, I have found the reason for this spurious interrupt. It actually isn't spurious afterall. QEMU is checking to see if the Event List is empty after every Event TRB is completed. Don't know why QEMU does this, but it does. On a mis-matched cycle bit, your code (the Consumer) is to update the xHC_INTERRUPTER_DEQUEUE register with the current TRB location *without* incrementing your internal Dequeue Pointer. This was my error, I was incrementing my internal Dequeue pointer. A simple code modification fixed this.
BenLunt wrote: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.
I still think this is in error. However, I seriously doubt that anything will be done to QEMU to fix it.

Anyway, I learned something today. I continue to learn something each day and that is what makes this hobby of ours so enjoyable.

Thanks,
Ben
- http://www.fysnet.net/osdesign_book_series.htm
Post Reply