Arm Cortex-M 开发者指南

概述

本页详细介绍了 Zephyr RTOS 中 Arm Cortex-M 架构的移植现状,并说明了为基于 Arm Cortex-M 的平台开发 Zephyr 应用时的关键事项。

支持的主要特性

下表汇总了不同 Arm Cortex-M 实现变体对主要操作系统特性的支持情况。

处理器系列族

架构变体

Arm v6-M

Arm v7-M

Arm v8-M

Arm v8.1-M

M0/M1

M0+

M3

M4

M7

M23

M33

M55

M85

操作系统特性

可编程的故障 IRQ 优先级

否

否

是

是

是

否

是

是

是

单线程内核支持

是

是

是

是

是

是

是

是

是

线程局部存储支持

是

是

是

是

是

是

是

是

是

中断处理

常规中断

是

是

是

是

是

是

是

是

是

动态中断

是

是

是

是

是

是

是

是

是

直接中断

是

是

是

是

是

是

是

是

是

零延迟中断

否

否

是

是

是

是

是

是

是

CPU 空闲

是

是

是

是

是

是

是

是

是

原生系统定时器(SysTick)

N [1]

是

是

是

是

是

是

是

是

内存保护

用户模式

否

是

是

是

是

是

是

是

是

硬件栈保护(MPU)

否

否

是

是

是

是

是

是

是

硬件辅助栈界限检查

否

否

否

否

否

Y [2]

是

是

是

特权执行禁止 [3]

否

否

否

否

否

否

否

是

是

硬件辅助控制流完整性

PACBTI

否

否

否

否

否

否

否

否

是

硬件辅助空指针解引用检测

否

否

是

是

是

是

是

是

是

硬件辅助原子操作

否

否

是

是

是

否

是

是

是

支持不可缓存区域

否

否

是

是

是

否

是

是

是

执行 SRAM 中的函数

否

否

是

是

是

否

是

是

是

浮点服务

否

否

否

是

是

否

是

是

是

DSP 指令集架构

否

否

否

是

是

否

是

是

是

可信执行

原生 TrustZone-M 支持

否

否

否

否

否

是

是

是

是

TF-M 集成

否

否

否

否

否

否

是

否

否

代码重定位

是

是

是

是

是

是

是

是

是

基于软件的向量表转发

是

是

是

是

是

是

是

是

是

硬件辅助计时函数

否

否

是

是

是

否

是

是

是

备注

操作系统特性

线程

线程栈对齐

每个 Zephyr 线程都定义有自己的栈内存。默认情况下,Cortex-M 强制要求线程栈按双字对齐,参见 CONFIG_STACK_ALIGN_DOUBLE_WORD 。如果启用了基于 MPU 的硬件辅助栈溢出检测( CONFIG_MPU_STACK_GUARD ),线程栈需要按更大的值对齐,该值由 CONFIG_ARM_MPU_REGION_MIN_ALIGN_AND_SIZE 表示。在 Arm v6-M 和 Arm v7-M 架构变体中,对于需要支持用户模式( CONFIG_USERSPACE )的应用,还要求线程栈按其自身大小对齐。在这种情况下,线程栈大小必须为 2 的幂。这些要求均由 CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT 体现,该选项在支持用户模式的 Arm v6-M 和 Arm v7-M 构建中强制启用。

栈指针

在线程模式下执行时,处理器使用进程栈指针(PSP)。在处理程序模式下执行时,即处理异常和硬件中断时,处理器使用主栈指针(MSP)。在线程模式下使用 PSP 便于在线程上下文切换期间操作线程栈指针 ,且不会影响处理程序模式下当前上下文的执行流程。

在 Arm Cortex-M 构建中,异常和中断共享同一块中断栈内存。选择中断栈大小时,需要考虑嵌套中断,因为每嵌套一层中断都会额外压入一个栈帧。开发者可以使用 CONFIG_ISR_STACK_SIZE 修改中断栈大小。

中断栈也用于启动早期阶段,以便内核在切换到主线程之前初始化主线程的栈。

线程上下文切换

在 Arm Cortex-M 构建中,PendSV 异常用于触发上下文切换,以切换到另一个线程。所有 Cortex-M 实现都具备 PendSV 异常。在所有 Cortex-M 变体中,PendSV 均配置为最低的中断优先级。这样设计的主要原因是

  • 利用 Cortex-M 处理器的尾链特性,从而减少上下文切换操作的次数。

  • 避免影响硬件中断的响应延迟。

