如何编译boot loader

来源:互联网 发布:python np.split axis 编辑:程序博客网 时间:2024/06/05 07:48

原文:

BootLoader实验--转载

链接:http://blog.chinaunix.net/uid-23711997-id-131364.html

BootLoader实验
【实验目的】
1、了解BootLoader的基本概念和框架结构
2、了解BootLoader引导操作系统的过程
3、掌握bootloader程序的编译方法
4、掌握BootLoader程序的使用方法
【实验原理】
1、bootLoader的作用
       PC 机中的引导加载程序由 BIOS和位于硬盘 MBR 中的 OS Boot Loader一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘 MBR 中的 Boot Loader 读到系统的 RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到 RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
       嵌入式系统中,通常并没有像 BIOS 那样的固件程序,因此整个系统的加载启动任务完全由 bootLoader 来完成。bootloader的主要作用:
(1)、初始化硬件设备
(2)、建立内存空间的映射图
(3)、完成内核的加载,为内核设置启动参数
2、bootLoader程序结构框架
       嵌入式系统中的boot Loader 的实现完全依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为第一阶段和第二阶段两大部分,依赖于CPU体系结构的代码,比如设备初始化代码等,通常都放在阶段1中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而阶段2 则通常用C语言来实现,这样可以实现一些复杂的功能,而且代码会具有更好的可读性和可移植性。
(1)、Boot Loader 的阶段1通常主要包括以下步骤:
l         硬件设备初始化;
l         拷贝Boot Loader的程序到RAM空间中;
l         设置好堆栈;
l         跳转到阶段2的C入口点。
(2)、Boot Loader的阶段2通常主要包括以下步骤:
l         初始化本阶段要使用到的硬件设备;
l         系统内存映射(memory map);
l         将kernel映像和根文件系统映像从Flash读到RAM空间中;
l         为内核设置启动参数;
l         调用内核。
3、bootLoader程序架构分析
3.1 Boot Loader 阶段1分析
(1) 基本的硬件初始化
       Boot Loader一开始就执行基本硬件初始化操作,其目的是为阶段2的执行以及随后的内核的执行准备好一些基本的硬件环境。它通常包括以下步骤:
l         初始化GPIO功能,通过GPIO来驱动LED,其目的是表明系统的状态是OK还是Error。如果板子上没有LED,那么也可以通过初始化UART向串口打印Boot Loader的Logo字符信息来完成这一点。(参考fixgpio.S

@ fixgpio.S
define_gpio:
……
       ldr          r1, =GPIO_BASE /* 0x40E00000*/
 
       ldr          r0, =_GPSR0           /*0x00003000*/
       str          r0, [r1, #GPSR0]        /*GPIO<13,12> is set as output */
       ldr          r0, =_GPCR0      /* 0x00000800*/
       str          r0, [r1, #GPCR0]   /*GPIO<11> is clear to level zero*/
       ldr          r0, =_GAFR0L       /* 0x80000000*/
       str          r0, [r1, #GAFR0L] /* GPIO(15) is set as alternate functions 2->nCS<1>*/
       ldr          r0, =_GAFR0U      /* 0x00000010 | (2<<0 )*/
       str          r0, [r1, #GAFR0U] /*GPIO<18,16> set as alternate functions 2
……
   
l         设置CPU的速度和时钟频率。(参考setup_memory.S部分代码)

clock_enable :/* start.S*/
#if defined(CONFIG_PXA25x)
       ldr          r0, =0x0001FFFF
#elif defined(CONFIG_PXA27x)
       ldr          r0, =0x01FFFFFF
#endif
       ldr          r1, =CKEN
       str          r0, [r1]
       mov        pc, lr
l         存储控制单元初始化。包括正确地设置系统动静态存储控制器的各个寄存器等。

setup_memory : /* memsetup.S*/
       @ change cpu speed function
       ldr          r1, =CCCR
       ldr          r0, =_CCCR
       str          r0, [r1]                                @ set CCCR 
……
(2)、将bootLoader程序加载到RAM空间

       /* copy bootloader to dynamic memory area*/
       ldr          r0, =0x00
       ldr          r1, =__boot_start /* @boot.ld.in file */
       ldr          r2, =__boot_end     /* @boot.ld.in file */
 1:   ldmia       r0!, {r3-r10}
       stmia       r1!, {r3-r10}
       cmp        r1, r2
       blt           1b
(3)、设置堆栈指针sp
       堆栈指针的设置是为了执行C语言代码作好准备。通常我们可以把sp的值安排在RAM
空间的最顶端(堆栈向下生长)。此外,在设置堆栈指针sp之前,也可以关闭led灯,以提示准备跳转到阶段2。经过上述这些执行步骤后,系统的物理内存布局应该如图3-1所示。

       @ set stack point
       ldr          sp, =stack_point-4  /*stack_point = __boot_start + 0x00100000;*/
图3-1 系统的物理内存布局
(4)、跳转到阶段2的C入口点
       在上述一切都就绪后,就可以跳转到Boot Loader的阶段2去执行了。在ARM系统中,
可以通过修改PC寄存器为合适的地址来实现。

ldr          pc, =main /* @ start.S file*/
3.2 Boot Loader 阶段2分析
       阶段2的代码通常用C语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通C语言应用程序不同的是,在编译和链接Boot Loader程序时,不能使用glibc库中的任何支持函数。可以直接把main()函数的起始地址作为整个阶段2执行映像的入口点。

       @ jump to c code
       ldr          pc, =main
(1)、初始化本阶段要使用到的硬件设备;
       本阶段初始化的硬件设备通常包括:
l         初始化至少一个串口,以便和终端用户进行I/O输出信息;
l         初始化计时器、
l         初始化网络传输等。
       在初始化这些设备之前,也可以重新把LED灯点亮,以表明程序bootLoader程序已经进入main()函数执行。设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。(参考main.c文件

    ……
(*(volatile unsigned short  *)(0x0a000000)) |= (1<<9)|(1<<8)|(1<<6)|(1<<15);
       
    uart_init();     //初始化串口
       time_init();      //初始化定时器
       config_init();   //内存映射配置初始化
       //信息提示
       printf("\033[H\033[J\n");              // clear screen.
    printf(" %s : bootloader for Xscale 270 board\n", PACKAGE);
    printf(" Copyright (C) 2002-2004 Emdoor Co,. ltd.\n");
       printf(" support : http://www.emdoor.com\n");
 
       iflash_init();    //flash存储器初始化
    PWMPCR0 = 0xff;
    PWMDCR0 = (0x4ff>>2);
        //let eth CS is ok
        (*(volatile unsigned long *)(0x4800000c)) = _MSC1_ED;
       eth_init(); //网络初始化
……
(2)、系统的内存映射
       所谓内存映射就是指在整个4GB物理地址空间中有哪些地址范围被分配用来寻址系统的RAM单元。虽然CPU通常预留出一大段足够的地址空间给系统RAM,但是在搭建具体的嵌入式系统时却不一定会实现CPU预留的全部RAM地址空间。也就是说,具体的嵌入式系统往往只把CPU预留的全部RAM地址空间中的一部分映射到RAM单元上。开发板的bootloader程序利用map __bsetup parts[]结构体对内存的分配进行配置(参考partition.c),从内存分布可以看出,PXA27X开发板SRAM和SDRAM起始地址的存储分布情况:
bootloader:   0x00000000_0x00040000,共占256K,SDRAM起始地址:0xA1E00000
kernel:         0x00040000_0x00180000,共占1.25M ,SDRAM起始地址:0xA0008000
root:     0x00180000_0x02000000,共占30.5M,SDRAM起始地址0xA0000000

struct map __bsetup parts[] = {
       {
              .name   = "loader",
              .sramb = LOADER_SRAM_BASE,           //0x00000000
              .srams = LOADER_MAX_SIZE,              //0x00040000->256K
              .dramb = LOADER_DRAM_BASE,          //0xA1E00000
              .drams = 0,
              .maxs   = LOADER_MAX_SIZE,     
       }, {
              .name   = "kernel",
              .sramb = KERNEL_SRAM_BASE,            //0x00040000
              .srams = KERNEL_MAX_SIZE,        //0x00140000->1.25M
              .dramb = KERNEL_DRAM_BASE,           //0xA0008000
              .drams = 0,
              .maxs   = KERNEL_MAX_SIZE,
       }, {
              .name   = "ramdisk",
              .sramb = RAMDISK_SRAM_BASE,        //0x00180000
              .srams = RAMDISK_MAX_SIZE,            //0x00300000->3M
              .dramb = RAMDISK_DRAM_BASE, //0xA1000000
              .drams = 0,
              .maxs   = RAMDISK_MAX_SIZE,
       }, {
              .name   = "root",
              .sramb = ROOTFS_SRAM_BASE,           //0x00180000
              .srams = ROOTFS_MAX_SIZE,                     //0x01e80000->30.5M
              .dramb = ROOTFS_DRAM_BASE,          //0xA0000000
              .drams = 0,
              .maxs   = ROOTFS_MAX_SIZE,
       }
};
 
(3)、加载内核映像和根文件系统映像
l         规划内存占用的布局
       这里包括两个方面:① 内核映像所占用的内存范围;② 根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。
       对于内核映像,一般将其拷贝到从(MEM_START+0x8000)这个基地址开始的大约1MB大小的内存范围内(嵌入式Linux的内核一般都不操过1MB)。为什么要把从MEM_START到MEM_START+0x8000这段32KB大小的内存空出来呢?这是因为Linux内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。
       而对于根文件系统映像,则一般将其拷贝到MEM_START+0x00100000开始的地方。如果用Ramdisk作为根文件系统映像,则其解压后的大小一般是1MB。
(4)、设置内核的启动参数
       应该说,在将内核映像和根文件系统映像拷贝到RAM空间中后,就可以准备启动Linux
内核了。但是在调用内核之前,应该作一步准备工作,即:设置Linux内核的启动参数。 Linux 2.4.x 以后的内核都倾向以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记ATAG_CORE开始,以标记ATAG_NONE结束。每个标记由tag_header结构和随后的特定参数值数据结构来组成。(参考文件:linux.c)

……
#define ATAG_NONE    0x00000000
struct tag_header {
       uint32 size;
       uint32 tag;
};
……
struct tag {
       struct tag_header hdr;
       union {
              struct tag_core              core;
              struct tag_mem32 mem;
              struct tag_videotext       videotext;
              struct tag_ramdisk ramdisk;
              struct tag_initrd      initrd;
              struct tag_serialnr   serialnr;
              struct tag_revision revision;
              struct tag_videolfb videolfb;
              struct tag_cmdline cmdline;
              struct tag_acorn     acorn;
              /*
               * DC21285 specific
               */
              struct tag_memclk memclk;
       } u;
};
       在嵌入式Linux系统中,通常需要由Boot Loader设置的常见启动参数有:ATAG_CORE、
ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。
比如,设置ATAG_CORE的代码如下:

void create_tags(void){
       tags = (struct tag *)BOOT_PARAMS;
       setup_tag_core(0, 0);
       setup_tag_initrd2(0xA1000000, 0x00420000);
       setup_end_tag();
       return;
}
 
static void setup_tag_core(uint32 rootdev, uint32 flags){
       tags->hdr.tag = ATAG_CORE;
       tags->hdr.size = tag_size(tag_core);
       tags->u.core.flags = flags;                                 // not use.
       tags->u.core.pagesize = 0;                                 // set read/write.
       tags->u.core.rootdev = 0;
       tags = tag_next(tags);
       return;
}
       其中,BOOT_PARAMS 表示内核启动参数在内存中的起始基地址,指针tags是一个struct tag类型的指针。宏tag_next()将以指向当前标记的指针为参数,计算出当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备ID就是在这里设置的。
       下面是设置ATAG_INITRD的示例代码,它告诉内核在RAM中的什么地方可以找到initrd映象(压缩格式)以及它的大小:

static void setup_tag_initrd2(uint32 start, uint32 size){
       tags->hdr.tag = ATAG_INITRD2;
       tags->hdr.size = tag_size(tag_initrd);
       tags->u.initrd.start = start;
       tags->u.initrd.size = size;
       tags = tag_next(tags);
       return;
}
最后,设置ATAG_NONE标记,结束整个启动参数列表:

static void setup_end_tag(void){
       tags->hdr.tag = ATAG_NONE;
       tags->hdr.size = 0;
       return;
}
(5)、调用内核
       Boot Loader调用Linux内核的方法是直接跳转到内核的第一条指令处,也即直接跳转
到MEM_START+0x8000地址处。在跳转时,下列条件要满足:
l         CPU寄存器的设置:
       R0=0;
       R1=机器类型ID;关于机器类型号,可以参见linux/arch/arm/tools/mach-types;
       R2=启动参数标记列表在RAM中起始基地址;
l         CPU 模式:
       必须禁止中断(IRQs和FIQs);
       CPU必须SVC模式;
l         Cache和MMU的设置:
       MMU必须关闭;
       指令Cache可以打开也可以关闭;
       数据Cache必须关闭;
如果用C语言,可以像下列示例代码这样来调用内核:(粗体表示)

static bool do_boot(int argc, char **argv){
       void (*kernel)(int zero, int arch);
       if (argc == 1) {
              struct map *mp;
              mp = find_map("kernel");
              if (!mp){ printf(" can't found map for kernel.\n"); goto invalid; }
              kernel = (void *)mp->dramb;
       } else if (argc == 2){
              bool res;
              ulong tmp;
              res = strtoul(argv[1], &tmp, 16);
              if (!res) goto invalid;
              kernel = (void *)tmp;
       } else goto invalid;
       if (!get_kernel_size(kernel)){ printf(" error: kernel is not exists.\n"); return false; }
       create_tags();
       printf("starting kernel ...\n");
       kernel(0, 200);
       return true;
invalid :
       boot_usage();
       return false;
}
 
【实验仪器】
1、装有Linux操作系统的PC机一台;
2、XSBase270或XSBase255 ARM实验开发平台一套
【实验内容】
1、bootLoader程序的编译
       为了编译Bootloader程序,需要事先在目标板上安装交叉编译工具Toolchain。(见linux使用手册Toolchain安装部分)。
(1)、利用 tar zxvf Boot-XSBase270.tar.gz 指令解压。
[root@localhost BootLoader]$tar zxvf Boot-XSBase270.tar.gz
       利用上述命令解压后,bootloader源代码解压到当前目录中Boot-XSBase270文件夹中。
(2)、编译。在解压的目录里进行make编译
[root@localhost BootLoader]$ cd Boot-XSBase270
[root@localhost Boot-XSBase270]$make
       编译完成后, 在当前目录下会生成bootloader映象文件boot。
2、bootLoader程序的下载
       将bootloader的映象文件boot拷贝Jflash-XSBase270目录下,连好JTAG下载器,并利用Jflash-XSBase270目录中jflashmm程序将bootloader映象文件boot烧写到开发板上。
[root@localhost Boot-XSBase270]$cp boot /root/EmdoorARM/Jflash/Jflash-XSBase270/
[root@localhost Boot-XSBase270]$ cd /root/EmdoorARM/Jflash/Jflash-XSBase270
[root@localhost Boot-XSBase270]$ ./jflashmm boot
3、bootLoader的使用方法
 Bootloader命令的使用方法。
help
用法
帮助
描述
简短显示各命令的介绍
参数
举例
Bboot> Help
Load
用法
load [kernel/ramdisk]
描述
把存放在FLASH中的映像文件拷贝到SDRAM中。在Autoboot
过程中会自动运行,把内核映像从FLASH加载到SDRAM中。
参数
Kernel    -    把内核映像从FLASH拷贝到SDRAM中
Ramdisk-     从FLASH中拷贝RAMDISK到SDRAM
举例
Bboot> load kernel
bootp
用法
Bootp
描述
运行bootp命令用来获取HOST主机发送的BOOTP的数据包,解析
BOOTP的数据包,获取本机的IP地址
参数
举例
Bboot> bootp
tftp
用法
Tftp 文件名 {address/loader/kernel/root/ramdisk}
描述
通过以太网下载主机的数据或文件到目标平台的SDRAM
参数
文件名 - 主机平台需要传输的文件名
loader - 把传输到目标平台的文件放置在SDRAM的loader区域
kernel - 把传输到目标平台的文件放置在SDRAM的kernel区域
root - 把传输到目标平台的文件放置在SDRAM的root区域
ramdisk - 把传输到目标平台的文件放置在SDRAM的ramdisk区域
address - 把传输到目标平台的文件放置在SDRAM的指定地址
举例
Bboot> tftp zImage kernel
flash
用法
Flash {loader/kernel/root/ramdisk}
描述
把SDRAM中的数据烧录到FLASH中特定的地址
参数
loader - 把SDRAM中的数据烧录到FLASH中的loader区域
kernel – 把SDRAM中的数据烧录到FALSH中的kernel区域
root – 把SDRAM中的数据烧录到FLASH中的root区域
ramdisk – 把SDRAM中的数据烧录到FLASH中的ramdisk的区域
举例
Bboot> flash kernel
erase
用法
erase {loader/kernel/ramdisk/root}
描述
擦除FALSH中的相应区域
参数
loader – 擦除FALSH中loder区域
kernel – 擦除FLASH中的kernel区域
root – 擦除FLASH中的root区域
ramdisk – 擦除FLASH中的ramdisk区域
举例
bboot> erase kernel
boot
用法
Boot
boot [addr]
描述
在SDRAM中运行kernel
通过参数中指定的地址运行kernel
参数
addr – kernel image address
举例
bboot> boot
set
用法
set [name] [value]
描述
设置IP地址,MAC地址以及autoboot参数
参数
Name [myipaddr] [destipaddr] [myhaddr] [autoboot]
Value [ip] [ip] [mac address] [load kernel; boot]
举例
bboot> set        //输出设置信息
bboot> set myipaddr 192.168.100.X    //改变目标平台的IP地址
bboot> set destipaddr 192.168.100.XX //改变主机平台的IP地址
bboot> set myhaddr 00:0E:6F:CE:59:21 //设置目标平台的MAC地址
bboot> set autoboot load kernel; boot   //装载kernel后自动启动
ping
用法
Ping
描述
检查目标平台和主机平台的网络连接
参数
Ping主机平台的ip地址
举例
Ping 192.168.100.xx
reboot
用法
Reboot
描述
软件复位
参数
None
举例
Reboot
【思考题】
1、分析bootloader源程序中的flash.c代码,画出该代码的流程图
2、利用实验二Makefile的知识点,分析bootloader源程序三个Makefile的结构与关系
3、某同学在bootloader源程序目录中,利用make编译bootloader源程序,出现如下错误:
[root@hostlocal Boot-XSBase270]make
compile start.S
make[1]: arm-linux-gcc: Command not found
make[1]:***[start.o] Error 127
make: *** [all] Error 2
       请分析产生错误的原因,并给出解决办法
4、bootloader主要功能有哪些?
5、请在阅读完bootloader程序后,画出bootloader程序框架流程.。

0 0
原创粉丝点击