,,,,
(南京南瑞继保电气有限公司,南京 211102)
电力系统控制保护设备是一种专用嵌入式系统,一般由嵌入式处理器完成保护、测控、人机通信功能。这类装置需要实现两类功能:其一是通信和人机接口,通常包括以太网TCP/IP通信、UART通信、通信协议处理、LCD显示控制、磁盘存储等功能;其二是实时性计算控制,主要用于电气量的实时采样和分析计算,对系统的实时响应性能有较高要求。传统的电力系统控制保护设备通常使用嵌入式CPU(例如ARM、PowerPC)运行嵌入式操作系统(例如VxWorks、Linux)实现第一类功能,使用DSP裸机系统(Bare Metal)实现第二类功能。随着半导体技术持续发展,多核处理器芯片已成为市场上的主流,出现了类似Zynq-7000系列集成双核ARM和FPGA的高度集成SoC器件。本文在同一片ARM双核处理器SoC芯片Zynq-7000上实现了非对称多处理模式(AMP),其中一个CPU核心上运行Linux操作系统,与此同时另外一个CPU核心上运行裸机系统。
Zynq-7000是Xilinx公司的SoC芯片,片上集成了ARM Cortex-A9双CPU核心处理系统和FPGA。除此之外,Zynq-7000还集成了片内RAM(OCM)、DDR接口,以及GPIO、UART、以太网控制器、USB、SPI、I2C、SD卡控制器等多种外设接口[1]。
基于SoC的系统硬件设计简洁,实现框图如图1所示。Zynq-7000 SoC外接DDR存储器(作为主存)和SPI Flash存储器(作为程序和数据的外部存储)构成最小系统。另有片上的以太网控制器和UART外接相应电路作为通信、调试之用。
图1 硬件设计框图
在本方案中,Zynq-7000 SoC集成了两个CPU核心:CPU0和CPU1,工作在AMP模式。其中CPU0上运行Linux操作系统,CPU1上运行裸机系统,经过合理的资源分配,两者并行运行互不干扰。
本方案的启动流程是:CPU0按照正常的Linux启动流程启动,此时CPU1处于禁用状态。CPU0启动完成后,在Linux环境下可加载CPU1程序并启动CPU1。在系统运行过程中CPU0可随时复位CPU1、重新加载CPU1程序并重新启动CPU1。
本方案通过配置ARM中断控制器(GIC)的方法实现了不同中断在两个CPU核心之间的分配,少数电力二次应用相关的专用中断由CPU1响应,其它外设(例如以太网控制器、UART、SPI等)的中断由CPU0响应。
Xilinx提供了一种在Linux环境下通过remoteproc驱动程序加载CPU1裸机程序并启动CPU1的方法[2]。remoteproc本身较为复杂,存在诸多不稳定因素[3]。此外remoteproc要求裸机程序以固件的形式编译进内核模块,这使得裸机程序与Linux源代码紧密耦合。不同功能的电力系统控制保护设备需要多样化的CPU1裸机程序,remoteproc的这一限制不利于裸机程序的开发和维护。
针对电力系统控制保护设备的实际需求,本文设计了一种简洁的方案,能够在Linux用户态下动态加载启动CPU1的裸机程序。具体步骤如下:①操作寄存器复位CPU1;②加载CPU1裸机程序;③操作寄存器重新启动CPU1。
上述步骤可重复进行,每次可按需为CPU1加载不同的裸机程序。
开发Linux内核模块,提供字符设备/dev/dsp。Linux用户空间程序通过调用字符设备/dev/dsp的系统调用函数进行上述各步骤操作。
寄存器A9_CPU_RST_CTRL用于复位和启动CPU核心[1],如表1所列。
表1 A9_CPU_RST_CTRL寄存器
向A9_CPU_RST_CTRL写入值0x22使CPU1进入复位状态。将此操作封装进字符设备/dev/dsp的ioctl系统调用,用户态程序通过调用ioctl命令字0x6501控制CPU1复位。
系统预留出部分DDR空间给CPU1使用,Linux不会占用此处空间。本方案使用的硬件平台共有512 MB DDR空间,分配情况如表2所列。
表2 内存分配
为使Linux避开CPU1专用内存区域,Linux启动时传入参数“mem=64M@0x00000000 mem=352M@0x0A000000”。
CPU1专用内存区域位于内存空间中部而不是末尾,目的是将来可能会开发不同DDR容量的硬件平台,使用这种分配机制可令CPU1占用的内存地址保持不变,进而保证CPU1裸机程序兼容不同DDR容量的硬件平台。
Linux用户空间程序可调用字符设备/dev/dsp的mmap系统调用,将CPU1专用内存区域映射到本地用户空间。因CPU1复位重新启动后缓存未使能,为保证程序的正确性,此映射为no cache方式。
CPU1裸机程序镜像以ELF文件形式存放在Flash存储器上。ELF文件是GNU工具链的默认输出格式,便于生成和管理,其内部格式如图2所示[4]。
图2 ELF格式
Linux用户空间程序按照图2右视图解析ELF文件,根据Program header table遍历每一个segment,将segment的内容加载到已通过mmap映射到本地用户空间的CPU1专用内存区域。
图3 ICD IPTR寄存器
Flash存储器上可存放多个ELF文件,用户空间程序每次可按需加载不同的ELF文件。
CPU1复位重新启动后,程序指针(PC)值为0x0000 0000,即从内存地址0x0000 0000开始执行指令。提前向内存地址0x0000 0000处写入如下小段程序:
0xE59F0000,
/*ldr r0,[pc];+8 bytes*/
0xE12FFF10, /*bx r0 */
0x04000000
此段程序的功能是令CPU1跳转到裸机程序加载的入口地址处执行。查看Zynq-7000 Linux内核链接脚本可知,0x0000 0000地址开始的4 KB内存不会被Linux访问,所以此操作不会影响CPU0的Linux运行。与参考文献[3]提供的方法相比,本方案更加简洁,且不会占用OCM空间。
向A9_CPU_RST_CTRL写入值0x00使CPU1脱离复位状态开始启动。将此操作封装进字符设备/dev/dsp的ioctl系统调用,用户态程序通过ioctl命令字0x6502调用控制CPU1启动运行。
按照第2.1~2.3节的步骤重复加载启动CPU1,多次实验后发现CPU1出现偶发性的程序崩溃问题。
进一步调试发现,CPU1程序变量有时会意外改变。排除其它因素的影响后,猜测此现象与L2缓存有关。在Zynq-7000的双核ARM系统中,两个CPU核心拥有各自的L1缓存,所以CPU1复位时CPU1的L1缓存随之复位。L2缓存被两个CPU核心共享,CPU1复位时L2缓存不会复位。CPU1复位前某些变量可能已映射到L2缓存,重新启动后若上述位置的L2缓存发生write back,CPU1程序的变量值就会意外改变,这有可能造成程序崩溃。
为证实上述猜想,在第2.1节的复位CPU1操作后调用ARM Linux内核提供的outer_flush_range函数flush L2缓存,解除L2缓存与主存的映射关系。增加该措施后,再重复加载启动CPU1,没有再出现CPU1程序崩溃问题。
ARM处理器通过GIC模块进行中断配置和中断响应。GIC的ICD IPTR寄存器可配置特定中断由CPU0或CPU1响应[2],如图3所示。
Linux启动时传入参数“maxcpus=1”,此参数限制Linux在CPU0上运行,Linux下使能的外设中断全部分配到CPU0。CPU1启动后,配置ICD IPTR将CPU1专用的中断分配给CPU1。
模拟电力系统控制保护设备的典型工作模式,为Zynq-7000片上集成的FPGA编写程序,产生周期为250 μs和1000 μs的两个周期中断,分别记为中断A和中断B。
在CPU1裸机程序中响应中断A和中断B。在中断A服务程序中翻转一个GPIO(记为GPIO X);在中断B服务程序中翻转另外一个GPIO(记为GPIO Y)。
使用本方案提供的方法加载运行CPU1的裸机程序,使用示波器观察GPIO X和GPIO Y的电平波形。
示波器观测到的波形如图4所示。
图4 实验结果
示波器捕获到周期为250 μs和1000 μs的稳定波形,说明CPU1裸机程序加载成功且正确响应中断。多次重复加载CPU1程序,图4波形能够稳定复现。
[1] Xilinx Inc.Zynq-7000 All Programmable SoC Technical Reference Manual [EB/OL].[2018-03].https://www.xilinx.com/support/documentation/user_g'uides/ug585-Zynq-7000-TRM.pdf.
[2] Xilinx Inc.Zynq AMP Linux FreeRTOS Guide [EB/OL]. [2018-03].https://www.xilinx.com/support/documentation/sw_manuals/petalinux2014_2/ug978-petalinux-Zynq-amp.pdf.
[3] 李鑫志,戈志华,刘向明.基于ARM平台AMP架构下从核重复加载设计与实现[J].计算机应用与软件,2017(1):218-221.
[4] TISCommittee.Portabie Formats Specification,Version 1.1. Executabie and Linkabie Format(ELF) [Z].1995.