| .. _memory_domain: |
| |
| Memory Protection Design |
| ######################## |
| |
| Zephyr's memory protection design is geared towards microcontrollers with MPU |
| (Memory Protection Unit) hardware. We do support some architectures, such as x86, |
| which have a paged MMU (Memory Management Unit), but in that case the MMU is |
| used like an MPU with an identity page table. |
| |
| All of the discussion below will be using MPU terminology; systems with MMUs |
| can be considered to have an MPU with an unlimited number of programmable |
| regions. |
| |
| There are a few different levels on how memory access is configured when |
| Zephyr memory protection features are enabled, which we will describe here: |
| |
| Boot Time Memory Configuration |
| ****************************** |
| |
| This is the configuration of the MPU after the kernel has started up. It should |
| contain the following: |
| |
| - Any configuration of memory regions which need to have special caching or |
| write-back policies for basic hardware and driver function. Note that most |
| MPUs have the concept of a default memory access policy map, which can be |
| enabled as a "background" mapping for any area of memory that doesn't |
| have an MPU region configuring it. It is strongly recommended to use this |
| to maximize the number of available MPU regions for the end user. On |
| ARMv7-M/ARMv8-M this is called the System Address Map, other CPUs may |
| have similar capabilities. |
| |
| - A read-only, executable region or regions for program text and ro-data, that |
| is accessible to user mode. This could be further sub-divided into a |
| read-only region for ro-data, and a read-only, executable region for text, but |
| this will require an additional MPU region. This is required so that |
| threads running in user mode can read ro-data and fetch instructions. |
| |
| - Depending on configuration, user-accessible read-write regions to support |
| extra features like GCOV, HEP, etc. |
| |
| Assuming there is a background map which allows supervisor mode to access any |
| memory it needs, and regions are defined which grant user mode access to |
| text/ro-data, this is sufficient for the boot time configuration. |
| |
| Hardware Stack Overflow |
| *********************** |
| |
| :kconfig:option:`CONFIG_HW_STACK_PROTECTION` is an optional feature which detects stack |
| buffer overflows when the system is running in supervisor mode. This |
| catches issues when the entire stack buffer has overflowed, and not |
| individual stack frames, use compiler-assisted :kconfig:option:`CONFIG_STACK_CANARIES` |
| for that. |
| |
| Like any crash in supervisor mode, no guarantees can be made about the overall |
| health of the system after a supervisor mode stack overflow, and any instances |
| of this should be treated as a serious error. However it's still very useful to |
| know when these overflows happen, as without robust detection logic the system |
| will either crash in mysterious ways or behave in an undefined manner when the |
| stack buffer overflows. |
| |
| Some systems implement this feature by creating at runtime a 'guard' MPU region |
| which is set to be read-only and is at either the beginning or immediately |
| preceding the supervisor mode stack buffer. If the stack overflows an |
| exception will be generated. |
| |
| This feature is optional and is not required to catch stack overflows in user |
| mode; disabling this may free 1-2 MPU regions depending on the MPU design. |
| |
| Other systems may have dedicated CPU support for catching stack overflows |
| and no extra MPU regions will be required. |
| |
| Thread Stack |
| ************ |
| |
| Any thread running in user mode will need access to its own stack buffer. |
| On context switch into a user mode thread, a dedicated MPU region will be |
| programmed with the bounds of the stack buffer. A thread exceeding its stack |
| buffer will start pushing data onto memory it doesn't have access to and a |
| memory access violation exception will be generated. |
| |
| Thread Resource Pools |
| ********************* |
| |
| A small subset of kernel APIs, invoked as system calls, require heap memory |
| allocations. This memory is used only by the kernel and is not accessible |
| directly by user mode. In order to use these system calls, invoking threads |
| must assign themselves to a resource pool, which is a k_mem_pool object. |
| Memory is drawn from a thread's resource pool using :c:func:`z_thread_malloc` |
| and freed with :c:func:`k_free`. |
| |
| The APIs which use resource pools are as follows, with any alternatives |
| noted for users who do not want heap allocations within their application: |
| |
| - :c:func:`k_stack_alloc_init` sets up a k_stack with its storage |
| buffer allocated out of a resource pool instead of a buffer provided by the |
| user. An alternative is to declare k_stacks that are automatically |
| initialized at boot with :c:macro:`K_STACK_DEFINE()`, or to initialize the |
| k_stack in supervisor mode with :c:func:`k_stack_init`. |
| |
| - :c:func:`k_pipe_alloc_init` sets up a k_pipe object with its |
| storage buffer allocated out of a resource pool instead of a buffer provided |
| by the user. An alternative is to declare k_pipes that are automatically |
| initialized at boot with :c:macro:`K_PIPE_DEFINE()`, or to initialize the |
| k_pipe in supervisor mode with :c:func:`k_pipe_init`. |
| |
| - :c:func:`k_msgq_alloc_init` sets up a k_msgq object with its |
| storage buffer allocated out of a resource pool instead of a buffer provided |
| by the user. An alternative is to declare a k_msgq that is automatically |
| initialized at boot with :c:macro:`K_MSGQ_DEFINE()`, or to initialize the |
| k_msgq in supervisor mode with :c:func:`k_msgq_init`. |
| |
| - :c:func:`k_poll` when invoked from user mode, needs to make a kernel-side |
| copy of the provided events array while waiting for an event. This copy is |
| freed when :c:func:`k_poll` returns for any reason. |
| |
| - :c:func:`k_queue_alloc_prepend` and :c:func:`k_queue_alloc_append` |
| allocate a container structure to place the data in, since the internal |
| bookkeeping information that defines the queue cannot be placed in the |
| memory provided by the user. |
| |
| - :c:func:`k_object_alloc` allows for entire kernel objects to be |
| dynamically allocated at runtime and a usable pointer to them returned to |
| the caller. |
| |
| The relevant API is :c:func:`k_thread_heap_assign` which assigns |
| a k_heap to draw these allocations from for the target thread. |
| |
| If the system heap is enabled, then the system heap may be used with |
| :c:func:`k_thread_system_pool_assign`, but it is preferable for different |
| logical applications running on the system to have their own pools. |
| |
| Memory Domains |
| ************** |
| |
| The kernel ensures that any user thread will have access to its own stack |
| buffer, plus program text and read-only data. The memory domain APIs are the |
| way to grant access to additional blocks of memory to a user thread. |
| |
| Conceptually, a memory domain is a collection of some number of memory |
| partitions. The maximum number of memory partitions in a domain |
| is limited by the number of available MPU regions. This is why it is important |
| to minimize the number of boot-time MPU regions. |
| |
| Memory domains are *not* intended to control access to memory from supervisor |
| mode. In some cases this may be unavoidable; for example some architectures do |
| not allow for the definition of regions which are read-only to user mode but |
| read-write to supervisor mode. A great deal of care must be taken when working |
| with such regions to not unintentionally cause the kernel to crash when |
| accessing such a region. Any attempt to use memory domain APIs to control |
| supervisor mode access is at best undefined behavior; supervisor mode access |
| policy is only intended to be controlled by boot-time memory regions. |
| |
| Memory domain APIs are only available to supervisor mode. The only control |
| user mode has over memory domains is that any user thread's child threads |
| will automatically become members of the parent's domain. |
| |
| All threads are members of a memory domain, including supervisor threads |
| (even though this has no implications on their memory access). There is a |
| default domain ``k_mem_domain_default`` which will be assigned to threads if |
| they have not been specifically assigned to a domain, or inherited a memory |
| domain membership from their parent thread. The main thread starts as a |
| member of the default domain. |
| |
| Memory Partitions |
| ================= |
| |
| Each memory partition consists of a memory address, a size, |
| and access attributes. It is intended that memory partitions are used to |
| control access to system memory. Defining memory partitions are subject |
| to the following constraints: |
| |
| - The partition must represent a memory region that can be programmed by |
| the underlying memory management hardware, and needs to conform to any |
| underlying hardware constraints. For example, many MPU-based systems require |
| that partitions be sized to some power of two, and aligned to their own |
| size. For MMU-based systems, the partition must be aligned to a page and |
| the size some multiple of the page size. |
| |
| - Partitions within the same memory domain may not overlap each other. There is |
| no notion of precedence among partitions within a memory domain. Partitions |
| within a memory domain are assumed to have a higher precedence than any |
| boot-time memory regions, however whether a memory domain partition can |
| overlap a boot-time memory region is architecture specific. |
| |
| - The same partition may be specified in multiple memory domains. For example |
| there may be a shared memory area that multiple domains grant access to. |
| |
| - Care must be taken in determining what memory to expose in a partition. |
| It is not appropriate to provide direct user mode access to any memory |
| containing private kernel data. |
| |
| - Memory domain partitions are intended to control access to system RAM. |
| Configuration of memory partitions which do not correspond to RAM |
| may not be supported by the architecture; this is true for MMU-based systems. |
| |
| There are two ways to define memory partitions: either manually or |
| automatically. |
| |
| Manual Memory Partitions |
| ------------------------ |
| |
| The following code declares a global array ``buf``, and then declares |
| a read-write partition for it which may be added to a domain: |
| |
| .. code-block:: c |
| |
| uint8_t __aligned(32) buf[32]; |
| |
| K_MEM_PARTITION_DEFINE(my_partition, buf, sizeof(buf), |
| K_MEM_PARTITION_P_RW_U_RW); |
| |
| This does not scale particularly well when we are trying to contain multiple |
| objects spread out across several C files into a single partition. |
| |
| Automatic Memory Partitions |
| --------------------------- |
| |
| Automatic memory partitions are created by the build system. All globals |
| which need to be placed inside a partition are tagged with their destination |
| partition. The build system will then coalesce all of these into a single |
| contiguous block of memory, zero any BSS variables at boot, and define |
| a memory partition of appropriate base address and size which contains all |
| the tagged data. |
| |
| .. figure:: auto_mem_domain.png |
| :alt: Automatic Memory Domain build flow |
| :align: center |
| |
| Automatic Memory Domain build flow |
| |
| Automatic memory partitions are only configured as read-write |
| regions. They are defined with :c:macro:`K_APPMEM_PARTITION_DEFINE()`. |
| Global variables are then routed to this partition using |
| :c:macro:`K_APP_DMEM()` for initialized data and :c:macro:`K_APP_BMEM()` for |
| BSS. |
| |
| .. code-block:: c |
| |
| #include <app_memory/app_memdomain.h> |
| |
| /* Declare a k_mem_partition "my_partition" that is read-write to |
| * user mode. Note that we do not specify a base address or size. |
| */ |
| K_APPMEM_PARTITION_DEFINE(my_partition); |
| |
| /* The global variable var1 will be inside the bounds of my_partition |
| * and be initialized with 37 at boot. |
| */ |
| K_APP_DMEM(my_partition) int var1 = 37; |
| |
| /* The global variable var2 will be inside the bounds of my_partition |
| * and be zeroed at boot size K_APP_BMEM() was used, indicating a BSS |
| * variable. |
| */ |
| K_APP_BMEM(my_partition) int var2; |
| |
| The build system will ensure that the base address of ``my_partition`` will |
| be properly aligned, and the total size of the region conforms to the memory |
| management hardware requirements, adding padding if necessary. |
| |
| If multiple partitions are being created, a variadic preprocessor macro can be |
| used as provided in ``app_macro_support.h``: |
| |
| .. code-block:: c |
| |
| FOR_EACH(K_APPMEM_PARTITION_DEFINE, part0, part1, part2); |
| |
| Automatic Partitions for Static Library Globals |
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| |
| The build-time logic for setting up automatic memory partitions is in |
| ``scripts/gen_app_partitions.py``. If a static library is linked into Zephyr, |
| it is possible to route all the globals in that library to a specific |
| memory partition with the ``--library`` argument. |
| |
| For example, if the Newlib C library is enabled, the Newlib globals all need |
| to be placed in ``z_libc_partition``. The invocation of the script in the |
| top-level ``CMakeLists.txt`` adds the following: |
| |
| .. code-block:: none |
| |
| gen_app_partitions.py ... --library libc.a z_libc_partition .. |
| |
| For pre-compiled libraries there is no support for expressing this in the |
| project-level configuration or build files; the toplevel ``CMakeLists.txt`` must |
| be edited. |
| |
| For Zephyr libraries created using ``zephyr_library`` or ``zephyr_library_named`` |
| the ``zephyr_library_app_memory`` function can be used to specify the memory |
| partition where all globals in the library should be placed. |
| |
| Pre-defined Memory Partitions |
| ----------------------------- |
| |
| There are a few memory partitions which are pre-defined by the system: |
| |
| - ``z_malloc_partition`` - This partition contains the system-wide pool of |
| memory used by libc malloc(). Due to possible starvation issues, it is |
| not recommended to draw heap memory from a global pool, instead |
| it is better to define various sys_heap objects and assign them |
| to specific memory domains. |
| |
| - ``z_libc_partition`` - Contains globals required by the C library and runtime. |
| Required when using either the Minimal C library or the Newlib C Library. |
| Required when option:`CONFIG_STACK_CANARIES` is enabled. |
| |
| Library-specific partitions are listed in ``include/app_memory/partitions.h``. |
| For example, to use the MBEDTLS library from user mode, the |
| ``k_mbedtls_partition`` must be added to the domain. |
| |
| Memory Domain Usage |
| =================== |
| |
| Create a Memory Domain |
| ---------------------- |
| |
| A memory domain is defined using a variable of type |
| :c:struct:`k_mem_domain`. It must then be initialized by calling |
| :c:func:`k_mem_domain_init`. |
| |
| The following code defines and initializes an empty memory domain. |
| |
| .. code-block:: c |
| |
| struct k_mem_domain app0_domain; |
| |
| k_mem_domain_init(&app0_domain, 0, NULL); |
| |
| Add Memory Partitions into a Memory Domain |
| ------------------------------------------ |
| |
| There are two ways to add memory partitions into a memory domain. |
| |
| This first code sample shows how to add memory partitions while creating |
| a memory domain. |
| |
| .. code-block:: c |
| |
| /* the start address of the MPU region needs to align with its size */ |
| uint8_t __aligned(32) app0_buf[32]; |
| uint8_t __aligned(32) app1_buf[32]; |
| |
| K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf), |
| K_MEM_PARTITION_P_RW_U_RW); |
| |
| K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf), |
| K_MEM_PARTITION_P_RW_U_RO); |
| |
| struct k_mem_partition *app0_parts[] = { |
| app0_part0, |
| app0_part1 |
| }; |
| |
| k_mem_domain_init(&app0_domain, ARRAY_SIZE(app0_parts), app0_parts); |
| |
| This second code sample shows how to add memory partitions into an initialized |
| memory domain one by one. |
| |
| .. code-block:: c |
| |
| /* the start address of the MPU region needs to align with its size */ |
| uint8_t __aligned(32) app0_buf[32]; |
| uint8_t __aligned(32) app1_buf[32]; |
| |
| K_MEM_PARTITION_DEFINE(app0_part0, app0_buf, sizeof(app0_buf), |
| K_MEM_PARTITION_P_RW_U_RW); |
| |
| K_MEM_PARTITION_DEFINE(app0_part1, app1_buf, sizeof(app1_buf), |
| K_MEM_PARTITION_P_RW_U_RO); |
| |
| k_mem_domain_add_partition(&app0_domain, &app0_part0); |
| k_mem_domain_add_partition(&app0_domain, &app0_part1); |
| |
| .. note:: |
| The maximum number of memory partitions is limited by the maximum |
| number of MPU regions or the maximum number of MMU tables. |
| |
| Memory Domain Assignment |
| ------------------------ |
| |
| Any thread may join a memory domain, and any memory domain may have multiple |
| threads assigned to it. Threads are assigned to memory domains with an API |
| call: |
| |
| .. code-block:: c |
| |
| k_mem_domain_add_thread(&app0_domain, app_thread_id); |
| |
| If the thread was already a member of some other domain (including the |
| default domain), it will be removed from it in favor of the new one. |
| |
| In addition, if a thread is a member of a memory domain, and it creates a |
| child thread, that thread will belong to the domain as well. |
| |
| Remove a Memory Partition from a Memory Domain |
| ---------------------------------------------- |
| |
| The following code shows how to remove a memory partition from a memory |
| domain. |
| |
| .. code-block:: c |
| |
| k_mem_domain_remove_partition(&app0_domain, &app0_part1); |
| |
| The k_mem_domain_remove_partition() API finds the memory partition |
| that matches the given parameter and removes that partition from the |
| memory domain. |
| |
| Available Partition Attributes |
| ------------------------------ |
| |
| When defining a partition, we need to set access permission attributes |
| to the partition. Since the access control of memory partitions relies on |
| either an MPU or MMU, the available partition attributes would be architecture |
| dependent. |
| |
| The complete list of available partition attributes for a specific architecture |
| is found in the architecture-specific include file |
| ``include/arch/<arch name>/arch.h``, (for example, ``include/arch/arm/aarch32/arch.h``.) |
| Some examples of partition attributes are: |
| |
| .. code-block:: c |
| |
| /* Denote partition is privileged read/write, unprivileged read/write */ |
| K_MEM_PARTITION_P_RW_U_RW |
| /* Denote partition is privileged read/write, unprivileged read-only */ |
| K_MEM_PARTITION_P_RW_U_RO |
| |
| In almost all cases ``K_MEM_PARTITION_P_RW_U_RW`` is the right choice. |
| |
| Configuration Options |
| ********************* |
| |
| Related configuration options: |
| |
| * :kconfig:option:`CONFIG_MAX_DOMAIN_PARTITIONS` |
| |
| API Reference |
| ************* |
| |
| The following memory domain APIs are provided by :zephyr_file:`include/kernel.h`: |
| |
| .. doxygengroup:: mem_domain_apis |