[Solved] Finding Interface and Endpoint descriptors

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

[Solved] Finding Interface and Endpoint descriptors

Post by foliagecanine »

I'm working on my USB code and I am able to retrieve device descriptors, string descriptors, and configuration descriptors.
However, I don't know whether I'm doing the interface (and endpoint) descriptors correctly.
foliagecanine wrote:At first, I thought that the interface descriptors come directly after the configuration descriptors, but the output doesn't seem right:

Code: Select all

QEMU USB Keyboard
...
Interface 0
  Class: 3
  Subclass: 3
  Protocol: 3
Protocol 3 for a USB keyboard? I would think it would be protocol 1. Also, the USB mouse returns these same values.
Edit: A bug in my code caused the class to be displayed for all 3 values. This is fixed. However, the other problems still remain

The only interface in one of the configurations for a USB Network Device returned 34 endpoint descriptors!
And from there, I thought the endpoint descriptors are directly after the interface descriptors, but I don't think these values are right:

Code: Select all

QEMU USB Keyboard
...
Interface 0
...
    Endpoint 0
      Address: 0x11
      Max Packet Size: 256
      Interval: 34ms
Endpoint address 0x11? I don't think that's right.

Am I missing something? Are the values aligned to something?
Last edited by foliagecanine on Thu Nov 19, 2020 7:07 pm, edited 1 time in total.
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: Finding Interface and Endpoint descriptors

Post by foliagecanine »

Hmm... I've stumbled upon an unknown descriptor and can't seem to find out what it is.

Since I was getting weird values, I dumped memory for where I was getting my endpoint descriptor data from:

Code: Select all

0134012: 0x09 0x21 0x11 0x01 0x00 0x01 0x22 0x3f
013401a: 0x00 0x07 0x05 0x81 0x03 0x08 0x00 0x0a
And there's the endpoint descriptor at 0x13401b!
But what is the descriptor in the middle? It has a type of 0x21 and a length of 9.

I guess to fix my problem with the endpoint descriptors, I'll find the interface descriptor and then check the header for each descriptor until I find the endpoint descriptor (and continue doing this until I find the one I want).
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: Finding Interface and Endpoint descriptors

Post by foliagecanine »

I guess I should've done a quick Ctrl+F on the HID spec.

It's the HID descriptor. Apparently the endpoint descriptor is a "subset" of the HID descriptor (according to the spec) so the HID descriptor comes first.

I guess the "correct" way to find interface and endpoint descriptors is to just iterate through all the descriptors, check the headers, and add the length of the descriptor until you find the descriptor type, then loop until correct one is found.

I'm surprised the method for finding interface, endpoint(, and other) descriptors isn't mentioned anywhere in the wiki. Perhaps we should add that?
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: Finding Interface and Endpoint descriptors

Post by BenLunt »

Hi,

Just a few notes, if I may.

Any and all Endpoint Descriptors are contained within the Interface Descriptor which is contained within the Config Descriptor. However, please note that there can be multiple Interface Descriptors *and* multiple Config Descriptors, as well as Alternate Interface Descriptors containing multiple Interface Descriptors all grouped together. Try enumerating a camera device with multiple resolution capabilities and you will see.
foliagecanine wrote:Protocol 3 for a USB keyboard? I would think it would be protocol 1. Also, the USB mouse returns these same values.
Edit: A bug in my code caused the class to be displayed for all 3 values. This is fixed. However, the other problems still remain
Even with USB, backward compatibility comes into play. Most HID mice and keyboards will allow you to set the protocol used. This protocol usually defaults to the Boot Protocol returning a known and set Report Descriptor. For example, the Boot Protocol for the Mouse might be

Code: Select all

  byte 0: Buttons
  byte 1: X displacement
  byte 2: Y displacement
This is so a Legacy BIOS or other firmware can enumerate and use most any mouse before a vendor specific driver is installed. Once this driver is installed, it can query the mouse for other report formats and then choose one of these formats, which might be the same or could be completely different.

This is where the HID comes into play. Using the "Get Report Descriptor Request", you can retrieve the HID values that will describe the report returned. Here is an example from page 15-9 of my book:

Code: Select all

