1. SMP系统开发基础与硬件知识解析
在嵌入式系统开发中,特别是基于ARM架构的多核处理器(SMP)系统开发,深入理解底层硬件机制至关重要。本文将结合RK3288平台的实际案例,详细解析SMP系统开发所需的核心硬件知识。
1.1 CP15协处理器与系统控制
CP15是ARM架构中的系统控制协处理器,它提供了对处理器核心功能的精细控制。通过CP15,开发者可以访问和修改多种系统寄存器,实现对MMU、缓存、分支预测等关键功能的配置。
1.1.1 基础控制寄存器操作
以下是几个最常用的CP15寄存器操作示例:
c复制// 读取CP15寄存器c1的值(系统控制寄存器SCTLR)
uint32_t read_p15_c1(void) {
uint32_t value;
__asm__ __volatile__(
"mrc p15, 0, %0, c1, c0, 0"
: "=r" (value)
:
: "memory");
return value;
}
// 写入CP15寄存器c1
void write_p15_c1(uint32_t value) {
__asm__ __volatile__(
"mcr p15, 0, %0, c1, c0, 0"
:
: "r" (value)
: "memory");
}
// 启用MMU
write_p15_c1(read_p15_c1() | (1 << 0));
// 禁用数据缓存
write_p15_c1(read_p15_c1() & ~(1 << 2));
在实际开发中,这些操作通常封装在更高级的API中,但在调试和性能优化时,直接操作这些寄存器可以提供更大的灵活性。
1.1.2 处理器ID读取
在多核系统中,识别当前运行的CPU核心是基础操作。通过CP15可以获取处理器ID:
c复制uint32_t smp_processor_id(void) {
uint32_t tmp;
__asm__ __volatile__(
"mrc p15,0,%0,c0,c0,5\n"
"and %0,%0,#0x3\n"
: "=r" (tmp)
:
: "memory");
return tmp;
}
这个函数返回0到3的值,对应四核处理器中的各个核心。在SMP系统中,这个信息对于核心间的任务分配和同步至关重要。
1.2 SCTLR寄存器详解
SCTLR(System Control Register)是ARMv7架构中最重要的系统控制寄存器,它控制着处理器的核心行为。以下是SCTLR各关键位的功能解析:
| 位 | 名称 | 功能描述 |
|---|---|---|
| 0 | M | MMU使能位。1=启用MMU,0=禁用MMU |
| 2 | C | 数据缓存使能位。1=启用数据缓存,0=禁用数据缓存 |
| 12 | I | 指令缓存使能位。1=启用指令缓存,0=禁用指令缓存 |
| 19 | WXN | 写执行从不。1=使能写保护,0=禁用写保护 |
| 22 | U | 非对齐访问控制。1=启用非对齐访问,0=禁用非对齐访问 |
| 25 | EE | 异常字节序。1=大端模式,0=小端模式 |
通过读取SCTLR寄存器,可以全面了解当前系统的配置状态:
c复制uint32_t sctlr;
__asm__ __volatile__(
"mrc p15, 0, %0, c1, c0, 0\n"
: "=r" (sctlr)
:
: "memory");
printf("MMU状态: %s\n", (sctlr & (1 << 0)) ? "启用" : "禁用");
printf("数据缓存: %s\n", (sctlr & (1 << 2)) ? "启用" : "禁用");
printf("指令缓存: %s\n", (sctlr & (1 << 12)) ? "启用" : "禁用");
1.3 CPSR寄存器与处理器模式
CPSR(Current Program Status Register)包含了当前处理器的状态信息,包括条件标志、中断禁用位和当前处理器模式等。理解CPSR对于系统级编程和调试至关重要。
CPSR的关键位包括:
- N(bit31):负数标志
- Z(bit30):零标志
- C(bit29):进位标志
- V(bit28):溢出标志
- I(bit7):IRQ中断禁用
- F(bit6):FIQ中断禁用
- M[4:0]:处理器模式控制
处理器模式决定了哪些寄存器可用以及处理器的特权级别。常见的处理器模式包括:
| 模式编码 | 模式名称 | 描述 |
|---|---|---|
| 10000 | User | 用户模式,非特权模式 |
| 10011 | Supervisor(SVC) | 内核模式,操作系统通常运行在此模式 |
| 10111 | Abort(ABT) | 数据或指令预取异常时进入 |
| 11011 | Undef(UND) | 遇到未定义指令时进入 |
| 11111 | System(SYS) | 与用户模式共享寄存器,但有特权 |
快速修改中断使能位的汇编指令:
assembly复制CPSID I @ 禁用IRQ中断
CPSIE I @ 启用IRQ中断
注意:在修改CPSR寄存器时需要特别注意,不当的操作可能导致系统不稳定。特别是在修改处理器模式时,必须确保相应的栈指针和链接寄存器已正确设置。
2. SMP系统关键外设与资源管理
2.1 电源管理单元(PMU)
在RK3288平台上,PMU(Power Management Unit)负责处理系统的电源状态转换。当从PSCI模式切换到spin-table模式时,PMU配置需要特别注意。
典型的RK3288 PMU配置如下:
dts复制rockchip-suspend {
compatible = "rockchip,pm-rk3288";
status = "okay";
rockchip,sleep-mode-config = <
(0
|RKPM_CTR_PWR_DMNS
|RKPM_CTR_GTCLKS
|RKPM_CTR_PLLS
|RKPM_CTR_ARMOFF_LPMD
|RKPM_CTR_SYSCLK_OSC_DIS
)
>;
rockchip,wakeup-config = <
(0
| RKPM_GPIO_WKUP_EN
)
>;
};
关键PMU寄存器:
- 0x0090:配置低功耗模式是否触发idle请求
- 0x00a0:控制核心电源域状态
实操经验:在spin-table模式下,rockchip_suspend节点可能导致系统卡在default_idle_call中。这是因为spin-table不使用SMC指令,而rockchip_suspend的实现可能假设了PSCI/SMC的存在。在这种情况下,最简单的解决方案是暂时禁用rockchip_suspend节点。
2.2 通用寄存器文件(GRF)
GRF(General Register Files)是Rockchip处理器中的一组多功能寄存器,用于配置各种芯片级功能。在RK3288上,GRF寄存器包括:
- 系统控制寄存器:控制启动行为、时钟源选择等
- IO复用配置寄存器:设置各个引脚的功能
- 电源管理相关寄存器:控制各个电源域的行为
GRF寄存器的访问通常通过内存映射IO实现,地址范围在0xff770000附近。在设备树中,GRF节点通常定义为:
dts复制grf: syscon@ff770000 {
compatible = "rockchip,rk3288-grf", "syscon";
reg = <0xff770000 0x1000>;
};
2.3 定时器系统
RK3288平台上有三类定时器需要关注:
-
ARM架构定时器:arm,armv7-timer
- 每个核心都有自己的私有定时器
- 用于系统调度和性能计数
-
Alive Timer(保活定时器):timer6和timer7
- 在低功耗状态下仍能运行
- 用于唤醒计时和看门狗功能
-
CPU Timer:timer0-5
- 通用的定时器资源
- 可用于各种定时需求
在SMP系统中,定时器的同步尤为重要。特别是当使用spin-table启动从核时,需要确保从核的定时器与主核同步初始化。
2.4 SRAM管理
SRAM(静态随机存取存储器)在SMP启动过程中扮演着关键角色。spin-table机制依赖于共享的SRAM区域来实现主核与从核之间的通信。
典型的SRAM配置如下:
dts复制smp-sram@ff700000 {
compatible = "rockchip,rk3066-smp-sram";
reg = <0x0 0xff700000 0x0 0x8000>; // 32KB
};
spin-table工作原理:
- 主核(CPU0)将启动地址写入SRAM中的特定位置
- 主核通过发送事件信号唤醒从核(CPU1-CPU3)
- 从核上电后,首先读取SRAM中的启动地址
- 从核跳转到指定地址开始执行
注意事项:SRAM区域必须在设备树中正确声明,并且确保不被其他驱动或应用占用。在内存紧张的系统中,需要仔细规划SRAM的使用。
2.5 通用中断控制器(GIC)
GIC(Generic Interrupt Controller)是ARM架构中用于管理中断的组件。在多核系统中,GIC负责将中断分发到各个处理器核心。
GIC的关键功能包括:
- 中断优先级管理
- 处理器核心间中断(IPI)支持
- 中断屏蔽和状态查��
GIC寄存器分为两部分:
- Distributor(GICD):全局中断分发控制
- CPU Interface(GICC):每个核心独有的接口
在设备树中,GIC通常配置为:
dts复制gic: interrupt-controller@ffc01000 {
compatible = "arm,cortex-a15-gic";
#interrupt-cells = <3>;
interrupt-controller;
reg = <0xffc01000 0x1000>,
<0xffc02000 0x2000>;
};
GICD_ITARGETSR寄存器用于配置中断的目标处理器。在多核系统中,可以灵活地将不同中断分配到不同核心,实现负载均衡。
3. SMP启动协议比较与实现
3.1 PSCI启动协议
PSCI(Power State Coordination Interface)是ARM定义的电源管理标准接口,它提供了一组标准化的函数用于核心的电源管理,包括:
- CPU_ON:启动指定的从核
- CPU_OFF:关闭当前核心
- CPU_SUSPEND:进入低功耗状态
- AFFINITY_INFO:查询核心状态
PSCI的实现通常依赖于安全监控模式(Secure Monitor),通过SMC(Secure Monitor Call)指令触发。在RK3288平台上,PSCI的设备树配置如下:
dts复制psci {
compatible = "arm,psci-1.0";
method = "smc";
};
&cpu0 {
enable-method = "psci";
};
PSCI的优点包括:
- 标准化接口,便于移植
- 支持安全和非安全状态切换
- 提供完整的电源管理功能集
然而,PSCI依赖于BL31等安全监控代码,在不支持或无法使用安全启动的环境中可能无法工作。
3.2 spin-table启动协议
spin-table是一种较为简单的SMP启动方法,它不依赖于安全监控模式,而是通过共享内存区域实现核心间的通信。
spin-table的工作原理:
- 主核将要执行的入口地址写入共享内存(通常为SRAM)
- 主核执行内存屏障确保写入完成
- 主核发送事件信号唤醒从核
- 从核上电后,轮询共享内存中的地址
- 一旦发现有效地址,从核跳转到该地址执行
在RK3288平台上启用spin-table需要以下配置:
- 内核配置:
makefile复制CONFIG_SMP=y
CONFIG_HAVE_ARM_SCU=y
CONFIG_ARM_GIC=y
CONFIG_MFD_SYSCON=y
# CONFIG_ARM_PSCI is not set
- 设备树配置:
dts复制smp-sram@ff700000 {
compatible = "rockchip,rk3066-smp-sram";
reg = <0x0 0xff700000 0x0 0x8000>;
};
&cpu0 {
enable-method = "spin-table";
cpu-release-addr = <0xff700000>;
};
spin-table的优点:
- 不依赖安全监控模式
- 实现简单,调试方便
- 适用于没有安全启动环境的系统
spin-table的局限性:
- 缺乏标准化的电源管理接口
- 需要预留共享内存区域
- 功能相对PSCI较为有限
3.3 从PSCI迁移到spin-table的实践经验
在实际项目中,由于Rockchip的miniloader、optee等组件不开源,我们不得不从PSCI迁移到spin-table模式。以下是关键的迁移步骤和经验:
-
修改uboot配置:
- 使用uboot2017版本的SPL+TPL启动
- 关闭optee和trust相关功能
-
内核配置变更:
- 禁用CONFIG_ARM_PSCI
- 确保CONFIG_SMP和相关依赖启用
-
设备树调整:
- 移除所有psci节点
- 添加smp-sram节点
- 将cpu节点的enable-method改为"spin-table"
-
电源管理适配:
- 禁用rockchip_suspend节点或修改其实现
- 检查所有低功耗相关代码路径
-
测试验证:
- 确认所有核心都能正常启动
- 验证核心间中断(IPI)功能
- 测试系统在负载下的稳定性
调试技巧:在spin-table调试过程中,可以通过在共享内存区域附近添加调试打印来跟踪启动流程。例如,在uboot和内核中都添加对cpu-release-addr地址的读写日志,可以帮助定位问题。
迁移后的系统启动日志应显示所有核心都通过spin-table启动成功:
code复制CPU1: Booted secondary processor [410fd034]
CPU2: Booted secondary processor [410fd034]
CPU3: Booted secondary processor [410fd034]
4. SMP系统开发中的常见问题与解决方案
4.1 核心启动失败问题排查
当从核无法正常启动时,可以按照以下步骤排查:
-
检查共享内存配置:
- 确认设备树中的smp-sram节点地址与uboot一致
- 验证该内存区域未被其他驱动占用
-
检查启动地址写入:
- 在uboot中确认正确写入了启动地址
- 使用内存查看命令检查共享内存区域内容
-
验证事件信号:
- 使用逻辑分析仪或示波器检查从核唤醒信号
- 确认电源管理单元配置正确
-
检查从核初始化代码:
- 确认从核的初始执行环境正确设置
- 验证从核的MMU和缓存配置
4.2 核心间同步问题
在多核系统中,核心间的同步问题可能导致随机崩溃或数据损坏。常见的解决方案包括:
-
正确使用内存屏障:
c复制// 写入共享数据前 dmb(); // 数据内存屏障 // 读取共享数据后 dmb(); // 确保读取顺序 -
使用原子操作:
c复制static atomic_t shared_counter = ATOMIC_INIT(0); void increment_counter(void) { atomic_inc(&shared_counter); } -
合理使用自旋锁:
c复制static DEFINE_SPINLOCK(shared_lock); void access_shared_resource(void) { unsigned long flags; spin_lock_irqsave(&shared_lock, flags); // 访问共享资源 spin_unlock_irqrestore(&shared_lock, flags); }
4.3 中断分配与负载均衡
在SMP系统中,合理分配中断对于性能至关重要。可以通过以下方式优化:
-
设置中断亲和性:
c复制
irq_set_affinity(irq, cpumask_of(cpu)); -
监控中断分布:
bash复制cat /proc/interrupts -
使用irqbalance服务自动调整中断分配
4.4 低功耗管理适配
从PSCI切换到spin-table后,低功耗管理需要特别注意:
-
CPU空闲状态管理:
- 实现平台特定的cpu_ops结构体
- 提供正确的进入/退出空闲状态方法
-
系统挂起/恢复:
- 修改挂起流程,避免依赖SMC指令
- 测试所有低功耗状态下的唤醒功能
-
电源域控制:
- 确保从核下电/上电序列正确
- 验证电压/频率调节功能
4.5 性能优化技巧
-
缓存一致性优化:
- 合理使用__cache_aligned属性
- 避免错误的缓存共享
-
核心间通信优化:
- 使用IPI(处理器间中断)代替轮询
- 优化共享数据结构布局
-
任务调度调优:
- 设置正确的CPU亲和性
- 利用SMP负载均衡特性
性能分析工具推荐:
- perf:系统级性能分析
- LTTng:低开销的系统跟踪
- ARM DS-5:针对ARM架构的深度分析
5. 深入spin-table实现原理
5.1 spin-table的软件实现架构
spin-table启动协议的软件实现涉及多个层次的协作:
-
Bootloader层(通常是U-Boot):
- 初始化共享内存区域
- 设置从核的启动地址
- 发送事件信号唤醒从核
-
内核启动层:
- 提供CPU操作函数集(cpu_ops)
- 实现核心启动和停止的底层函数
- 处理核心间的同步
-
设备树描述:
- 定义共享内存区域
- 指定启动方法为spin-table
- 配置各个CPU节点的属性
典型的spin-table相关数据结构:
c复制struct cpu_operations {
const char *name;
int (*cpu_init)(struct device_node *dn, unsigned int cpu);
int (*cpu_prepare)(unsigned int cpu);
int (*cpu_boot)(unsigned int cpu);
void (*cpu_postboot)(void);
};
5.2 从核启动的详细流程
从核通过spin-table启动的详细步骤:
-
硬件复位:
- 从核上电或复位后,处于等待状态
- 程序计数器(PC)指向特定的启动地址
-
地址轮询阶段:
- 从核不断检查共享内存中的启动地址
- 使用WFE(Wait For Event)指令降低功耗
-
地址验证:
- 一旦发现非零地址,验证其有效性
- 执行必要的缓存和内存屏障操作
-
上下文准备:
- 设置初始栈指针
- 初始化核心特定的寄存器
-
跳转执行:
- 将PC设置为指定的启动地址
- 开始执行内核指定的启动代码
5.3 主核与从核的协作机制
主核和从核通过以下机制协同工作:
-
共享内存通信:
- cpu-release-addr:从核的启动地址
- status字段:从核的状态反馈
-
事件信号:
- 主核使用SEV(Send Event)指令唤醒从核
- 从核使用WFE(Wait For Event)等待信号
-
内存一致性:
- 使用数据同步屏障(DSB)确保写入可见性
- 使用指令同步屏障(ISB)确保指令流正确
5.4 设备树配置详解
spin-table模式需要正确的设备树配置。以下是关键节点的详细说明:
- smp-sram节点:
dts复制smp-sram@ff700000 {
compatible = "rockchip,rk3066-smp-sram";
reg = <0x0 0xff700000 0x0 0x8000>;
// 关键属性:
// - compatible:匹配平台特定的SRAM驱动
// - reg:SRAM的物理地址和大小
};
- CPU节点配置:
dts复制&cpu0 {
enable-method = "spin-table";
cpu-release-addr = <0xff700000>;
// 关键属性:
// - enable-method:指定启动方法
// - cpu-release-addr:共享内存地址
};
- 确保没有冲突的配置:
- 删除所有psci相关节点
- 检查是否有其他驱动占用SRAM区域
5.5 调试技巧与工具
调试spin-table启动问题可以使用以下方法:
-
硬件调试器:
- 使用JTAG调试器监控各个核心的状态
- 设置硬件断点观察关键内存区域
-
调试打印:
- 在关键路径添加early_printk输出
- 使用console=ttyS0,115200 earlycon内核参数
-
内存检查:
- 使用md命令(U-Boot)或/dev/mem(Linux)检查共享内存
- 验证启动地址是否正确写入
-
信号分析:
- 使用逻辑分析仪检查核心唤醒信号
- 验证电源管理相关引脚的状态
调试示例:当从核无法启动时,可以检查以下方面:
- 共享内存地址是否正确写入(在U-Boot中使用md命令)
- 从核是否接收到唤醒事件(使用示波器检查信号)
- 从核的初始PC值是否正确(通过JTAG读取)
6. 从理论到实践:RK3288 SMP启动实战
6.1 硬件平台准备
RK3288是Rockchip推出的一款四核Cortex-A17处理器,广泛应用于各种嵌入式设备。在进行SMP开发前,需要准备:
- 开发板或目标设备
- 串口调试工具
- JTAG调试器(可选,用于深度调试)
- 必要的电源和线缆
6.2 软件开发环境搭建
-
工具链安装:
bash复制sudo apt-get install gcc-arm-linux-gnueabihf -
获取内核源码:
bash复制git clone https://github.com/rockchip-linux/kernel.git cd kernel git checkout -b my-smp-branch origin/develop-4.4 -
配置编译环境:
bash复制export ARCH=arm export CROSS_COMPILE=arm-linux-gnueabihf- make rockchip_defconfig
6.3 内核配置与编译
-
配置SMP支持:
bash复制
make menuconfig确保以下选项启用:
- CONFIG_SMP=y
- CONFIG_HAVE_ARM_SCU=y
- CONFIG_ARM_GIC=y
- CONFIG_MFD_SYSCON=y
禁用: - CONFIG_ARM_PSCI
-
编译内核:
bash复制
make -j4 zImage dtbs
6.4 设备树修改
针对spin-table模式,需要修改设备树:
-
添加SRAM节点:
dts复制smp-sram@ff700000 { compatible = "rockchip,rk3066-smp-sram"; reg = <0x0 0xff700000 0x0 0x8000>; }; -
修改CPU节点:
dts复制&cpu0 { enable-method = "spin-table"; cpu-release-addr = <0xff700000>; }; -
删除PSCI相关配置
6.5 U-Boot适配
U-Boot需要正确初始化spin-table:
-
确保以下配置启用:
makefile复制
CONFIG_ARMV7_LPAE=y CONFIG_OF_LIBFDT=y CONFIG_SYS_THUMB_BUILD=y -
实现平台特定的CPU启动代码:
c复制void secondary_cpu_reset(void) { // 设置启动地址 writel((uintptr_t)secondary_entry, CPU_RELEASE_ADDR); // 确保写入完成 __asm__ __volatile__("dsb" : : : "memory"); // 唤醒从核 __asm__ __volatile__("sev"); }
6.6 系统启动验证
-
通过串口观察启动日志:
code复制CPU1: Booted secondary processor [410fd034] CPU2: Booted secondary processor [410fd034] CPU3: Booted secondary processor [410fd034] -
验证所有核心在线:
bash复制cat /proc/cpuinfo -
测试SMP功能:
bash复制
stress -c 4
6.7 性能测试与优化
-
基准测试:
bash复制
sysbench cpu --threads=4 run -
中断分布检查:
bash复制cat /proc/interrupts -
调度统计:
bash复制cat /proc/schedstat
优化建议:根据测试结果调整中断亲和性、CPU频率调节策略和任务调度参数,以获得最佳性能。
7. 高级主题与扩展方向
7.1 异构多核系统考虑
RK3288采用同构四核设计,但在更复杂的系统中可能需要考虑:
-
big.LITTLE架构:
- 高性能核心与高能效核心协同工作
- 需要特定的调度策略
-
专用加速器:
- GPU、DSP等协处理器
- 统一的地址空间管理
-
实时核心:
- 混合关键性系统设计
- 隔离和保护机制
7.2 安全扩展与TrustZone
虽然本文讨论的是非安全启动模式,但在实际产品中可能需要考虑:
-
安全启动链:
- 从ROM到应用层的完整验证
- 防止未授权代码执行
-
TrustZone隔离:
- 安全世界与非安全世界的划分
- 安全监控模式的作用
-
加密加速:
- 硬件加密引擎的使用
- 密钥管理策略
7.3 实时性优化
对于实时应用,可以进一步优化:
-
中断延迟:
- 禁用内核抢占关键区域
- 优化中断处理路径
-
调度策略:
- 使用实时调度类
- 设置适当的优先级
-
内存锁定:
- 防止关键内存被换出
- 确保确定性访问时间
7.4 电源管理深度优化
在spin-table基础上实现更精细的电源管理:
-
核心休眠:
- 实现WFI(Wait For Interrupt)支持
- 动态电压频率调整
-
集群电源控制:
- 多核心协同下电
- 唤醒顺序优化
-
外设电源门控:
- 按需启用外设时钟
- 电源域动态管理
7.5 调试与性能分析进阶
-
跟踪调试:
- ETM(Embedded Trace Macrocell)使用
- 函数调用图分析
-
性能采样:
- 基于PMU的性能计数器
- 热点函数识别
-
系统可视化:
- 使用Trace Compass分析系统行为
- 多核执行时序图
在实际项目中,根据具体需求选择合适的扩展方向。spin-table作为基础的SMP启动方法,为系统提供了稳定的多核支持,而在此基础上可以构建更复杂的功能和优化。
