2. 多核硬件平台与BSP适配

AMP(Asymmetric Multiprocessing,非对称多处理)与SMP最大的区别在于:每个CPU核运行独立的操作系统镜像或裸机程序,核与核之间没有统一调度器,因此“谁在哪个核上跑、从哪里启动、占用哪些外设”必须在BSP和硬件层面显式约定。本节围绕目标硬件选型、多核启动地址配置、BSP核间资源分配、串口与调试通道规划、硬件初始化顺序五个方面展开,目标是让读者能够针对一块具体板卡,规划出一套可落地的AMP启动与资源划分方案。

一、目标硬件选型:从核数、内存映射与启动方式入手

AMP平台选型不能只看“核多不多”,而要重点考察三件事:核间是否对称、每个核能否独立复位与启动、外设与内存能否被静态划分。常见候选平台如下:

平台典型核结构AMP适配要点
Xilinx Zynq-7000 / Zynq UltraScale+双核Cortex-A9 或 4×A53 + 2×R5CPU0由FSBL引导,CPU1可通过SEV指令或复位向量独立启动;R5核适合跑硬实时任务,A53跑Linux或VxWorks
NXP T4240(QorIQ)12×e6500双线程核核间通过CoreNet fabric互联,支持每核独立复位;需配置LAW窗口与DDR分区,避免多核争抢同一内存段
NXP i.MX8(如i.MX8QM)4×A53 + 2×A72 + M4F异构明显,A核与M4F通过MU(Messaging Unit)通信;M4F可跑VxWorks或裸机,A核跑Linux

选型时建议优先确认:目标核是否支持独立复位向量、是否有专用TCM或紧耦合内存、核间中断控制器(如Zynq的IPI、i.MX8的MU)是否可用。若这些条件不满足,AMP方案会退化为“伪AMP”,核间同步将非常脆弱。

二、多核启动地址配置:谁先跑、谁等谁

AMP启动的核心是“主核引导从核”。以Zynq-7000为例,上电后CPU0从BootROM开始执行FSBL,CPU1默认处于等待事件(WFE)状态。要让CPU1运行VxWorks镜像,需在CPU0的BSP中完成以下动作:

/* 示例:CPU0 唤醒 CPU1 并跳转到指定入口 */
#define CPU1_ENTRY_ADDR  0x00100000   /* CPU1镜像加载地址 */
#define CPU1_STACK_ADDR  0x001F0000

/* 1. 将CPU1镜像从Flash拷贝到DDR */
memcpy((void*)CPU1_ENTRY_ADDR, (void*)FLASH_CPU1_OFFSET, CPU1_IMAGE_SIZE);

/* 2. 写入CPU1入口地址到复位向量寄存器 */
Xil_Out32(CPU1_RESET_VECTOR, CPU1_ENTRY_ADDR);

/* 3. 通过SEV指令唤醒CPU1 */
__asm__ volatile("sev");

/* 4. CPU1启动后自行初始化MMU、Cache并进入VxWorks */

关键参数是入口地址与栈地址。入口地址必须与链接脚本中CPU1镜像的起始地址一致,栈地址要避开CPU0已使用的内存区。T4240上则通过RCW(Reset Configuration Word)和核复位控制寄存器实现类似功能,每个核有独立的复位向量偏移。i.MX8的M4F核通常由A核通过SCU(System Controller Unit)释放复位并设置入口。

注意:从核启动前,主核必须确保从核镜像已完整写入DDR,并执行Cache刷新,否则从核可能取到旧数据。建议在唤醒前调用cacheFlush()。

三、BSP中核间资源分配:内存、外设与中断

AMP下资源必须静态划分,不能依赖操作系统动态申请。典型划分包括:

  • 内存分区:CPU0使用0x00000000–0x0FFFFFFF,CPU1使用0x10000000–0x1FFFFFFF,中间留4KB共享内存用于核间通信。
  • 外设归属:串口0归CPU0调试用,串口1归CPU1;以太网控制器按核分配,避免同一寄存器被两个核同时写。
  • 中断路由:GIC中每个SPI中断需配置目标核,PPI中断默认归属本核。核间中断(IPI)用于通知对方“数据已就绪”。

在BSP的config.h或hwconf.c中,通常以宏定义方式固化这些划分,例如:

#define CPU0_MEM_BASE   0x00000000
#define CPU0_MEM_SIZE   0x10000000
#define CPU1_MEM_BASE   0x10000000
#define CPU1_MEM_SIZE   0x10000000
#define SHM_BASE        0x0FFFF000
#define SHM_SIZE        0x00001000
#define UART0_BASE      0xE0000000   /* CPU0调试串口 */
#define UART1_BASE      0xE0001000   /* CPU1调试串口 */

四、串口与调试通道规划

AMP调试最怕“两个核抢一个串口”。规划原则是:每个核至少独占一个UART,主核串口用于打印启动日志和异常,从核串口用于输出自身任务状态。若板卡UART数量不足,可用以下替代方案:

  • 通过共享内存+IPI实现“从核日志转发到主核串口”,但会增加主核负担。
  • 使用JTAG分别连接各核,通过调试器查看从核PC和寄存器。
  • 在从核代码中预留一个内存日志缓冲区,主核定期读取并打印。

调试通道还应包括:核间心跳计数器(放在共享内存中,双方定期递增)、看门狗喂狗分工、以及从核异常时的复位策略。建议在共享内存头部定义固定结构体,便于双方解析。

五、硬件初始化顺序

AMP启动的初始化顺序必须严格分层,否则会出现“从核已跑但时钟未配”的随机故障。推荐顺序如下:

  1. 主核完成时钟、PLL、DDR控制器初始化,确保内存可用。
  2. 主核配置MMU和Cache,建立自身地址映射。
  3. 主核初始化共享内存区域,清零并建立通信结构。
  4. 主核加载从核镜像到DDR,刷新Cache。
  5. 主核配置从核的复位向量和栈指针,释放从核复位。
  6. 从核启动后先初始化自身MMU、Cache,再初始化专属外设。
  7. 双方通过IPI或共享内存标志位完成握手,进入各自主循环。

下面用一张信号链/时序图概括上述启动与资源划分关系:

AMP多核启动与资源划分信号链 CPU0(主核 / VxWorks) ① 时钟/PLL/DDR 初始化 ② MMU/Cache 建立自身映射 ③ 初始化共享内存 + 通信结构 ④ 加载CPU1镜像到DDR并刷Cache ⑤ 写CPU1复位向量 + SEV唤醒 ⑥ 等待CPU1握手标志 ⑦ 进入主循环 / 调试串口0 CPU1(从核 / VxWorks) 等待事件(WFE) 收到SEV,从入口地址取指 初始化自身MMU/Cache 初始化专属外设(UART1等) 写握手标志 → 进入主循环 SEV唤醒 握手标志 共享内存 0x0FFFF000 心跳 / 标志 / 数据 IPI通知

图中左侧为主核CPU0的初始化序列,右侧为从核CPU1的启动序列,中间紫色块为共享内存与IPI通道。可以看到,主核在完成DDR和共享内存初始化后才唤醒从核,从核启动后先建立自身运行环境再写握手标志,双方通过共享内存和IPI完成同步。实际项目中,这套顺序和地址划分应固化在BSP配置文件中,并在板级初始化阶段通过打印日志逐条确认,避免“从核跑飞”后难以定位。