在嵌入式系统开发领域,ARM Cortex-A9作为一款经典的中高端处理器内核,广泛应用于工业控制、汽车电子和消费电子等领域。这款处理器虽然性能优异,但在实际使用中仍存在一些微架构层面的设计限制,我们称之为"Errata"(勘误)。这些错误往往与处理器的流水线设计、缓存一致性机制以及内存访问顺序等底层特性相关,理解这些错误对开发稳定可靠的嵌入式系统至关重要。
Cortex-A9的勘误主要分为三类:Category A(必须修复)、Category B(建议修复)和Category C(轻微影响)。其中Category B类错误最为典型,它们通常不会导致处理器完全无法工作,但在特定条件下可能引发死锁、数据一致性问题等严重故障。这类错误往往与多核协同(SMP)、缓存维护操作以及内存屏障指令的使用密切相关。
在Cortex-A9处理器中,当满足以下条件时可能出现死锁:
这种特定序列会导致内部数据侧排空请求信号保持粘滞状态,ISB指令会等待数据侧排空,而排空又需要ISB完成,从而形成死锁循环。
ARM官方推荐的解决方案是在异常处理程序开头添加DSB指令。具体实现如下:
assembly复制abort_handler:
DSB ; 添加内存屏障
; 正常的异常处理代码
MOVS pc, lr
提示:这个解决方案适用于所有r2p2到r3p0版本的Cortex-A9处理器,从r4p0开始该问题已被修复。
这种错误常见于以下场景:
在Linux内核中,类似的屏障操作常见于arch/arm/mm目录下的缓存维护代码中,特别是在处理页表更新的场景。
当处理器执行以下操作序列时可能发生死锁:
在特定时序条件下,这会导致处理器死锁。该问题影响所有r1-r3版本的Cortex-A9。
推荐在每次进入加载独占/存储独占循环前插入DMB指令:
assembly复制lock_acquire:
DMB ; 内存屏障确保之前写操作完成
LDREX r1, [r0]
CMP r1, #0
STREXEQ r1, r2, [r0]
CMPEQ r1, #0
BNE lock_acquire
虽然DMB指令会引入少量性能开销(通常约10-20个时钟周期),但在访问强有序内存区域时必不可少。在Linux内核的自旋锁实现(如arch/arm/include/asm/spinlock.h)中,我们可以看到类似的内存屏障使用模式。
在多核Cortex-A9系统(SMP模式)中,当一个处理器持续执行包含DMB指令的短循环时,可能阻止另一个处理器广播的CP15操作完成,导致服务拒绝。
危险循环的特征:
有三种可行的解决方案:
assembly复制MRC p15,0,r0,c15,c0,1
ORR r0,r0,#0x10
MCR p15,0,r0,c15,c0,1
assembly复制wait_loop:
DMB
LDR r1, [r2] ; 插入非条件加载
B wait_loop
这种问题常见于:
在RT-Thread等实时操作系统中,类似的解决方案被用于解决多核同步问题。
当满足以下条件时可能出现系统级死锁:
虽然这种情况较为罕见,但在高负载多核系统中仍可能发生。
设置诊断控制寄存器的bit[21]:
assembly复制MRC p15,0,r0,c15,c0,1
ORR r0,r0,#0x200000
MCR p15,0,r0,c15,c0,1
此解决方案会禁用总线接口单元中的"直接驱逐"优化,可能导致全缓存行写入时的带宽略有下降(约5-10%性能影响)。
当更新缓存性转换表项时,新旧条目可能暂时对页表遍历不可见,导致意外的转换错误。这种情况通常发生在操作系统改变物理页映射时。
推荐两种解决方案:
assembly复制; 假设r0包含转换表项地址
MCR p15,0,r0,c7,c14,1 ; 清理并无效化缓存行
CPSID i ; 禁用中断
STR r1, [r0] ; 更新表项
DSB
ISB
CPSIE i ; 重新启用中断
在Linux内核的页表管理代码(如arch/arm/mm/pgd.c)中,ARM架构相关部分就包含了类似的缓存维护操作序列。
当MMU和分支预测都启用时执行分支指令,然后禁用MMU但保持分支预测启用,可能导致推测取指违反ARM架构规则。
在禁用MMU前执行BPIALL操作:
assembly复制MCR p15,0,r0,c7,c5,6 ; BPIALL
DSB
; 现在可以安全禁用MMU
对不可缓存、可共享的正常内存区域执行写操作时,在特定条件下可能重复执行写请求,导致同步问题。
在关键同步操作后添加DMB:
assembly复制STR r0, [r1] ; 清除通信变量
DMB ; 确保写操作完成
另一种方法是确保通信变量独占64位对齐的内存区域:
assembly复制ALIGN 8
comm_var DCD 0
unused_var DCD 0
ISB指令会被错误地计入性能监控事件0x0C(软件PC改变)和0x0D(立即分支)。
使用事件0x90单独计数ISB,然后从0x0C和0x0D的结果中减去ISB计数。
调试APB接口上地址0xD18和0xD1C的主ID寄存器别名未实现,读取返回0。
始终使用原始地址0xD00访问MIDR寄存器。
在基于Cortex-A9的嵌入式系统开发中,处理这些勘误时需要注意以下几点:
屏障指令使用原则:
多核编程建议:
性能权衡:
调试技巧:
在Linux内核的ARM架构相关代码中(如arch/arm/include/asm/),我们可以找到许多处理这些勘误的实际例子,这些代码经过长期验证,值得参考借鉴。