arm linux 启动流程之 ppcboot

来源:互联网 发布:淘宝网安全地垫 编辑:程序博客网 时间:2024/07/24 00:47

不是每一行代码都必须读懂,我只是大概地过一下流程
毕竟这些都是比较成熟的代码,没必要去改的
是针对我自己的板子的,硬件配置如下
cpu是s3c2410
board type 是 smdk2410
16M Nor Flash 地址是 0x0---0xFFFFFF
64M SDRAM     地址是 0x30000000---0x33FFFFFF
软件是华恒版的
ppcboot 2.0 和 linux 2.4.18
仔细分析了一下启动的流程,能更好地理解硬件和软件的配合
方便移植。
我们在flash的开始处烧写了ppcboot.bin,这是可执行的二进制文件
注意和ELF可执行性文件是有区别的。
cpu上电后可以从直接从flash地址0处取指令来执行
开始的代码在ppcboot-2.0.0\cpu\arm920t\start.s中
这里需要提一下编译链接时用到的一个很重要的链接文件
ppcboot-2.0.0\board\smdk2410\ppcboot.lds
这个文件给出了代码中各标号的基地址,和各个段的链接顺序
ENTRY(_start)
SECTIONS
{
        . = 0x00000000;

        . = ALIGN(4);
 .text      :
 {
   cpu/arm920t/start.o (.text)
   *(.text)
 }

        . = ALIGN(4);
        .rodata : { *(.rodata) }

        . = ALIGN(4);
        .data : { *(.data) }

        . = ALIGN(4);
        .got : { *(.got) }

 armboot_end_data = .;

        . = ALIGN(4);
        .bss : { *(.bss) }

 armboot_end = .;
}
可以看到程序的入口是_start标号指示的,而cpu/arm920t/start.o
则被安排在程序最开始的地方,这个标号就是在start.s中
但是还有一点是需要特别注意的,开始我也是因为这个地方而没有很好地理解程序
虽然lds中有 . = 0x00000000 这一句,指示链接基地址,不过其实这句是不起作用的,
真正的链接基地址在ppcboot-2.0.0\config.mk中指定的
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)
其中-Ttext $(TEXT_BASE)就是指定链接地址为TEXT_BASE的值
因而是可变的,TEXT_BASE在ppcboot-2.0.0\board\smdk2410\config.mk中定义
TEXT_BASE = 0x33F00000
ppcboot-2.0.0\config.mk是包括到Makefile中的,
在Makefile中有$(LD) $(LDFLAGS) $(OBJS) $(LIBS) $(LIBS) -Map ppcboot.map -o ppcboot
所以说起来真正的链接地址是0x33F00000,其实这样在把ppcboot拷到Ram中就可以实现无缝跳转了
.globl _start
_start: b       reset
跳到renset
reset: ldr     r0, =pWTCON
 mov     r1, #0x0
 str     r1, [r0]
..........................
 bl cpu_init_crit  //bl跳转会回来
relocate: //下面开始要把ppcboot拷到Ram中
 adr r0, _start  /* r0 <- current position of code */
 ldr r2, _armboot_start
 ldr r3, _armboot_end
 sub r2, r3, r2  /* r2 <- size of armboot */
 ldr r1, _TEXT_BASE  /* r1 <- destination address */
 add r2, r0, r2  /* r2 <- source end address */
以上代码需要注意的一点是 adr 和 ldr 的区别
adr取得是当前pc相关的偏移地址,在这里程序还是在flash中运行
所以取得地址是以0x0为基址的
而ldr取的是_armboot_start所指的值
.globl _armboot_start
_armboot_start:
 .word _start
看到它的值也是_start的地址,不过我们这里取的是绝对地址,是在链接是确定的以
TEXT_BASE为基址的.由于_start的偏移是0,所以r0是0,r2就是TEXT_BASE
copy_loop:
 ldmia r0!, {r3-r10}
 stmia r1!, {r3-r10}
 cmp r0, r2
 ble copy_loop
循环copy
 ldr r0, _armboot_end  /* set up the stack */
 add r0, r0, #CONFIG_STACKSIZE
 sub sp, r0, #12  /* leave 3 words for abort-stack */

 ldr pc, _start_armboot
_start_armboot: .word start_armboot

//通过这一句跳转到ppcboot-2.0.0\lib_arm\board.c中的start_armboot函数去执行了
start_armboot的绝对地址也是以TEXT_BASE为基址的,所以可以顺利的实现无缝跳转了.
接着下来就是一系列初始化的工作了
首先定义了一个全局的数据结构 gd_t gd_data;
DECLARE_GLOBAL_DATA_PTR 这个宏定义的是一个全局的gd_t类型的指针gd
gd = &gd_data;
这样以后就可以用gd来访问gd_data这个数据结构了
 for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
  if ((*init_fnc_ptr)() != 0) {
   hang ();
  }
 }
