cancel
Showing results for 
Search instead for 
Did you mean: 

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

freddie_chopin
Associate III
Posted on January 13, 2017 at 18:29

Today in one of my projects I've enabled a new feature of my RTOS (

http://distortos.org/

) 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:

0690X000006062lQAA.png

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...

#malloc #memory-corruption #race-condition #heap #rtos #usb
21 REPLIES 21

You haven't been involved in any critical device, have you? MISRA avoids mallocs because it introduces ways of failure in the application, undefined and non deterministic behaviours and, of course, leaves the door open to memory leaks. Those problems are definitely not in the past.

You bet I was involved. Not really those where lives might be in danger, but still critical. What MISRA ignores is that the times where you had 32K RAM or so on a controller are long gone. A microcontroller today might have more RAM as an IBM PC of the eighties. Dynamic allocation used carefully might even be beneficial to a system as memory can be re-used. Would you imagine the PC-software of today built with no dynamic allocation? Embedded systems are going the same route as the PC, only 10 to 15 years delayed.

I am not advocating the blind use of dynamic allocation, but I can't stand those paranoics that are forbidding it. There are cases where, if judiciously used, dynamic allocation can be an asset and not a liability.