0x00000000  05 01 09 02 A1 01 09 01-A1 00 05 09 19 01 29 03
0x00000010  15 00 25 01 75 01 95 03-81 02 75 05 95 01 81 01
0x00000020  05 01 09 30 09 31 09 38-15 81 25 7F 75 08 95 03
0x00000030  81 06 C0 09 3C 05 FF 09-01 15 00 25 01 75 01 95
0x00000040  02 B1 22 75 06 95 01 B1-01 C0
It should describe the following (or similar) report:

Code: Select all

  byte 0: Buttons  
           Bits 7-3 = reserved
              Bit 2 = Button 3
              Bit 1 = Button 2
              Bit 0 = Button 1
  byte 1: X displacement
           Range of -127 to +127
  byte 2: Y displacement
           Range of -127 to +127
  byte 3: Z displacement
           Range of -127 to +127
If you use the HIDParser utility included with the source code of my book, you can see how this works. For example, here is part of a Report Descriptor from another mouse:

Code: Select all

05 01 Usage Page (Generic Desktop)
09 02 Usage (Mouse)
A1 01 Collection (Application)
09 01   Usage (Pointer) (Inherits the Usage Page)
A1 00   Collection (Physical)
05 01     Usage Page (Generic Desktop)
09 30     Usage (X)
09 31     Usage (Y)
09 38     Usage (Wheel)
15 81     Logical Min (-127)
25 7F     Logical Max (127)
75 08     report size (8)
95 03     report count (3)
81 06     Input (Data,Var,Rel,NoW,Lin,PState,NoNull,Bit)
Notice that the report describes three (3) usage items (X, Y, Wheel), describes that they all use a range of -127 to +127, the report size is 8 bits per, and there are three (3) items. This gives the format of the X, Y, and Z parts of the report.

Since the buttons are of a different size (1 instead of 8), and a different Logical Min and Max, (0 to 1, instead of -127 to +127), a new set of parameters are given and another "action" line is used. Here is a section that might describe these buttons:

Code: Select all

15 00     Logical Min (0)
25 01     Logical Max (1)
75 01     report size (1)
95 02     report count (2)
B1 22     Feature (Data,Var,Abs,NoW,Lin,NoPre,NoNull,Bit)
75 06     report size (6)
95 01     report count (1)
B1 01     Feature (Cons,Array,Abs,NoW,Lin,PState,NoNull,Bit)
Notice now that the Logical Min and Max, the Size, and the Count have changed. Also notice that there are six (6) reserved bits after these buttons and before the next item. Imagine if these six (6) bits were not given--the X, Y, and Z values would be unaligned. (A small tidbit would be that the manufacturer could have used a reserved field of six 1-bit fields compared to one 6-bit field).

Another thing to know about periodic driven devices, such as mice, is you have to tell the device how often to report this information. A Mouse might report it so often that all of your USB Stack Bandwidth is used up on Movement Reports, almost all of these reports being the exact same thing. Therefore, it is a good idea to send the "Set Idle" request to the mouse to tell it to only report a change in a report, rather than constantly reporting the same report. However, with this in mind you have to handle Stalls and Request Errors. Not all mice support the "Set Idle" request.

Back to the Set Protocol Request. Once you know what type of mouse you have and its capabilities, now you can tell the mouse what HID report to return. Imagine a gaming mouse with all three axis and multiple buttons as well as two or three wheels. All perfectly legal and acceptable as long as you parse the HID report correctly and then know what to do with the results.

Chapter 15 of my book explains in detail what you should expect from a typical mouse, how to set it up, and then the reports it might return. Chapter 16 does this for HID keyboards. Chapter 25 along with the source code mentioned above, describes how to parse an HID Report.

Just for fun, HID devices are practically unlimited on what can be done. For example, have you ever played one of those modern Game Consoles and the handheld controller vibrates on occasion? This may simply be an HID report sent to the controller to "turn on" vibration, and then another sent again to "turn it off". These controllers also have motion controls that may simply be an axis value via an HID report descriptor.

Did you know that a keyboard accepts an HID report from the host? A keyboard usually only sends reports to the host when a key is pressed or released. However, the keyboard might accept a report from the host to turn on or off the Status LEDs. If the interface includes an INTERRUPT OUT endpoint, it might have an HID report identifying the format to use. If the interface doesn't contain an INTERRUPT OUT endpoint, sending the "Set Report" request can be used. One thing to remember, turning on or off one of the LEDs does not change the status of the keyboard's state. The LEDs are simply an indicator and your driver must keep it in sync with the state of the keyboard, also noting that the keyboard itself does not keep a Caps-Lock (or other) state. It is simply a key to tell the host to set an internal state.

