xHCI Scheduled Requests

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
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

xHCI Scheduled Requests

Post by foliagecanine »

Hello again,
For once I'm not posting because my code has a bug.

Anyways, I was wondering: how do you create a scheduled request with xHCI.
I don't know if "scheduled request" is the right term for it, but I'll try to explain what I mean:

In my UHCI driver, I follow Ben's USB Book's suggestion of having different queue heads. One for 1ms, 2ms, 4ms, 8ms, etc.
Then I can link any transfer descriptor I want under there and it will interrupt every __ms (provided there is data and I reset the Active bit when the interrupt occurs).

But with xHCI, I can't seem to find a way to do that without using something like the system timer, since (as far as I can tell) to have a schedule run you have to write a doorbell.
It then processes the commands until it hits a different cycle bit. The problem is, the TD needs to be continuously running to catch any new data.

I'm trying to get my xHCI driver to work with my already existing HID driver implementation. However, it calls my

Code: Select all

usb_create_interval_in(uint16_t device_address, void *output_buffer, uint8_t interval_id, uint8_t endpoint_address, uint16_t max_packet_size, uint16_t buffer_size)
function. This is simple enough to do with UHCI, but I can't seem to figure out how to do this on xHCI.

This might have something to do with Isochronous transfers, but I'm not sure.
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: xHCI Scheduled Requests

Post by BenLunt »

Hi,

The xHCI is a different beast and handles things quite differently, though in my opinion, much better.

For example, in the previous controllers, you are right, you can place a TD in one of the periodical schedules and the controller will execute it at a given interval.

The xHCI does this differently. See section 4.14.3 of the xHCI version 1.0 specification. Here is a quote of one of the bullet points:
If multiple Interrupt TDs are posted to an Interrupt endpoint Transfer Ring, the xHC should consume no more than one TD per ESIT.
So you need to make sure and set the ESIT to the interval you wish, usually found in the device's endpoint descriptor.

Hope this helps,
Ben
- http://www.fysnet.net/the_universal_serial_bus.htm
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

BenLunt wrote:So you need to make sure and set the ESIT to the interval you wish, usually found in the device's endpoint descriptor.
Ok. So if I wanted a 16ms interval, I would put a 7 in the endpt.interval field (2^7 * 0.125 = 16).

But as far as I know, you must ring the doorbell whenever you want to do something on a ring.
How would you make this work for a periodic schedule?

slot.entries contains the number of endpoints active, correct? If so, how do you add more endpoints to a slot context? Do you have to send a disable slot command then an enable slot command again (except changing the slot.entries value)?
BenLunt wrote:Hope this helps
Well, you've at least put me on the right track :)
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

Hmm... I think I found something.
In the endpoint descriptor there is a field called "endpoint type", and for the control endpoint, I write a 4 (bidirectional control).
For a mouse, I would set this field to 7 (Interrupt in) to use this ring to read data, correct? How does this field change the behavior of the ring?
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: xHCI Scheduled Requests

Post by BenLunt »

If you set the interval to 16ms, the controller will only consume one TRB every 16ms. What if you place five TRBs in the INTERRUPT ring and ring the door bell (once)? The controller will consume five TRBs, one each 16ms interval and be done, correct?

Here is one way of doing it, not necessarily the best, nor the worst. Just one way of doing it.

What if you place three Transfer TRBs, one Transfer Event TRB, two more Transfer TRBs, and ring the door bell (once)?

When your driver receives the EVENT TRB on the Event ring, you can determine that it is coming from the INTERRUPT ring, telling you that you only have two more TRBs on that ring, therefore indicating that you need to insert another set of TDs on that ring, including an Event TRB.

With the Event mechanism of the xHCI, the controller can indicate to your driver that you need to do something. In this case, add more TRBs to the transfer ring. This way there is no polling of the schedule. You can let the controller handle all of the events. Hence the name.

Place a few transfer TRBs (also called Transfer Descriptors when one or more TRBs are used for one transfer) on the ring and include a Transfer Event TRB. Then the driver that handles the EVENT ring can place more TDs on the interrupt ring. It is a good idea to place the Transfer Event TRB in the middle of the TRBs so that it is not the last TRB. This way, if the EVENT ring driver is "late", you still have a few intervals remaining before you run out of TDs.

