hardware/arch/arm_cortex_m.html · hardware/arch/arm_cortex_m.rst 공식 원문 ↗

Zephyr 3.7.0 · Hardware · Architecture · Arm Cortex-M

Arm Cortex-M 개발자 가이드

Cortex-M에서 Zephyr thread와 interrupt, memory protection이 실제로 어떻게 동작하는지 원문 전체 흐름으로 설명합니다.

Official pathhardware/arch/arm_cortex_m.html
Source filehardware/arch/arm_cortex_m.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Arm v6-M부터 v8.1-M까지의 기능표를 바탕으로 PendSV context switch, SVC·ZLI priority, MPU region 운용, FP context, bootloader 연계와 QEMU 검증 환경을 연결해 읽습니다.

Execution

PSP · MSP · PendSV · SVC

Protection

MPU · stack limit · userspace

Platform

CMSIS · linker · QEMU · boot

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 4D9650124A7DF9648261D9EAEB4D39634EF09D3B8EA2A7269234CF516B6A0979
.. _arm_cortex_m_developer_guide:

Arm Cortex-M Developer Guide
############################

Overview
********

This page contains detailed information about the status of the Arm Cortex-M
architecture porting in the Zephyr RTOS and describes key aspects when
developing Zephyr applications for Arm Cortex-M-based platforms.

Key supported features
**********************

The table below summarizes the status of key OS features in the different
Arm Cortex-M implementation variants.