What an interesting hobby we have, huh?

Hope this helps,
Ben
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: Finding Interface and Endpoint descriptors

Post by nullplan »

BenLunt wrote:However, please note that there can be multiple Interface Descriptors *and* multiple Config Descriptors,
True, but I personally have never seen a USB device with multiple configurations. Not even my UMTS modem. That is a weird device that first shows up as CD drive. You then have to issue a usb-modeswitch command and that will make it turn into a modem. I thought it might have multiple configurations, but no, it has only one. The mode switch makes it disconnect from the bus and reconnect in the other personality.

And all other USB devices I have ever looked at have only had a single configuration. Multiple configurations would have the drawback that only one can be active at a time, thus disabling all other configurations.
Carpe diem!
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: Finding Interface and Endpoint descriptors

Post by foliagecanine »

Thanks for the info BenLunt!

However, I'm still having trouble with several things, and a couple questions.

USB Error Interrupts:
BenLunt wrote:However, with this in mind you have to handle Stalls and Request Errors.
How does one handle errors (in general)? For TD errors, that is pretty easy for most things; I just ignore the device or the descriptor (unless there's a bug), depending on what errored.
But what I'm getting now is a USB error interrupt.
I haven't mapped the USB IRQ (I know how, just haven't gotten around to it), so I have my generic interrupt dump on several IRQs (7,9,10,11).
For one specific device, a (VMware passed-through) Arduino UNO, when I try to request the string descriptor (which works for all other devices), it generates a USB interrupt. Here's a register dump:

Code: Select all

USBCMD : 0x00C1
USBSTS : 0x0002
USBINTR: 0x0000
FRNUM  : 0x0592
PORTSC1: 0x00C1
PORTSC2: 0x0002
It shows me that there's a "USB Error," but when I try to return from the interrupt, immediately another one comes with the same values! And USBINTR is zero, meaning no interrupts, right? How does one stop the interrupts? Do you have to clear the USB Error Interrupt bit in USBSTS after an interrupt?

Null Headers:
BenLunt wrote:Any and all Endpoint Descriptors are contained within the Interface Descriptor which is contained within the Config Descriptor. However, please note that there can be multiple Interface Descriptors *and* multiple Config Descriptors, as well as Alternate Interface Descriptors containing multiple Interface Descriptors all grouped together.
Alright. But what is the proper method of finding the interface and endpoint descriptors?
I'm still having some troubles, especially when it comes to endpoint descriptors. When my loop iterates through the headers, it encounters a null header (0x00 0x00 0x00 0x00 ...then more actual data) and since the "length" is 0, it would infinitely loop, so instead it just stops and disregards the device.
Is the descriptor before this another descriptor that has a "total length" field?
nullplan wrote:
BenLunt wrote:However, please note that there can be multiple Interface Descriptors *and* multiple Config Descriptors,
True, but I personally have never seen a USB device with multiple configurations.
Actually, the emulated -device usb-net in QEMU has two configurations. I haven't figured out what each does, but it's out there.

Only EHCI:
As it turns out, NONE of my real PCs have working UHCI controllers. Two of my PCs have ONLY (two) EHCI controllers. How is this possible?! I thought they had to have UHCI or OHCI companion controllers to be able to use Low-speed or full-speed devices (which do work with both PCs)
The only one which "has" one has a broken UHCI controller (USB doesn't work on Windows either, have to use a CardBus adapter), so I'm stuck with QEMU and VMware.
Next I'll write an OHCI controller driver, but I want to make sure my generic USB driver works first.

Other Stuff:
However, I noticed that my driver does not find ANY mass storage devices on VMware. This could either be a problem with the way VMware takes control of the usb driver or something in my code. I do however, find the mouse, bluetooth USB adapter, and Arduino (although this one errors) attached via pass-through to the VM with my driver.

I've finally gotten USB Hub support so I don't just have to enumerate the VMware Virtual Mouse and VMware Virtual Hub every time in VMware :)
New code is now uploaded at https://github.com/foliagecanine/tritium-os.
UHCI code
USB code
USB Hub code
USB Headers
Debug code (Line 48 is the function that calls the string descriptor function where the USB Error Interrupt occurs)

