embOS-MPU

From SEGGER Knowledge Base
Jump to navigation Jump to search

The Guardian of Memory: What embOS-MPU does

embOS-MPU extends SEGGER's embOS RTOS by incorporating memory protection through the processor's Memory Protection Unit (MPU). It prevents tasks from interfering with each other or with the OS kernel, thereby improving system reliability, robustness, and security. It also guarantees that should a bug occur in one task, all other tasks (and the operating system) will continue executing. Each privileged task therefore enjoys full access to the whole memory. Any unprivileged task only has specific access to each distinct memory region.

With embOS-MPU, a device driver consists of two parts:

  • One part that runs in the privileged state with full access to system resources and the ability to execute any instruction.
  • One part that runs in the unprivileged state with restricted access, limited to specific operations and memory areas.

Actual peripheral access is performed in the privileged part only. embOS-MPU ensures that there is only one explicit and safe way to switch from unprivileged to privileged.

Basic concepts: Working with unprivileged tasks

mini

Newly-created unprivileged tasks have read access to RAM and ROM by default. They may also execute code in RAM and ROM. Write access, however, is restricted to its own task stack. To access peripherals, additional memory locations and OS control structures, device drivers as well as specific embOS API may be called from within an unprivileged task.

Access to peripherals

In a default configuration, an unprivileged task has no access to any peripheral. A specific device driver is needed to access peripherals from within such a task, for example, when using UART, SPI or port pins. SEGGER can provide device drivers, but it is mostly the users who implement them.

Access to other memory regions

You can grant an unprivileged task access to additional memory regions. For example, a task may need to write LCD data to a framebuffer in RAM, but a device driver is deemed inefficient for this purpose. Access permissions are fully configurable in regards to read access, write access and code execution.

Access to OS objects

An unprivileged task has no direct or indirect write access to embOS objects. However, indirect write access to these objects via embOS-MPU API may be granted for each individual object. This requires all objects to be created in a privileged state.

MPU violation management

Should an unprivileged task violate access restrictions or trigger a fault exception by some other means, it is automatically terminated. An optional user callback function is then executed to notify the application. This callback may also perform further actions with regards to that task (such as restarting the whole system).

Example application: Simulating a stack overflow

embOS-MPU offers simple and straightforward runtime configuration that is easy to integrate into both new and existing products. It provides an unlimited number of privileged and unprivileged tasks 100 % sandboxed.

The following example details embOS-MPU’s ease-of-use:

#include "RTOS.h"
#include "BSP.h"
#include <stdio.h>

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/
//
// Linker symbols to get section addresses.
// embOS-MPU needs to know where RAM, ROM and the OS section is located.
//
extern unsigned int __FLASH_segment_start__;
extern unsigned int __FLASH_segment_size__;
extern unsigned int __RAM_segment_start__;
extern unsigned int __RAM_segment_size__;
extern unsigned int __os_start__;
extern unsigned int __os_size__;
//
// Control structures for tasks and event object
//
static OS_EVENT HW_Event;
static OS_TASK TCBHP;
static OS_TASK TCBLP;
//
// Stack for privileged task.
// This stack has not to be aligned because it does not affect MPU settings
//
static OS_STACKPTR int StackHP[128];
//
// Stack for unprivileged task. This will be used for MPU settings and with Cortex-M it requires alignment.
// In most cases, the same power of 2 will work for both size and alignment
//
static OS_STACKPTR int StackLP[128] __attribute__ ((aligned (512)));
//
// List of allowed OS objects for the LPTask
//
static const OS_MPU_OBJ _aObjList[] = { {(OS_U32)&HW_Event, OS_MPU_OBJTYPE_EVENT},
                                        {(OS_U32)NULL,      OS_MPU_OBJTYPE_INVALID}};  // Last entry

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _HPTask()
*
*  Function description
*    High priority task.
*    Periodically resumes the low priority task via an event object
*/
static void _HPTask(void) {
  while (1) {
    BSP_ToggleLED(1);
    OS_EVENT_Set(&HW_Event);  // Signal Event to wake LP task
    OS_Delay(200);
  }
}