因此,Cortex-M 中的上下文切换不是原子操作,即它可能被硬件中断 抢占 ,但必须完成当前上下文切换操作后,才能开始新的线程上下文切换。

线程上下文切换通常执行以下操作

  • 切出当前线程时,处理器会保存

    • 被调用者保存寄存器(R4 - R11),将其存入内核内存中该线程的被调用者保存寄存器存储区

    • 线程当前的运行 模式

      • 用户执行模式或特权执行模式

      • 是否存在活动的浮点上下文

      • 当前处理程序上下文(PendSV)的 EXC_RETURN 值

    • 如果当前线程具有活动的浮点上下文,则将浮点被调用者保存寄存器(S16 - S31)存入该线程的浮点被调用者保存寄存器存储区

    • 当前线程的 PSP,它指向当前线程异常栈帧的起始位置。该栈帧包含被切出线程的调用者保存上下文和返回地址。

  • 切入新线程时,处理器会

    • 从新线程的被调用者保存寄存器存储区中恢复其被调用者保存寄存器

    • 恢复新线程的运行 模式

    • 如果切入的线程在上次切出前具有活动的浮点上下文,则恢复浮点被调用者保存寄存器

    • 重新配置动态 MPU 区域,以允许用户线程访问其栈和应用内存,和/或在线程特权栈的底部配置用于防止栈溢出的 MPU 保护区

    • 恢复待切入线程的 PSP,并重新配置栈指针限制寄存器(如果适用,参见 CONFIG_BUILTIN_STACK_GUARD )

    • 如果启用了基于哨兵值的栈边界检查,则对切入的线程执行栈边界检查(参见 CONFIG_STACK_SENTINEL )。

PendSV 异常返回序列在弹出异常栈帧的过程中,恢复新线程的调用者保存寄存器和返回地址。

上下文切换机制的实现位于 arch/arm/core/cortex_m/swap_helper.S 。

栈边界检查(Arm v8-M)

Armv8-M 和 Armv8.1-M 变体支持使用 MSPLIM 和 PSPLIM 内核寄存器进行栈边界检查。设置 CONFIG_BUILTIN_STACK_GUARD 后,此功能即被启用。启用栈边界检查后,线程的特权栈或用户栈由 PSPLIM 寄存器保护,中断栈由 MSPLIM 寄存器保护。MSPLIM 仅在内核启动期间配置 一次 ,而 PSLIM 会在每次线程上下文切换时重新配置,也会在系统调用期间线程从默认栈切换到特权栈或反向切换时重新配置。重新配置 PSPLIM

  • 运行时开销相对较低(通过 MSR 指令完成配置)

  • 不影响中断延迟

  • 无需为栈保护预留任何内存区域

  • 不占用 MPU 区域

因此,它被认为是 Cortex-M 应用中一种轻量且非常高效的栈溢出检测机制。

栈溢出会触发 Arm v8-M 提供的专用 UsageFault 异常。

中断处理特性

本节介绍 Arm Cortex-M 中异常和中断处理的若干方面。

中断优先级

可用的(可配置)中断优先级数量由 NVIC 中实现的中断优先级位数决定;每个 Cortex-M 平台都需要使用 Devicetree 描述这一信息:

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

保留的优先级

部分中断优先级保留给操作系统使用。

按照设计,系统故障异常具有最高优先级。在 Baseline Cortex-M 中,这实际上由硬件强制保证,因为 HardFault 是唯一可用的处理器故障异常,其优先级高于任何可配置的异常优先级。

在 Mainline Cortex-M 中,可用的故障异常(例如 MemManageFault、UsageFault 等)被分配到最高的 可配置 优先级。( CONFIG_CPU_CORTEX_M_HAS_PROGRAMMABLE_FAULT_PRIOS 明确表示该 Cortex-M 实现支持可配置的故障优先级。)

此优先级从不与硬件中断共享(下文将介绍一个例外)。因此,在常规 ISR 中发生的处理器故障将由相应的故障处理程序处理,而不会升级为 HardFault, 与线程模式下发生的处理器故障类似 。