And thank you Ben for writing the book. It was the way I even got started on USB (the wiki page, the spec, and other sites were all too complicated for me to start with). Easily worth the $10 I spent (Kindle).

Anyways, thanks for replying.
BenLunt wrote:What an interesting hobby we have, huh?
Indeed. :D
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: Finding Interface and Endpoint descriptors

Post by BenLunt »

foliagecanine wrote:How does one handle errors (in general)? For TD errors, that is pretty easy for most things; I just ignore the device or the descriptor (unless there's a bug), depending on what errored.
But what I'm getting now is a USB error interrupt.
I haven't mapped the USB IRQ (I know how, just haven't gotten around to it), so I have my generic interrupt dump on several IRQs (7,9,10,11).
For one specific device, a (VMware passed-through) Arduino UNO, when I try to request the string descriptor (which works for all other devices), it generates a USB interrupt. Here's a register dump:

Code: Select all

USBCMD : 0x00C1
USBSTS : 0x0002
USBINTR: 0x0000
FRNUM  : 0x0592
PORTSC1: 0x00C1
PORTSC2: 0x0002
It shows me that there's a "USB Error," but when I try to return from the interrupt, immediately another one comes with the same values! And USBINTR is zero, meaning no interrupts, right? How does one stop the interrupts? Do you have to clear the USB Error Interrupt bit in USBSTS after an interrupt?
Each of the bits in the Status register are Write/Clear, meaning you need to write a 1 to that bit to clear it.

However, I believe that you have an error in your schedule and either one of the TDs has the IOC bit set *or* your schedule is running into no-mans-land and it is interpreting the bad data as the IOC bit being set. Remember, the schedule is incremented every millisecond (called a frame) and if your schedule has any TD within that frame with the IOC bit set, it will generate an interrupt at the end of the frame, even if the TD is marked inactive (See note 1).

As for the String Descriptor Request, you must check to see if the device actually has a String Descriptor for that index. For example, not all devices have String Descriptors for index 1. The specific descriptors will tell you which index to use for that particular descriptor.

As for correcting errors, if it is an error (not a hardware error), you need to remove that TD (or group of TDs and hopefully a QUEUE) from the schedule. The controller might be trying to continue where it left off on the next frame.

As for Stall errors, you need to clear the stall with a SETUP packet on the Control Pipe. (Remember that clearing a stall on a BULK pipe will reset the toggle bit to zero)
foliagecanine wrote:But what is the proper method of finding the interface and endpoint descriptors?
I'm still having some troubles, especially when it comes to endpoint descriptors. When my loop iterates through the headers, it encounters a null header (0x00 0x00 0x00 0x00 ...then more actual data) and since the "length" is 0, it would infinitely loop, so instead it just stops and disregards the device.
Is the descriptor before this another descriptor that has a "total length" field?
The Configuration Descriptor has a total length field that encompasses all of the descriptors within this configuration, which includes the Interface and Endpoint descriptors. If you are encountering zeros, you have done one of two things:
1) Skipped past the end of the descriptor
2) Did not read in the whole descriptor
Note that the Configuration descriptor can be larger than 255 bytes. This is not likely on a standard mouse, but a camera or other device might have larger descriptor lengths for the TotalLen field of the Config Descriptor.
I have found that it is a good idea, when first learning about this stuff, is to dump the whole Config Descriptor to the screen in the old DOS DEBUG style dump.
For example, here is a dump of a USB3 Thumb Drive:

Code: Select all

Complete Config Descriptor
 01890091  09 02 2C 00 01 01 00 80-70 09 04 00 00 02 08 06
 018900A1  50 00 07 05 81 02 00 04-00 06 30 0E 00 00 00 07
 018900B1  05 02 02 00 04 00 06 30-0E 00 00 00
Then tear it apart:

Code: Select all

Config Descriptor
 01890091  09 02 2C 00 01 01 00 80-70
Interface Descriptor
 0189009A  09 04 00 00 02 08 06 50-00
 Endpoint Descriptor
 018900A3  07 05 81 02 00 04 00
 USB3 Endpoint Companion Descriptor
 018900AA  06 30 0E 00 00 00
 Endpoint Descriptor
 018900B0  07 05 02 02 00 04 00
 USB3 Endpoint Companion Descriptor
 018900B7  06 30 0E 00 00 00
