Armv8-M架构参考手册给Arm Cortex-M CPU定义了一组规则,只有符合这里面的所有规则,才可以说CPU是兼容ARM架构的,因此,Arm还专门开发了ACK工具,用于检查自己或客户的CPU产品是否符合架构规则。
Armv8-M programmer model主要包含如下,下文将分别阐述,不过FP、MVE和Exception内容较多,后续单开文章讲它们。

PE states
不像Armv8-A架构用于A32、A64和T32三种指令集,Armv8-M只支持T32指令集。它定义的PE使用模型有:
- Thread模式:应用程序。复位时选择该模式,而且PC会加载reset handler起始地址。
- Handler模式:操作系统内核和相关功能,用于管理系统资源。PE只在该模式下处理exceptions。
在Handler模式下,PE使用main stack (MSP),在Thread模式下,CONTROL.SPSEL决定PE是使用process stack (PSP)还是main stack (MSP)。在exception进入和退出时,PE会自动更新CONTROL.SPSEL。
Thread模式下可以继续细分PE是否是privileged或unprivileged(是通过CONTROL.nPRIV来决定Thread模式的privilege,当然只有privileged写才能修改nPRIV的值),Handler模式总是privileged,无法改写。Privilege可以确定一个资源是否允许访问,而且通常privileged执行比unprivileged执行可以访问更多的资源。
TrustZone
如果PE实现了Security Extension,也就是支持TrustZone,那么会有Secure和Non-secure两种模式,那么标记为Secure的memory区域和其它关键资源只能被处于Secure状态的PE访问。
PE的Security状态切换是通过Branch跳转到处于不同Security状态的地址来进行的。如下图所示。

Secure内存中的SG指令是Secure代码的有效入口点,它可以防止Non-secure代码跳转到Secure代码中的任意地址。当PE处于Secure状态时,任何尝试执行任何位于Non-secure内存的指令都会导致INVTRAN SecureFault产生。
因此,除了SG指令,在Non-secure状态下执行Secure代码是不可能的。在Secure状态下也不可能执行Non-secure代码。
在memory访问中,NS-Req指示PE或DAP请求执行memory访问时所处的Security状态。NS-Attr将memory访问标记为Secure或Non-secure。对于PE data accesses,NS-Req等于当前的Security状态。对于PE和DAP访问,NS-Attr的取值如下:

CPU刚上来时,马上会去找VTOR来取vector table的内容。Vector table包含了以下两部分:
- 复位时main stack的指针初始值;
- 每个exception handler的起始地址;
Exception number定义了这些exception的handler入口的顺序:

Vector fetch可以使用instruction interface来执行,并避免匹配到DWT和watchpoints。
在具有security extension的PE中,会实现了两个vector table,分别是secure vector table和non-secure vector table。这两个vector table放置的基地址是IMPLEMENTATION DEFINED是否可配置的。
- PE支持每个vector table基地址是可配置的,那么会有两个vector table offset registers:VTOR_S和VTOR_NS;
- PE不支持任意一个向量表基地址是可配置的,那么VTOR_S和VTOR_NS是WI属性;
如果PE支持每个vector table基地址可配置:
- 针对secure状态的exception使用VTOR_S来确定secure vector table的基地址;
- 针对non-secure状态的exception使用VTOR_NS来确定non-secure vector table的基地址;
如果PE没有支持security extension,那么只有一个VTOS寄存器。
寄存器
Armv8-M支持以下类型的寄存器:

