DSP

在Visual DSP下移植u-boot

2019-07-13 16:06发布

快乐虾 http://blog.csdn.net/lights_joy/ lights@hb165.com    本文适用于 ADSP-BF561 Vistual DSP 5.0update 5 u-boot-1.1.6    欢迎转载,但请保留作者信息  

1.     前言

Boot Loader(内核引导程序)是在操作系统内核运行之前运行的一段自举程序,用于初始化硬件设备、改变处理器运行模式、重组中断向量和建立内存空间映射图,从而将系统的软硬件带到一个合适的状态或者用户定制的特定状态,以便为最终加载操作系统内核准备好正确的环境。 目前嵌入式Linux系统常用的Boot Loaderarm-bootredbootU-Boot等。U-Boot是当前比较流行的遵循GPL条件的开放源码项目[1]U-Boot具有源码公开的特点,开发人员可根据自身需要进行裁减;支持多种处理器和嵌入式操作系统内核;具有多种设备驱动源码:支持种引导方式;具有功能强大且成熟、稳定等诸多优点,故在嵌入式系统开发过程中广泛采用。 ADSP-BF561 处理器是ADI Blackfin 系列中的高性能产品,针对于多媒体和通信方面的各种应用。该器件的核心由两枚独立的Blackfin 处理器组成。它集成了一套通用的数字图像处理外围设备,为数字图像处理和多媒体应用创建了一个完整的系统级片上解决方案[2] 目前,在BF561上使用u-boot的一般方法是使用GNU Toolchain将其编译成BF561的目标代码,然后下载到目标板上运行。而VisualDSP++ADI公司推出的开发DSP程序的IDE,其界面友好,提供了几乎所有现代IDE的功能。目前尚未有人在VDSP下编译u-boot的。一方面是由于编译器技术的差异,另一方面也由于没有这样的需求。但是假如它能够在VDSP下编译,那么也就意味着可以通过仿真器单步执行调试,对于引导技术的学习和研究将可达到事半功倍的效果! 在整个移植过程中,主要涉及汇编器,C编译器和链接器的差异处理,下面进行详细的描述。

2.     程序结构

当在GNU Toolchain下进行u-boot的开发时,它使用Makefile对编译流程进行控制,逐一进入u-boot的每个子目录进行编译。但是在Visual Dsp++IDE环境下,如果将整个u-boot的代码做为一个工程来创建,有一个比较难以处理的问题:在不同的目录下有一些重名的文件,这些重名的文件将产生相同名称的目标文件,因此Visual Dsp无法支持在一个工程中使用同名的源文件,哪怕它们是放在不同的目录下也不行。 为了解决这个问题,可以将为每个不同的目录创建不同的dlb文件,这是VDSP使用的库文件,但是每个不同的库文件生成的目标文件将放在不同的目录下,因而就可以解决重名的问题。

3.     汇编器的差异

gcc的汇编器和VDSP的汇编器语法大部分相同,只有少量的语法差异,如下表所示: 问题描述 VDSP的解决方法 u-boot中,经常使用 #if (CONFIG_COMMANDS & CFG_CMD_xxx) 这样的定义,其中CONFIG_COMMANDS这个常量是做为一个64位整数来对待的。 VDSP不支持在#if中使用64位整数进行运算,因此只能自定义一个宏,进行如下修改: #ifdef CONFIG_CMD_xxx 在这里,如果需要使用xxx指定的功能,就在工程选项中定义这个宏,否则不定义。 u-boot中,经常使用 .macro serial_early_init #ifdef CONFIG_DEBUG_EARLY_SERIAL        call _serial_initialize; #endif .endm 这样的定义。 VDSP不支持.macro,只能用#define来近似模拟,但是#define中不能再使用#ifdef这样的条件编译,因此只能修改为: #ifdef CONFIG_DEBUG_EARLY_SERIAL        #define serial_early_init  /                      call _serial_initialize; #else        #define serial_early_init  #endif GCC的汇编支持在一行中仅使用一个分号,而没有其它语句。 VDSP不支持,必须去除分号。 GCC在汇编函数的末尾要使用 .size name, .-name VDSP不支持.size,直接使用 .name.end: GCC的嵌入汇编中可以使用 %0=b%2(z); 类似这样的语句。 VDSP下一解释就变成了r0 = bp0r0p0的使用可能与实际值不同),应该修改为 %0 = b[%2] (z) GCC的汇编中可以使用”m”作为指示符。 VDSP中不支持”m”,相应地改为”a”或者”p” 对于( R7 : 0, P5 : 0) = [ SP ++ ];  这样的语句,GCC并不限制在R7和冒号之间插入空格。 VDSP下必须写成: ( R7:0, P5:0) = [ SP ++ ]; R7:0之间不允许有任何空格。 当然,还有其它的一些差异,如jump 1f等,但是在u-boot的移植过程中没有的遇到,因此不做介绍。