Dump your whole descriptor and see what you get.
foliagecanine wrote:As it turns out, NONE of my real PCs have working UHCI controllers. Two of my PCs have ONLY (two) EHCI controllers. How is this possible?! I thought they had to have UHCI or OHCI companion controllers to be able to use Low-speed or full-speed devices (which do work with both PCs)
The only one which "has" one has a broken UHCI controller (USB doesn't work on Windows either, have to use a CardBus adapter), so I'm stuck with QEMU and VMware.
Next I'll write an OHCI controller driver, but I want to make sure my generic USB driver works first.
It is perfectly legal to not have a companion controller. However, without a companion controller, low- and full-speed devices--mice, keyboards, etc.--will not enumerate. However, manufacturers found out that it is cheaper to install a rate-matching hub on the card than to install one or more companion controllers. A rate-matching hub is the same as an EHCI controller with a single port with a high-speed hub plugged into that port, this hub allowing low- and full-speed, as well as high-speed devices to be attached. A rate-matching hub became less expensive to manufacture, so EHCI controllers started to be shipped with this setup.

However, now your software must support external hubs, since this rate-matching hub is pretty much an external hub. The other caveat is that enabling this RMH is vendor specific, not USB specific.
foliagecanine wrote: However, I noticed that my driver does not find ANY mass storage devices on VMware. This could either be a problem with the way VMware takes control of the usb driver or something in my code. I do however, find the mouse, bluetooth USB adapter, and Arduino (although this one errors) attached via pass-through to the VM with my driver.
I don't use VMware so can't help you there. However, if the mass storage device is USB 3.0, VMware (as well as the Host) may be enumerating it as USB 3.0 only and since you are only using UHCI and EHCI, you won't see it.
foliagecanine wrote: And thank you Ben for writing the book. It was the way I even got started on USB (the wiki page, the spec, and other sites were all too complicated for me to start with). Easily worth the $10 I spent (Kindle).

Anyways, thanks for replying.
Thank you for the kind words. Of this hobby, USB is my favorite subject. I try to help where I can, when I can.

Ben

Note 1: I am recalling all of this from memory and haven't done anything with UHCI in years, so please verify before you take it as fact. :-)
foliagecanine
Member
Member
Posts: 148
Joined: Sun Aug 23, 2020 4:35 pm

Re: Finding Interface and Endpoint descriptors

Post by foliagecanine »

BenLunt wrote:I have found that it is a good idea, when first learning about this stuff, is to dump the whole Config Descriptor to the screen in the old DOS DEBUG style dump.
For example, here is a dump of a USB3 Thumb Drive:

Code: Select all

...
Then tear it apart
Good idea. I've done it for a device I know is having problems (QEMU's usb-net) and here's the output:

Code: Select all

09 02 43 00 02 02 09 C0 40 00 00 00 00 01 02 02 
FF 06 05 24 00 10 01 05 24 01 00 01 04 24 02 00 
05 24 06 00 01 07 05 81 03 10 00 20 09 04 01 00 
02 0A 00 00 04 07 05 82 02 40 00 00 07 05 02 02 
00 00 00
And to split it into their descriptors:

Code: Select all

Config Descriptor: 09 02 43 00 02 02 09 C0 40 
Umm...: 00 00 00 00 01 02 02 FF [06
Class Specific: 05 24 00 10 01] *OR*
[Device Qualifier: 06 05 24 00 10 01]
Class Specific: 05 24 01 00 01 
Class Specific: 04 24 02 00
Class Specific: 05 24 06 00 01 
Endpoint Descriptor: 07 05 81 03 10 00 20
Interface Descriptor: 09 04 01 00 02 0A 00 00 04
Endpoint Descriptor: 07 05 82 02 40 00 00
Endpoint Descriptor: 07 05 02 02 00 00 00
Now, I don't plan on writing a driver for this specific device, but want to make sure my method to check it is correct.
What is going on here?

---
BenLunt wrote:Each of the bits in the Status register are Write/Clear, meaning you need to write a 1 to that bit to clear it.

