We find ourselves in USBD_OTG_ISR_Handler() in driver/usb_dcd_int.c Every interrupt comes there unlike the HS device which uses several different interrupt numbers. The game is to figure out exactly what caused the interrupt. The code does this:
v = USB_OTG_READ_REG32(&pdev->regs.GREGS->GINTSTS); v &= USB_OTG_READ_REG32(&pdev->regs.GREGS->GINTMSK);It reads an interrupt status register, then masks out only the interrupts that should be enabled. If this value is zero, it was a spurious interrupt and we are done. If not, we examine this value.
Multiple bits in this register could be set (multiple things could be happening) and they will all get handled.
Let's suppose the "outepintr" bit is set. This indicates an OUT endpoint interrupt (remember OUT is data coming to us that we will read). We then call DCD_HandleOutEP_ISR(), which is also in driver/usb_dcd_int.c. Many things can cause an OUT endpoint interrupt, so we need to find out which has happened, and for which endpoint.
First we want to know which endpoint is causing the interrupt. There could be more than one, and they each get individual attention. To find out, we read a pair of registers like this:
v = USB_OTG_READ_REG32(&pdev->regs.DREGS->DAINT); v &= USB_OTG_READ_REG32(&pdev->regs.DREGS->DAINTMSK); return ((v & 0xffff0000) >> 16);
We look at the TRM to learn about the DAINT register. The DAINT register has 32 bits. The upper 16 are for OUT endpoints, the lower 16 are for IN endpoints -- hence the 16 bit right shift above. We find out which endpoint is interrupting by looking at these 16 bits. The driver uses a loop, looks at the low bit, then shifts by one until the value goes zero to indicate it needs look no more.
There are 2 sets of 16 endpoint interrupt registers. One set for OUT and the other set for IN endpoints. We read the appropriate one like this:
v = USB_OTG_READ_REG32(&pdev->regs.OUTEP_REGS[epnum]->DOEPINT); v &= USB_OTG_READ_REG32(&pdev->regs.DREGS->DOEPMSK);Note the DREGS here (we saw GREGS above). DREGS indicates this is a "device mode" register. GREGS indicates a global register.
Only 3 interrupts are enabled for OUT interrupts (there are more that could be), namely:
doepint.b.xfercompl -- transfer complete doepint.b.epdisabled -- endpoint disabled doepint.b.setup -- setup phase done (only for EP 0)We are interested in "transfer complete" meaning that a packet has arrived. I get curious and add some printout to DataOutStage, which gets called in this case. When we plug in the board and enumeration happens, we see:
DataOutStage, epnum = 0 DataOutStage, epnum = 0 DataOutStage, epnum = 0 ...Then I use my little tool on the linux side to send a single 200 byte packet and see:
DataOutStage, epnum = 1 DataOutStage, epnum = 1 DataOutStage, epnum = 1 DataOutStage, epnum = 1USBD_DataOutStage() is in library/usbd_core.c. It examines epnum, and if it is 0, calls code to deal with setup packets. otherwise it calls (via a callback), the routine usbd_cdc_DataOut() in library/usbd_cdc_core.c
Things get interesting here. This is our code for the CDC class, and at this point it simply ignores epnum (so it assumes epnum=1). It just calls VCP_DataRx() in vcp/usbd_cdc_vcp.c and if somehow control information on endpoint 2 was getting sent, it would get mingled with the usual traffic on endpoint 2.
I pay special attention to this last point, because I have a theory that the extra endpoint mandated by the CDC class is entirely pointless for our VCP. What I would do would be to test for endpoint 2 and take care to just discard any traffic, but apparently no traffic is expected.
A final note on this is that the CDC class was originally designed for modems and more elaborate communication devices than a simple virtual terminal. Such devices no doubt need the extra channel to indicate loss of carrier, to set communication parameters, and who knows what. Things we don't need.
DCD_EP_Tx (pdev, CDC_IN_EP, (uint8_t*)&APP_Tx_Buffer[USB_Tx_ptr], USB_Tx_length);Note the endpoint being specified by CDC_IN_EP. You can find this in vcp/usbd_conf.h as follows:
#define CDC_IN_EP 0x81 /* EP1 for data IN */ #define CDC_OUT_EP 0x01 /* EP1 for data OUT */ #define CDC_CMD_EP 0x82 /* EP2 for CDC commands */Aha! Notice the definition for CDC_CMD_EP on endpoint 2. This is the useless endpoint I keep complaining about.
This is only referenced in library/usbd_cdc_core.c It is advertised in enumeration packets, a buffer is allocated for data. It is used in the following call:
DCD_EP_Open(pdev, CDC_CMD_EP, CDC_CMD_PACKET_SZE, USB_OTG_EP_INT);But I never see "PrepareRx" called on it, as though data is expected. I am satisfied that this never can or will get used for our VCP.
My idea here is that someday the entire VCP directory might be replaced by something else. Maybe a HID class. But more likely with a customization of the CDC class to be a generic terminal device. Like whatever it is these ubiquitous USB to serial devices enumerate as. Linux ends up making them a /dev/ttyUSB0 or such and they don't have the silly useless signaling endpoint. I plan to just claim to be one of those. I tried this once before and there is some extra setup signaling, presumably to set baud rate and such. We can ignore those, but we will need to respond appropriately as if we are acting upon them. I have notes on all this somewhere.
This little code rearrangement is how it should have been all along (and I am to blame, I think). It positions things to replace the vcp directory with something else.
Tom's Computer Info / tom@mmto.org