.globl _start _start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq |
0x0地址开始是ARM异常向量表,学过ARM体系结构与编程的都明白,非常简单,不多废话。一上电的第一条指令是跳转到reset复位处理程序:
reset: /* 进入SVC模式 */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit /* we do sys-critical inits */ #endif #ifndef CONFIG_SKIP_RELOCATE_UBOOT relocate: ...... |
一般不要定义CONFIG_SKIP_LOWLEVEL_INIT,因此,接下来跳转到cpu_init_crit处开始执行:
cpu_init_crit: /* 屏蔽所有中断 */ /* 设置时钟源,关闭除FFUART,SRAM,SDRAM,FLASH以外的外设时钟 */ ...... #ifdef CFG_CPUSPEED ldr r0, CC_BASE /* 时钟控制寄存器基址 */ ldr r1, cpuspeed /* cpuspeed: .word CFG_CPUSPEED */ str r1, [r0, #CCCR] mov r0, #2 mcr p14, 0, r0, c6, c0, 0 setspeed_done: #endif /* CFG_CPUSPEED */ /* 跳转到lowlevel_init,这里ip即r12,用作暂存寄存器 */ mov ip, lr bl lowlevel_init mov lr, ip /* Memory interfaces are working. Disable MMU and enable I-cache. */ ldr r0, =0x2001 ...... /* 关闭MMU,使能I-Cache(可选) */ mov pc, lr /* 这里是从cpu_init_crit返回到relocate标号 */ |
可见,在cpu_init_crit中的主要工作是设置时钟,配置处理器主频(这时CPU的工作频率还没有改变),调用lowlevel_init函数进行底层初始化(包括调整处理器工作频率、系统总线频率、存储器时钟频率以及存储系统的初始化等工作),随后关闭MMU并使能I-Cache,再返回。
lowlevel_init函数在board/lubbock/lowlevel_init.S中定义,其流程都是按照PXA27X的开发手册来的,所以不再赘述。仅指出,其中的寄存器在include/asm-arm/arch-pxa/pxa-regs.h头文件中定义,寄存器初始化值在include/configs/lubbock.h中定义。另外,在后面的实际移植工作中,由于目标板XSBASE270使用的PXA270处理器,可使用adsvix开发板的lowlevel_init.S文件(lubbock中没有开启turbo模式)。
接着程序的执行线索进行分析。从cpu_init_crit返回后就开始relocate(重定位),即将U-boot从FLASH存储器搬运到SDRAM中TEXT_BASE开始的存储空间(TEXT_BASE在board/lubbock/config.mk中定义),并初始化堆栈(清零.bss段),以在SDRAM中开始进入到Bootloader stage 2的C程序入口。Relocate部分开始的代码如下:
/* 之前已定义的部分变量有: _TEXT_BASE: .word TEXT_BASE _armboot_start: .word _start _bss_start: .word __bss_start _bss_end: .word _end */ relocate: /* relocate U-Boot to RAM */ adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ cmp r0, r1 /* don't reloc during debug */ beq stack_setup ldr r2, _armboot_start /* 读入_start到r2 */ ldr r3, _bss_start /* 读入__bss_start到r3 */ sub r2, r3, r2 /* r2 <- size of armboot */ add r2, r0, r2 /* r2 <- source end address */ copy_loop: ldmia r0!, {r3-r10} /* copy from source address [r0] */ stmia r1!, {r3-r10} /* copy to target address [r1] */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop /* Set up the stack */ stack_setup: ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */ #ifdef CONFIG_USE_IRQ sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ) #endif sub sp, r0, #12 /* leave 3 words for abort-stack */ clear_bss: ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x00000000 /* clear */ clbss_l:str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp r0, r1 ble clbss_l ldr pc, _start_armboot _start_armboot: .word start_armboot |
这是很经典的一段代码,相信学习凡是过ARM编程的,都分析过这段代码,所以也不再赘述。之所以列出这段代码,一是为了找到C程序入口start_armboot,二是为了给出U-Boot的一个存储器映射图:
这个图可以帮助我们更好地理解后续的C语言代码以及U-Boot对内存的分配与使用情况。
接下来进入到Bootloader Stage 2即C语言代码部分,入口是start_armboot,对应的源文件是lib_arm/board.c,这一文件对所有的ARM处理器都是通用的,因此在移植的时候不用修改。相关源代码如下:
DECLARE_GLOBAL_DATA_PTR; /* 在include/asm-arm/global_data.h中定义的一个全局寄存器变量的声明: * #define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8") * 用于存放全局数据结构体gd_t的地址。 */ void start_armboot (void) { init_fnc_t **init_fnc_ptr; char *s; #ifndef CFG_NO_FLASH ulong size; #endif #if defined(CONFIG_VFD) || defined(CONFIG_LCD) /* 本次移植暂不配置VFD和LCD,后面也将不考虑的部分略去 */ /* 初始化全局数据结构体指针gd */ gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); ....../* memset在lib_generic/string.c中定义*/ memset ((void*)gd, 0, sizeof (gd_t)); /*用0填充全局数据表*gd */ gd->bd = (bd_t*)((char*)gd - sizeof(bd_t)); memset (gd->bd, 0, sizeof (bd_t)); /*用0填充(初始化) *gd->bd */ monitor_flash_len = _bss_start - _armboot_start; for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) { if ((*init_fnc_ptr)() != 0) { hang (); /* 打印错误信息并死锁 */ } } #ifndef CFG_NO_FLASH /* configure available FLASH banks */ size = flash_init (); /* drivers/cfi_flash.c或自定义 */ display_flash_config (size); #endif /* CFG_NO_FLASH */ /*armboot_start is defined in the board-specific linker script*/ mem_malloc_init (_armboot_start - CFG_MALLOC_LEN); ...... /* initialize environment */ env_relocate (); /* IP Address */ gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr"); /* MAC Address */ { int i; ulong reg; char *s, *e; char tmp[64]; i = getenv_r ("ethaddr", tmp, sizeof (tmp)); s = (i > 0) ? tmp : NULL; for (reg = 0; reg < 6; ++reg) { gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0; if (s) s = (*e) ? e + 1 : e; } } devices_init (); /* get the devices list going. */ ....... jumptable_init (); console_init_r (); /* fully init console as a device */ enable_interrupts (); /* enable exceptions */ /* Perform network card initialisation if necessary */ #if defined(CONFIG_DRIVER_SMC91111)| |defined (CONFIG_DRIVER_LAN91C96) if (getenv ("ethaddr")) smc_set_mac_addr(gd->bd->bi_enetaddr); #endif /* CONFIG_DRIVER_SMC91111 || CONFIG_DRIVER_LAN91C96 */ /* Initialize from environment */ if ((s = getenv ("loadaddr")) != NULL) { load_addr = simple_strtoul (s, NULL, 16); } #if defined(CONFIG_CMD_NET) if ((s = getenv ("bootfile")) != NULL) copy_filename (BootFile, s, sizeof (BootFile)); #endif #ifdef BOARD_LATE_INIT board_late_init (); #endif ...... /*main_loop() can return to retry autoboot, if so just run it again.*/ for (;;) { main_loop (); } } |
gd_t是全局数据表类型,在include/asm-arm/global_data.h中定义如下:
/* Keep it *SMALL* and remember to set CFG_GBL_DATA_SIZE > sizeof(gd_t) */ typedef struct global_data { bd_t *bd; unsigned long flags; unsigned long baudrate; unsigned long have_console; /* serial_init() was called */ unsigned long reloc_off; /* Relocation Offset */ unsigned long env_addr; /* Address of Environment struct */ unsigned long env_valid; /*Checksum of Environment valid?*/ unsigned long fb_base; /* base address of frame buffer */ ....... void **jt; /* jump table */ } gd_t; |
其中,bd_t在include/asm-arm/u-boot.h中定义如下:
typedef struct bd_info { int bi_baudrate; /* serial console baudrate */ unsigned long bi_ip_addr; /* IP Address */ unsigned char bi_enetaddr[6]; /* Ethernet adress */ struct environment_s *bi_env; ulong bi_arch_number; /* unique id for this board */ ulong bi_boot_params; /* where this board expects params*/ struct /* RAM configuration */ { ulong start; ulong size; }bi_dram[CONFIG_NR_DRAM_BANKS]; } bd_t; |
jt是函数数组指针,随后将在jumptable_init()函数中初始化。
从lib_arm/board.c的源码不难分析出系统的启动流程:首先初始化全局数据表,然后顺序执行函数指针数组init_sequence中的一系列初始化函数——由其在本文件中的相关定义可得知初始化流程:
typedef int (init_fnc_t) (void); init_fnc_t *init_sequence[] = { cpu_init, /* basic cpu dependent setup -- cpu/pxa/cpu.c */ board_init, /* basic board setup --board/lubbock/lubbock.c */ interrupt_init, /* set up exceptions -- cpu/pxa/interrupts.c */ env_init, /* initialize environment -- common/env_flash.c */ init_baudrate, /* initialze baudrate settings--lib_arm/board.c */ serial_init, /* serial communications setup--cpu/pxa/serial.c */ console_init_f, /* stage 1 init of console -- common/console.c */ display_banner, /* say that we are here -- lib_arm/board.c */ #if defined(CONFIG_DISPLAY_BOARDINFO) checkboard, /* display board info */ #endif dram_init, /* configure available RAM banks --board/lubbock/lubbock.c */ display_dram_config, /* lib_arm/board.c */ NULL, }; |
在执行这个函数序列的过程中,任何一个函数异常返回都会导致u-boot“死锁”或说“挂起”在hang()函数的死循环当中。
若一切顺利,接下来就调用flash_init()函数初始化CFI FLASH(针对NOR型闪存而言),该函数在drivers/cfi_flash.c中定义,不过,只有在目标板头文件中”#define CFG_FLASH_CFI_DRIVER”之后该驱动才会被编译;在lubbock的u-boot实现当中,include/configs/lubbock.h中没有定义CFG_FLASH_CFI_DRIVER,而是在board/lubbock/ flash.c中实现了自己的FLASH驱动,包括flash_init()在内。在移植U-Boot时,可以根据实际情况选择使用U-Boot自带的FLASH驱动还是自己编写新的驱动。如果配置了NAND闪存,还会对其进行初始化;笔者的XSABSE270板没有焊接NAND FLASH,故对此不作讨论。
接下来调用env_relocate()函数初始化环境变量,该函数在common/env_common.c文件中定义。在同一文件中可以发现还定义了一个字符数组default_environment[],用于描述缺省的环境变量,这些都要在include/configs/lubbock.h头文件中进行设置,包括启动命令CONFIG_BOOTCOMMAND,波特率CONFIG_BAUDRATE,IP地址CONFIG_IPADDR等等。
然后是获取自设置的目标板的网络地址,包括IP地址和MAC地址。
再然后是调用common/devices.c中定义的devices_init()函数来创建设备列表,并初始化相应的设备,主要是”stdin”,”stdout”,”stderr”以及自定义的设备如I2C,LCD等。这些相关代码是与平台无关的,因此从移植的角度考虑,不必作细致的研究与分析。
接着调用common/exports.c中定义的jumptable_init()函数,初始化全局数据表中的跳转表gd->jt,跳转表是一个函数指针数组,定义了u-boot中基本的常用的函数库;而gd->jt是这个函数指针数组的首指针。部分代码如下:
void jumptable_init (void) { int i; gd->jt = (void **) malloc (XF_MAX * sizeof (void *)); for (i = 0; i < XF_MAX; i++) gd->jt[i] = (void *) dummy; gd->jt[XF_get_version] = (void *) get_version; gd->jt[XF_malloc] = (void *) malloc; gd->jt[XF_free] = (void *) free; gd->jt[XF_getenv] = (void *) getenv; gd->jt[XF_setenv] = (void *) setenv; ...... } |
上面的XF_get_version, XF_malloc, XF_free等在include/exports.h的枚举变量中定义,因此,实际上是作为”Label式整型序号”使用,即XF_get_version=1, XF_malloc=2, XF_free=3 ...,相关代码如下:
enum { /* include/exports.h */ #define EXPORT_FUNC(x) XF_ ## x , #include <_exports.h> #undef EXPORT_FUNC XF_MAX }; EXPORT_FUNC(get_version) EXPORT_FUNC(getc) EXPORT_FUNC(tstc) EXPORT_FUNC(putc) EXPORT_FUNC(puts) EXPORT_FUNC(printf) ......... /* include/_exports.h */ |
由于这些也是平台无关的代码,因此在移植过程中也不必深究。
然后是调用common/console.c中定义的函数console_init_r()初始化串口控制台,这同样是平台无关的代码,所以不必关心。
这时U-Boot的基本功能已经初始化完毕,便可开中断,并进行附加功能的配置与初始化,包括网卡驱动配置,目标板使用LAN91C1111网卡,对应SMC91111网卡驱动,可以根据需要配置其他的网卡驱动如CS8900等,这些都在include/configs/lubbock.h中定义。
然后是调用board/lubbock/lubbock.c中定义的board_late_init()函数进行板级的后期初始化,实际上是配置stdout和stderr的硬件设备。相关源代码如下:
int board_late_init(void) { setenv("stdout", "serial"); setenv("stderr", "serial"); return 0; } |
最后需要注意的一个很重要的文件是lib_arm/armlinux.c,它实现的功能包括设置内核启动参数,并负责将这些参数传递给内核,最后跳转到Linux内核入口函数,将控制权交给内核。
具体传递哪些参数,是通过在include/configs/lubbock.c中指定条件编译选项来控制的,对应于lib_arm/armlinux.c中的部分源代码形式如下:
#if defined (CONFIG_SETUP_MEMORY_TAGS) || / defined (CONFIG_CMDLINE_TAG) || / defined (CONFIG_INITRD_TAG) || / defined (CONFIG_SERIAL_TAG) || / defined (CONFIG_REVISION_TAG) static void setup_start_tag (bd_t *bd); # ifdef CONFIG_SETUP_MEMORY_TAGS static void setup_memory_tags (bd_t *bd); # endif static void setup_commandline_tag (bd_t *bd, char *commandline); # ifdef CONFIG_INITRD_TAG static void setup_initrd_tag (bd_t *bd, ulong initrd_start, ulong initrd_end); # endif static void setup_end_tag (bd_t *bd); static struct tag *params; #endif ...... void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[], ulong addr, ulong *len_ptr, int verify) { ...... void (*theKernel)(int zero, int arch, uint params); ...... #ifdef CONFIG_CMDLINE_TAG char *commandline = getenv ("bootargs"); #endif theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); ...... theKernel (0, bd->bi_arch_number, bd->bi_boot_params); } |
关于这个参数列表中各个参数的定义及含义,以及参数列表的初始化过程,可以参考Booting ARM Linux一文。内核是如何找到这个参数列表在内存中的位置,以接收这些参数的呢?实际上,参数列表(tag list)在内存中的起始地址会保存在通用寄存器R2中,并传递给内核。而按照习惯或说惯例,通常tag list的首地址(物理地址)会设置为RAM起始地址+ 0x100偏移量,因此R2的值实际上是确定不变的。另外,还要正确设置R0和R1的值,在呼叫内核时,R0的值应为0,R1中则应保存机器类型(machine type)编号。R0,R1和R2都会作为参数传递给内核。
在上面的代码中,定义了一个函数指针theKernel,通过倒数第二条语句将内核入口地址赋给theKernel(hdr是include/image.h中定义的一个image_header结构体类型的数据,hdr->ih_ep中保存了内核入口地址,ntohl的功能是字节顺序的大小端转换,相关代码可以参考tools/mkimage.c),最后,根据APCS规则,将0, bd->bi_arch_number, bd->bi_boot_params 依次作为参数通过R0,R1和R2传递给theKernel函数,并进入内核启动部分。
至此,我们已经从源代码入手简要分析了U-Boot的启动流程,在这个过程中,我们对前一篇文章“添加新的目标板定义”也有了更进一步的理解和认识:为什么要添加这些文件;哪些文件是平台相关的并且必须要根据平台特性进行修改的;哪些文件是平台无关的,是不需要修改的,只需在头文件中作适当配置即可。
下一节,我们将给出移植U-Boot到XSBASE270开发板的实例。
U-Boot的移植之(三)实战篇:移植U-Boot到XSBASE270开发板
具体方法可参考第一节,本篇给出部分细节和要点,假定$U-BOOT为源码根目录。
############################################################ # (1)建立目标板目录 # 其中lowlevel_init.S采用adsvix的文件,以开启turbo mode,并注释掉 # 其中对pxavoltage.S文件中initPXAvolatage函数的调用。 ############################################################ cd board/ cp -arv lubbock xsbase270 mv xsbase270/lubbock.c xsbase270/xsbase270.c cp adsvix/lowlevel_init.S xsbase270/ vim xsbase270/lowlevel_init.S @setvoltage: @ mov r10, lr @ bl initPXAvoltage @ mov lr, r10 ############################################################ # (2)建立目标板配置头文件 ############################################################ cd $U-BOOT/include/configs cp lubbock.h xsbase270.h ############################################################ # (3)修改Makefile ############################################################ #########在$U-BOOT/Makefile中添加: xsbase270_config: unconfig @$(MKCONFIG) $(@:_config=) arm pxa xsbase270 #########在$U-BOOT/Makefile中修改CROSS_COMPILE: CROSS_COMPILE = arm-iwmmxt-linux-gnueabi- #########在$U-BOOT/board/xsbase270/Makefile中修改: #COBJS := lubbock.o flash.o COBJS := xsbase270.o |
这里去掉了flash.c文件,因为它是在lubbock板中自定义的FLASH存储器驱动,lubbock不使用U-Boot自带的FLASH驱动;而在本次移植中,我们将使用U-Boot自带的drivers/cfi_flash.c作为XSBASE270开发板的NOR型闪存28F128K18C的驱动程序,具体过程后述。
实际移植过程中还可能要作如下改动:
############################################################# # (1) cpu/pxa/config.mk ############################################################# #armv5-->armv5te, modified by aaron wong PLATFORM_CPPFLAGS += -march=armv5te -mtune=xscale ############################################################# # (2) include/asm-arm/mach-types.h ############################################################# /* added by aaron */ #define MACH_TYPE_XSBASE270 1141 |
编译U-Boot:
export BUILD_DIR=~/u-boot_xsbase270/build/ make xsbase270_config make |
作其他必要修改,直至能正常编译通过。然后再进行后续的针对目标板的定制步骤。
U-Boot第一阶段的代码包括:
(1) cpu/pxa/start.S (平台无关,处理器架构相关)
(2) board/xsbase270/lowlevel_init.S (平台与处理器型号相关)
(3) board/xsbase270/config.mk (平台相关,设置TEXT_BASE)
(4) include/configs/xsbase270.h (平台相关,设置寄存器初值等)
lowlevel_init.S已在第一步作了相应修改。config.mk中设置TEXT_BASE(U-Boot的链接起始地址),暂时不改动(0xa3080000)。
xsbase270.h中定义了系统初始化时的寄存器初值(主要是GPIO配置,时钟与处理器频率设置,片上存储器控制器与存储系统的初始化),这需要根据平台进行配置。下面给出部分代码示例及注释:
/* * High Level Configuration Options (easy to change) */ #define CONFIG_PXA27X 1 /*to keep PXA27x specific code*/ #define CONFIG_XSBASE270 1 #define BOARD_LATE_INIT 1 #undef CONFIG_USE_IRQ /* we don't need IRQ/FIQ stuff */ ...... /* * Size of malloc() pool */ #define CFG_MALLOC_LEN (CFG_ENV_SIZE + 128*1024) #define CFG_GBL_DATA_SIZE 128 /* * Stack sizes * The stack sizes are set up in start.S using the settings below */ #define CONFIG_STACKSIZE (128*1024) /* regular stack */ #ifdef CONFIG_USE_IRQ #define CONFIG_STACKSIZE_IRQ (4*1024) /* IRQ stack */ #define CONFIG_STACKSIZE_FIQ (4*1024) /* FIQ stack */ #endif /* * Miscellaneous configurable options */ #define CFG_CPUSPEED 0x207 /* cpu start-up frequency,91MHz */ /* * GPIO settings */ #define CFG_GPSR0_VAL 0x00003000 #define CFG_GPSR1_VAL 0x00000000 #define CFG_GPSR2_VAL 0x00010000 #define CFG_GPSR3_VAL 0x00020000 #define CFG_GPCR0_VAL 0x00000800 ...... /* * Clock settings */ #define CFG_CKEN 0x00400200 #define CFG_CCCR 0x08000290 /* 520 MHz */ /* * Memory settings */ #define CFG_MSC0_VAL 0x7FF82BD0 #define CFG_MSC1_VAL 0x7FF87FF8 #define CFG_MSC2_VAL 0x7FF87FF8 #define CFG_MDCNFG_VAL 0x00001AC9 #define CFG_MDREFR_VAL 0x0000001E #define CFG_MDMRS_VAL 0x00000000 ...... /* * PCMCIA and CF Interfaces */ #define CFG_MECR_VAL 0x00000001 #define CFG_MCMEM0_VAL 0x00010504 #define CFG_MCMEM1_VAL 0x00010504 ...... |
U-Boot第二阶段的大部分代码是平台无关的。从移植的角度,我们仅需要关注下面一些平台相关的代码:
(1) include/configs/xsbase270.h:通过使用定义或取消定义相关的预编译变量,用于对平台无关的代码进行平台相关的定制,包括定制U-Boot命令、缺省的环境变量、存储器映射、串口控制台配置、驱动程序等。
(2) board/xsbase270/xsbase270.c:板级初始化,只需进行最基本的配置,包括设置mach-type,启动参数列表首地址,设置标准输入输出设备,获取系统RAM配置信息等。
(3) 驱动程序的移植。最基本的是FLASH存储器驱动程序和以太网卡驱动程序。对于U-Boot中已经支持的器件,可以进行简单移植,否则需要自己加入相关的设备驱动程序。
下面对以上三部分分别阐述。
可以参考lubbock.h,adsvix.h等相关开发板的设置,另外也可以从U-Boot源码的README文件获取更多信息。
(1) 存储器映射配置:
/* * Physical Memory Map */ #define CONFIG_NR_DRAM_BANKS 4 /* we have 2 banks of DRAM*/ #define PHYS_SDRAM_1 0xa0000000 /* SDRAM Bank #1 */ #define PHYS_SDRAM_1_SIZE 0x04000000 /* 64 MB */ #define PHYS_SDRAM_2 0xa4000000 /* SDRAM Bank #2 */ #define PHYS_SDRAM_2_SIZE 0x00000000 /* 0 MB */ #define PHYS_SDRAM_3 0xa8000000 /* SDRAM Bank #3 */ #define PHYS_SDRAM_3_SIZE 0x00000000 /* 0 MB */ #define PHYS_SDRAM_4 0xac000000 /* SDRAM Bank #4 */ #define PHYS_SDRAM_4_SIZE 0x00000000 /* 0 MB */ #define PHYS_FLASH_1 0x00000000 /* Flash Bank #1 */ #define PHYS_FLASH_2 0x04000000 /* Flash Bank #2 */ #define PHYS_FLASH_SIZE 0x02000000 /* 32 MB */ #define PHYS_FLASH_BANK_SIZE 0x02000000 /* 32 MB Banks */ #define PHYS_FLASH_SECT_SIZE 0x00040000 /* 256 KB sectors (x2) */ #define CFG_DRAM_BASE 0xa0000000 #define CFG_DRAM_SIZE 0x04000000 #define CFG_FLASH_BASE PHYS_FLASH_1 //you can also add other IO address map here, such as a FPGA |
(2) 定制U-Boot命令:
在include/config_cmd_default.h头文件中已经预定义了一些常用的U-Boot命令,我们可以在include/configs/xsbase270.h中包含该头文件,对于其中已定义的不需要的命令,可用undef去除;对于要添加的命令,使用define定义相关的符号即可。
/* * Command line configuration. */ #include #define CONFIG_CMD_PING |
(3) 控制台串口配置:
包括指定控制台所用的PXA27X串口,缺省的串口通信波特率等。
/* * select serial console configuration */ #define CONFIG_FFUART 1 /* we use FFUART on XSBASE270 */ #define CONFIG_BAUDRATE 115200 |
(4) 环境变量设置
包括BOOTP选项设置,缺省环境变量设置,启动参数列表配置等。
/* * BOOTP options */ #define CONFIG_BOOTP_BOOTFILESIZE #define CONFIG_BOOTP_BOOTPATH #define CONFIG_BOOTP_HOSTNAME #define CONFIG_BOOTDELAY 3 #define CONFIG_ETHADDR 08:00:3e:26:0a:5b #define CONFIG_NETMASK 255.255.255.0 #define CONFIG_IPADDR 192.168.0.21 #define CONFIG_SERVERIP 192.168.0.250 #define CONFIG_BOOTCOMMAND "bootm 80000" #define CONFIG_BOOTARGS "root=/dev/ram0,rw mem=64M console=ttyS0, 115200" #define CONFIG_CMDLINE_TAG #define CONFIG_TIMESTAMP /* allow to overwrite serial and ethaddr */ #define CONFIG_ENV_OVERWRITE |
其中,CONFIG_BOOTCOMMAND和CONFIG_BOOTARGS在后续的引导内核实验中还需要进行修正。
(5) 网卡驱动程序配置:
/* * Hardware drivers */ #define CONFIG_DRIVER_SMC91111 #define CONFIG_SMC91111_BASE 0x0C000000 #define CONFIG_SMC_USE_32_BIT 1 |
XSBASE270采用的网卡是LAN91C111,U-Boot自带的驱动程序drivers/smc91111.c可支持这款网卡,因此只要在这里作相应的配置即可。CONFIG_SMC91111_BASE要根据PXA27X对网卡的地址译码来决定(片选信号CSx和高位地址线),CONFIG_SMC_USE_32_BIT指定了网卡工作于32位数据总线模式。可以查看驱动程序源代码得到更多配置选项。
(6) NOR型闪存驱动程序配置:
U-Boot本身支持一系列符合CFI(Common Flash Interface)接口规范的闪存,其缺省支持的闪存芯片信息在include/flash.h中定义,该头文件中还定义了CFI闪存驱动所必需的数据结构和其他物理及结构特性描述符。NAND闪存驱动在drivers/nand目录下,这里不予考虑。CFI是针对NOR型FLASH所提出的一种获取闪存芯片物理和结构参数的操作规程和标准。
XSBASE270采用两片Intel 28F128K18C的兼容CFI标准的NOR型闪存,单片容量为16MB,数据线宽度为16-bit,两片并作一个32MB容量的数据宽度为32-bit的BANK来使用。在头文件include/flash.h中没有定义该芯片的相关信息,可以手动添加;这并不是必须的,如果你并不需要使用这些信息的话(例如将CFI驱动所检测到的Device Id与头文件中定义的Device ID进行比对与验证)。
/* file : include/flash.h */ #define INTEL_ID_28F128K18 0x88068806 /* added by aaron */ #define FLASH_28F128K18 0x00BA /*Intel 28F128K18 (128M=8Mx16)*/ |
要使用U-Boot自带的CFI闪存驱动,必须要作的是在include/configs/xsbase270.h中添加如下定义:
#define CFG_FLASH_CFI #define CFG_FLASH_CFI_DRIVER 1 /* avoid long time detection, added by aaron ,see include/flash.h */ #define CFG_FLASH_CFI_WIDTH FLASH_CFI_32BIT #define CFG_MAX_FLASH_BANKS 1 /* max number of memory banks */ #define CFG_MAX_FLASH_SECT 128 /*max number of sectors on one chip*/ /* timeout values are in ticks */ #define CFG_FLASH_ERASE_TOUT (25*CFG_HZ) /*Timeout for Flash Erase */ #define CFG_FLASH_WRITE_TOUT (25*CFG_HZ) /*Timeout for Flash Write */ /* write flash less slowly */ #define CFG_FLASH_USE_BUFFER_WRITE 1 |
另外,如果把环境变量保存在FLASH中,还有如下相关定义:
/* NOTE: many default partitioning schemes assume the kernel starts at * the second sector, not an environment. You have been warned! */ #define CFG_MONITOR_BASE 0 #define CFG_MONITOR_LEN PHYS_FLASH_SECT_SIZE #define CFG_ENV_IS_IN_FLASH 1 #define CFG_ENV_ADDR (PHYS_FLASH_1 + PHYS_FLASH_SECT_SIZE) #define CFG_ENV_SECT_SIZE PHYS_FLASH_SECT_SIZE #define CFG_ENV_SIZE (PHYS_FLASH_SECT_SIZE / 16) /* If defined, hardware flash sectors protection is used instead of * U-Boot software protection. */ #define CFG_FLASH_PROTECTION |
(7) 其他配置:
/* * Miscellaneous configurable options */ #define CFG_HUSH_PARSER 1 #define CFG_PROMPT_HUSH_PS2 "> " #define CFG_LONGHELP /* undef to save memory */ #ifdef CFG_HUSH_PARSER #define CFG_PROMPT "$ " /* Monitor Command Prompt */ #endif #define CFG_CBSIZE 256 /* Console I/O Buffer Size*/ /* Print Buffer Size */ #define CFG_PBSIZE (CFG_CBSIZE+sizeof(CFG_PROMPT)+16) #define CFG_MAXARGS 16 /* max number of command args */ #define CFG_BARGSIZE CFG_CBSIZE /*Boot Argument Buffer Size*/ #define CFG_DEVICE_NULLDEV 1 #define CFG_MEMTEST_START 0xa0400000 /* memtest works on */ #define CFG_MEMTEST_END 0xa0800000 /* 4 ... 8 MB in DRAM */ #undef CFG_CLKS_IN_HZ /* everything, incl board info, in Hz */ /*default load address */ #define CFG_LOAD_ADDR (CFG_DRAM_BASE + 0x8000) #define CFG_HZ 3686400 /* incrementer freq: 3.6864 MHz */ /* valid baudrates */ #define CFG_BAUDRATE_TABLE { 9600, 19200, 38400, 57600, 115200 } |
至此,目标板配置头文件xsbase270.h就完成了。
只需修改board_init()函数即可,完整代码如下:
#include DECLARE_GLOBAL_DATA_PTR; /* Miscelaneous platform dependent initialisations */ int board_init (void) { /* memory and cpu-speed are setup before relocation */ /* so we do _nothing_ here */ /* arch number of XSBASE270-Board */ gd->bd->bi_arch_number = MACH_TYPE_XSBASE270; /* adress of boot parameters */ gd->bd->bi_boot_params = 0xa0000100; return 0; } int board_late_init(void) { setenv("stdout", "serial"); setenv("stderr", "serial"); return 0; } int dram_init (void) { gd->bd->bi_dram[0].start = PHYS_SDRAM_1; gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE; gd->bd->bi_dram[1].start = PHYS_SDRAM_2; gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE; gd->bd->bi_dram[2].start = PHYS_SDRAM_3; gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE; gd->bd->bi_dram[3].start = PHYS_SDRAM_4; gd->bd->bi_dram[3].size = PHYS_SDRAM_4_SIZE; return 0; } |
最主要的是闪存和网卡驱动程序的移植。由于使用U-Boot自带的CFI闪存驱动程序和SMC91111网卡驱动程序,应此只要在头文件中进行相关配置即可完成。具体见3.1节。如果需要自行添加相关的设备驱动,则需要在board/xsbase270/目录下添加驱动源文件,并将其添加到该目录下的Makefile中进行编译与链接。
至此,针对特定目标板的U-Boot软件移植工作基本完成。在下一节中,将简单讨论U-Boot的基本的硬件调试方法与技巧
U-Boot的移植之(四)调试篇:下载U-Boot到目标板进行调试
编译完成之后,得到的几个重要文件是:
(1) u-boot.bin: 116K,原始二进制文件,用于下载到启动ROM进行系统引导;
(2) u-boot: 384K,ELF格式映像文件,可加载到SDRAM或SRAM中进行调试;
(3) u-boot.srec: Motorola S-Records格式映像。
(4) System.map: U-Boot映像文件的符号表,各符号的链接地址。
最有效的调试方法是下载到目标板的启动闪存,使用硬件仿真器进行跟踪调试。使用Skyeye,Qemu等软件仿真器不能达到真实的调试效果,尤其不能真实反映第一阶段的底层初始化过程,只适合作U-Boot的学习与研究之用。有人提出在没有硬件仿真器的情况下,使用“点灯大法(利用目标板的LED指示程序运行阶段)”进行跟踪调试,这实际上无异于盲人摸象,特别是在底层初始化阶段,一条指令就可能导致异常。也有人提出注释掉start.S中的lowlevel_init调用,将U-Boot映像加载到SDRAM中进行调试,这实际上只能对U-Boot进行功能调试,而无法跟踪U-Boot的底层初始化过程。当然,如果实在嫌烧写FLASH的速度较慢,又心疼其擦写寿命,也可以将U-Boot映像加载到片上SRAM中调试,因为U-Boot的开始一部分代码是位置无关的(除了后6个异常向量外,不过并不构成影响);这要求片上SRAM够大,因为U-Boot的映像大小约有300K-400K。
笔者使用Banyan-U ARM EMULATOR JTAG仿真器,结合AXD软件平台进行调试。
首先将u-boot.bin下载到FLASH地址0x0,连接好串口,启动minicom或超级终端,目标板上电后,串口控制台无任何输出。这很有可能是lowlevel_init那段代码出了问题,因为它牵涉到GPIO的配置,处理器时钟频率设置,系统总线频率与存储器的时序匹配及初始化,稍有差错就会当机。当然也有可能是串口的配置不正确,但这部分比较简单,出错的可能性比较小。
对于下载到FLASH存储器的原始二进制文件,只能进行汇编级的跟踪调试。先利用objdump工具生成U-Boot映像的反汇编代码:
arm-iwmmxt-linux-gnueabi-objdump -S u-boot > u-boot.S |
反汇编代码u-boot.S和符号表System.map将是跟踪调试过程中的得力助手。
另一个重要的调试技巧是在AXD中现场修改寄存器和存储单元的内容,这样可以帮助我们找到问题所在,而不必每次改动都重新编译u-boot,也避免了FLASH的频繁烧写。
例如,笔者在单步跟踪调试时,发现在地址0xa30804b4处,使用指令”str r1, [r0]”配置GPDR1(GPIO方向寄存器1)后,存储器0x0地址开始的大片内容全部被更改,导致异常终止。这时可以在该处设置一个断点,复位目标板全速运行到断点处,修改寄存器r1的值(GPDR1的初值),再执行该条指令。经试验发现,对于XSBASE270开发板,必须要先初始化GAFRx,再初始化GPDRx,才不致于发生上述异常。而在start.S中,是先完成GPDRx的初始化之后,再初始化GAFRx的,因此需要在源代码中将这两段代码的位置互换,重新编译后,再下载到FLASH中。
U-Boot的串口控制台输出如下:
U-Boot 1.3.0-rc2 (Oct 16 2007 - 01:57:29) DRAM: 64 MB Flash: 32 MB In: serial Out: serial Err: serial Hit any key to stop autoboot: 0 $ |
另一个问题是环境变量的设置与保存。将环境变量保存在FLASH中,使用setenv命令设置环境变量,再使用saveenv命令保存,这样在下次开机时,就会使用新的环境变量。如果使用的是U-Boot自带的CFI闪存驱动,在保存环境变量时可能会出现如下问题:
$ setenv ipaddr 192.168.1.21 $ saveenv Saving Environment to Flash... Un-Protected 1 sectors Erasing Flash... Flash erase error at address 40000 Block Erase Error. Block locked. done Erased 1 sectors |
这是因为缺省情况下U-Boot对FLASH有软件写保护,这时在U-Boot启动完毕后即使使用jflashmm工具也无法对FLASH进行烧写:
[aaronwong@localhost Jflash-XSBase270]$ sudo ./jflashmm u-boot.bin JFLASH Version 5.01.007 COPYRIGHT (C) 2000 - 2003 Intel Corporation PLATFORM SELECTION: Processor= PXA27x Development System= XSBase270 Data Version= 1.00.001 PXA27x revision ?? Found flash type: 28F128K18 Erasing block at address 0 Error, Block erase timed out |
解决办法可参考Uboot-Users邮件列表Erase error on dual P30(CFI) flash chips主题讨论,具体是在include/configs/xsbase270.h中定义CFG_FLASH_PROTECTION,该选项在README文件中的描述如下:
- CFG_FLASH_PROTECTION If defined, hardware flash sectors protection is used instead of U-Boot software protection. |
修改完毕重新编译U-Boot,在目标板上电后U-Boot启动完毕之前,使用jflashmm工具将新的u-boot.bin烧写到目标板启动闪存。这时可成功修改环境变量并保存到FLASH中:
$ setenv ipaddr 192.168.1.21 $ saveenv Saving Environment to Flash... . done Un-Protected 1 sectors Erasing Flash... . done Erased 1 sectors Writing to Flash... done . done Protected 1 sectors |
一旦U-Boot的基本功能调试通过,能正常在目标板运行,剩余的工作就是根据实际情况调整TEXT_BASE以及内核引导参数,使用U-Boot来引导Linux内核。