SVC 异常通常配置为最高的可配置优先级(下文将介绍一个例外)。Zephyr 内核使用 SVC 来分派系统调用、触发运行时系统错误(例如内核 oops 或 panic),或实现 IRQ 卸载。

在 Baseline Cortex-M 中,SVC 的优先级可以与同样被赋予最高可配置优先级的其他异常或硬件中断共享(因此,中断处理期间发生的内核运行时错误会升级为 HardFault。故障处理例程中的额外逻辑确保能够成功检测到此类运行时错误)。

而在 Mainline Cortex-M 中,SVC 的优先级是 保留的 ,因此通常仅与优先级可配置的故障异常共享。这简化了 Mainline Cortex-M 架构中的故障处理例程,因为内核运行时错误由 SVC 处理程序处理(即使内核错误发生在 ISR 上下文中,也不会升级为 HardFault)。

在 Mainline Cortex-M 构建中,硬件中断被分配的优先级低于 SVC。

当 Zephyr 应用支持零延迟中断(ZLI)时,上述规则存在一个例外。此类中断被设计为具有高于任何硬件或系统中断的优先级。如果在 Mainline Cortex-M 构建中启用了 ZLI 特性(参见 CONFIG_ZERO_LATENCY_IRQS ),则

  • ZLI 被分配到最高的可配置优先级

  • SVC 被分配到第二高的可配置优先级

  • 常规硬件中断被分配的优先级低于 SVC。

Cortex-M 中的优先级配置在 include/zephyr/arch/arm/cortex_m/exception.h 中实现。

锁定和解锁 IRQ

在 Baseline Cortex-M 中,中断锁定通过 PRIMASK 寄存器实现。

arch_irq_lock()

会将 PRIMASK 寄存器设置为 1,最终屏蔽所有优先级可配置的 IRQ。虽然这满足了操作系统锁定中断的要求,但其后果是内核运行时错误(触发 SVC)会升级为 HardFault。

在 Mainline Cortex-M 中,中断锁定通过 BASEPRI 寄存器实现(Mainline Cortex-M 构建会选择 CONFIG_CPU_CORTEX_M_HAS_BASEPRI ,以表明实现了 BASEPRI 寄存器)。通过修改 BASEPRI(或 BASEPRI_MAX),arch_irq_lock() 会屏蔽所有系统中断和硬件中断,但以下情况除外:

  • SVC

  • 处理器故障

  • ZLI

这允许零延迟中断在操作系统临界区内触发。此外,无论错误发生时 IRQ 是否已被锁定,Zephyr 都能以 完全相同的方式 处理系统(处理器和内核)故障。这也允许在 IRQ 被锁定时分派系统调用。

备注

Mainline Cortex-M 的故障处理设计和配置确保所有处理器和内核故障均由相应的异常处理程序处理,绝不会升级为 HardFault。换言之,只有修改了默认故障处理配置的 Zephyr 应用才可能发生 HardFault。这种设计的主要目的是将 HardFault 异常保留用于处理安全关键应用中的特殊错误情况。

动态直接中断

Cortex-M 构建支持在运行时安装直接中断服务例程。直接中断专为性能关键的中断处理而设计,不会经过所有通用的 Zephyr 中断处理代码。

通过启用 CONFIG_DYNAMIC_DIRECT_INTERRUPTS 来启用动态直接中断。

请注意,启用动态直接中断还需要在内核中启用动态中断支持(参见 CONFIG_DYNAMIC_INTERRUPTS )。

Zero Latency interrupts

如上所述,在 Mainline Cortex-M 应用中,Zephyr 内核保留最高的可配置中断优先级供自身使用(SVC)。中断锁定不会屏蔽 SVC。零延迟中断可用于设置具有最高中断优先级的中断,该中断不会被中断锁定阻塞。要使用 ZLI 功能,需要启用 CONFIG_ZERO_LATENCY_IRQS 。

零延迟 IRQ 具有最小的中断延迟,因为它们始终会抢占常规硬件中断或系统中断。

但请注意,由于 ZLI ISR 的运行优先级高于内核异常,因此它们 不能使用 任何内核功能。此外,由于 ZLI 中断优先级与处理器故障优先级相同,ZLI ISR 中发生的故障会升级为 HardFault,无法以处理常规处理器故障的相同方式进行处理。开发者需要了解这一限制。

