Page 1 of 1

XHCI: EP ring 0 not working as I expected

Posted: Sat Feb 26, 2022 2:04 pm
by SanderR
Hello,

Since my XHCI code was not working at all on real hardware and it was quite messy, I decided to rewrite me whole XHCI code with the experience I got during the development on other drivers.
This time I was more successfull, but there are some things I cannot understand yet.

A) In the XHCI specification, it is stated in chapter 4.0.0 that you can get the portcount by reading the Structural Parameters 1 (HCSPARAMS1) and then read the Number of Ports (MaxPorts) field. On real hardware it works perfectly, on QEMU and VirtualBox they both give the value 1.

B) In the XHCI specification, it is stated in chapter 6.4.6 that the TRB types in the event ring, start with 3* but on real hardware, 30, is seen as 00, 31 is visible in real hardware as 01. Is this a known behavour?

C) In the event ring, you see events like port connection, enabled port, etcetra these give in the field TRB_pointer high and TRB_pointer low a address of the TRB element that caused the event. But when I use the transfer ring, the address is not defined in TRB_pointer.

D) When I push things on the transfer ring and ring the doorbell, I get a event_trb with the resultcode of 0 but I do not see any transfer on my buffer.
When I chance the value inside the commandpackage, it still succeed.

You have here the link of my XHCI file, I hope you can help me.
https://github.com/AdeRegt/SanderOSUSB/ ... dev/xhci.c

Re: XHCI: EP ring 0 not working as I expected

Posted: Sat Feb 26, 2022 8:05 pm
by BenLunt
SanderR wrote:Hello,

Since my XHCI code was not working at all on real hardware and it was quite messy, I decided to rewrite me whole XHCI code with the experience I got during the development on other drivers.
This time I was more successfull, but there are some things I cannot understand yet.

A) In the XHCI specification, it is stated in chapter 4.0.0 that you can get the portcount by reading the Structural Parameters 1 (HCSPARAMS1) and then read the Number of Ports (MaxPorts) field. On real hardware it works perfectly, on QEMU and VirtualBox they both give the value 1.
Note that this isn't the count of (physical) sockets, this is the count of port register sets. Depending on if the sockets are USB3 or not, you can have up to 2 register sets per socket.
For example, if you have 4 sockets and 2 of them are USB 3, you will have 6 register sets, and will be indicated in the HCSPARAMS1:MaxPorts register as 6.
SanderR wrote:B) In the XHCI specification, it is stated in chapter 6.4.6 that the TRB types in the event ring, start with 3* but on real hardware, 30, is seen as 00, 31 is visible in real hardware as 01. Is this a known behavour?

C) In the event ring, you see events like port connection, enabled port, etcetra these give in the field TRB_pointer high and TRB_pointer low a address of the TRB element that caused the event. But when I use the transfer ring, the address is not defined in TRB_pointer.

D) When I push things on the transfer ring and ring the doorbell, I get a event_trb with the resultcode of 0 but I do not see any transfer on my buffer.
When I chance the value inside the commandpackage, it still succeed.

You have here the link of my XHCI file, I hope you can help me.
I don't quite understand the remaining three questions. However, it sounds a whole lot like 3 scenarios:
1) You aren't waiting for the hardware to actually complete the task. QEMU will (instantly) complete the task do to the fact that there is no (real) hardware involved.
2) You don't have your (paging) memory set up correctly. You must use physical addresses with the hardware. Are you using Linear addresses throughout your driver, converting them to physical addresses? If so, check your code.
3) The most likely scenario is that you are not reading the physical memory as 32-bit dwords. QEMU requires you to access all (USB) hardware as 32-bit reads and writes. If your code looks like you are reading 32-bit dwords, double check, because the optimizer may optimize it to a BYTE or WORD read as shown in the URL I pointed to earlier.

Ben

Re: XHCI: EP ring 0 not working as I expected

Posted: Sun Feb 27, 2022 5:15 am
by SanderR
Thank you for your answer,

I understand question A now.