+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |                                   | **Processor families**                                                                      |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Architecture variant            |                                   | Arm v6-M                  | Arm v7-M                    | Arm v8-M             | Arm v8.1-M |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |                                   | **M0/M1**       | **M0+** | **M3** |   **M4**  | **M7** | **M23** |   **M33**  |  **M55**   |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| **OS Features**                 |                                   |                                                                                             |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Programmable fault              |                                   |                 |         |        |           |        |         |            |            |
| IRQ priorities                  |                                   |        Y        |   N     |   Y    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Single-thread kernel support    |                                   |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Thread local storage support    |                                   |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Interrupt handling              |                                   |                                                                                             |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   Regular interrupts              |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   Dynamic interrupts              |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   Direct  interrupts              |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   Zero Latency interrupts         |        N        |   N     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| CPU idling                      |                                   |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Native system timer (SysTick)   |                                   |        N [#f1]_ |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Memory protection               |                                   |                                                                                             |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   User mode                       |        N        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 |   HW stack protection (MPU)       |        N        |   N     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 | HW-assisted stack limit checking  |        N        |   N     |   N    |    N      |    N   |Y [#f2]_ |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| HW-assisted null-pointer        |                                   |                 |         |        |           |        |         |            |            |
| dereference detection           |                                   |        N        |   N     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| HW-assisted atomic operations   |                                   |        N        |   N     |   Y    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|Support for non-cacheable regions|                                   |        N        |   N     |   Y    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Execute SRAM functions          |                                   |        N        |   N     |   Y    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Floating Point Services         |                                   |        N        |   N     |   N    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| DSP ISA                         |                                   |        N        |   N     |   N    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Trusted-Execution               |                                                                                                                                 |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 | Native TrustZone-M support        |        N        |   N     |   N    |    N      |    N   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
|                                 | TF-M integration                  |        N        |   N     |   N    |    N      |    N   |    N    |     Y      |   N        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| Code relocation                 |                                   |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| SW-based vector table relaying  |                                   |        Y        |   Y     |   Y    |    Y      |    Y   |    Y    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+
| HW-assisted timing functions    |                                   |        N        |   N     |   Y    |    Y      |    Y   |    N    |     Y      |   Y        |
+---------------------------------+-----------------------------------+-----------------+---------+--------+-----------+--------+---------+------------+------------+

Notes
=====

.. [#f1] SysTick is optional in Cortex-M1
.. [#f2] Stack limit checking only in Secure builds in Cortex-M23

OS features
***********

Threads
=======

Thread stack alignment
----------------------

Each Zephyr thread is defined with its own stack memory. By default, Cortex-M enforces a double word thread stack alignment, see
:kconfig:option:`CONFIG_STACK_ALIGN_DOUBLE_WORD`. If MPU-based HW-assisted stack overflow detection (:kconfig:option:`CONFIG_MPU_STACK_GUARD`)
is enabled, thread stacks need to be aligned with a larger value, reflected by :kconfig:option:`CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE`.
In Arm v6-M and Arm v7-M architecture variants, thread stacks are additionally required to align with a value equal to their size,
in applications that need to support user mode (:kconfig:option:`CONFIG_USERSPACE`). The thread stack sizes in that case need to be a power
of two. This is all reflected by :kconfig:option:`CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT`, that is enforced in Arm v6-M and Arm v7-M
builds with user mode support.

Stack pointers
--------------

While executing in thread mode the processor is using the Process Stack Pointer (PSP). The processor uses the Main Stack Pointer (MSP)
while executing in handler mode, that is, while servicing exceptions and HW interrupts. Using PSP in thread mode *facilitates thread
stack pointer manipulation* during thread context switching, without affecting the current execution context flow in
handler mode.

In Arm Cortex-M builds a single interrupt stack memory is shared among exceptions and interrupts. The size of the interrupt stack needs
to be selected taking into consideration nested interrupts, each pushing an additional stack frame. Developers can modify the interrupt
stack size using :kconfig:option:`CONFIG_ISR_STACK_SIZE`.

The interrupt stack is also used during early boot so the kernel can initialize the main thread's stack before switching to the main thread.

Thread context switching
========================

In Arm Cortex-M builds, the PendSV exception is used in order to trigger a context switch to a different thread.
PendSV exception is always present in Cortex-M implementations. PendSV is configured with the lowest possible
interrupt priority level, in all Cortex-M variants. The main reasons for that design are

* to utilize the tail chaining feature of Cortex-M processors, and thus limit the number of context switch
  operations that occur.
* to not impact the interrupt latency observed by HW interrupts.

As a result, context switch in Cortex-M is non-atomic, i.e. it may be *preempted* by HW interrupts,
however, a context-switch operation must be completed before a new thread context-switch may start.

Typically a thread context-switch will perform the following operations

* When switching-out the current thread, the processor stores

   * the callee-saved registers (R4 - R11) in the thread's container for callee-saved registers,
     which is located in kernel memory
   * the thread's current operation *mode*

        * user or privileged execution mode
        * presence of an active floating point context
        * the EXC_RETURN value of the current handler context (PendSV)

   * the floating point callee-saved registers (S16 - S31) in the thread's container for FP
     callee-saved registers, if the current thread has an active FP context
   * the PSP of the current thread which points to the beginning of the current thread's exception
     stack frame. The latter contains the caller-saved context and the return address of the switched-out
     thread.

* When switching-in a new thread the processor

   * restores the new thread's callee-saved registers from the thread's
     container for callee-saved registers
   * restores the new thread's operation *mode*
   * restores the FP callee-saved registers if the switched-in thread had
     an active FP context before being switched-out
   * re-programs the dynamic MPU regions to allow a user thread access its stack and application
     memories, and/or programs a stack-overflow MPU guard at the bottom of the thread's
     privileged stack
   * restores the PSP for the incoming thread and re-programs the stack pointer limit
     register (if applicable, see :kconfig:option:`CONFIG_BUILTIN_STACK_GUARD`)
   * optionally does a stack limit checking for the switched-in thread, if
     sentinel-based stack limit checking is enabled (see :kconfig:option:`CONFIG_STACK_SENTINEL`).

PendSV exception return sequence restores the new thread's caller-saved registers and the
return address, as part of unstacking the exception stack frame.

The implementation of the context-switch mechanism is present in
:file:`arch/arm/core/cortex_m/swap_helper.S`.

Stack limit checking (Arm v8-M)
-------------------------------

Armv8-M and Armv8.1-M variants support stack limit checking using the MSPLIM and PSPLIM
core registers. The feature is enabled when :kconfig:option:`CONFIG_BUILTIN_STACK_GUARD` is set.
When stack limit checking is enabled, both the thread's privileged or user stack, as well
as the interrupt stack are guarded by PSPLIM and MSPLIM registers, respectively. MSPLIM is
configured *once* during kernel boot, while PSLIM is re-programmed during every thread
context-switch or during system calls, when the thread switches from using its default
stack to using its privileged stack, and vice versa. PSPLIM re-programming

* has a relatively low runtime overhead (programming is done with MSR instructions)
* does not impact interrupt latency
* does not require any memory areas to be reserved for stack guards
* does not make use of MPU regions

It is, therefore, considered as a lightweight but very efficient stack overflow
detection mechanism in Cortex-M applications.

Stack overflows trigger the dedicated UsageFault exception provided by Arm v8-M.

Interrupt handling features
===========================

This section describes certain aspects around exception and interrupt
handling in Arm Cortex-M.

Interrupt priority levels
-------------------------

The number of available (configurable) interrupt priority levels is
determined by the number of implemented interrupt priority bits in
NVIC; this needs to be described for each Cortex-M platform using
DeviceTree:

.. code-block:: devicetree

    &nvic {
            arm,num-irq-priority-bits = <#priority-bits>;
    };


Reserved priority levels
------------------------

A number of interrupt priority levels are reserved for the OS.

By design, system fault exceptions have the highest priority level. In
*Baseline* Cortex-M, this is actually enforced by hardware, as HardFault
is the only available processor fault exception, and its priority is
higher than any configurable exception priority.

In *Mainline* Cortex-M, the available fault exceptions (e.g. MemManageFault,
UsageFault, etc.) are assigned the highest *configurable* priority level.
(:kconfig:option:`CONFIG_CPU_CORTEX_M_HAS_PROGRAMMABLE_FAULT_PRIOS` signifies explicitly
that the Cortex-M implementation supports configurable fault priorities.)

This priority level is never shared with HW interrupts (an exception to
this rule is described below). As a result, processor faults occurring in regular
ISRs will be handled by the corresponding fault handler and will not escalate to
a HardFault, *similar to processor faults occurring in thread mode*.

SVC exception is normally configured with the highest configurable priority level
(an exception to this rule will be described below).
SVCs are used by the Zephyr kernel to dispatch system calls, trigger runtime
system errors (e.g. Kernel oops or panic), or implement IRQ offloading.

In Baseline Cortex-M the priority level of SVC may be shared with other exceptions
or HW interrupts that are also given the highest configurable priority level (As a
result of this, kernel runtime errors during interrupt handling will escalate to
HardFault. Additional logic in the fault handling routines ensures that such
runtime errors are detected successfully).

In Mainline Cortex-M, however, the SVC priority level is *reserved*, thus normally it
is only shared with the fault exceptions of configurable priority. This simplifies the
fault handling routines in Mainline Cortex-M architecture, since runtime kernel errors
are serviced by the SVC handler (i.e no HardFault escalation, even if the kernel errors
occur in ISR context).

HW interrupts in Mainline Cortex-M builds are allocated a priority level lower than the SVC.

One exception to the above rules is when Zephyr applications support Zero Latency Interrupts
(ZLIs). Such interrupts are designed to have a priority level higher than any HW or system
interrupt. If the ZLI feature is enabled in Mainline Cortex-M builds (see
:kconfig:option:`CONFIG_ZERO_LATENCY_IRQS`), then

* ZLIs are assigned the highest configurable priority level
* SVCs are assigned the second highest configurable priority level
* Regular HW interrupts are assigned priority levels lower than SVC.

The priority level configuration in Cortex-M is implemented in
:file:`include/zephyr/arch/arm/cortex_m/exception.h`.

Locking and unlocking IRQs
--------------------------

In Baseline Cortex-M locking interrupts is implemented using the PRIMASK register.

.. code-block:: c

  arch_irq_lock()

will set the PRIMASK register to 1, eventually, masking all IRQs with configurable
priority. While this fulfils the OS requirement of locking interrupts, the consequence
is that kernel runtime errors (triggering SVCs) will escalate to HardFault.

In Mainline Cortex-M locking interrupts is implemented using the BASEPRI register (Mainline
Cortex-M builds select :kconfig:option:`CONFIG_CPU_CORTEX_M_HAS_BASEPRI` to signify that BASEPRI register is
implemented.). By modifying BASEPRI (or BASEPRI_MAX) arch_irq_lock() masks all system and HW
interrupts with the exception of

* SVCs
* processor faults
* ZLIs

This allows zero latency interrupts to be triggered inside OS critical sections.
Additionally, this allows system (processor and kernel) faults to be handled by Zephyr
in *exactly the same way*, regardless of whether IRQs have been locked or not when the
error occurs. It also allows for system calls to be dispatched while IRQs are locked.

.. note::

   Mainline Cortex-M fault handling is designed and configured in a way that all processor
   and kernel faults are handled by the corresponding exception handlers and never result
   in HardFault escalation. In other words, a HardFault may only occur in Zephyr applications
   that have modified the default fault handling configurations. The main reason for this
   design was to reserve the HardFault exception for handling exceptional error conditions
   in safety critical applications.

Dynamic direct interrupts
-------------------------

Cortex-M builds support the installation of direct interrupt service routines during
runtime. Direct interrupts are designed for performance-critical interrupt
handling and do not go through all of the common Zephyr interrupt handling
code.

Direct dynamic interrupts are enabled via switching on
:kconfig:option:`CONFIG_DYNAMIC_DIRECT_INTERRUPTS`.

Note that enabling direct dynamic interrupts requires enabling support for
dynamic interrupts in the kernel, as well (see :kconfig:option:`CONFIG_DYNAMIC_INTERRUPTS`).

Zero Latency interrupts
-----------------------

As described above, in Mainline Cortex-M applications, the Zephyr kernel reserves
the highest configurable interrupt priority level for its own use (SVC). SVCs will
not be masked by interrupt locking. Zero-latency interrupt can be used to set up
an interrupt at the highest interrupt priority which will not be blocked by interrupt
locking. To use the ZLI feature :kconfig:option:`CONFIG_ZERO_LATENCY_IRQS` needs to be enabled.

Zero latency IRQs have minimal interrupt latency, as they will always preempt regular HW
or system interrupts.

Note, however, that since ZLI ISRs will run at a priority level higher than the kernel
exceptions they **cannot use** any kernel functionality. Additionally, since the ZLI
interrupt priority level is equal to processor fault priority level, faults occurring
in ZLI ISRs will escalate to HardFault and will not be handled in the same way as regular
processor faults. Developers need to be aware of this limitation.

CPU Idling
==========

The Cortex-M architecture port implements both k_cpu_idle()
and k_cpu_atomic_idle(). The implementation is present in
:file:`arch/arm/core/cortex_m/cpu_idle.c`.

In both implementations, the processor
will attempt to put the core to low power mode.
In k_cpu_idle() the processor ends up executing WFI (Wait For Interrupt)
instruction, while in k_cpu_atomic_idle() the processor will
execute a WFE (Wait For Event) instruction.

When using the CPU idling API in Cortex-M it is important to note the
following:

* Both k_cpu_idle() and k_cpu_atomic_idle() are *assumed* to be invoked
  with interrupts locked. This is taken care of by the kernel if the APIs
  are called by the idle thread.
* After waking up from low power mode, both functions will *restore*
  interrupts unconditionally, that is, regardless of the interrupt lock
  status before the CPU idle API was called.

The Zephyr CPU Idling mechanism is detailed in :ref:`cpu_idle`.

Memory protection features
==========================

This section describes certain aspects around memory protection features
in Arm Cortex-M applications.

User mode system calls
----------------------

User mode is supported in Cortex-M platforms that implement the standard (Arm) MPU
or a similar core peripheral logic for memory access policy configuration and
control, such as the NXP MPU for Kinetis platforms. (Currently,
:kconfig:option:`CONFIG_ARCH_HAS_USERSPACE` is selected if :kconfig:option:`CONFIG_ARM_MPU` is enabled
by the user in the board default Kconfig settings).

A thread performs a system call by triggering a (synchronous) SVC exception, where

* up to 5 arguments are placed on registers R1 - R5
* system call ID is placed on register R6.

The SVC Handler will branch to the system call preparation logic, which will perform
the following operations

* switch the thread's PSP to point to the beginning of the thread's privileged
  stack area, optionally reprogramming the PSPLIM if stack limit checking is enabled
* modify CONTROL register to switch to privileged mode
* modify the return address in the SVC exception stack frame, so that after exception
  return the system call dispatcher is executed (in thread privileged mode)

Once the system call execution is completed the system call dispatcher will restore the
user's original PSP and PSPLIM and switch the CONTROL register back to unprivileged mode
before returning back to the caller of the system call.

System calls execute in thread mode and can be preempted by interrupts at any time. A
thread may also be context-switched-out while doing a system call; the system call will
resume as soon as the thread is switched-in again.

The system call dispatcher executes at SVC priority, therefore it cannot be preempted
by HW interrupts (with the exception of ZLIs), which may observe some additional interrupt
latency if they occur during a system call preparation.

MPU-assisted stack overflow detection
-------------------------------------

Cortex-M platforms with MPU may enable :kconfig:option:`CONFIG_MPU_STACK_GUARD` to enable the MPU-based
stack overflow detection mechanism. The following points need to be considered when enabling the
MPU stack guards

* stack overflows are triggering processor faults as soon as they occur
* the mechanism is essential for detecting stack overflows in supervisor threads, or
  user threads in privileged mode; stack overflows in threads in user mode will always be
  detected regardless of :kconfig:option:`CONFIG_MPU_STACK_GUARD` being set.
* stack overflows are always detected, however, the mechanism does not guarantee that
  no memory corruption occurs when supervisor threads overflow their stack memory
* :kconfig:option:`CONFIG_MPU_STACK_GUARD` will normally reserve one MPU region for programming
  the stack guard (in certain Arm v8-M configurations with :kconfig:option:`CONFIG_MPU_GAP_FILLING`
  enabled 2 MPU regions are required to implement the guard feature)
* MPU guards are re-programmed at every context-switch, adding a small overhead to the
  thread swap routine. Compared, however, to the :kconfig:option:`CONFIG_BUILTIN_STACK_GUARD` feature,
  no re-programming occurs during system calls.
* When :kconfig:option:`CONFIG_HW_STACK_PROTECTION` is enabled on Arm v8-M platforms the native
  stack limit checking mechanism is used by default instead of the MPU-based stack overflow
  detection mechanism; users may override this setting by manually enabling :kconfig:option:`CONFIG_MPU_STACK_GUARD`
  in these scenarios.

Memory map and MPU considerations
=================================

Fixed MPU regions
-----------------

By default, when :kconfig:option:`CONFIG_ARM_MPU` is enabled a set of *fixed* MPU regions
are programmed during system boot.

* One MPU region programs the entire flash area as read-execute.
  User can override this setting by enabling :kconfig:option:`CONFIG_MPU_ALLOW_FLASH_WRITE`,
  which programs the flash with RWX permissions. If :kconfig:option:`CONFIG_USERSPACE` is
  enabled unprivileged access on the entire flash area is allowed.
* One MPU region programs the entire SRAM area with privileged-only
  RW permissions. That is, an  MPU region is utilized to disallow execute permissions on
  SRAM. (An exception to this setting is when :kconfig:option:`CONFIG_MPU_GAP_FILLING` is disabled (Arm v8-M only);
  in that case no SRAM MPU programming is done so the access is determined by the default
  Arm memory map policies, allowing for privileged-only RWX permissions on SRAM).
* All the memory regions defined in the devicetree with the property
  ``zephyr,memory-attr`` defining the MPU permissions for the memory region.
  See the next section for more details.

The above MPU regions are defined in :file:`arch/arm/core/mpu/arm_mpu_regions.c`.
Alternative MPU configurations are allowed by enabling :kconfig:option:`CONFIG_CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS`.
When enabled, this option signifies that the Cortex-M SoC will define and
configure its own fixed MPU regions in the SoC definition.

Fixed MPU regions defined in devicetree
---------------------------------------

When the property ``zephyr,memory-attr`` is present in a memory node, a new MPU
region will be allocated and programmed during system boot. When used with the
:dtcompatible:`zephyr,memory-region` devicetree compatible, it will result in a
linker section being generated associated to that MPU region.

For example, to define a new non-cacheable memory region in devicetree:

.. code-block:: devicetree

   sram_no_cache: memory@20300000 {
        compatible = "zephyr,memory-region", "mmio-sram";
        reg = <0x20300000 0x100000>;
        zephyr,memory-region = "SRAM_NO_CACHE";
        zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE) )>;
   };

This will automatically create a new MPU entry in with the correct name, base,
size and attributes gathered directly from the devicetree.

Static MPU regions
------------------

Additional *static* MPU regions may be programmed once during system boot. These regions
are required to enable certain features

* a RX region to allow execution from SRAM, when :kconfig:option:`CONFIG_ARCH_HAS_RAMFUNC_SUPPORT` is
  enabled and users have defined functions to execute from SRAM.
* a RX region for relocating text sections to SRAM, when :kconfig:option:`CONFIG_CODE_DATA_RELOCATION_SRAM` is enabled
* a no-cache region to allow for a none-cacheable SRAM area, when :kconfig:option:`CONFIG_NOCACHE_MEMORY` is enabled
* a possibly unprivileged RW region for GCOV code coverage accounting area, when :kconfig:option:`CONFIG_COVERAGE_GCOV` is enabled
* a no-access region to implement null pointer dereference detection, when :kconfig:option:`CONFIG_NULL_POINTER_EXCEPTION_DETECTION_MPU` is enabled

The boundaries of these static MPU regions are derived from symbols exposed by the linker, in
:file:`include/linker/linker-defs.h`.

Dynamic MPU regions
-------------------

Certain thread-specific MPU regions may be re-programmed dynamically, at each thread context switch:

* an unprivileged RW region for the current thread's stack area (for user threads)
* a read-only region for the MPU stack guard
* unprivileged RW regions for the partitions of the current thread's application memory
  domain.


Considerations
--------------

The number of available MPU regions for a Cortex-M platform is a limited resource.
Most platforms have 8 MPU regions, while some Cortex-M33 or Cortex-M7 platforms may
have up to 16 MPU regions. Therefore there is a relatively strict limitation on how
many fixed, static and dynamic MPU regions may be programmed simultaneously. For platforms
with 8 available MPU regions it might not be possible to enable all the aforementioned
features that require MPU region programming. In most practical applications, however,
only a certain set of features is required and 8 MPU regions are, in many cases, sufficient.

In Arm v8-M processors the MPU architecture does not allow programmed MPU regions to
overlap. :kconfig:option:`CONFIG_MPU_GAP_FILLING` controls whether the fixed MPU region
covering the entire SRAM is programmed. When it does, a full SRAM area partitioning
is required, in order to program the  static and the dynamic MPU regions. This increases
the total number of required MPU regions. When :kconfig:option:`CONFIG_MPU_GAP_FILLING` is not
enabled the fixed MPU region covering the entire SRAM is not programmed, thus, the static
and dynamic regions are simply programmed on top of the always-existing background region
(full-SRAM partitioning is not required).
Note, however, that the background SRAM region allows execution from SRAM, so when
:kconfig:option:`CONFIG_MPU_GAP_FILLING` is not set Zephyr is not protected against attacks
that attempt to execute malicious code from SRAM.


Floating point Services
=======================

Both unshared and shared FP registers mode are supported in Cortex-M (see
:ref:`float_v2` for more details).

When FPU support is enabled in the build
(:kconfig:option:`CONFIG_FPU` is enabled), the
sharing FP registers mode (:kconfig:option:`CONFIG_FPU_SHARING`)
is enabled by default. This is done as some compiler configurations
may activate a floating point context by generating FP instructions
for any thread, regardless of whether floating point calculations are
performed, and that context must be preserved when switching such
threads in and out.

The developers can still disable the FP sharing mode in their
application projects, and switch to Unshared FP registers mode,
if it is guaranteed that the image code does not generate FP
instructions outside the single thread context that is allowed
(and supposed) to do so.

Under FPU sharing mode, the callee-saved FPU registers are saved
and restored in context-switch, if the corresponding threads have
an active FP context. This adds some runtime overhead on the swap
routine. In addition to the runtime overhead, the sharing FPU mode

* requires additional memory for each thread to save the callee-saved
  FP registers
* requires additional stack memory for each thread, to stack the caller-saved
  FP registers, upon exception entry, if an FP context is active. Note, however,
  that since lazy stacking is enabled, there is no runtime overhead of FP context
  stacking in regular interrupts (FP state preservation is only activated in the
  swap routine in PendSV interrupt).


Misc
****

Chain-loadable images
=====================

Cortex-M applications may either be standalone images or chain-loadable, for instance,
by a bootloader. Application images chain-loadable by bootloaders (or other applications)
normally occupy a specific area in the flash denoted as their *code partition*.
:kconfig:option:`CONFIG_USE_DT_CODE_PARTITION` will ensure that a Zephyr chain-loadable image
will be linked into its code partition, specified in DeviceTree.

HW initialization at boot
-------------------------

In order to boot properly, chain-loaded applications may require that the core Arm
hardware registers and peripherals are initialized in their reset values. Enabling
:kconfig:option:`CONFIG_INIT_ARCH_HW_AT_BOOT` Zephyr to force the initialization of the
internal Cortex-M architectural state during boot to the reset values as specified
by the corresponding Arm architecture manual.

Software vector relaying
------------------------

In Cortex-M platforms that implement the VTOR register (see :kconfig:option:`CONFIG_CPU_CORTEX_M_HAS_VTOR`),
chain-loadable images relocate the Cortex-M vector table by updating the VTOR register with the offset
of the image vector table.

Baseline Cortex-M platforms without VTOR register might not be able to relocate their
vector table which remains at a fixed location. Therefore, a chain-loadable image will
require an alternative way to route HW interrupts and system exceptions to its own vector
table; this is achieved with software vector relaying.

When a bootloader image enables :kconfig:option:`CONFIG_SW_VECTOR_RELAY`
it is able to relay exceptions and interrupts based on a vector table
pointer that is set by the chain-loadable application. The latter sets
the :kconfig:option:`CONFIG_SW_VECTOR_RELAY_CLIENT` option to instruct the boot
sequence to set the vector table pointer in SRAM so that the bootloader can
forward the exceptions and interrupts to the chain-loadable image's software
vector table.

While this feature is intended for processors without VTOR register, it
may also be used in Mainline Cortex-M platforms.

Code relocation
===============

Cortex-M support the code relocation feature. When
:kconfig:option:`CONFIG_CODE_DATA_RELOCATION_SRAM` is selected,
Zephyr will relocate .text, data and .bss sections
from the specified files and place it in SRAM. It is
possible to relocate only parts of the code sections
into SRAM, without relocating the whole image text
and data sections. More details on the code relocation
feature can be found in :ref:`code_data_relocation`.


Linking Cortex-M applications
*****************************

Most Cortex-M platforms make use of the default Cortex-M
GCC linker script in :file:`include/zephyr/arch/arm/cortex_m/scripts/linker.ld`,
although it is possible for platforms to use a custom linker
script as well.


CMSIS
*****

Cortex-M CMSIS headers are hosted in a standalone module repository:
`zephyrproject-rtos/cmsis <https://github.com/zephyrproject-rtos/cmsis>`_.

:kconfig:option:`CONFIG_CPU_CORTEX_M` selects :kconfig:option:`CONFIG_HAS_CMSIS_CORE` to signify that
CMSIS headers are available for all supported Cortex-M variants.

Testing
*******

A list of unit tests for the Cortex-M porting and miscellaneous features
is present in :file:`tests/arch/arm/`. The tests suites are continuously
extended and new test suites are added, in an effort to increase the coverage
of the Cortex-M architecture support in Zephyr.

QEMU
****

We use QEMU to verify the implemented features of the Cortex-M architecture port in Zephyr.
Adequate coverage is achieved by defining and utilizing a list of QEMU targets,
each with a specific architecture variant and Arm peripheral support list.

The table below lists the QEMU platform targets defined in Zephyr
along with the corresponding Cortex-M implementation variant and the peripherals
these targets emulate.

+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
|                                 | **QEMU target**                                                                             |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| Architecture variant            | Arm v6-M           | Arm v7-M                            | Arm v8-M        | Arm v8.1-M     |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
|                                 | **qemu_cortex_m0** | **qemu_cortex_m3** | **mps2_an385** | **mps2_an521**  | **mps3_an547** |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| **Emulated features**           |                                                                                             |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| NVIC                            | Y                  | Y                  | Y              | Y               | Y              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| BASEPRI                         | N                  | Y                  | Y              | Y               | Y              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| SysTick                         | N                  | Y                  | Y              | Y               | Y              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| MPU                             | N                  | N                  | Y              | Y               | Y              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| FPU                             | N                  | N                  | N              | Y               | N              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| SPLIM                           | N                  | N                  | N              | Y               | Y              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+
| TrustZone-M                     | N                  | N                  | N              | Y               | N              |
+---------------------------------+--------------------+--------------------+----------------+-----------------+----------------+

Maintainers & Collaborators
***************************

The status of the Arm Cortex-M architecture port in Zephyr is: *maintained*.
The updated list of maintainers and collaborators for Cortex-M can be found
in :file:`MAINTAINERS.yml`.

Part 3

한국어 전문 번역

개요

이 페이지는 Zephyr RTOS의 Arm Cortex-M 아키텍처 포팅 상태를 자세히 설명하고, Arm Cortex-M 기반 플랫폼용 Zephyr 애플리케이션을 개발할 때 알아야 할 핵심 동작을 다룹니다.

주요 지원 기능

다음 표는 Arm Cortex-M 구현 계열별 주요 운영체제 기능의 지원 상태를 요약합니다.

Processor families
Architecture variantArm v6-MArm v7-MArm v8-MArm v8.1-M
M0/M1M0+M3M4M7M23M33M55
OS 기능
Programmable fault IRQ prioritiesYNYYYNYY
Single-thread kernel 지원YYYYYYYY
Thread local storage 지원YYYYYYYY
Interrupt 처리
일반 interruptYYYYYYYY
동적 interruptYYYYYYYY
Direct interruptYYYYYYYY
Zero Latency interruptNNYYYYYY
CPU idleYYYYYYYY
기본 system timer(SysTick)N 1YYYYYYY
Memory protection
User modeNYYYYYYY
HW stack protection(MPU)NNYYYYYY
HW 지원 stack limit 검사NNNNNY 2YY
HW 지원 null pointer 역참조 감지NNYYYYYY
HW 지원 atomic operationNNYYYNYY
Non-cacheable region 지원NNYYYNYY
SRAM 함수 실행NNYYYNYY
Floating Point ServiceNNNYYNYY
DSP ISANNNYYNYY
Trusted Execution
기본 TrustZone-M 지원NNNNNYYY
TF-M 통합NNNNNNYN
Code relocationYYYYYYYY
Software 기반 vector table relayYYYYYYYY
HW 지원 timing 함수NNYYYNYY

주석

  1. SysTick은 Cortex-M1에서 선택 기능입니다.
  2. Stack limit 검사는 Cortex-M23의 Secure build에서만 지원됩니다.

운영체제 기능

Thread

Thread stack 정렬

Zephyr thread마다 자체 stack memory가 있습니다. Cortex-M은 기본적으로 CONFIG_STACK_ALIGN_DOUBLE_WORD에 따라 thread stack을 double word 경계에 맞춥니다. MPU 기반 hardware stack overflow 감지(CONFIG_MPU_STACK_GUARD)를 켜면 CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE가 나타내는 더 큰 정렬값이 필요합니다.

Arm v6-M과 Arm v7-M에서 user mode(CONFIG_USERSPACE)를 지원하는 애플리케이션은 thread stack을 stack 크기와 같은 값에 맞춰 정렬해야 하며, stack 크기도 2의 거듭제곱이어야 합니다. 이 제약은 CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT로 표현되고 user mode를 지원하는 해당 build에서 강제됩니다.

Stack pointer

Processor는 thread mode에서 Process Stack Pointer(PSP)를 사용하고, exception과 hardware interrupt를 처리하는 handler mode에서는 Main Stack Pointer(MSP)를 사용합니다. Thread mode에서 PSP를 사용하면 handler mode의 현재 실행 흐름에 영향을 주지 않고 context switch 중 thread stack pointer를 조작하기 쉽습니다.

Arm Cortex-M build에서는 모든 exception과 interrupt가 하나의 interrupt stack을 공유합니다. 중첩 interrupt마다 stack frame이 하나씩 더 쌓이므로 이를 고려해 크기를 정해야 하며, CONFIG_ISR_STACK_SIZE로 조정할 수 있습니다. 이 interrupt stack은 초기 boot에서도 사용되며, kernel이 main thread로 전환하기 전에 main thread stack을 초기화할 때 필요합니다.

Thread context switch

Arm Cortex-M build는 다른 thread로 전환하도록 PendSV exception을 일으킵니다. PendSV는 모든 Cortex-M 구현에 존재하며 가능한 가장 낮은 interrupt priority로 설정됩니다.

  • Cortex-M의 tail chaining 기능을 활용해 실제 context switch 횟수를 줄입니다.
  • Hardware interrupt가 관찰하는 interrupt latency를 늘리지 않습니다.

따라서 Cortex-M의 context switch는 atomic하지 않으며 hardware interrupt가 중간에 선점할 수 있습니다. 다만 하나의 thread context switch는 새 context switch가 시작되기 전에 끝나야 합니다.

현재 thread를 내보낼 때 processor는 다음 상태를 저장합니다.

  • Callee-saved register R4~R11을 kernel memory에 있는 thread의 callee-saved register 저장소에 보관합니다.
  • User/privileged 실행 mode, 활성 floating-point context의 존재 여부, 현재 PendSV handler context의 EXC_RETURN 값을 포함한 동작 mode를 저장합니다.
  • 현재 thread에 활성 FP context가 있으면 floating-point callee-saved register S16~S31을 별도 저장소에 보관합니다.
  • 현재 thread의 exception stack frame 시작을 가리키는 PSP를 저장합니다. 이 frame에는 caller-saved context와 thread의 복귀 주소가 들어 있습니다.

새 thread를 들여올 때 processor는 다음 작업을 수행합니다.

  • 새 thread의 callee-saved register와 동작 mode를 복원합니다.
  • 그 thread가 이전에 활성 FP context를 가졌다면 FP callee-saved register도 복원합니다.
  • User thread가 자신의 stack과 application memory에 접근하도록 dynamic MPU region을 다시 설정하고, 필요하면 privileged stack 아래쪽에 MPU stack-overflow guard를 설정합니다.
  • 들어오는 thread의 PSP를 복원하고, 적용 가능한 경우 CONFIG_BUILTIN_STACK_GUARD에 따라 stack pointer limit register를 다시 설정합니다.
  • CONFIG_STACK_SENTINEL 기반 검사를 켰다면 들어오는 thread의 stack limit도 선택적으로 검사합니다.

PendSV exception return 과정은 exception stack frame을 꺼내면서 새 thread의 caller-saved register와 복귀 주소를 복원합니다. 구현은 arch/arm/core/cortex_m/swap_helper.S에 있습니다.

Stack limit 검사(Arm v8-M)

Armv8-M과 Armv8.1-M은 MSPLIM·PSPLIM core register를 이용한 stack limit 검사를 지원하며 CONFIG_BUILTIN_STACK_GUARD로 활성화합니다. Thread의 privileged/user stack은 PSPLIM이, interrupt stack은 MSPLIM이 보호합니다. MSPLIM은 kernel boot 때 한 번 설정되고 PSPLIM은 context switch 때마다, 그리고 system call에서 기본 stack과 privileged stack 사이를 바꿀 때마다 다시 설정됩니다.

  • MSR instruction으로 설정하므로 실행 시간 부담이 비교적 작습니다.
  • Interrupt latency에 영향을 주지 않습니다.
  • Stack guard용 memory 영역을 따로 예약하지 않습니다.
  • MPU region을 사용하지 않습니다.

따라서 Cortex-M 애플리케이션에서 가볍지만 효율적인 stack overflow 감지 방식입니다. Stack overflow가 발생하면 Arm v8-M의 전용 UsageFault exception이 발생합니다.

Interrupt 처리 기능

이 절은 Arm Cortex-M의 exception 및 interrupt 처리에서 중요한 부분을 설명합니다.

Interrupt priority level

사용 가능한 configurable interrupt priority level 수는 NVIC에 실제 구현된 interrupt priority bit 수로 정해집니다. 각 Cortex-M 플랫폼은 DeviceTree에 이를 기술해야 합니다.

&nvic {
        arm,num-irq-priority-bits = <#priority-bits>;
};

예약된 priority level

운영체제는 여러 interrupt priority level을 예약합니다. 설계상 system fault exception이 가장 높은 priority를 가집니다. Baseline Cortex-M에서는 HardFault가 유일한 processor fault exception이고 모든 configurable exception보다 priority가 높으므로 hardware가 이 규칙을 강제합니다.

Mainline Cortex-M에서는 MemManageFault·UsageFault 같은 fault exception을 가장 높은 configurable priority에 배치합니다. CONFIG_CPU_CORTEX_M_HAS_PROGRAMMABLE_FAULT_PRIOS는 구현에 configurable fault priority가 있음을 명시합니다. 이 level은 원칙적으로 hardware interrupt와 공유하지 않으므로 일반 ISR에서 난 processor fault도 thread mode에서 난 fault처럼 해당 fault handler가 처리하며 HardFault로 escalate하지 않습니다.

SVC exception도 보통 가장 높은 configurable priority로 설정됩니다. Zephyr kernel은 SVC로 system call을 dispatch하고, kernel oops/panic 같은 runtime system error를 일으키며, IRQ offloading을 구현합니다.

Baseline Cortex-M에서는 SVC priority가 같은 최고 configurable priority를 받은 다른 exception 또는 hardware interrupt와 공유될 수 있습니다. 따라서 interrupt 처리 중 발생한 kernel runtime error가 HardFault로 escalate할 수 있으며, fault 처리 routine의 추가 logic이 이를 감지합니다.

Mainline Cortex-M에서는 SVC priority가 예약되어 보통 configurable fault exception하고만 공유됩니다. 그래서 ISR context에서 kernel error가 나도 HardFault로 escalate하지 않고 SVC handler가 처리합니다. 일반 hardware interrupt에는 SVC보다 낮은 priority가 배정됩니다.

예외는 Zero Latency Interrupt(ZLI)입니다. Mainline build에서 CONFIG_ZERO_LATENCY_IRQS를 켜면 ZLI는 가장 높은 configurable priority, SVC는 두 번째, 일반 hardware interrupt는 SVC보다 낮은 priority를 받습니다. 구현은 include/zephyr/arch/arm/cortex_m/exception.h에 있습니다.

IRQ 잠금과 해제

Baseline Cortex-M은 PRIMASK register로 interrupt를 잠급니다.

arch_irq_lock()

이 호출은 결국 PRIMASK를 1로 만들어 configurable priority를 가진 모든 IRQ를 mask합니다. 운영체제의 interrupt 잠금 요구는 만족하지만, kernel runtime error가 일으킨 SVC도 HardFault로 escalate할 수 있습니다.

Mainline Cortex-M은 BASEPRI register로 interrupt를 잠그며 CONFIG_CPU_CORTEX_M_HAS_BASEPRI로 구현 여부를 표시합니다. arch_irq_lock()은 BASEPRI 또는 BASEPRI_MAX를 바꿔 SVC, processor fault, ZLI를 제외한 system/hardware interrupt를 mask합니다.

  • 운영체제 critical section 안에서도 ZLI가 발생할 수 있습니다.
  • IRQ가 잠겼는지와 관계없이 processor/kernel fault를 같은 방식으로 처리할 수 있습니다.
  • IRQ가 잠긴 상태에서도 system call을 dispatch할 수 있습니다.

Mainline Cortex-M의 기본 fault 처리는 processor·kernel fault가 해당 exception handler에서 처리되고 HardFault로 escalate하지 않도록 설계됩니다. 애플리케이션이 기본 fault 설정을 바꾼 경우에만 HardFault가 생길 수 있습니다. 이는 safety-critical 애플리케이션의 특별한 오류 조건 처리를 위해 HardFault를 남겨두려는 설계입니다.

동적 direct interrupt

Cortex-M build는 runtime에 direct ISR을 설치할 수 있습니다. Direct interrupt는 성능이 중요한 처리를 위한 것으로 공통 Zephyr interrupt 처리 code를 모두 거치지 않습니다. CONFIG_DYNAMIC_DIRECT_INTERRUPTS로 켜며, kernel의 dynamic interrupt 지원인 CONFIG_DYNAMIC_INTERRUPTS도 함께 활성화해야 합니다.

Zero Latency interrupt

Mainline Cortex-M에서 kernel은 보통 자체 SVC를 위해 가장 높은 configurable interrupt priority를 예약하고, interrupt 잠금은 SVC를 mask하지 않습니다. CONFIG_ZERO_LATENCY_IRQS를 켜면 잠금에 막히지 않는 가장 높은 priority의 zero-latency interrupt를 구성할 수 있습니다.

ZLI는 일반 hardware/system interrupt를 항상 선점하므로 interrupt latency가 매우 작습니다. 하지만 kernel exception보다 높은 priority에서 실행되기 때문에 ZLI ISR은 kernel 기능을 사용할 수 없습니다. 또한 processor fault와 같은 priority이므로 ZLI ISR 안의 fault는 HardFault로 escalate하며 일반 processor fault와 같은 방식으로 처리되지 않습니다. 개발자는 이 제약을 고려해야 합니다.

CPU idle

Cortex-M port는 k_cpu_idle()과 k_cpu_atomic_idle()을 모두 구현하며 code는 arch/arm/core/cortex_m/cpu_idle.c에 있습니다. 두 함수 모두 core를 저전력 mode로 전환하려고 하지만 전자는 WFI(Wait For Interrupt), 후자는 WFE(Wait For Event)를 실행합니다.

  • 두 함수는 interrupt가 잠긴 상태에서 호출된다고 가정합니다. Idle thread가 부르면 kernel이 이 조건을 보장합니다.
  • 저전력 mode에서 깨어난 뒤에는 호출 전 잠금 상태와 관계없이 두 함수가 interrupt를 무조건 복원합니다.

Zephyr CPU idle mechanism의 세부 내용은 공식 CPU Idling 문서를 참고하십시오.

Memory protection 기능

이 절은 Arm Cortex-M 애플리케이션의 memory protection 기능에서 주의할 점을 설명합니다.

User mode system call

표준 Arm MPU 또는 NXP Kinetis MPU처럼 memory access policy를 설정·제어하는 유사 core peripheral logic을 구현한 Cortex-M 플랫폼은 user mode를 지원합니다. 현재 board 기본 Kconfig에서 사용자가 CONFIG_ARM_MPU를 켜면 CONFIG_ARCH_HAS_USERSPACE가 선택됩니다.

Thread는 synchronous SVC exception을 일으켜 system call을 수행합니다. 최대 5개 argument는 R1~R5에, system call ID는 R6에 둡니다.

SVC handler는 system call 준비 logic으로 분기해 다음 작업을 수행합니다.

  • Thread의 PSP가 privileged stack 영역 시작을 가리키게 바꾸고, stack limit 검사를 켰다면 PSPLIM도 다시 설정합니다.
  • CONTROL register를 바꿔 privileged mode로 전환합니다.
  • SVC exception stack frame의 return address를 수정해 exception return 뒤 thread privileged mode에서 system call dispatcher가 실행되게 합니다.

System call이 끝나면 dispatcher는 사용자의 원래 PSP·PSPLIM을 복원하고 CONTROL register를 다시 unprivileged mode로 바꾼 뒤 호출자에게 돌아갑니다.

System call은 thread mode에서 실행되므로 언제든 interrupt가 선점할 수 있고, 수행 중 context switch로 빠졌다가 thread가 다시 들어오면 계속됩니다. 다만 준비 단계의 dispatcher는 SVC priority에서 실행되므로 ZLI를 제외한 hardware interrupt는 선점할 수 없고 이 구간에 발생한 interrupt는 latency가 조금 늘 수 있습니다.

MPU 지원 stack overflow 감지

MPU가 있는 Cortex-M은 CONFIG_MPU_STACK_GUARD로 MPU 기반 stack overflow 감지를 켤 수 있습니다.

  • Stack overflow가 발생하는 즉시 processor fault를 일으킵니다.
  • Supervisor thread 또는 privileged mode의 user thread overflow를 감지하는 데 필수입니다. Unprivileged user mode thread의 overflow는 이 설정과 관계없이 항상 감지됩니다.
  • Overflow 자체는 항상 감지하지만 supervisor thread가 넘쳤을 때 memory corruption이 전혀 없다고 보장하지는 않습니다.
  • 보통 stack guard용 MPU region 하나를 예약합니다. Arm v8-M에서 CONFIG_MPU_GAP_FILLING을 켠 일부 구성은 두 region이 필요합니다.
  • Context switch마다 MPU guard를 다시 설정해 작은 overhead가 생깁니다. CONFIG_BUILTIN_STACK_GUARD와 비교하면 system call 중에는 다시 설정하지 않습니다.
  • Arm v8-M에서 CONFIG_HW_STACK_PROTECTION을 켜면 기본적으로 MPU 방식 대신 native stack limit 검사를 사용합니다. 필요하면 CONFIG_MPU_STACK_GUARD를 직접 켜 덮어쓸 수 있습니다.

Memory map과 MPU 고려 사항

고정 MPU region

기본적으로 CONFIG_ARM_MPU를 켜면 system boot 중 다음 고정 MPU region을 설정합니다.

  • 전체 flash를 read-execute로 설정하는 region 하나를 둡니다. CONFIG_MPU_ALLOW_FLASH_WRITE로 RWX를 허용할 수 있고 CONFIG_USERSPACE를 켜면 전체 flash의 unprivileged access도 허용됩니다.
  • 전체 SRAM을 privileged-only RW로 설정해 실행 권한을 막는 region 하나를 둡니다. Arm v8-M에서 CONFIG_MPU_GAP_FILLING을 끈 경우에는 SRAM region을 따로 설정하지 않아 기본 Arm memory map policy에 따라 privileged-only RWX가 됩니다.
  • zephyr,memory-attr property로 MPU 권한을 정의한 모든 devicetree memory region도 설정합니다.

이 region들은 arch/arm/core/mpu/arm_mpu_regions.c에 정의됩니다. CONFIG_CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS을 켜면 SoC 정의가 자체 고정 MPU region을 구성할 수 있습니다.

Devicetree에 정의한 고정 MPU region

zephyr,memory-attr가 memory node에 있으면 boot 중 새 MPU region을 할당하고 설정합니다. zephyr,memory-region compatible과 함께 사용하면 그 MPU region에 대응하는 linker section도 생성합니다. 다음은 non-cacheable memory region을 정의하는 예입니다.

sram_no_cache: memory@20300000 {
     compatible = "zephyr,memory-region", "mmio-sram";
     reg = <0x20300000 0x100000>;
     zephyr,memory-region = "SRAM_NO_CACHE";
     zephyr,memory-attr = <( DT_MEM_ARM(ATTR_MPU_RAM_NOCACHE) )>;
};

이 정의로 devicetree에서 가져온 이름, base, size, attribute를 가진 MPU entry가 자동 생성됩니다.

Static MPU region

추가 static MPU region은 system boot 때 한 번 설정할 수 있으며 다음 기능에 필요합니다.

  • CONFIG_ARCH_HAS_RAMFUNC_SUPPORT를 켜고 SRAM 실행 함수를 정의했을 때의 RX region
  • CONFIG_CODE_DATA_RELOCATION_SRAM으로 text section을 SRAM에 옮길 때의 RX region
  • CONFIG_NOCACHE_MEMORY로 non-cacheable SRAM 영역을 만들 때의 no-cache region
  • CONFIG_COVERAGE_GCOV의 GCOV coverage accounting 영역을 위한, 필요하면 unprivileged RW인 region
  • CONFIG_NULL_POINTER_EXCEPTION_DETECTION_MPU의 null pointer 역참조 감지를 위한 no-access region

이 static MPU region의 경계는 include/linker/linker-defs.h가 노출하는 linker symbol에서 가져옵니다.

Dynamic MPU region

Thread별 MPU region 일부는 context switch마다 동적으로 다시 설정됩니다.

  • 현재 user thread의 stack 영역을 위한 unprivileged RW region
  • MPU stack guard를 위한 read-only region
  • 현재 thread application memory domain의 partition을 위한 unprivileged RW region

고려 사항

Cortex-M 플랫폼에서 쓸 수 있는 MPU region 수는 제한됩니다. 대부분은 8개이고 일부 Cortex-M33/M7은 최대 16개입니다. 따라서 고정·static·dynamic region을 동시에 몇 개까지 설정할 수 있는지 엄격한 한계가 있습니다. 8개인 플랫폼에서는 앞의 모든 기능을 한꺼번에 켤 수 없지만, 실제 애플리케이션은 보통 일부 기능만 필요하므로 8개로 충분한 경우가 많습니다.

Arm v8-M MPU는 설정한 region끼리 겹칠 수 없습니다. CONFIG_MPU_GAP_FILLING은 전체 SRAM을 덮는 고정 MPU region을 만들지 제어합니다. 켜면 static·dynamic region을 넣기 위해 SRAM 전체를 빈틈없이 partition해야 하므로 필요한 region 수가 늘어납니다. 끄면 전체 SRAM 고정 region 없이 기존 background region 위에 static·dynamic region을 직접 설정할 수 있습니다.

단, background SRAM region은 SRAM 실행을 허용합니다. 따라서 CONFIG_MPU_GAP_FILLING을 끄면 SRAM에 악성 code를 넣어 실행하려는 공격으로부터 Zephyr를 보호하지 못합니다.

Floating-point service

Cortex-M은 unshared FP register mode와 shared FP register mode를 모두 지원합니다. Build에서 CONFIG_FPU를 켜면 CONFIG_FPU_SHARING도 기본으로 활성화됩니다. Compiler 설정에 따라 실제 부동소수점 계산을 하지 않는 thread에도 FP instruction이 생성되어 FP context가 활성화될 수 있고, 그런 thread를 전환할 때도 context를 보존해야 하기 때문입니다.

Image code가 FP instruction을 FP 사용이 허용된 단 하나의 thread context 밖에서는 절대로 만들지 않는다고 보장할 수 있다면 개발자가 FP sharing을 끄고 unshared mode를 사용할 수 있습니다.

FPU sharing mode에서는 활성 FP context를 가진 thread를 전환할 때 callee-saved FPU register를 저장·복원하므로 swap routine에 실행 시간 overhead가 생깁니다.

  • 각 thread의 callee-saved FP register를 저장할 memory가 추가로 필요합니다.
  • 활성 FP context에서 exception entry가 일어나면 caller-saved FP register를 쌓을 thread stack 공간도 추가로 필요합니다. Lazy stacking을 사용하므로 일반 interrupt에서 FP context를 곧바로 쌓는 runtime overhead는 없고, PendSV의 swap routine에서만 보존이 활성화됩니다.

기타

Chain-loadable image

Cortex-M 애플리케이션은 standalone image일 수도 있고 bootloader 같은 다른 image가 넘겨 실행하는 chain-loadable image일 수도 있습니다. Chain-loadable image는 보통 flash의 특정 code partition에 놓이며, CONFIG_USE_DT_CODE_PARTITION을 켜면 DeviceTree가 지정한 code partition에 Zephyr image를 link합니다.

Boot 때 hardware 초기화

Chain-loaded 애플리케이션이 올바르게 boot하려면 Arm core hardware register와 peripheral이 reset 값이어야 할 수 있습니다. CONFIG_INIT_ARCH_HW_AT_BOOT을 켜면 Zephyr가 boot 중 내부 Cortex-M architecture state를 해당 Arm architecture manual의 reset 값으로 초기화합니다.

Software vector relay

VTOR register를 구현한 Cortex-M 플랫폼(CONFIG_CPU_CORTEX_M_HAS_VTOR)에서 chain-loadable image는 VTOR에 자기 vector table offset을 써 vector table을 옮깁니다.

VTOR가 없는 Baseline Cortex-M은 고정된 vector table을 옮길 수 없을 수 있습니다. 이때 chain-loadable image의 vector table로 hardware interrupt와 system exception을 보내기 위해 software vector relay를 사용합니다.

Bootloader가 CONFIG_SW_VECTOR_RELAY를 켜면 chain-loadable 애플리케이션이 설정한 vector table pointer를 이용해 exception과 interrupt를 relay할 수 있습니다. 애플리케이션은 CONFIG_SW_VECTOR_RELAY_CLIENT를 켜 boot sequence가 SRAM의 vector table pointer를 설정하게 하며, bootloader는 이를 통해 애플리케이션의 software vector table로 전달합니다. 이 기능은 VTOR가 없는 processor를 위한 것이지만 Mainline Cortex-M에서도 사용할 수 있습니다.

Code relocation

Cortex-M은 code relocation을 지원합니다. CONFIG_CODE_DATA_RELOCATION_SRAM을 선택하면 지정한 file의 .text, data, .bss section을 SRAM으로 옮깁니다. 전체 image의 text/data를 옮기지 않고 일부 code section만 SRAM으로 이동할 수도 있습니다.

Cortex-M 애플리케이션 linking

대부분의 Cortex-M 플랫폼은 기본 GCC linker script include/zephyr/arch/arm/cortex_m/scripts/linker.ld를 사용하지만, 플랫폼이 custom linker script를 사용할 수도 있습니다.

CMSIS

Cortex-M CMSIS header는 별도 module repository인 zephyrproject-rtos/cmsis에서 관리됩니다. CONFIG_CPU_CORTEX_M은 CONFIG_HAS_CMSIS_CORE를 선택해 지원되는 모든 Cortex-M variant에서 CMSIS header를 쓸 수 있음을 표시합니다.

시험

Cortex-M port와 여러 기능의 unit test는 tests/arch/arm/에 있습니다. Zephyr는 Cortex-M architecture 지원 coverage를 늘리기 위해 test suite를 계속 확장하고 새 suite를 추가합니다.

QEMU

Zephyr는 QEMU로 Cortex-M architecture port의 구현 기능을 검증합니다. 서로 다른 architecture variant와 Arm peripheral 지원 조합을 가진 여러 QEMU target을 정의해 적절한 coverage를 확보합니다.

다음 표는 Zephyr의 QEMU platform target과 각 target이 모사하는 Cortex-M 구현 variant 및 peripheral 기능을 보여 줍니다.

QEMU target
Architecture variantArm v6-MArm v7-MArm v8-MArm v8.1-M
qemu_cortex_m0qemu_cortex_m3mps2_an385mps2_an521mps3_an547
Emulated features
NVICYYYYY
BASEPRINYYYY
SysTickNYYYY
MPUNNYYY
FPUNNNYN
SPLIMNNNYY
TrustZone-MNNNYN

Maintainer와 collaborator

Zephyr의 Arm Cortex-M architecture port 상태는 maintained입니다. 최신 maintainer와 collaborator 목록은 MAINTAINERS.yml에서 확인할 수 있습니다.

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.