CPU 空闲

Cortex-M 架构移植实现了 k_cpu_idle() 和 k_cpu_atomic_idle()。实现位于 arch/arm/core/cortex_m/cpu_idle.c 。

在这两种实现中,处理器都会尝试让核心进入低功耗模式。在 k_cpu_idle() 中,处理器最终会执行 WFI(等待中断)指令,而在 k_cpu_atomic_idle() 中,处理器会执行 WFE(等待事件)指令。

在 Cortex-M 中使用 CPU 空闲 API 时,需要注意以下几点:

  • k_cpu_idle() 和 k_cpu_atomic_idle() 均 假定 在中断已锁定的情况下调用。如果这些 API 由空闲线程调用,内核会负责锁定中断。

  • 从低功耗模式唤醒后,这两个函数都会无条件地 恢复 中断,即不考虑调用 CPU 空闲 API 之前的中断锁定状态。

Zephyr 的 CPU 空闲机制详见 CPU 空闲 。

内存保护功能

本节介绍 Arm Cortex-M 应用中内存保护功能的若干方面。

用户模式系统调用

实现了标准(Arm)MPU 或类似核心外设逻辑以配置和控制内存访问策略的 Cortex-M 平台支持用户模式,例如 Kinetis 平台的 NXP MPU。(目前,如果用户在开发板默认 Kconfig 设置中启用了 CONFIG_ARM_MPU ,则会选中 CONFIG_ARCH_HAS_USERSPACE 。)

线程通过触发(同步)SVC 异常来执行系统调用,其中

  • 最多 5 个参数放入寄存器 R1 - R5

  • 系统调用 ID 放入寄存器 R6。

SVC 处理程序会跳转到系统调用准备逻辑,该逻辑将执行以下操作

  • 将线程的 PSP 切换为指向该线程特权栈区域的起始位置;如果启用了栈边界检查,还会重新设置 PSPLIM

  • 修改 CONTROL 寄存器以切换到特权模式

  • 修改 SVC 异常栈帧中的返回地址,使异常返回后执行系统调用分派器(在线程特权模式下)

系统调用执行完成后,系统调用分派器会恢复用户原来的 PSP 和 PSPLIM,并在返回系统调用的调用者之前,通过 CONTROL 寄存器切换回非特权模式。

系统调用在线程模式下执行,随时可能被中断抢占。线程在执行系统调用期间也可能因上下文切换而被切出;一旦该线程再次被切入,系统调用就会恢复执行。

系统调用分派器以 SVC 优先级执行,因此不会被硬件中断(ZLI 除外)抢占。如果硬件中断在系统调用准备期间发生,可能会产生一些额外的中断延迟。

MPU 辅助的栈溢出检测

配备 MPU 的 Cortex-M 平台可以启用 CONFIG_MPU_STACK_GUARD,以启用基于 MPU 的栈溢出检测机制。启用 MPU 栈保护时,需要考虑以下几点

  • 栈溢出一旦发生,就会触发处理器故障异常

  • 该机制对于检测监督者线程或处于特权模式的用户线程中的栈溢出至关重要;对于处于用户模式的线程,无论是否设置 CONFIG_MPU_STACK_GUARD,其栈溢出始终会被检测到。

  • 栈溢出始终会被检测到,但该机制无法保证监督者线程的栈内存溢出时不会发生内存破坏

  • CONFIG_MPU_STACK_GUARD 通常会预留一个 MPU 区域用于配置栈保护(在启用了 CONFIG_MPU_GAP_FILLING 的某些 Arm v8-M 配置中,需要 2 个 MPU 区域才能实现栈保护功能)

  • 每次上下文切换时都会重新配置 MPU 保护区域,为线程切换例程增加少量开销。不过,与 CONFIG_BUILTIN_STACK_GUARD 功能相比,系统调用期间不会进行重新配置。

  • 在 Arm v8-M 平台上启用 CONFIG_HW_STACK_PROTECTION 时,默认使用原生栈限值检查机制,而非基于 MPU 的栈溢出检测机制;在这些情况下,用户可以手动启用 CONFIG_MPU_STACK_GUARD 来覆盖此设置。

指针认证和分支目标识别(PACBTI)