Does this make sense? Of course this means you have to have an EVENT handler installed, passing messages between rings, but I hope you get the idea.

One more note is that since the interval will most likely never change, i.e.: a mouse will always have an interval of 16ms (for example), you can place 128 TRBs on its ring before you use the event TRB. In fact, the more TRBs you use before an Event TRB, the less your driver has to consume CPU bandwidth. If you have a ring of 4096 entries, use them all before an Event is used.

There are a few more specifics that must be followed, but this should give you an idea.
Ben
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

I think I'm starting to get it...

So as long as I'm placing TRBs on the ring as fast or faster than it is consuming them, I'll never need to ring the doorbell again?

So theoretically, I could do something like this:

Code: Select all

DEDEDEDEL
^       |
+-------+
Where D = Data TRB, E = Transfer Event TRB, L = Link TRB, and I would just have to flip all the cycle bits every time I get to the Link TRB?

Is there a way to execute multiple TRBs in one ESIT interval?

Also, what happens if there's a NAK? Does it advance to the next TRB or does it just have a different status. In the second case, would the Transfer Event TRB fire an interrupt if its IOC bit were on?
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: xHCI Scheduled Requests

Post by BenLunt »

foliagecanine wrote:I think I'm starting to get it...

So as long as I'm placing TRBs on the ring as fast or faster than it is consuming them, I'll never need to ring the doorbell again?
In theory, yes. However, when the controller gets to the end of the ring and toggles its internal cycle bit, it will stop when it finds a cycle mismatch, unless of course you correctly place TRBs with the new cycle bit at the first of the ring.

However, this isn't practical and don't rely upon the fact that you can place TRBs on the ring faster than it can consume them.
foliagecanine wrote: So theoretically, I could do something like this:

Code: Select all

DEDEDEDEL
^       |
+-------+
Where D = Data TRB, E = Transfer Event TRB, L = Link TRB, and I would just have to flip all the cycle bits every time I get to the Link TRB?
In theory, yes, this would work, but now you are relying on the fact that your code will always be ahead of the controller. This is not the case for many reasons.

What happens when your task scheduler doesn't come back around to your xHCI/Mouse/USB/Whatever task in time? You will continue to place TRBs on the schedule, but now you're not in sync with the controller and it may or may not consume any TDs. The instant the cycle bit is a mismatch, it stops and will not start again until a doorbell is rung. Yet, you are continuing to place TRBs on the ring, overwriting TRBs that may or may not have been consumed.
foliagecanine wrote:Is there a way to execute multiple TRBs in one ESIT interval?
From the quote I mentioned before from the specification, and for what you are asking (you assuming a complete transfer is a TRB, not TD), this is a NO. (Transfer Descriptors can contain multiple TRBs, and the specification states TDs, not TRBs)
Let me quote it again:
If multiple Interrupt TDs are posted to an Interrupt endpoint Transfer Ring, the xHC should consume no more than one TD per ESIT.
...
if the Interrupt TD Transfer Size is greater than the Max ESIT Payload, then the TD may take multiple ESITs to complete.
Since your mouse will most likely only require one TRB per transfer, then the TD will be 1 TRB in size. With this in mind, the controller will only consume 1 TD (and TRB) per interval.
foliagecanine wrote:Also, what happens if there's a NAK? Does it advance to the next TRB or does it just have a different status. In the second case, would the Transfer Event TRB fire an interrupt if its IOC bit were on?
The specification states:
A Short Packet shall terminate an IN Interrupt TD and the next TD (if present) shall be scheduled in the next ESIT.
This is only short packets. I would have to go over my notes for NAKs.

Ben
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

As I was writing this post, I think I finally understood what you said.

I misunderstood where the IOC bit was set on your suggestion (I thought the Data TRBs did not have the IOC bit set).
Now I see what you are saying. Something like this:

Code: Select all