init_fnc_ptr中是一系列初始化函数的指针
init_fnc_t *init_sequence[] = {
 cpu_init,  /* basic cpu dependent setup */
 board_init,  /* basic board dependent setup */
 interrupt_init,  /* set up exceptions */
 env_init,  /* initialize environment */
 init_baudrate,  /* initialze baudrate settings */
 serial_init,  /* serial communications setup */
 display_banner,
 dram_init,  /* configure available RAM banks */
 display_dram_config,

 NULL,
};
基本上serial_init后我们就可以用printf函数来打印信息了.
 for (;;) {
  main_loop ();
 }
进入了主循环,在M:\ppcboot-2.0.0\common\main.c中
 {
  char c = 'y';
  unsigned long timedata;
  printf("start linux now(y/n):");
  timedata = 0;
  for (;;) {
   while (!tstc()) { /* while no incoming data */
    if (timedata++ > 3000 * 100 *3)
     goto bootm; /* timed out */
   }
  c = getc();
  }
tstc()是测试串口是否有数据输入,显然没有的话就会等待time out跳出
bootm:
  if(c == 'y'||c == 'Y'){
   strcpy(lastcommand , "bootm 30008000 30800000\r");
   flag = 0;
   rc = run_command (lastcommand, flag);
   if (rc <= 0) {
    /* invalid command or not repeatable, forget it */
    lastcommand[0] = 0;
   }
  }
  else{
   printf("\n\n");
  }
 }
这样如果串口没有输入或者输入时y Y 的话,就会去执行bootm 30008000 30800000\r这条命令
否则就会到ppcboot的命令行等待输入.
执行bootm 30008000 30800000\r这条命令会调用ppcboot-2.0.0\common\cmd_bootm.c中的
do_bootm函数,具体的命令怎样被分解,选择调用函数的机制我就不多说了,追着run_command去就是了
在do_bootm中调用了do_bootm_linux函数,这个函数在ppcboot-2.0.0\lib_arm\armlinux.c中
    ret = memcpy((void *)0x30008000, (void *)0x40000, 0x100000);
    if (ret != (void *)0x30008000)
     printf("copy kernel failed\n");
    else
    printf("copy kernel done\n");     

    ret = memcpy((void *)0x30800000, (void *)0x140000, 0x440000);
    if (ret != (void *)0x30800000)
              printf("haha failed\n");
    else
              printf("copy ramdisk done\n");

首先把kernel和ramdisk都拷到Ram相应的地方去.
    setup_linux_param(0x30000000 + LINUX_PARAM_OFFSET); //也在armlinux.c中
    #define LINUX_PARAM_OFFSET 0x100
建立要传给内核的参数,参数的地址都是固定的,所以内核也知道去这里取参数
参数格式比较复杂,我这里好像传得参数不多
void setup_linux_param(ulong param_base)
{
 struct param_struct *params = (struct param_struct *)param_base;
...............
}
只是通过一个param_struct的结构体来传参数的,不过现在一般都用另一种tag标记的传参方法
一个主要的参数时char linux_cmd[] = "initrd=0x30800000,0x440000  root=/dev/ram init=/linuxrc console=ttyS0";
 if (linux_cmd == NULL) {
  printf("Wrong magic: could not found linux command line\n");
 } else {
  memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
  printf("linux command line is: \"%s\"\n", linux_cmd);
 }
是比较重要的.移植的时候常常需要修改
接着call_linux(0, 0xc1, 0x30008000); 看出来是准备调到linux去了
0xc1是machine type,这三个参数分别给了r0,r1,r2,这些都是调用内核的约定
void  call_linux(long a0, long a1, long a2)
{
__asm__(
 "mov r0, %0\n"
 "mov r1, %1\n"
 "mov r2, %2\n"
 "mov ip, #0\n"
 "mcr p15, 0, ip, c13, c0, 0\n" /* zero PID */
 "mcr p15, 0, ip, c7, c7, 0\n" /* invalidate I,D caches */
 "mcr p15, 0, ip, c7, c10, 4\n" /* drain write buffer */
 "mcr p15, 0, ip, c8, c7, 0\n" /* invalidate I,D TLBs */
 "mrc p15, 0, ip, c1, c0, 0\n" /* get control register */
 "bic ip, ip, #0x0001\n"  /* disable MMU */
 "mcr p15, 0, ip, c1, c0, 0\n" /* write control register */
 "mov pc, r2\n"
 "nop\n"
 "nop\n"
 : /* no outpus */
 : "r" (a0), "r" (a1), "r" (a2)
 );
}
mov pc, r2 就是这句吧,调到了30008000去执行内核了

接下来就到内核了吧

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dansen_xu/archive/2007/08/12/1739650.aspx