Armv8.1-M 指针认证和分支目标识别(PACBTI)扩展是 Armv8.1-M 架构配置的可选扩展,实现了以下控制流完整性保护方法:

  • 返回地址签名和认证(PAC-RET),用于缓解面向返回编程(ROP)类型的攻击。

  • 放置 BTI 指令(BTI),用于缓解面向跳转编程(JOP)类型的攻击。

在具备硬件支持(例如 Cortex-M85)和编译器支持时,可以通过选择以下配置之一,在 Zephyr 构建时启用 PACBTI:

这些配置选项确保将启用 PACBTI 指令的编译器标志添加到构建中,具体如下:

  • GCC 工具链使用 -mbranch-protection=。

此外,系统会根据为 CONFIG_ARM_PACBTI 选择的分支保护选项,自动选择 CONFIG_ARM_PAC 和 CONFIG_ARM_BTI。这些配置选项通过启用 CONTROL 寄存器和 FVP 中相应的 PACBTI 位,强制实施 PACBTI 保护。

为了进一步增强指针认证,Zephyr 支持通过启用 CONFIG_ARM_PAC_PER_THREAD,为每个线程使用独立且具备密码学安全性的 PAC 密钥。有关密钥生成来源和配置的更多详细信息,请参阅 CONFIG_ARM_PAC_PER_THREAD 的 Kconfig 帮助。

限制:

  • 只有面向具备 PACBTI 硬件支持的 Armv8.1-M Mainline 处理器(例如 Cortex-M85)的构建,才能充分使用此功能。

  • Zephyr 集成的 SDK 目前包含 GCC 12.2,该版本不支持 PACBTI,因此必须使用外部 GCC 工具链(建议使用 14.3 或更高版本)才能获得 PACBTI 支持。有关如何设置工具链,请参阅 此文档。

有关 PACBTI 的更多信息,请参阅官方 Arm 文档 和 Arm 社区博客

内存映射和 MPU 注意事项

固定 MPU 区域

默认情况下,启用 CONFIG_ARM_MPU 后,会在系统启动期间配置一组 固定 MPU 区域。

  • 一个 MPU 区域将整个 flash 区域配置为可读、可执行。用户可以通过启用 CONFIG_MPU_ALLOW_FLASH_WRITE 来覆盖此设置,将 flash 配置为具有 RWX 权限。如果启用了 CONFIG_USERSPACE,则允许对整个 flash 区域进行非特权访问。

  • 一个 MPU 区域将整个 SRAM 区域配置为仅允许特权访问的 RW 权限。也就是说,利用一个 MPU 区域来禁止在 SRAM 中执行代码。(此设置的例外情况是禁用 CONFIG_MPU_GAP_FILLING 时(仅适用于 Arm v8-M);此时不会对 SRAM 进行 MPU 配置,因此访问权限由默认的 Arm 内存映射策略决定,允许对 SRAM 进行仅限特权访问的 RWX 操作。)

  • 在 Devicetree 中通过 zephyr,memory-attr 属性定义 MPU 权限的所有内存区域。更多详细信息请参阅下一节。

上述 MPU 区域在 arch/arm/core/mpu/arm_mpu_regions.c 中定义。启用 CONFIG_CPU_HAS_CUSTOM_FIXED_SOC_MPU_REGIONS 可以使用其他 MPU 配置。启用此选项表示 Cortex-M SoC 将在其 SoC 定义中定义并配置自己的固定 MPU 区域。

在 Devicetree 中定义的固定 MPU 区域

当内存节点中存在 zephyr,memory-attr 属性时,系统会在启动期间分配并配置一个新的 MPU 区域。与 Devicetree compatible zephyr,memory-region 配合使用时,还会生成一个与该 MPU 区域关联的链接器段。

例如,要在 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_MPU_RAM_NOCACHE>;
};

这会自动创建一个新的 MPU 条目,其正确的名称、基地址、大小和属性均直接从 Devicetree 获取。更多详细信息请参阅 缓存基础 和 内存属性。

静态 MPU 区域