However, I believe that you have an error in your schedule and either one of the TDs has the IOC bit set *or* your schedule is running into no-mans-land and it is interpreting the bad data as the IOC bit being set. Remember, the schedule is incremented every millisecond (called a frame) and if your schedule has any TD within that frame with the IOC bit set, it will generate an interrupt at the end of the frame, even if the TD is marked inactive (See note 1).
I "solved" this problem by writing the value 3 to the USBSTS register when an interrupt occurs. It clears the USB Error interrupt bit and the USBINT, and (to my surprise) simply timed out afterwards and caused no further problems (besides not giving a string descriptor). I can then just give it a generic "Unknown USB Device" string.
BenLunt wrote:However, manufacturers found out that it is cheaper to install a rate-matching hub on the card than to install one or more companion controllers. A rate-matching hub is the same as an EHCI controller with a single port with a high-speed hub plugged into that port, this hub allowing low- and full-speed, as well as high-speed devices to be attached. A rate-matching hub became less expensive to manufacture, so EHCI controllers started to be shipped with this setup.
Interesting. I did see a rate-matching hub for each port on the EHCI controllers, but I figured they were just regular USB hubs with a fancy name. I guess I need to make an EHCI driver now :) . One thing I did notice though, I would think that with a RMH it would have all devices show up as 480M, but they do show up with their proper speeds (1.5M for low-speed, 12M for full-speed and 480M for high-speed)

---

As for VMware and the mass storage devices, I can't find any indication that they are being connected to the VM at all. I had VMware output a serial log and nothing indicated that there were more devices that failed enumeration. The USB Floppy Drive I had (which I know isn't a MSD) enumerated though...

---

Just a question, but out of OHCI, EHCI, or XHCI, which do you think is the easiest to understand and implement (question is for anyone who's implemented USB)?
I have all 3 of these controller types available for testing either in virtual machines (all 3) or physical machines (OHCI and EHCI, sadly not XHCI).

Anyways, again, thanks for the info.
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: Finding Interface and Endpoint descriptors

Post by BenLunt »

foliagecanine wrote:Good idea. I've done it for a device I know is having problems (QEMU's usb-net) and here's the output:

Code: Select all

09 02 43 00 02 02 09 C0 40 00 00 00 00 01 02 02 
FF 06 05 24 00 10 01 05 24 01 00 01 04 24 02 00 
05 24 06 00 01 07 05 81 03 10 00 20 09 04 01 00 
02 0A 00 00 04 07 05 82 02 40 00 00 07 05 02 02 
00 00 00
If I would take a guess at what is going on, I would think that your code is actually writing a 32-bit DWORD zero to offset 0x09 in the buffer, somewhere after the point that you retrieve it and before the point where you print it out. It just so happens to be at offset 9, the length of the descriptor you are retrieving. This seems too coincidental. Do you use the dword after the 9 bytes as a status location?

