AnsweredAssumed Answered

Does USB device code for STM32 really allocate dynamic memory from interrupts?

Question asked by Freddie Chopin on Jan 13, 2017
Latest reply on Jun 5, 2017 by Valentin

Today in one of my projects I've enabled a new feature of my RTOS (distortos - advanced real-time operating system) which ensures some functions are not used in interrupts. This checks - among others - for any use of mutexes from interrupts, as it is a logical error to do that (first of all - interrupts cannot "block" and additionally using mutexes in interrupts violates the concept of "ownership").

 

You may imagine how surprised I was when the critical error was raised even before the application would fully start... See this call stack:

 

It turns out that ST's code for USB device (CDC, audio, HID, DFU, MSC) in STM32 happily allocates dynamic memory in interrupts... The path is like this:

  • OTG_FS_IRQHandler() interrupt,
  • HAL_PCD_IRQHandler(),
  • HAL_PCD_SetupStageCallback(),
  • USBD_LL_SetupStage(),
  • USBD_StdDevReq(),
  • USBD_SetConfig(),
  • USBD_SetClassConfig(),
  • indirect call of a function stored in "Init" field of USBD_ClassTypeDef struct.

 

The "Init" pointer in USBD_ClassTypeDef struct is initialized with an address of functions like USBD_CDC_Init(), and these function - almost right at the beginning - call malloc() via USBD_malloc() macro...

 

As malloc() is generally _NOT_ reentrant, it has to be protected from concurrent access from multiple contexts/threads. If a real-time operating system is used, this protection is usually implemented with a mutex or pausing the scheduler (preventing context switches). In single threaded applications malloc() is usually not protected at all, because most of the people are reasonable enough and don't even consider using malloc() in interrupts. In both of these cases adding ST's USB device code sets the application on a path to certain failure if it actually uses dynamic allocation anywhere else:

  • when locking is done with mutexes in a multithreaded system - any attempt to block in interrupt handler (like locking a locked mutex used to serialize access to malloc()) is undefined behaviour and will most likely severely corrupt the system, worse - as mutexes serializing access to malloc() are usually recursive, such call can lead to a typical race condition, causing heap corruption,
  • when locking is done by pausing the scheduler in a multithreaded system - interrupts are not affected by that locking mechanism, malloc() may be re-entered from two different contexts, leading to heap corruption,
  • in a single-threaded application with no locking - an allocation in interrupt during an allocation in main thread will lead to heap corruption.

 

This whole discussion obviously also applies to free(), which is called in interrupt context via at least 3 different code paths...

Outcomes