2009年4月20日 星期一

u-boot 開機程式流程

64M SDRAM     地址是 0x30000000---0x33FFFFFF
------------------------------------------------------------------------------------------------------------------

我們在flash的開始處燒寫了u-boot.bin,這是可執行的二進制文件
注意和ELF可執行性文件是有區別的, 可察看 u-boot.map 參照記憶體位址是否正確.

cpu上電後可以從直接從flash地址0處取指令來執行

開始的代碼在 u-boot\cpu\arm920t\start.S中

這裡需要提一下編譯鏈接時用到的一個很重要的鏈接文件
u-boot/board/smdk2440/u-boot.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 這一句,指示鏈接基地址,不過其實這句是不起作用的,
真正的鏈接基地址在 board/samsung/smdk2440/config.mk 中指定的
LDFLAGS += -Bstatic -T $(LDSCRIPT) -Ttext $(TEXT_BASE)
其中-Ttext $(TEXT_BASE)就是指定鏈接地址為TEXT_BASE的值




因而是可變的,TEXT_BASE在 board/samsung/smdk2440/config.mk中定義
TEXT_BASE = 0x33F00000
board/samsung/smdk2440/config.mk 是包括到Makefile中的,
在Makefile中有$(LD) $(LDFLAGS) $(OBJS) $(LIBS) $(LIBS) -Map u-boot.map -o u-boot
所以說起來真正的鏈接地址是0x33F00000,其實這樣在把u-boot拷到Ram中就可以實現無縫跳轉了
.globl _start
_start: b       reset
跳到renset
reset: ldr     r0, =pWTCON
 mov     r1, #0x0
 str     r1, [r0]
..........................
 bl cpu_init_crit  //bl跳轉會回來
relocate: //下面開始要把u-boot拷到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

//通過這一句跳轉到u-boot\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:\u-boot\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這條命令
否則就會到u-boot的命令行等待輸入.
執行bootm 30008000 30800000\r這條命令會調用u-boot\common\cmd_bootm.c中的
do_bootm函數,具體的命令怎樣被分解,選擇調用函數的機制我就不多說了,追著run_command去就是了
在do_bootm中調用了do_bootm_linux函數,這個函數在u-boot\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去執行內核了

接下來就到內核了吧

2009年4月2日 星期四

Embedded Linux Booting Process

往往開發embedded linux device的開發者都會面臨到一個大問題就是開機過程太久,
Linux在開機時會將一連串的程序叫起並啟動, 所以會花許久的時間等待所有程序被一一叫起,
在這邊介紹兩種方法可以平行處理初始化的程序.






先介紹傳統PC上linux開機流程是如何運作的




BIOS –> Kernel Boot –> System Init



  • BIOS 初始化硬體週邊, 例如主機板, CD-ROM, …
  • Kernel Boot



第1階段的Boot程式是存放在MBR之中, 第1階段的Boot loader透過partition
table找到第二階段的Boot loader程式, 並將他load進SDRAM並執行它, 接下來會將kernel
image和initial RAM disk image (initrd)也會被load到SDRAM上.




Kernel Boot雖然是很複雜的過程, 但幾乎都寫成機器語言了, 所以Kernel Boot執行的時間相當短,
執行完後第一個被執行的程序是init, 是所有程序之母



  • System Init



Linux是利用Init程序去初始化所有服務跟應用程式, 會開啟/etc/inittab檔案, 裡面可以設定如何初始化整個系統,
下面是個configuration example, 裡面的格式為id:runlevel:action:process



# The default runlevel


id:2:initdefault






# Boot-time system configuration/initialization script


si::sysinit:/etc/init.d/rcS






# Runlevels


l0:0:wait:/etc/init.d/rc 0


l1:1:wait:/etc/init.d/rc 1


l2:2:wait:/etc/init.d/rc 2


l3:3:wait:/etc/init.d/rc 3


l4:4:wait:/etc/init.d/rc 4


l5:5:wait:/etc/init.d/rc 5


l6:6:wait:/etc/init.d/rc 6


z6:6:respawn:/sbin/sulogin






# How to react to ctrl-alt-del


ca:12345:ctrlaltdel:/sbin/shutdown -t1 -a -r now



Init程序是有缺點的,所花的時間是串列累加的, 若能改成連漪式累加時間是最佳的選擇, 下一篇會介紹3種改進boot時間的程序:
Initng, Upstart, Monitoring init performance with bootchart




參考文獻:




1.
http://www.ibm.com/developerworks/linux/library/l-boot-faster/




2. http://209.85.175.104/search?q=cache:aU49jDJGEhUJ:www.zhuaxia.com/pre_channel/4806028/%3FlogId%3D181+openmoko+%E6%B5%81%E7%A8%8B&hl=zh-TW&ct=clnk&cd=7&gl=tw