November 6, 2023

Let's learn USB! -- Enumerations

You can find dozens if not hundreds of explanations of USB enumeration if you search online. My intent here is to ignore most or all of those. (I have read one or more of those several years ago.) I want to see "enumeration in action" using the papoon code I now have running. Nonetheless, I have found this description one of the best and it corresponds most closely to what I have observed in my experiments:

I ran into a curious problem. When my code booted, I initialized Papoon then went into a loop watching for a state variable to indicate CONFIGURED. I set that loop up with a 9 second timeout. So it waits 9 seconds, nothing happens and the loop exits. I ignore the status and try to run my demo (wich waits for received characters and echos them). Nothing is going to be received since we have not enumerated.

But the whole papoon structure is interrupt driven. I unplug and replug the cable. Now linux knows something needs attention and it tries to enumerate. The enumeration fails because of my printf() messages screwing up the timing. If I disable (again) my messages, the first wait does timeout, but then the enumeration works just fine later, being state driven.

Enumeration takes about 6 full seconds. This is measured with a stopwatch from when I plug in the cable to when it announces that the device is configured.

All of this suggests at least two code changes. One is that I could (should?) check that state variable and not jump into the demo if (and/or until) it is "CONFIGURED". Another is to track down where that state is set and switch on debug only when the state transitions to "CONFIGURED". I could even consider setting up a callback to notify me when enumeration finishes (not to mention, when it starts).

Does the "send()" method try to send data if the device is not configured. The demo would never do this (it tries to receive data before it sends anything).

Studying enumeration without screwing it up

But in some sense, we are getting ahead of ourselves. We want to find a way to study enumeration without having our messages screw it up. Here is the first part of a log from a failed enumeration:

USB lp interrupt 1
IH: usb reset
USB lp interrupt 2
CTR - control 00008210
USB lp interrupt 3
CTR - control 00008200
USB lp interrupt 4
CTR - control 00008210
USB lp interrupt 5
IH: usb reset
USB lp interrupt 6
CTR - control 00008210
USB lp interrupt 7
CTR - control 00008200
USB lp interrupt 8
CTR - control 00008210
USB lp interrupt 9
CTR - control 00008200
USB lp interrupt 10
CTR - control 00008210
USB lp interrupt 11
IH: usb reset
When I first enter the interrupt handler, I print the "USB lp" message and show a count of how many interrupts this is. Then the "IH" message comes from inside the interrupt handler in papoon. We either get a "usb reset" or a "CTR" at this stage. CTR indicates "correct transfer" and may indicate either a packet received or that a packet has been successfully sent. The word "control" indicates that this is endpoint 0 (the "control" endpoint). The control endpoint will be used for all traffic dealingwith enumeration. I then show the "istr" register (the interrupt status), which is a 16 bit register.
Let's look at some of the values we see in the istr:

0x8000 is the bit that tells us this is a CTR interrupt, so no surprise there.
0x0200 is SOF (start of frame). We don't enable this interrupt and I am not sure why we might want to, so we can ignore this.
0x0010 is the DIR bit and the 4 bits following are the endpoint number (zero). DIR tells us if we have data coming or going.
DIR = 1 is an OUT transaction (data from the host to us).
DIR = 0 is an IN transaction (data from us to the host).

Also notice the resets. For whatever reason, enumeration asks for several USB resets. These are accomplished by pulsing the FRES bit ("force reset") in the CNTL register for 100 ms. Enumeration starts with a reset, which sort of makes sense, followed by the host sending us something. To learn more we need to look at packet memory and the endpoint registers.

Capture and examine some PMA data

We will also capture the endpoint registers. We are well aware that this will cause enumeration to fail, but what we are after here is education.

Packet memory (PMA) is a bunch of 16 bit words on a 32 bit bus. So even though there is 512 bytes of actual SRAM involved, it uses 1024 bytes of address space. When I write a 32 bit value like 0xdeadbeef to PMA from the ARM, the value 0x0000beef gets stored.

Here is what PMA looks like after papoon is initialized and before there is any traffic:

EPR-0 = 00003210
EPR-1 = 00000622
PMA 00000000: 0180 0040 01C0 8400 0178 0000 0000 0000
PMA 00000010: 0000 0000 0138 8400 00F8 0000 0000 0000

And here is what we have once the first packet has arrived:

EPR-0 = 0000EA60
EPR-1 = 00000622
PMA 00000000: 0180 0040 01C0 8408 0178 0000 0000 0000
PMA 00000010: 0000 0000 0138 8400 00F8 0000 0000 0000
PMA 00000020: 0000 0000 0000 0000 0000 0000 0000 0000
...
PMA 000001B0: 0000 0000 0000 0000 0000 0000 0000 0000
PMA 000001C0: 0680 0100 0000 0040 DDDD 0000 0000 0000
PMA 000001D0: 0000 0000 0000 0000 0000 0000 0000 0000

And when papoon sends the first response:

EPR-0 = 000062A0
EPR-1 = 00000622
PMA 00000000: 0180 0012 01C0 8400 0178 0000 0000 0000
PMA 00000010: 0000 0000 0138 8400 00F8 0000 0000 0000
PMA 00000020: 0000 0000 0000 0000 0000 0000 0000 0000
...
PMA 00000170: 0000 0000 0000 0000 0000 0000 0000 0000
PMA 00000180: 0112 0200 0002 4000 0483 5740 0200 0201
PMA 00000190: 0103 0000 0000 0000 0000 0000 0000 0000
PMA 000001A0: 0000 0000 0000 0000 0000 0000 0000 0000
PMA 000001B0: 0000 0000 0000 0000 0000 0000 0000 0000
PMA 000001C0: 0680 0100 0000 0040 DDDD 0000 0000 0000
PMA 000001D0: 0000 0000 0000 0000 0000 0000 0000 0000
The first part of PMA holds the BTABLE. For us only the first 4 words are of interest -- for now. These describe the buffers for endpoint 0 (the control endpoint), as follows:
Addr--Tx = 0180
Count-Tx = 0040 (then 0012)
ADDR--RX = 01c0
Count-Rx = 8400 (then 8408)
So we first receive 8 bytes as follows:
PMA 000001C0: 0680 0100 0000 0040 DDDD 0000 0000 0000
Then we respond with 0x12 bytes like so:
PMA 00000180: 0112 0200 0002 4000 0483 5740 0200 0201
PMA 00000190: 0103 0000 0000 0000 0000 0000 0000 0000
After this we receive something else, then a reset, but we will look into that later.

What does all of this mean? The datasheet explains part of it, but for the contents of the packets we will need to go to the USB documents, along with studying the source code!

Before we examine packet contents, we should see what the data sheet tells us about the values in the endpoint register for endpoint 0, as well as the PMA values as well. We see the following values for our endpoint register:

EPR-0 = 00003210
EPR-0 = 0000EA60
EPR-0 = 000062A0
There is plenty of information here, but nothing worth describing in detail.

And here are the corresponding values for the BTABLE entry for endpoint 0

PMA 00000000: 0180 0040 01C0 8400
PMA 00000000: 0180 0040 01C0 8408
PMA 00000000: 0180 0012 01C0 8400
Here 0x180 is the location of the Tx buffer and 0x1c0 is the location of the Rx buffer. The Tx counts give us no surprises, other than being initialized to 0x40 (64). The Rx count field holds some extra information in the upper 6 bits. The 0x8000 bit indicates that the block size is given in 32 byte "units". The next 5 bits give the size (here the value is "2"). So we have 2 "units" of 32 bytes, so a 64 byte Rx buffer size.

What is that first packet?

Namely the following data. Notice that a trailing DDDD has appeared which is not included in the count of 8 bytes. I am also somewhat uncertain about byte order.
PMA 000001C0: 0680 0100 0000 0040 DDDD 0000 0000 0000
I am not yet able to decypher the source code, so I am using this: This is a setup packet. There are definite time limits about how quickly this must be responded to (50ms if there is no "data stage" and 500ms otherwise). Setup packets are 8 bytes in size. The fields are:
Request Type (1 byte) = 80
Request      (1 byte) = 06  get descriptor
Value        (2 bytes) = 0001 ??
Index        (2 bytes) = 0
Length       (2 bytes) = 0x40 = 64 ??

And what about the response

We respond with the following 18 (0x12) bytes:
PMA 00000180: 0112 0200 0002 4000 0483 5740 0200 0201
PMA 00000190: 0103
This is a descriptor. The first byte gives the length (here 0x12) which clearly includes the length byte itself. The next byte (0x01) indicates this is a device descriptor.

Here is a device descriptor:

Length    (1 byte) = 0x12
Type      (1 byte) = 0x01	device
bcdUSB    (2 byte) = 0x0002	USB 2.0
class     (1 byte) = 2
subclass  (1 byte) = 0
protocol  (1 byte) = 0
max pkt   (1 byte) = 0x40 = 64
vendor    (2 byte) = 0x0483
product   (2 byte) = 0x5740
release   (2 byte) = 0x0200   version 2.00 (bcd)
iManuf    (1 byte) = 01
iProd     (1 byte) = 02
iSer      (1 byte) = 03
iConf     (1 byte) = 01
The linux messages when enumeration succeeds are:
Nov  6 12:53:55 trona kernel: usb 4-1.2: New USB device found, idVendor=0483, idProduct=5740, bcdDevice= 2.00
Nov  6 12:53:55 trona kernel: usb 4-1.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
Nov  6 12:53:55 trona kernel: usb 4-1.2: Product: STM32 Virtual COM Port
Nov  6 12:53:55 trona kernel: usb 4-1.2: Manufacturer: STMicroelectronics
Nov  6 12:53:55 trona kernel: usb 4-1.2: SerialNumber: 8717472853518850066fff48

Byte order in the above

We know for a fact that ARM is little endian. But there seems to be a confusing mix in the above. The request type and request fields are in swapped order, but the length field in the setup packet seems big endian. And in the device descriptor, most single byte fields seem to be in swapped order, but the vendor and product fields are used "as-is". The inconsistency seems to be in how to interpret 2 byte fields.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org