March 20, 2025

Black Pill boards - F411 USB -- USB endpoints, part 7 - more on reading

I just happened to be poking around in driver/usb_core.c and ran across the routine USB_OTG_ReadPacket(). I got interested in this because it reads data, I had a debug message, but it wasn't clear what endpoint it was reading data for.

As it turns out this routine doesn't need to know, and that is interesting in itself. There are two calls to this routine in driver/usb_dcd_int.c both from DCD_HandleRxStatusQueueLevel_ISR ().

Both of these routines are involved in taking data from the unified Rx FIFO. The handler routine has an instructive comment telling us that it "gets the Status from the top of the FIFO". This 32 bit word describes data on the fifo which follows. It contains the endpoint number, a byte count, and some bits that describe what follows. The two cases that result in a call to USB_OTG_ReadPacket() are:

STS_DATA_UPDT
STS_SETUP_UPDT
Other cases are just ignored (presumably there is no associated data to be read and the status works describes whatever event might be of interest.

Hardwre registers

To actually read the packet, the code does this:
uint32_t *fifo = pdev->regs.DFIFO[0];
dest = USB_OTG_READ_REG32(fifo)
32 bit words are read from the FIFO (so the FIFO must pad/align as needed)

The macro to read the device register is nothing mysterious:

#define USB_OTG_READ_REG32(reg)  (*(__IO uint32_t *)(reg))
The structure indicated by "regs" is interesting. It is in driver/usb_regs.h. It holds pointers to subsections of the USB hardware
typedef struct USB_OTG_core_regs //000h
{
  USB_OTG_GREGS         *GREGS;
  USB_OTG_DREGS         *DREGS;
  USB_OTG_HREGS         *HREGS;
  USB_OTG_INEPREGS      *INEP_REGS[USB_OTG_MAX_TX_FIFOS];
  USB_OTG_OUTEPREGS     *OUTEP_REGS[USB_OTG_MAX_TX_FIFOS];
  USB_OTG_HC_REGS       *HC_REGS[USB_OTG_MAX_TX_FIFOS];
  __IO uint32_t         *HPRT0;
  __IO uint32_t         *DFIFO[USB_OTG_MAX_TX_FIFOS];
  __IO uint32_t         *PCGCCTL;
};
So we have serveral fifos in an array given by DFIFO. This is initialized in driver/usb_core.c in USB_OTG_SelectCore() as follows:
#define USB_OTG_DATA_FIFO_OFFSET             0x1000
#define USB_OTG_DATA_FIFO_SIZE               0x1000

for (i = 0; i < pdev->cfg.host_channels; i++) {
    pdev->regs.DFIFO[i] = (uint32_t *)(baseAddress + USB_OTG_DATA_FIFO_OFFSET +\
      (i * USB_OTG_DATA_FIFO_SIZE));
  }
It is funny that a variable "host_channels" gives the count for a device, but that is OK. Indeed, the TRM shows that each endpoint has a FIFO, with endpoint 0 being at offset 0x1000

This same routine -- USB_OTG_SelectCore() -- also sets up the other pointers in this structure, like this:

#define USB_OTG_CORE_GLOBAL_REGS_OFFSET      0x000
#define USB_OTG_DEV_GLOBAL_REG_OFFSET        0x800
#define USB_OTG_DEV_IN_EP_REG_OFFSET         0x900

pdev->regs.GREGS = (USB_OTG_GREGS *) (baseAddress + USB_OTG_CORE_GLOBAL_REGS_OFFSET);
pdev->regs.DREGS = (USB_OTG_DREGS *) (baseAddress + USB_OTG_DEV_GLOBAL_REG_OFFSET);

Getting the status from the top of the FIFO

This is done by:
status.d32 = USB_OTG_READ_REG32( &pdev->regs.GREGS->GRXSTSP );
This is one of the chip global registers. It is at offset 0x20 and is described as "read and pop receive status". There is a register right next to it that reads the status, but does not pop it.

Conclusions

All of this starts to give a picture of how the Rx FIFO works. At some point actually studying the TRM should fill in the details.
Feedback? Questions? Drop me a line!

Tom's Computer Info / tom@mmto.org