基于FTP的多片C66xx系列网络动态加载
2021-10-20张开生孟爱权
张开生 汪 慧 孟爱权
(西安电子工程研究所 西安 710100)
0 引言
对于嵌入式芯片而言,Boot就是自启动,在DSP的调试阶段,可执行的代码文件(.out)存放在安装了TI开发环境CCS所在的计算机上,在线连接仿真器调试时,由仿真器将可执行代码load到DSP的内存中运行并调试。但软件成熟后,需要将用户的可执行代码存放在片外Flash或者别的设备(GMAC/PCIE/SRIO加载方式)中,系统上电后将可执行代码自动加载到内存中,实现可执行代码的上电自动运行,这一过程就是通常所说的自启动或者Boot[1]。
片外的Flash分为NAND Flash和NOR Flash两种,其中在DSP和FPGA加载过程中常用的是NOR Flash,因为NOR Flash和普通的内存访问方式类似,可以支持随机访问,这使它既具有支持XIP(eXecute In Place,即芯片内执行)的特性,又具备像普通RAM一样执行程序的能力,因此NOR Flash是自启动代码的绝佳载体。
本文将对DSP的加载过程和方式进行简要介绍,重点阐述多核器件的启动流程、启动方式选择、启动文件格式生成和文件内容,提出基于网络FTP的动态加载方式,详细阐述了多片网络动态加载的实现步骤。
1 启动流程
对于TI的C66xx系列DSP而言,无论是单核加载还是多核加载,均是DSP上电/系统复位后所有核的PC指针指向RBL代码,RBL是在芯片出厂的时候就已固化到芯片内部NOR Flash中的一段程序,程序起始地址为0x20B0_0000。无论对于单核DSP还是多核DSP而言,在上电/系统复位后所有核同时从地址0x20B0_0000运行片内的RBL(Rom Bootloader)程序[2],但从核(多核DSP中定义Core0为主核,其余为从核)的RBL在判断核ID号不为0后进入等待状态,等待Core0发送启动地址BOOT_MAGIC_ADDRESS和核间IPC中断,从核收到核间中断后将PC指针指向启动地址并开始执行程序[3]。
而主核的RBL通过读取寄存器DEVSTAT寄存器的值判断启动模式,根据启动方式进行接口初始化和CPU时钟PLL的配置,同时根据启动方式决定是否进行数据搬移,如果需要进行数据搬移,如SPI加载,就需要将SPI Flash中的可执行程序加载到DSP的相应内存中,如果不需要搬移,就直接跳转到外接NOR Flash的起始地址(如EMIF16加载时外接的NOR Flash地址0X7000_0000)执行[4]。
RBL程序执行的流程如图1所示,Core0执行的RBL负责将用户程序加载到合适的存储位置(例如:L2、片内共享存储、DDR、EMIF RAM空间),然后Core0执行用户程序。
图1 DSP自启动流程
在某些启动方式的加载过程中,加载启动之前需要预留L2最后0XD23F字节空间(0X0087_2DC1 ~0X0087_FFFF约53KB)[1],这段保留空间作为RBL程序执行时的堆栈、配置、参数存储空间,在用户程序加载起来之后(CPU开始执行_c_int00)就可以使用,如果用户需要使用这段预留的空间,必需将一些未初始化的变量在.cmd内存配置文件中进行定义。
用户程序启动后的主程序入口地址固定指向_c_int00函数,而_c_int00是C/C++程序初始化代码的入口地址,其主要功能是完成建立C程序的运行环境,为进入main()函数进行系统初始化,因此它是芯片运行支持库中的一个重要函数。DSP上电时,由RBL负责引导至_c_int00,_c_int00主要完成以下工作:
1)定义系统栈.stack,并初始化栈指针,配置相关寄存器;
2)初始化全局变量(.cinit),从.cinit段将初始化数据拷贝到.bss段中相应的变量;
3)若使用C++,还会完成全局对象构造(.pinit);
4)调用main函数运行C程序;
5)当main函数return时,调用exit函数;
当_c_int00函数执行完毕之后,即完成了为C语言的准备工作,系统就转到C语言的main()函数。
另外如果程序在链接文件.cmd中采用-c选项,则编译链接后的可执行程序会将全局变量的初始化放在_c_int00()函数中进行,在此函数中会调用_auto_init(CINIT)函数,将.cinit段的内容拷入.bss中相应的变量中,此过程是在系统上电后进入main()函数之前执行的;如果程序在链接时采用-cr选项,则编译后的可执行程序中全局变量需要使用loader进行初始化,这种方法一般用于在JTAG调试时,CCS即为loader。
2 启动方式选择
DSP芯片在上电复位过程中通过采样锁存13个引脚BOOTMODE[12:0]/GPIO[13:1]的状态值到芯片内部DEVSTAT寄存器[5],采样锁存时序如图2所示,RBL程序获取启动模式后,执行相应的加载流程,用户程序加载完成后这些GPIO配置引脚就可以作为普通输入输出引脚使用。
图2 启动模式采样锁存时序
在RESETFULL信号拉高前后12个时钟周期内,GPIO配置管脚的值会被DSP采样锁存,锁存的值存储在芯片内部地址为0x02620020的DEVSTAT寄存器中,在片内RBL启动加载流程时,RBL通过读取DEVSTAT寄存器的值决定执行相应的加载程序,例如选择启动方式为EMIF启动时,需在芯片复位上升沿期间将GPIO[15:0]管脚值设置为0X1821,选择I2C启动时管脚值为0X160D,设置的管脚值可以在DEVSTAT寄存器中查看,在调试过程中,可以在CCS中点击“System Reset”后,通过修改DEVSTAT寄存值,选择不同的启动方式/强制软件引导加载,而不用每次通过硬件修改/上电复位等选择启动方式。
DSP的启动方式可以分为以下三种方式[6]。
1)No Boot启动方式,相关的外设设备为EMIF,用户代码存放在EMIF Flash,RBL检测到启动方式为No Boot后,RBL执行直接跳转到EMIF Flash空间首地址,然后由Core0直接读取并执行用户程序;
2)Host Boot启动方式,相关的设备有PCIE/GMAC/SRIO,用户程序由另外的设备通过上述接口搬移到相应的存储位置,搬移完成后由Core0跳转并执行用户程序;
3)Slave Boot启动方式,相关的设备有EMIF/SPI/I2C/UART,用户的可执行程序以boot table格式存放在芯片的片外Flash中(从首地址开始存放),加载过程中由RBL初始化Flash接口,RBL程序通过启动接口将用户程序搬移到指定地址,然后Core0跳转到用户程序的_c_int00函数处,由Core0向从核发送IPC中断,启动从核。
3 加载文件解析
用TI的编程工具CCS(Code Composer Studio)编译连接生成后缀为.out可执行文件,此目标文件格式被称作通用目标文件格式(Common Object File Format,COFF)。COFF文件是按照模块化思想对程序进行管理的,它的最小单位称为段(Section)。段是占据一个连续空间的代码块或者数据块,与其他段一起映射到存储器内。但各个段是分开的,各有功能特色。对于C语言文件,编译器生成的代码段分配在.text段中,全局变量和静态变量分配在.bss段中,而局部变量或寄存器变量分配到.stack段,还有其他段和自定义段,可以查询TI的有关文档。
连接器生成的可执行COFF文件(后缀为.out),含有一些定位符号和文件头等信息,这些信息能够被仿真器识别,仿真器可以从COFF文件中提取有用的程序,并把提取的程序加载到DSP的L2 SRAM。但是,如果我们采用FLASH、PCI或SRIO等加载时,COFF文件中的一些信息不能被识别,而且由于含有的无效信息较多,COFF文件比较大,因此,我们应该对COFF文件进行提取和精简处理。这就需要用到TI提供的十六进制转换工具(Hex6x.exe),该转换工具可以把COFF文件转换成需要的格式。
为了使RBL从Flash加载时知道搬移的目的地址和数据大小,需要将用户的.out文件转换成RBL能够识别使用的Boot Table格式(后缀.dat);用户程序固化烧写的过程就是将用户的.out文件通过工具链转换成Boot Table格式的.dat文件,然后将.dat文件固化到Flash中。转化工具链中主要用到的有:
首先利用Hex转换工具hex6x.exe将.out文件和.rmd转换为.btbl文件;其次利用mergebtbl工具将多个.out文件生成的对应的.btbl文件合成成一个.btbl文件;最后利用bconvert64x.exe和b2ccs.exe两个工具,将文件.btbl转换为最终烧写到Flash中的.dat文件。TI提供的转换工具的可执行程序和源码位于软件安装包“X: imcsdk_2_01_02_05 oolsoot_loaderiblsrcutil”路径下,使用者可以根据实际需要对转换工具进行修改,例如官方b2i2c.exe支持的最大文件长度为0X20000个字节,如果最终生成的.dat文件超出该长度,需要用户修改宏定义的文件上限大小,否则会出现文件烧写长度不够的情况,导致程序加载失败。
最终生成的.dat文件的Boot Table由3个部分组成:首先是包含32bit的程序入口地址(_c_int00的地址);其次是Boot Table的核心部分,也是占据文件大小最大的部分,该部分由若干个文件段组成,每个文件段第一个32bit是段字节数(需要搬移的字节长度4n),第二个是段文件搬移的目的地址,紧接着是n个需要搬移的数据,Boot Table文件格式如图3所示[7-8]。
图3 Boot Table文件格式
RBL在搬运Boot Table格式的用户程序时,按照格式不断地读取和搬移段数据,当读取到搬移段字节数为0(接收标识)时,则PC指针跳转到Boot Table最开始指定的32位头地址(_c_int00函数地址),Core0开始执行用户程序。
4 多片DSP网络动态加载
在本项目中,由于系统中需要使用16片TI 6678,如果采用传统的仿真器进行程序固化,那么在调试和程序烧写过程中需要重复且频繁地烧写用户程序.dat文件到片外Flash,为减轻程序更新操作的复杂减少更新时间,本文提出一种基于PowerPC的FTP服务器的动态加载方法,该方法的系统硬件架构如图4所示,本系统由4块DSP板卡组成,每块板卡中搭载4个DSP芯片,每个DSP芯片的网络接口与板上的网络交换芯片相连,4个DSP板卡的16个6678芯片与中心控制板的网络通过交换芯片进行互联,基于交换芯片的网络互联机制为网络加载提供物理通路。
图4 多片网络加载硬件架构
具体加载过程为:在每个DSP外挂的Flash中固化统一的一个小的用户程序,该程序在上电后由Flash自动加载到共享存储区的前128KB空间(0x0C000000-0X0C020000该空间在正常用户程序中作为数据接收缓存用,在动态加载阶段作为加载程序执行的运行空间),由Core0实现每个芯片的时钟、DDR、网络初始化,其余7个核处于Idle状态,程序中根据槽位号和DSP节点号为每个DSP芯片设置一个固定的网络IP地址(例如:192.168.0.1---192.168.0.16),网络地址设定完毕后启动TCP客户端线程,等待运行于中心控制板的TCP服务器启动;16个 DSP中实际需要运行的用户程序首先利用工具链将.out文件转换成对应的.dat文件存放到PowerPC的FTP服务器上,每个芯片的用户程序以文件名区分,例如dsp1.dat,dsp2.dat……dsp16.dat,中心控制板在上电启动后,从FTP服务器中按照文件名依次读取DSP的用户程序,同时根据确定的IP地址利用TCP协议将DSP程序从FTP服务器中传输到DSP芯片,DSP接收到文件后,判断接收最后字节是否是Boot Table文件结束标识(32位全0),如果数据流中没有结束标识则继续接收,如果出现结束标识,则认为程序文件传输完毕,Core0将接收到的数据文件按照Boot Table文件格式,首先获取32位头地址(用户程序_c_int00函数地址),然后根据Boot Table文件格式将用户程序搬移到指定地址,Core0搬移完成后,将_c_int00地址写入到从核L2的最后4字节(0x1X87FFFC,X为核号),依次向从核发送IPC中断启动从核,然后Core0跳转到_c_int00地址执行,至此,整个DSP芯片的用户程序实现了从中心控制板的FTP服务器到DSP芯片内执行的过程。针对加载过程中的地址空间划分,在RBL阶段由片内固化程序占用L2空间最后54字节,在动态加载程序执行时,仅占用所有存储空间中共享存储区前128KB空间,该128KB空间用于程序的代码段、常量段、向量端、堆栈段等程序执行段,在程序加载完成后,共享区占用的128KB存储空间在跳转到用户程序时被全部交给用户程序使用,即整个程序加载完成后用户程序可以使用DSP芯片的所有存储空间。
5 结束语
本文详细介绍了TI C66XX系列多核启动的流程和加载方式,分析了加载文件格式,提出将用户程序上传到FTP服务器实现用户程序在线动态加载,该方式同传统的仿真器单个烧写的方式相比,显著提高了程序更新的效率,同时由于在每个DSP的Flash固化的程序全部一致,因此系统内的4块DSP板卡无需固定槽位,每个DSP上电后的属性和地位由槽位号和节点号确定,提高硬件板卡的通用性,这种加载方式对于提高开发效率,缩短产品研发周期,具有非常重要的工程实践意义。