4.     C编译器的差异

VDSPC编译器和GCCC编译器兼容性不错,绝大部分代码不需要修改即可运行,主要有以下几个地方的差异: 问题描述 VDSP的解决方法 u-boot/include/asm/posix_type.h中有这样一个定义: typedef enum { false = 0, true = 1 } bool; VDSP直接将bool做为一个内置的数据类型来处理,因而对于这个定义可以直接注释掉。 u-boot-1.1.6-2008R1/cpu/blackfin/traps.c中定义了一个数组: const struct memory_map const bfin_memory_map[] = { VDSP不支持使用两个const的修饰,因而只能改为: const struct memory_map bfin_memory_map[] = { u-boot中使用了一个宏: #define DECLARE_GLOBAL_DATA_PTR     register gd_t * volatile gd asm ("P5") 使用这个宏可以声明一个叫gd的局部指针变量,而这个指针的值是存放在P5这个寄存器中的。U-boot使用这种方式来进行程序的优化。 VDSP中,不支持此定义,因此可以这样修改: #define DECLARE_GLOBAL_DATA_PTR     extern gd_t * volatile gd 然后在board.c中添加一个定义: gd_t * volatile gd; 这样修改的结果是将gd这个指针放在了SDRAM中,性能略有下降,但是还是可以接受的。 lib_blackfin/board.c中有一个函数: void init_cplbtables(void) {          void icplb_add(uint32_t addr, uint32_t data)          {                    *(ICPLB_ADDR + i) = addr;                    *(ICPLB_DATA + i) = data;          } } 在这里它将icplb_add函数直接放到了init_cplbtables函数里面。 这在VDSP中是不允许的,应该将此函数的实现提取到外面来。 对于在u-boot移植过程中未出现的编译器差异,在此不做介绍。

5.     链接器的差异

u-boot的链接文件为u-boot-1.1.6-2008R1/board/bf561-ezkit/u-boot.lds.s,在其中定义了各段的布局,而VDSP5则使用u-boot.ldf做为链接文件,下面就是对它的移植。当然,我们可以根据自己的需要随时进行调整,在此只是尽可能与原文件保持一致。 问题描述 VDSP的解决方法 u-boot.lds.s中有这样的内存区定义:      ram     : ORIGIN = CFG_MONITOR_BASE, LENGTH = CFG_MONITOR_LEN 在这里CFG_MONITOR_BASECFG_MONITOR_LEN是预先定义好的两个常数。 不能在LDF中直接使用START (CFG_MONITOR_BASE)这样的语句,但由于u-boot将其自身放在SDRAM最高的256K中,因此可以直接在STARTEND中指定这个范围。修改后的代码为:    MEM_SDRAM               { TYPE(RAM) START(0x03fc0000) END(0x03ffffff) WIDTH(8) } gcc下编译的程序,默认的程序段名称是.text,所以在u-boot.lds.s中的第一个段就是:      .text :      {          *(.text)      } >ram 改写后如下: .text {      INPUT_SECTION_ALIGN(4)          INPUT_SECTIONS($LIBRARIES_UBOOT(program)) } > MEM_SDRAM        .bss :      {          . = ALIGN(4);          __bss_start = .;          *(.sbss) *(.scommon)          *(.dynbss)          *(.bss)          *(COMMON)          __bss_end = .;      } >ram            .bss          {     INPUT_SECTION_ALIGN(4)                    __bss_start = .;                    INPUT_SECTIONS($LIBRARIES_UBOOT(.sbss) $LIBRARIES_UBOOT(.scommon))                   INPUT_SECTIONS($LIBRARIES_UBOOT(.dynbss))                    INPUT_SECTIONS($LIBRARIES_UBOOT(.bss))                   INPUT_SECTIONS($LIBRARIES_UBOOT(COMMON))                    __bss_end = .;          } > MEM_SDRAM u-boot中有部分代码需要放在L1中,因此在u-boot.lds.s中定义了两个段: .text_l1 : {          . = ALIGN(4);          __stext_l1 = .;          *(.l1.text)          . = ALIGN(4);          __etext_l1 = .; } >l1_code AT>ram __stext_l1_lma = LOADADDR(.text_l1);   .data_l1 : {          . = ALIGN(4);          __sdata_l1 = .;          *(.l1.data)          *(.l1.bss)          . = ALIGN(4);          __edata_l1 = .; } >l1_data AT>ram __sdata_l1_lma = LOADADDR(.data_l1); 一个代码段和一个数据段。 相应的,在u-boot.ldf中为其添加两个段: .text_l1 {      INPUT_SECTION_ALIGN(4)          __stext_l1 = .;          __stext_l1_lma = .;                   INPUT_SECTIONS($LIBRARIES_UBOOT(.l1.text))      INPUT_SECTION_ALIGN(4)          __etext_l1 = .; } > MEM_A_L1_CODE   .data_l1 {      INPUT_SECTION_ALIGN(4)          __sdata_l1 = .;          __sdata_l1_lma = .;                   INPUT_SECTIONS($LIBRARIES_UBOOT(.l1.data))          INPUT_SECTIONS($LIBRARIES_UBOOT(.l1.bss))      INPUT_SECTION_ALIGN(4)          __edata_l1 = .; } > MEM_A_L1_DATA_A 这里需要注意的是__stext_l1_lma__sdata_l1_lma两个地址的定义。这两个地址仅当BF561不使用方式1启动时才有效果,在此种情况下,要放到L1中的内容刚开始是存放在FLASH上的,u-boot初始化时会将要这些代码或数据复制到L1中的相应位置,而__stext_l1_lma__sdata_l1_lma两个地址分别指向了这些代码或者数据在FLASH上的存储位置。但是由于我的板子使用的是方式1,因此就直接将这两个值指向L1段起始的位置。  

6.     使用VDSP

VDSP的优点不仅仅在于提供了一个很好的IDE,还有它提供了一些非常实用的且效率很高的常用函数,因而如何在移植时充分发挥VDSP库的性能就是一个很重要的课题。

1)     使用自定义的crt代码

在使用vdsp5的向导创建一个新的工程时,它会默认生成一个*_basiccrt.s的文件,其中的*号为工程的名称。这就是在AB两个核中使用的crt文件了,然后通过ldf文件中的相应设置将crt代码放在两个核各自的起始位置上(A核为0xffa0 0000B核为0xff60 0000)。 u-boot使用的crt文件为u-boot-1.1.6-2008R1/cpu/blackfin/start.s,我们希望用此文件来替换vdsp向导生成的u-boot_basiccrt.s。为此需要先将start.s添加到corea.dlb工程中,当然,由于编译器的不同,需要做一些适当的修改。然后在ldf文件中删除A核对u-boot_basiccrt.s的引用: 因为u-boot_basiccrt.s中入口为start,而start.s中的入口为_start,所以还必须修改如下两行:    RESOLVE(start, 0xFFA00000)    KEEP(start,_main) 将之修改为    RESOLVE(_start, 0xFFA00000)    KEEP(_start,_main)

2)     第二阶段的程序入口