GPR寄存器的R13也是Stack pointer,R14也是Link Register,R15也是Program Counter。Mask Registers主要用于修改exception的优先级。Control寄存器用于控制PE的一些feature是否打开或切换。SP limit register主要是保护堆栈,防止溢出。XPSR存放程序执行的状态。VPR和FPSCR用于FP或MVE下,控制predication和feature,以及提供程序状态。
Arm architecture需要Context synchronization event(CSE, 上下文同步事件)来保证对architecture中描述的任何memory-mapped registers的任何修改的可见性。当发生CSE事件时,在CSE事件之后的指令可以看到CSE之前已经完成的对memory-mapped registers的写操作(不管之后的指令是想direct read还是indirect read,都是可以看见的)。
以下这些事件会产生CSE:
- 执行ISB操作。在执行ISB指令且通过condition code检查;
- 处理exception,包括tail-chaining;
- 从exception返回;
- 进入Debug状态;
- 退出Debug状态;
通过interstating branches和返回的的security状态转换不是CSE事件。
Lockup
Lockup是一种PE状态,这种状态出现的场景是:当PE停止执行指令来响应错误时,但由于当前的execution priority无法将错误升级到恰当的HardFault handler。比如,在处理secure HardFault过程中,又出现了synchronous exception要升级为secure HardFault的场景,但此时由于secure fault已经active了,因此无法升级为secure HardFault了,这时就发生lockup了。
Arm建议在实现中提供一个LOCKUP信号,当PE处于lockup状态时,向外部系统发出PE处于lockup的信号。
当PE处于lockup时:
- 读取DHCSR.S_LOCKUP的值为1;
- 读取PC的值为0xEFFFFFFE,这是一个XN地址;
- PE停止取值和执行指令了;
- 如果实现有LOCKUP信号,那么LOCKUP信号会置起来;
Asynchronous busfault不会导致lockup。
退出lockup只能通过以下几种方式之一:
- Cold reset
- Warm reset
- 进入debug状态
- 通过NMI exception来抢占
不过Arm认为进入lockup状态是一种致命的情况了,建议直接reset了。
Coprocessor support
Arm architecture支持0-16个协处理器,CP0到CP15。如果CDE不使用CP0到CP7,则CP0到CP7是IMPLEMENTATION DEFINED的。CP0到CP7是否可以在secure和non-secure状态下使用,或者协处理器是否只能在secure和non-secure状态下启用,这是由IMPLEMENTATION定义的。
Arm预留了CP8到CP15,CP10到CP11被保留以支持Floating-point Extension,CP10控制CP11浮点指令。
发给未实现或禁用的协处理器的指令会导致NOCP UsageFault。如果协处理器不能完成一条指令,则生成UNDEFINSTR UsageFault。
Custom Datapath Extension
CDE在协处理器指令空间中引入了两条指令的三种类型:
- 三种类型对GPR寄存器进行操作,包括condition code标记APSR_nzcv
- 三种类型仅对floating-point或SIMD寄存器进行操作
在floating-point或SIMD寄存器上操作的Custom Datapath instruction使用以下方法之一:
- 32-bit S 寄存器;
- 64-bit D寄存器
- 128-bit Q寄存器
CDE指令的三种类型由以下模式定义:
- <operation code> <destination register>.
- <operation code> <destination register>, <source register>.
- <operation code> <destination register>, <source register 1>, <source register 2>.
Custom Datapath instruction的目的寄存器可选读取,也可选写入。
Custom Datapath instructions可以在现有的协处理器编码和编号空间中找到,并与之关联。Custom Datapath instructions属于与协处理器编号相关的编码空间,范围从0到7。
启用实现了Custom Datapath Extension的协处理器空间与其它IMPLEMENTATION DEFINED协处理器相同,都是使用IsCPEnabled()。
如果协处理器与Custom Datapath Extension相关联,那么该协处理器不能执行以下指令。
- CDP, CDP2.
- LDC, LDC2 (immediate).
- LDC, LDC2 (literal).
- MCR, MCR2.
- MCRR, MCRR2.
- MRC, MRC2.
- MRRC, MRRC2.
- STC, STC2.
当执行CDE指令时,PE检查与CDE相关的协处理器是否启用。如果需要访问另一个协处理器,例如floating-point extension或MVE,则执行第二次协处理器检查。
CDE指令如下:
- VCX1, VCX1 (vector).
- VCX2, VCX2 (vector).
- VCX3 , VCX3 (vector).
PACBTI
PAC和BTI特性可以针对每个security状态和privilege级别独立启用或禁用。
PAC
Pointer Authentication Code。由加密算法生成的哈希值。PAC也指用于生成哈希的过程。对于pointer authentication,有四个128-bit keys,如下图:

在下列情况下,特权软件可以通过MSR和MRS指令访问密钥:
- 将寄存器值PAC_KEY_P_3: PAC_KEY_P_2: PAC_KEY_P_1: PAC_KEY_P_0连接成PAC_KEY_P。
- 将寄存器值PAC_KEY_U_3: PAC_KEY_U_2: PAC_KEY_U_1: PAC_KEY_U_0连接成PAC_KEY_U
对于pointer authentication指令,是否使用以下方法生成加密值是IMPLEMENTATION DEFINED:
- QARMA5
- IMPLEMENTATION DEFINED algorithm
- QARMA3
Arm建议应该使用QARMA系列算法进行pointer authentication。
PAC由PACBTI、PAC和PACG指令生成。
如果PAC authentication指令验证PAC失败,则会生成INVSTATE UsageFault。如果在验证PAC之前,用于产生PAC的任何输入参数(三个通用寄存器的任何一个或加密密钥)被修改,那么会产生fault。指令AUT、BXAUT和AUTG用于验证PAC。
加密算法的输出是32位PAC,因此输入参数和加密密钥的不同组合可能产生相同的PAC。PAC authenticatio指令无法检测到这种类型的加密冲突,并且不会生成INVSTATE UsageFault。
有了PAC之后,在有Branch发生时,刚进入Branch,PE会将LR+硬件密钥+SP一起算出一个合法签名PAC放到R12中,R12和LR会一起压到栈中。在Branch返回时,会将R12和LR一起返回校验。由于其他人无法知道密钥,因此R12的值是不可能被篡改的,这样就保护了LR值。这是一套硬件、编译器以及架构强制绑定的流程。
BTI
Branch Target Identification。用于创建和识别有效分支branch landing pads的机制。它用于保护跳转的地址是正确的。EPSR.B在exception entry时会自动stack和清0,并在exception return中恢复。
当BTI启用时,则任何指令可以设置EPSR.B或LO_BRANCH_INFO.BTI为1的称为BTI setting指令。比如BLX、BLXNS、BX、BXNS、LDR_PC等。
任何指令可以设置EPSR.B为0的称为BTI clearing指令。BTI, SG, PACBTI是BTI clearing指令。如果EPSR.B为1,BIT clearing指令将会把它清0,甚至BTI没有enable也会。
当EPSR.B为1,则下一条执行的指令必须是BTI clearing指令或BKPT指令,否则会产生INVSTATE UsageFault。如果下一条指令是BTI clearing指令,那么EPSR.B会被清0。但如果下一条是BKPT指令,EPSR.B不会被清0,而且会保持原来的值且不会产生INVSTATE UsageFault。