in the CMSIS stm32YYYYxxx.h headers, USB_OTG_FS and USB_OTG_HS symbols are defined using the USB_OTG_GlobalTypeDef structure, which contains only the "global" registers. Other registers are accessed in the library through ad-hoc concocted macros, inconsistent with the style of the CMSIS header. It's not that hard to write a typedef containing all the registers of the USB IP, even being fully backwards compatible. Please think about it.
What "Other registers" are missing in CMSIS header files? Could you please explain and provide examples?
They are not *missing*, the issue is a bit more complex than just that.
Look at RM0090 rev.14, 34.16.1 CSR memory map. The actual registers are split up onto 4 address regions, then there are 2 more regions to access the FIFO RAM (as FIFO and as direct RAM). Two regions are missing from that map: a region starting at offset 0x900 for the IN-endpoint-specific registers, and a region starting at offset 0xB00 for the OUT-endpoint-specific registers.
However, this partitioning is rather arbitrary, and these regions are still belonging to ONE peripheral, instantiated twice in case of the 'F4 in question - as FS and HS, with compatible register layout (extra registers for HS).
Now working out of [STM32Cube_FW_F4_V1.15.0]\Drivers\CMSIS\Device\ST\STM32F4xx\Include\stm32f407xx.h but all others using the Synopsys USB IP should be similar if not literally the same. There are several separate struct typedefs defined in the header, describing the registers layouts:
roughly mirroring the partitioning described above, adding one more arbitrary partition and omitting a representation of the "RAM" regions, and also omitting Power and clock gating control and status registers. This just underlines how useless these partitionings practically are, as they are not followed consequently (they would be great for structuring the documentation, but that would need much more attention to the details).
[A sidenote: characteristically, the constants in table in 34.16.1 in RM0090 are denoted with h suffix for hexadecimal, instead of the 0x prefix as used elsewhere in RM0090. I've pointed this out in that "doc_errors" text long ago (namely at rev.4), and since then it has been corrected but only in selected spots in the RM, although I've also given a simple clue how to find most of them by a trivial textual search. But, the h-suffixed constants are used in the discussed headers, too - even if it is only in comment, it's simply ridiculous in a C-source text, hitting eye of anybody versed in C, and indicates how little attention is paid to the details throughout.]
The problem is, that only one of these structs is then used for the "instantiation"
#define USB_OTG_FS ((USB_OTG_GlobalTypeDef *) USB_OTG_FS_PERIPH_BASE)
#define USB_OTG_HS ((USB_OTG_GlobalTypeDef *) USB_OTG_HS_PERIPH_BASE)
It means, that if you use the symbol USB_OTG_FS (or USB_OTG_HS) as the "instance" in the program, there's no way to access all the registers (except those in the "global/core" register subregion, i.e. the MAJORITY of registers is unaccessible simply), through simple and straighforward expressions, which can be used to access registers of other STM32 peripherals (ETH is maybe in more woeful state, but let's leave that for some other time).
There are various non-systemic augmentation macros defined ad-hoc then in Cube itself, outside the scope of CMSIS, and Cube the uses those to access all those registers.
The remedy is to typedef a struct embracing the *whole* address space of the USB peripheral; and then use that one to define the USB_OTG_FS and USB_OTG_HS symbols. It is relatively easy to do it in a backward-compatible fashion. We could then simply write:
USB_OTG_FS->DCFG |= USB_OTG_DCFG_DSPD * 0b11; // transceiver set to FULL_SPEED (other values are reserved)
USB_OTG_FS->outEP[epnum].DOEPCTL |= (USB_OTG_DOEPCTL_CNAK OR USB_OTG_DOEPCTL_EPENA); // EP enable
Of course, I am willing to discuss this to any depth necessary.