start.s的最后,CPU初始化已经完成,需要进入第二阶段的执行。它会将_cpu_init_f函数指针写入到INT15的向量表中,然后用raise 15进入下一阶段的运行,这是因为CPU复位之后处于RESET中断的状态,这是优先级很高的一个中断,在这种情况下,虽然可以对CPU进行完全的操作(Supervisor mode),但是却无法响应其它的中断请求。因此start.s将自身跳到中断15再运行,这样同时可以响应其它的中断,CPU也仍然处在Supervisor mode,可以进行完整的控制。 为了使用main函数,需要将原来跳转的cpu_init_f函数替换为main,这样每次下载u-boot之后,VDSP都会自动在main中中断下来,否则它将直接往下运行直到第一个断点。 由于程序直接跳转到main函数,因此还需要对main函数进行适当修改,使其调用原来的cpu_init_f __attribute__ ((__noreturn__)) void main(ulong bootflag, ulong loaded_from_ldr) {        cpu_init_f(bootflag, loaded_from_ldr); }

3)     添加头文件的引用

为了在所有的C文件中都可以使用VDSP的库函数,我们需要在一个合适的地方加上 #include u-boot的实现可以很容易看出,include/config.h是一个好地方,所以我们就在其上增加以下几行: #ifndef __ASSEMBLY__ #include #define getenv         u_boot_getenv #endif 之所以加上getenv的定义是因为它和u-boot中使用的函数重名了,但是二都实现的功能完全不同。

4)     库的初始化

VDSP库中,有些东西需要进行初始化,参考u-boot_basiccrt.s中的做法