系统启动期间还可以一次性配置额外的 静态 MPU 区域。启用某些功能需要这些区域。更多详细信息请参阅 缓存基础。

  • 启用 CONFIG_ARCH_HAS_RAMFUNC_SUPPORT 且用户定义了从 SRAM 执行的函数时,配置一个 RX 区域以允许从 SRAM 执行代码。

  • 启用 CONFIG_CODE_DATA_RELOCATION_SRAM 时,配置一个 RX 区域,用于将代码段重定位到 SRAM

  • 启用 CONFIG_NOCACHE_MEMORY 时,配置一个 nocache 区域,以提供不可缓存的 SRAM 区域

  • 启用 CONFIG_COVERAGE_GCOV 时,为 GCOV 代码覆盖率统计区域配置一个可能允许非特权访问的 RW 区域

  • 启用 CONFIG_NULL_POINTER_EXCEPTION_DETECTION_MPU 时,配置一个禁止访问的区域,以实现空指针解引用检测

这些静态 MPU 区域的边界由链接器公开的符号确定,这些符号位于 include/linker/linker-defs.h 中。

动态 MPU 区域

某些线程专属的 MPU 区域可以在每次线程上下文切换时动态重新配置:

  • 为当前线程的栈区域配置一个允许非特权访问的 RW 区域(适用于用户线程)

  • 为 MPU 栈保护配置一个只读区域

  • 为当前线程的应用内存域中的分区配置允许非特权访问的 RW 区域。

注意事项

Cortex-M 平台上的可用 MPU 区域数量是一种有限资源。大多数平台具有 8 个 MPU 区域,而某些 Cortex-M33 或 Cortex-M7 平台最多可具有 16 个 MPU 区域。因此,可以同时配置的固定、静态和动态 MPU 区域数量受到较为严格的限制。对于只有 8 个可用 MPU 区域的平台,可能无法启用上述所有需要配置 MPU 区域的功能。不过,在大多数实际应用中,只需要其中一部分功能,8 个 MPU 区域在许多情况下已经足够。

在 Arm v8-M 处理器中,MPU 架构不允许已配置的 MPU 区域重叠。CONFIG_MPU_GAP_FILLING 控制是否配置覆盖整个 SRAM 的固定 MPU 区域。配置该区域时,为了配置静态和动态 MPU 区域,必须对整个 SRAM 区域进行分区。这会增加所需 MPU 区域的总数。未启用 CONFIG_MPU_GAP_FILLING 时,不会配置覆盖整个 SRAM 的固定 MPU 区域,因此只需在始终存在的背景区域之上配置静态和动态区域(无需对整个 SRAM 进行分区)。但请注意,背景 SRAM 区域允许从 SRAM 执行代码,因此未设置 CONFIG_MPU_GAP_FILLING 时,Zephyr 无法防范试图从 SRAM 执行恶意代码的攻击。

浮点服务

Cortex-M 同时支持非共享和共享 FP 寄存器模式(更多详细信息请参阅 浮点服务)。

在构建中启用 FPU 支持(启用 CONFIG_FPU)时,默认会启用共享 FP 寄存器模式(CONFIG_FPU_SHARING)。这是因为某些编译器配置可能会为任何线程生成 FP 指令,从而激活浮点上下文,而不论该线程是否执行浮点运算;在切入和切出这些线程时,必须保留该上下文。

如果能够保证映像代码仅在唯一一个获准且预期使用 FP 指令的线程上下文中生成 FP 指令,开发者仍然可以在应用项目中禁用 FP 共享模式,切换到非共享 FP 寄存器模式。

在 FPU 共享模式下,如果相应线程具有活动的 FP 上下文,则在上下文切换期间会保存和恢复由被调用者保存的 FPU 寄存器。这会给切换例程增加一些运行时开销。除了运行时开销之外,FPU 共享模式

  • 需要为每个线程分配额外的内存,以保存由被调用者保存的浮点寄存器

  • 需要为每个线程分配额外的栈内存,以便在浮点上下文处于活动状态时,在进入异常时将由调用者保存的浮点寄存器压栈。但请注意,由于启用了延迟压栈,常规中断中没有浮点上下文压栈的运行时开销(仅在 PendSV 中断的切换例程中才会启用浮点状态保存)。

杂项

可链式加载的映像

Cortex-M 应用既可以是独立映像,也可以是可链式加载的映像,例如由引导加载程序加载。可由引导加载程序(或其他应用)链式加载的应用映像通常占用闪存中的特定区域,称为其 代码分区 。 CONFIG_USE_DT_CODE_PARTITION 可确保将 Zephyr 可链式加载的映像链接到 Devicetree 中指定的代码分区。