/*********************************************************************
*
*       _Recursive()
*
*  Function description
*    This function produces a stack overflow.
*    embOS detects the illegal memory access and automatically suspends
*    the task. Additionally the error callback function is called.
*/
static void _Recursive(unsigned int i) {
  volatile int k;
  k = i + 1;
  _Recursive(k);
}

/*********************************************************************
*
*       _Unpriv()
*
*  Function description
*    Routine for demonstrating an illegal memory access after
*    toggling an LED for 4 seconds.
*/
static void _Unpriv(void) {
  unsigned int i;

  for (i = 0u; i < 20u; i++) {
    OS_EVENT_Wait(&HW_Event);
    BSP_SetLED(0);
    OS_Delay(20);
    BSP_ClrLED(0);
  }
  //
  // Produce illegal memory access
  //
  _Recursive(1u);
}

/*********************************************************************
*
*       _LPTask()
*
*  Function description
*    Low priority task.
*/
static void _LPTask(void) {
  OS_MPU_SetAllowedObjects(&TCBLP, _aObjList);
  OS_MPU_SwitchToUnprivState();
  _Unpriv();
}

/*********************************************************************
*
*       _ErrorCallback()
*
*  Function description
*    User callback function which is called when an unprivileged task
*    does something disallowed, e.g. tries to write to memory which
*    does not belong to the task
*/
static void _ErrorCallback(OS_TASK* pTask, OS_MPU_ERRORCODE ErrorCode) {
  static const char* _sErrTxt[] = {  "OS_MPU_ERROR_INVALID_REGION", "OS_MPU_ERROR_INVALID_OBJECT",
                                     "OS_MPU_ERROR_INVALID_API", "OS_MPU_ERROR_HARDFAULT",
                                     "OS_MPU_ERROR_MEMFAULT", "OS_MPU_ERROR_BUSFAULT",
                                     "OS_MPU_ERROR_USAGEFAULT", "OS_MPU_ERROR_SVC"};
  printf("Task with ID 0x%x has been stopped due to error %s\n", (OS_U32)pTask, _sErrTxt[ErrorCode]);
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       main()
*/
int main(void) {
  OS_InitKern();                /* Initialize OS                 */
  OS_InitHW();                  /* Initialize Hardware for OS    */
  BSP_Init();                   /* Initialize LED ports          */
  //
  // Setup memory information, must be done before first task is created
  //
  OS_MPU_ConfigMem((OS_U32)&__FLASH_segment_start__, (OS_U32)&__FLASH_segment_size__,
                   (OS_U32)&__RAM_segment_start__,   (OS_U32)&__RAM_segment_size__,
                   (OS_U32)&__os_start__,            (OS_U32)&__os_size__);
  //
  // Setup optionally error callback function
  //
  OS_MPU_SetErrorCallback(&_ErrorCallback);
  //
  // Enable embOS-MPU support
  //
  OS_MPU_Init();
  OS_CREATETASK(&TCBHP, "HP Task", _HPTask, 100, StackHP);
  OS_CREATETASK(&TCBLP, "LP Task", _LPTask,  50, StackLP);
  OS_EVENT_Create(&HW_Event);
  OS_Start();                   /* Start multitasking            */
  return 0;
}

Detailed description

The example application simulates a stack overflow and shows how the issue is handled by embOS-MPU. Easy to use, embOS-MPU needs just three additional API calls:

  • OS_MPU_ConfigMem() tells the OS where RAM, ROM and the OS is located in the memory.
  • OS_MPU_SetErrorCallback() sets an optional callback function which is called whenever embOS-MPU detects an issue.
  • OS_MPU_Init() actually enables the memory protection unit.

The example application creates two tasks:

  • The HPTask runs completely in a privileged state.
  • The LPTask also starts in a privileged state but switches to unprivileged state with OS_MPU_SwitchToUnprivState().

The HPTask toggles a LED and sends events to the LPTask.

/*********************************************************************
*
*       main()
*/
int main(void) {
  OS_InitKern();                /* Initialize OS                 */
  OS_InitHW();                  /* Initialize Hardware for OS    */
  BSP_Init();                   /* Initialize LED ports          */
  //
  // Setup memory information, must be done before first task is created
  //
  OS_MPU_ConfigMem((OS_U32)&__FLASH_segment_start__, (OS_U32)&__FLASH_segment_size__,
                   (OS_U32)&__RAM_segment_start__,   (OS_U32)&__RAM_segment_size__,
                   (OS_U32)&__os_start__,            (OS_U32)&__os_size__);
  //
  // Setup optionally error callback function
  //
  OS_MPU_SetErrorCallback(&_ErrorCallback);
  //
  // Enable embOS-MPU support
  //
  OS_MPU_Init();
  OS_CREATETASK(&TCBHP, "HP Task", _HPTask, 100, StackHP);
  OS_CREATETASK(&TCBLP, "LP Task", _LPTask, 50, StackLP);
  OS_EVENT_Create(&HW_Event);
  OS_Start();                   /* Start multitasking           */
  return 0;
}

The HPTask toggles a LED and sends events to the LPTask.

/*********************************************************************
*
*       _HPTask()
*
*  Function description
*    High priority task.
*    Periodically resumes the low priority task via an event object
*/
static void _HPTask(void) {
  while (1) {
    BSP_ToggleLED(1);
    OS_EVENT_Set(&HW_Event); // Signal Event to wake LP task
    OS_Delay(200);
  }
}

The LPTask adds permission to the event object before it switches to the unprivileged state with OS_MPU_SwitchToUnprivState().

/*********************************************************************
*
*       _LPTask()
*
*  Function description
*    Low priority task.
*/
static void _LPTask(void) {
  OS_MPU_SetAllowedObjects(&TCBLP, _aObjList);
  OS_MPU_SwitchToUnprivState();
  _Unpriv();
}

The _Unpriv() function runs in unprivileged state and toggles another LED for four seconds before it calls the function _Recursive().

/*********************************************************************
*
*       _Unpriv()
*
*  Function description
*    Routine for demonstrating an illegal memory access after
*    toggling an LED for 4 seconds.
*/
static void _Unpriv(void) {
  unsigned int i;

  for (i = 0u; i < 20u; i++) {
    OS_EVENT_Wait(&HW_Event);
    BSP_SetLED(0);
    OS_Delay(20);
    BSP_ClrLED(0);
  }
  //
  // Produce illegal memory access
  //
  _Recursive(1u);
}

The function _Recursive() generates the actual stack overflow.

/*********************************************************************
*
*       _Recursive()
*
*  Function description
*    This function produces a stack overflow.
*    embOS detects the illegal memory access and automatically suspends
*    the task. Additionally the error callback function is called.
*/
static void _Recursive(unsigned int i) {
  volatile int k;
  k = i + 1;
  _Recursive(k);
}

embOS-MPU will terminate the LPTask as soon as the task tries to write to memory outside its task stack. Additionally the error callback function is called. The error callback function prints a message with the task id and the error cause. The HPTask and the OS are not affected and still run.

/*********************************************************************
*
*       _ErrorCallback()
*
*  Function description
*    User callback function which is called when an unprivileged task
*    does something disallowed, e.g. tries to write to memory which
*    does not belong to the task
*/
static void _ErrorCallback(OS_TASK* pTask, OS_MPU_ERRORCODE ErrorCode) {
  static const char* _sErrTxt[] = {  "OS_MPU_ERROR_INVALID_REGION", "OS_MPU_ERROR_INVALID_OBJECT",
                                     "OS_MPU_ERROR_INVALID_API", "OS_MPU_ERROR_HARDFAULT",
                                     "OS_MPU_ERROR_MEMFAULT", "OS_MPU_ERROR_BUSFAULT",
                                     "OS_MPU_ERROR_USAGEFAULT", "OS_MPU_ERROR_SVC"};
  printf("Task with ID 0x%x has been stopped due to error %s\n", (OS_U32)pTask, _sErrTxt[ErrorCode]);
}