使用的是正点原子zynq开发板

Zynq配置AMP模式(cpu0跑linuxc+cpu1跑裸机)

在 AMP 运行环境下,必须要小心以防止两个 CPU 争夺这些共享资源,在 SoC 硬件系统当中,有一些
资源是每个 CPU 私有的,而有一些资源则是公用的;

CPU 私有资源如下所示:
1)L1 cache(一级缓存);
2)CPU 私有外设中断(PPI);
3)内存管理单元(MMU);
4)CPU 私有定时器。
CPU 共享资源如下所示:
1)中断控制分配器(ICD);
2)DDR 内存;
3)片内存储器(OCM);
4)全局定时器(Global Timer);
5)SUC 和 L2 cache(二级缓存)。
6)片上外设,例如 GPIO、SD、QSPI、UART、I2C、SPI(共享外设中断)、USB、CAN、SPI 等等。
私有资源可以不用管,但是对于双核公共资源来说,必须要小心处理,避免两个 CPU 同时使用这些资源的情况下导致混乱、数据错误,例如同时控制某一个 GPIO、同时使用串口等。
对于 ZYNQ 来说,当硬件上电启动之后,BootROM 是第一个运行的代码(BootROM 代码被固化在ZYNQ SoC 片内 RoM 存储器中),并且运行于 CPU0 上;BootROM 代码会从启动介质中读取 BotROM 头信息,BootROM 头信息就存储在 BOOT.BIN 文件中,BootROM 头信息记录了 bitstream、用户代码等数据存放在 BOOT.BIN 文件中的位置偏移量以及对应在内存中加载地址,根据这些信息来加载 PL 端 bitstream流文件以及在 PS 端启动用户代码。
BootROM 启动之后会使 CPU1 进入等待事件模式(WFE),没有对其启用任何功能,也就是说此时CPU1 处于一个等待指令的状态;而这个指令由 CPU0 给它,说白了就是必须由 CPU0 来启动 CPU1,解除它的等待指令状态,使其运行我们的用户代码。CPU0 在 CPU1 上启动应用程序需要少量协议,当 CPU1 接收到 CPU0 给到的 SEV 指令后,它会被唤醒,然后立即读取 0xFFFFFFF0 这个内存地址中存放的数据,并把这个读取到的数据作为应用程序的入口地址,接着跳转到该地址启动应用程序。所以由此可知,如果在执行 SEV 指令的时候,地址 0xFFFFFFF0 中存放的是一个无效的应用程序入口地址,则结果是不可预测的。

创建CPU1工程

点击 File–>New–>Application Project 新建应用工程。

zynq配置emmc_zynq配置emmc

注意在 Processor 栏选择“ps7_cortexa9_1”,点击 Next 按钮进入工程模板选择界面,同样这里我们也是创建一个空工程,首先双击 CPU1 工程的链接脚本文件 lscript.ld 打开它,修改 CPU1 应用程序内存空间的基地址和大小,这里笔者将基地址设置为 0x21000000。大小设置为 0x200000;

zynq配置emmc_内存空间_02


此外我们还需要对 CPU1 工程的板级支持包做额外的设置

zynq配置emmc_应用程序_03

在配置界面当中,选择 drivers–>ps7_cortexa9_1,在 extra_compiler_flags 栏添加“-DUSE_AMP=1”; 该页面用于配置工程的交叉编译工具,例如compiler指定了编译工程源码时所使用的交叉编译工具arm none-eabi-gcc,而 compiler_flags 和 extra_compiler_flags 则是执行 arm-none-eabi-gcc 命令时所携带的选项,“-D”选项的作用则是定义一个宏,所以我们这里添加“-DUSE_AMP=1”其实就类似于定义了一个宏“#defineUSE_AMP 1”,这里定义的宏与普通.c、.h 文件中定义宏有所不同,这个宏在整个工程源码中都是有效的,你不需要包含任何的头文件都能使用它。

那问题来了?我们为什么需要定义这个宏,主要是跟 L2 Cache 有关系,因为 L2 Cache 属于 CPU0 和CPU1 的共享资源,都可以使用,这对于 AMP 运行模式来说是一个麻烦,所以这里直接让 CPU1 禁止使用L2 Cache,避免出现问题。当定义了这个宏之后,CPU1 就不可以使用 L2 Cache 了。

zynq配置emmc_arm_04

将共享内存地址设置为 0x25000000,同样这个地址既不在 CPU0 Linux 系统内存空间中、
也不在 CPU1 裸机应用程序内存空间中。

#define SHARE_MEM_ADDR 0x25000000U // 共享内存地址
  /*
   * 禁止共享内存 Cache,保持数据的一致性
   * S=b1 TEX=b100 AP=b11, Domain=b1111, C=b0, B=b0
   */
Xil_SetTlbAttributes(SHARE_MEM_ADDR, 0x14de2);
创建FSBL工程

菜单栏中选择“File->New->Application Project”

zynq配置emmc_zynq配置emmc_05

接下来点击 Next,并在示例工程中选择“Zynq FSBL”,最后点击“Finish”,如下图所示

zynq配置emmc_#define_06

FSBL 工程创建完成之后,SDK 软件会自动执行编译过程,生成 FSBL.elf 文件。 双击打开 fsbl 工程中的 main.c 源文件,在 main 函数前添加如下代码

#define sev() __asm__ ("sev") // C 语言内嵌汇编写法
#define CPU1_RUN_ADDR 0x21000000U
#define CPU1_COPY_ADDR 0xFFFFFFF0U
static void StartCpu1(void)
{
 Xil_Out32(CPU1_COPY_ADDR, CPU1_RUN_ADDR); // 将 0x10000000 拷贝到 0xFFFFFFF0 地址处
 dmb(); // 等待内存写入完成(同步)
 sev(); // 执行 sev 指令唤醒 CPU1
}

添加完成之后,然后在“HandoffAddress = LoadBootImage()”代码后面调用上面定义的函数 StartCpu1();

选中FSBL后点击工具栏Xilinx->Create boot image

zynq配置emmc_应用程序_07

u-boot在编译之前打开设备树文件,arch/arm/dts/atk-zynq-7020.dts找到 memory 节点reg 属性描述了 u-boot 和 Linux 系统所能使用的内存空间,起始地址为 0x0,大小为 0x40000000,也就是 1GB;这里我们将 0x40000000 修改为 0x20000000,也就是该为 512MB,修改完成之后保存退出;修改u-boot环境变量bootargs设置成只有一个cpu核,在u-boot源码board/xilinx/zynq/board.c

bootargs=console=ttyPS0,115200 maxcpus=1 earlyprintk root=/dev/mmcblk0p2 rw rootwait

目的是为了让 Linux 系统只能使用一个 CPU,也就是 CPU0,不要使用 CPU1,把 CPU1
独立出来运行一个裸机程序;