For the 3 points which you sended to me:
1) I am polling the event queue,when the event_trb is popped up, I start reading the contents . How should I wait for the completion of the real hardware?
Besides this, when I look at events like about getting a deviceid, it does give the address of the TRB. but when i use the transfer ring, it does not give an address.
2) My kernel is 32bit single tasking without paging.
3) I got -O0 set for the compiler for the XHCI file.

For me previous questions:
Let me give a example that I got from dumping the event_ring:
- When a portchance happens, I get: PortID: set, Completion-code: 1, TRB-type=2
- when a command completion event happens, CommandTRBPointer has the pointer to the TRB that I put on the command ring. Completion code is 1, TRBType is 1
- when a transfer event happens, TRBPointer is empty, Completion code is 1,Transfer length is 0, TRBType is 0, the buffer that i filled are compleetly empty

This is the thing that worries me:
- Is it normal that the TRB types are 2,1 and 0? the specs are talking about 30
- Why am I not getting a result?

Re: XHCI: EP ring 0 not working as I expected

Posted: Sun Feb 27, 2022 6:29 pm
by BenLunt
SanderR wrote:For the 3 points which you sended to me:
1) I am polling the event queue,when the event_trb is popped up, I start reading the contents . How should I wait for the completion of the real hardware?
Besides this, when I look at events like about getting a deviceid, it does give the address of the TRB. but when i use the transfer ring, it does not give an address.
2) My kernel is 32bit single tasking without paging.
3) I got -O0 set for the compiler for the XHCI file.
Even with optimizers off, your compiler most likely is still doing a BYTE or WORD read.
From the example I gave:

Code: Select all

if ((foo->bar & 0x000000FF) != 0x10) return 0;
A compiler most likely will read a byte from foo-bar. However, I don't know which compiler you are using, nor do I know if that particular 'optimization' is considered a compiler optimization or if it is a component of the compiler to automatically know it is a BYTE result and do a BYTE read.
If you are using a debugger, place a break point before or after this code and see what the compiler did. If you don't use a debugger, place a 'dummy' instruction before this code (xchg ebx,ebx), compile, then disassemble and search for the instruction.

An easy (not so efficient) fix is to call an external function (so that it is not in-lined) and read as a dword. This way the external function has no idea what foo->bar is. It simply reads a dword from the address you specify.
SanderR wrote: For me previous questions:
Let me give a example that I got from dumping the event_ring:
- When a portchance happens, I get: PortID: set, Completion-code: 1, TRB-type=2
- when a command completion event happens, CommandTRBPointer has the pointer to the TRB that I put on the command ring. Completion code is 1, TRBType is 1
- when a transfer event happens, TRBPointer is empty, Completion code is 1,Transfer length is 0, TRBType is 0, the buffer that i filled are compleetly empty

This is the thing that worries me:
- Is it normal that the TRB types are 2,1 and 0? the specs are talking about 30
- Why am I not getting a result?
You are to use the interrupters to know when a command is complete or an Event has occurred. A decent xHCI will have about 8 of them, the primary and 7 secondaries.

A completion code of 1 is a successful transfer. A TRB type of 2 is a SETUP packet, however that is not allowed on the Event Ring. I am guessing that your code may not be waiting for the event to actually happen before you read the Event ring. Either that, or you are reading from the wrong area within the Event Ring. How is your Cycle bit handled?

In your struct XHCI_TRB *waitForEvent() routine, you are simply waiting for a TRB's 64-bit Parameter field to equal an address you pass to it. First, you really should be using the interrupter mechanism to do this. Second, what if the controller writes the first 64-bit parameter to the Event Ring entry and your code matches it, in turn reads the rest of the TRB, but *before* the controller writes the rest of the TRB to memory?

The controller (Producer) has ownership of the memory all the way up to the point it fires the interrupt. Your code (Consumer) should not access that memory until then. As soon as the interrupt fires, your code becomes the owner of that memory. The same thing happens when you place a TRB in the command or transfer ring(s). Your code is the Producer all the way until the point you ring that ring's doorbell. Then, the Consumer (controller) owns that memory.

The Consumer must not access any memory the Producer owns until the Producer indicates that it is valid, by an interrupt or ringing of a doorbell.

Ben
- https://www.fysnet.net/the_universal_serial_bus.htm