How do you allocate the buffer for the Config Descriptor? Do you allocate/preserve 9 bytes for it and have a 32-bit DWORD after it for something else? For the Config Description, you should allocate at least 255 bytes, preferably a bit more.
foliagecanine wrote:The USB Floppy Drive I had (which I know isn't a MSD) enumerated though...
Acutally, the Floppy Drive is most likely a MSD device, using the CBI protocol. CBI standing for Control/Bulk/Interrupt. An MSD (Mass Storage Device) device can use the BBB (Bulk/Bulk/Bulk, aka Bulk Only), the CB, the CBI, the ASP (Attached SCSI Protocol), or any other protocol. USB Floppies most likely use the UFI Floppy Disk interface with either the CB or CBI protocol. (CB is Control/Bulk and CBI is Control/Bulk/Interrupt)
foliagecanine wrote:Just a question, but out of OHCI, EHCI, or XHCI, which do you think is the easiest to understand and implement (question is for anyone who's implemented USB)?
In my opinion, the OHCI is much easier to program for and use than UHCI. UHCI tends to be a software driven protocol while OHCI tends to be a hardware driven protocol. In other words, UHCI tends to require a bit more software interaction while OHCI tends to accomplish more using the hardware. Therefore, my opinion is the OHCI is easier and more efficient. However, due to manufacturer rights, you will find more UHCI controllers than OHCI controllers.

As for the other two controllers, I feel that xHCI finally got it right, while EHCI was a kludge of backward compatibility and modern devices. Therefore, if I had to choose which of the four, I would choose in the following order, best/easiest to worst/most difficult--again, just being my opinion:

xHCI, OHCI, UHCI, EHCI.

Note that most new computers will include only an xHCI, since it will handle all speeds, where as the other controllers are speed specific.

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

Re: Finding Interface and Endpoint descriptors

Post by foliagecanine »

Well, I think I found what was wrong... and I inadvertently fixed it without even realizing it!
Correct:

Code: Select all

09 02 43 00 02 02 09 C0 32 09 04 00 00 01 02 02 ...
Earlier today, I was trying to add a function that adds a queue to the queue chain for each interval (1ms, 2ms, 4ms, etc.) and I noticed this code:

Code: Select all

// Create the number of TDs required to transfer the data
uint16_t size_remaining = size;
for (i = 1; i < num_tds-1 && size_remaining; i++) {
	td[i].linkptr = (uint32_t)&p_td[i+1];
	td[i].flags0 = (device->lowspeed*UHCI_XFRDESC_LS) | UHCI_XFRDESC_CERR | UHCI_XFRDESC_STATUS_OFF(0x80);
	td[i].flags1 = UHCI_XFRDESC_MAXLEN_OFF(size_remaining <= device->max_pkt_size ? size_remaining-1 : device->max_pkt_size-1) | ((i&1)*UHCI_XFRDESC_DTOGGLE2) | UHCI_XFRDESC_DEVADDR_OFF(device->address) | UHCI_PID_IN;
	td[i].bufferptr = (uint32_t)p_buffer+((i-1)*8);
	size_remaining -= (size_remaining <= device->max_pkt_size ? size_remaining : device->max_pkt_size);
}
Much of the code is based around the device's max packet size (you can see several requests to the variable). However, in this line:
td.bufferptr = (uint32_t)p_buffer+((i-1)*8);
I hard-coded the value 8 for the buffer location. I decided to fix it as soon as I saw it (I didn't know whether this was an intentionally hard-coded value at the time or not). It worked the same, so I kept it.

Then I read your forum post about the value being overwritten just after the config descriptor. I decided to take a look at my config, interface, and endpoint code. I put a few more dumps to see if I could pinpoint the location of the problem. However, it worked first time!

The max packet size for any working devices was 8 and the max packet size for the usb-net was 64. That explained why only some devices weren't working. With the way that it overwrites data, I'm actually surprised it didn't cause more damage to the data.

---

BenLunt wrote:How do you allocate the buffer for the Config Descriptor? Do you allocate/preserve 9 bytes for it and have a 32-bit DWORD after it for something else? For the Config Description, you should allocate at least 255 bytes, preferably a bit more.
For any data transfer for UHCI, I actually allocate a using virtual pages (4096 bytes). The transfer's queue, all its TDs, the setup packet, and the data are all stored there.
The buffer is the last thing in the list (so it doesn't overwrite any data if it overflows).
I used to only allocate one (1) page per transfer, but recently created a method so it checks how much will be needed and adjusts accordingly.
For any descriptor requests for descriptors NOT stored after the config descriptor, it only requests the necessary data (for instance, 9 bytes for a config descriptor).
For any descriptor requests that ARE stored after the config descriptor, it requests the whole config descriptor (config.total_len). It then loops through all the headers (and data if applicable) until it finds the one requested.

---

BenLunt wrote:As for the other two controllers, I feel that xHCI finally got it right, while EHCI was a kludge of backward compatibility and modern devices.
I agree that xHCI is a smarter design than EHCI (dealing with multiple speeds).
The command rings, doorbells, and TRBs do seem somewhat complicated to me right now, but I'm sure I'll figure them out when I get to it.
Oh well. I better finish my HID code before I start working on another host controller (2 host controllers at once = 2x harder to find bugs!). Then I'll read up on xHCI and try implementing it.

---

BenLunt wrote:Actually, the Floppy Drive is most likely a MSD device, using the CBI protocol.
Oh. I saw CBI protocol and then thought it wasn't a MSD. I guess it does store "lots" of data, so that makes sense (Haha, "lots." You can't even fit a cell phone picture on one :)).
I have been looking at the VM logs and found out why I wasn't getting any mass storage devices except the floppy drive: They were connecting to EHCI! The floppy drive was the only full-speed mass storage device, so it connected to UHCI while the thumb drives were high-speed so they connected to EHCI. I probably should have thought of that.

Anyways, thanks for all your help. I'm sure I'll have more questions and problems later, but for now I think I'm good.
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