DDDDEDDDDEL
Chain bits are set on D4, D8, and E2. IOC Bit is on all TRBs except L.
When E1 interrupts, it should flip the Cycle bit of E1, D1-D4, and L.
When E2 interrupts, it should flip the Cycle bit of E2 and D5-D8.
When a D interrupts, it should send a message to the driver to signal that there is new data.

Also, by "as fast or faster" I did not mean based on a timer. I meant based off of interrupts. Sorry I wasn't clear on that.

-----------------------------------

In the xHCI spec, it says
Intel xHCI Spec rev. 1.2 Section 3.2.6.1 wrote:A Chain flag in the TRB is used to
identify the TRBs that comprise a TD. Therefore, a TD refers to a consecutive set of TRB data structures on a Transfer Ring, where the Chain flag is set in all but the last TRB of a TD
If I understand correctly, this means that you CAN execute multiple TRBs in one ESIT interval as long as they are chained together into a single TD.
Sorry my question wasn't clear.

-----------------------------------

And about NAKs, I think Rev. 1.2 Section 4.14.3.1 talks about it, but I'm not quite sure. I think it just schedules the next TD and waits until the next ESIT interval. I can't tell whether it interrupts if the IOC bit is on but receives a NAK though (the reason I'm concerned about NAKs is because keyboards and mice send NAKs when there's no new data and they are in idle mode).

Am I understanding this right? I'm just trying to figure out a "correct" way to get data from an interrupt in endpoint.
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: xHCI Scheduled Requests

Post by BenLunt »

foliagecanine wrote:Am I understanding this right? I'm just trying to figure out a "correct" way to get data from an interrupt in endpoint.
I might have misunderstood some of the things you were stating too. It is difficult to express intent via text, not speech, for sure.

Anyway, I think you are understanding what needs to be done. I am glad to help. Please feel free to continue to post and I will try my best to help some more.

Ben
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

I know this thread is a little old, but I just found something today:
Intel xHCI Specification, Rev. 1.2, Section 4.11.7 wrote:Software shall not define a Link TRB as the first TRB of a multi-TRB TD.
Software shall not define a Link TRB as the last TRB of a multi-TRB TD
So if you can't Chain a link TRB onto the end of another TRB, does it still take an entire ESIT interval?
I'm just wondering, since all of the TDs in the endpoint will be a single TRB, so if the Link TRB takes an entire ESIT, that would cause a little delay every once in a while.
The only way you could use the Chain bit of the Link TRB is if it were in the middle of two or more other TRBs.
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: xHCI Scheduled Requests

Post by BenLunt »

foliagecanine wrote:I know this thread is a little old, but I just found something today:
Intel xHCI Specification, Rev. 1.2, Section 4.11.7 wrote:Software shall not define a Link TRB as the first TRB of a multi-TRB TD.
Software shall not define a Link TRB as the last TRB of a multi-TRB TD
So if you can't Chain a link TRB onto the end of another TRB, does it still take an entire ESIT interval?
I'm just wondering, since all of the TDs in the endpoint will be a single TRB, so if the Link TRB takes an entire ESIT, that would cause a little delay every once in a while.
The only way you could use the Chain bit of the Link TRB is if it were in the middle of two or more other TRBs.
A LINK TRB should be all by itself, as the last TRB in the segment/ring, and should not be a part of another TD.
Yes, the LINK TRB will take a small delay, but the delay is in Nanoseconds.
Since the LINK TRB is not considered a transfer, the controller will continue to the next TD as if there was no LINK TRB.

Ben
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: xHCI Scheduled Requests

Post by foliagecanine »

BenLunt wrote:Yes, the LINK TRB will take a small delay, but the delay is in Nanoseconds.
Since the LINK TRB is not considered a transfer, the controller will continue to the next TD as if there was no LINK TRB.
Oh, ok. So since it's not considered a transfer it doesn't take an entire ESIT interval. Thank you for all your help.
My OS: TritiumOS
https://github.com/foliagecanine/tritium-os
void warranty(laptop_t laptop) { if (laptop.broken) return laptop; }
I don't get it: Why's the warranty void?
Post Reply