启动时的硬件初始化

为正常启动,链式加载的应用可能要求将 Arm 核心硬件寄存器和外设初始化为复位值。启用 CONFIG_INIT_ARCH_HW_AT_BOOT 可使 Zephyr 在启动期间强制将 Cortex-M 内部架构状态初始化为相应 Arm 架构手册规定的复位值。

软件向量转发

在实现了 VTOR 寄存器的 Cortex-M 平台上(参见 CONFIG_CPU_CORTEX_M_HAS_VTOR ),可链式加载的映像通过将映像向量表的偏移量写入 VTOR 寄存器来重定位 Cortex-M 向量表。

没有 VTOR 寄存器的 Baseline Cortex-M 平台可能无法重定位其向量表,向量表会保持在固定位置。因此,可链式加载的映像需要通过其他方式将硬件中断和系统异常路由到自身的向量表;这通过软件向量转发来实现。

引导加载程序映像启用 CONFIG_SW_VECTOR_RELAY 后,便可根据可链式加载的应用设置的向量表指针转发异常和中断。后者设置 CONFIG_SW_VECTOR_RELAY_CLIENT 选项,指示启动流程设置 SRAM 中的向量表指针,使引导加载程序能够将异常和中断转发到可链式加载映像的软件向量表。

虽然此功能面向没有 VTOR 寄存器的处理器,但也可用于 Mainline Cortex-M 平台。

Code relocation

Cortex-M 支持代码重定位功能。选择 CONFIG_CODE_DATA_RELOCATION_SRAM 后,Zephyr 会将指定文件中的 .text、data 和 .bss 段重定位到 SRAM 中。可以仅将部分代码段重定位到 SRAM,而无需重定位整个映像的代码段和数据段。有关代码重定位功能的更多详细信息,请参见 代码与数据重定位 。

链接 Cortex-M 应用

大多数 Cortex-M 平台使用 include/zephyr/arch/arm/cortex_m/scripts/linker.ld 中的默认 Cortex-M GCC 链接器脚本,但平台也可以使用自定义链接器脚本。

CMSIS

Cortex-M CMSIS 头文件通过独立的模块仓库提供:

Zephyr 已开始转向使用 CMSIS 6 作为 Cortex-M 内核头文件的默认来源。不过,目前 Zephyr 同时 包含 CMSIS 6 和旧版 CMSIS 5 模块中的头文件。

旧版 CMSIS 5 头文件仍然可用,主要用于兼容厂商 HAL,而所有新的架构层开发都应尽可能使用 CMSIS 6 头文件。

CONFIG_CPU_CORTEX_M 会选择 CONFIG_HAS_CMSIS_CORE ,表明所有受支持的 Cortex-M 变体均有可用的 CMSIS 头文件。

测试

针对 Cortex-M 移植及杂项功能的单元测试位于 tests/arch/arm/ 中。现有测试套件会持续扩展,也会不断添加新的测试套件,以提高对 Zephyr 中 Cortex-M 架构支持的测试覆盖程度。

QEMU

我们使用 QEMU 验证 Zephyr 中 Cortex-M 架构移植已实现的功能。通过定义并使用一组 QEMU 目标来实现充分的测试覆盖,每个目标均具有特定的架构变体和 Arm 外设支持列表。

下表列出了 Zephyr 中定义的 QEMU 平台目标,以及对应的 Cortex-M 实现变体和这些目标模拟的外设。

QEMU 目标

架构变体

Arm v6-M

Arm v7-M

Arm v8-M

Arm v8.1-M

qemu_cortex_m0

qemu_cortex_m3

mps2/an385

mps2/an521/cpu0

mps3/corstone300/an547

仿真功能

NVIC

是

是

是

是

是

BASEPRI

否

是

是

是

是

SysTick

否

是

是

是

是

MPU

否

否

是

是

是

FPU

否

否

否

是

否

SPLIM

否

否

否

是

是

TrustZone-M

否

否

否

是

否

维护者与协作者

Zephyr 中 Arm Cortex-M 架构移植的状态为:维护中 。Cortex-M 维护者和协作者的最新名单可在 MAINTAINERS.yml 中找到。