本人用的android平台用的bootloader用的是uboot,貌似大多數手持裝置平台都不用這個,因為功能過于強大用不上,反而顯得太複雜了。不知道這個平台開發者是怎麼想的。既然用了那就來分析一下,順便修改一下其中的幾個小問題,以符合我們的要求。
uboot等同于其他所有的bootloader程式,從根本上講是一個稍複雜的裸機程式,是最底層的東西,要分析裸機程式我們要從它的連接配接檔案開始。連接配接檔案(.lds檔案)定義了程式編譯之後整個連接配接過程,這樣我們就可以找到這個程式的第一句彙編代碼,進而來下一步分析。uboot的連結檔案代碼在android\bootable\bootloader\uboot-imx\u-boot.lds
檢視文本 列印 ?
- OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") //檔案輸出格式
- OUTPUT_ARCH(arm)
- ENTRY(_start) //首位址标示符
- SECTIONS
- {
- . = 0x00000000; //其實位址0
- . = ALIGN(4); //4位元組對齊
- .text : //代碼段
- {
- board/freescale/mx6q_sabresd/flash_header.o (.text.flasheader) //第一個檔案是board/freescale/mx6q_sabresd/flash_header.o
- cpu/arm_cortexa8/start.o //第二個cpu/arm_cortexa8/start.o
- board/freescale/mx6q_sabresd/libmx6q_sabresd.a (.text)
- lib_arm/libarm.a (.text)
- net/libnet.a (.text)
- drivers/mtd/libmtd.a (.text)
- drivers/mmc/libmmc.a (.text)
- . = DEFINED(env_offset) ? env_offset : .;
- common/env_embedded.o(.text)
- *(.text) //剩餘的所有代碼
- }
- . = ALIGN(4);
- .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } //readonly data 段
- . = ALIGN(4);
- .data : { *(.data) } //所有的readonly data
- . = ALIGN(4);
- .got : { *(.got) }
- . = .;
- __u_boot_cmd_start = .; //u_boot_cmd段,裡面是所有uboot指令的一個清單
- .u_boot_cmd : { *(.u_boot_cmd) }
- __u_boot_cmd_end = .;
- . = ALIGN(4);
- _end_of_copy = .;
- __bss_start = .; //bss段 就是記憶體資料段
- .bss : { *(.bss) }
- _end = .;
- }
從上面的代碼可以看出我們編譯生成的二進制應用程式組成是:代碼段->rodata段->uboot指令清單->bss段。我們 啟動 這個應用程式時候是從,0位址開始的,是以我們來看
board/freescale/mx6q_sabresd/flash_header.s這個檔案。
這個檔案中除了配置設定記憶體和宏定義的僞彙編指令以外,真正執行的指令有一條
檢視文本 列印 ?
- .section ".text.flasheader", "x"
- b _start
- .org CONFIG_FLASH_HEADER_OFFSET
也就是說,這個檔案一執行就直接跳到_start 位置處。_start 在android\bootable\bootloader\uboot-imx\cpu\arm_cortexa8\ start.S中,是以我們來看這個檔案代碼
檢視文本 列印 ?
- .globl _start
- _start: b reset
這裡直接跳轉的reset中接下來看
檢視文本 列印 ?
- reset:
- mrs r0, cpsr
- bic r0, r0, #0x1f
- orr r0, r0, #0xd3
- msr cpsr,r0
- #if (CONFIG_OMAP34XX) //因為我們的cpu不是ompa的 是以這段不會編譯
- .............................
- #endif
- #ifndef CONFIG_SKIP_LOWLEVEL_INIT
- bl cpu_init_crit
- #endif
這裡接下來執行cpu_init_crit
檢視文本 列印 ?
- cpu_init_crit:
- mov r0, #0 @ set up for MCR
- mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs
- mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
- mrc p15, 0, r0, c1, c0, 0
- bic r0, r0, #0x00002000 @ clear bits 13 (--V-)
- bic r0, r0, #0x00000007 @ clear bits 2:0 (-CAM)
- orr r0, r0, #0x00000002 @ set bit 1 (--A-) Align
- orr r0, r0, #0x00000800 @ set bit 12 (Z---) BTB
- mcr p15, 0, r0, c1, c0, 0
- mov ip, lr @ persevere link reg across call
- bl lowlevel_init @ go setup pll,mux,memory//執行lowlevel_init這個函數代碼在
- @\bootloader\uboot-imx\board\freescale\mx6q_sabresd\lowlevel_init.S中
- @主要對時鐘,外部ram,rom等進行了初始化代碼不貼了。
- mov lr, ip @ restore link
- mov pc, lr @ back to my caller
初始化完成後,接下來執行
檢視文本 列印 ?
- #ifndef CONFIG_SKIP_RELOCATE_UBOOT
- relocate: @ relocate U-Boot to RAM 将uboot重新定位到記憶體中
- 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 @如果在的話就直接跳轉到stack_setup
- ldr r2, _armboot_start @如果不在的話,加載_armboot_start位址到r2中。_armboot_start是uboot執行的主體c函數。
- ldr r3, _bss_start
- sub r2, r3, r2 @ r2 <- size of armboot計算bss_start-armboot_start 儲存到R2中,也就是uboot的總大小
- add r2, r0, r2 @ r2 <- source end address 計算出uboot代碼和rodata位址
- copy_loop: @ copy 32 bytes at a time //開始拷貝
- 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
- #endif
- stack_setup:
- ldr r0, _TEXT_BASE @ upper 128 KiB: relocated uboot
- sub r0, r0, #CONFIG_SYS_MALLOC_LEN @ malloc area//為c語言malloc函數配置設定記憶體
- sub r0, r0, #CONFIG_SYS_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//配置設定c語言堆棧
- and sp, sp, #~7 @ 8 byte alinged for (ldr/str)d
- clear_bss:
- ldr r0, _bss_start @ find start of bss segment //清除bss段
- ldr r1, _bss_end @ stop here
- mov r2, #0x00000000 @ clear value
- clbss_l:
- str r2, [r0] @ clear BSS location
- cmp r0, r1 @ are we at the end yet
- add r0, r0, #4 @ increment clear index pointer
- bne clbss_l @ keep clearing till at end
- #ifdef CONFIG_ARCH_MMU
- bl board_mmu_init //初始化mmu
- #endif
- ldr pc, _start_armboot @ jump to C code以上所有的初始化就已經完成了,接下類正式執行c語言代碼了。這才是我們的重點
- _start_armboot: .word start_armboot
接下來正式看C代碼,也就是start_armboot這個函數代碼在android\bootable\bootloader\uboot-imx\lib_arm\board.c中
檢視文本 列印 ?
- void start_armboot (void)
- {
- init_fnc_t **init_fnc_ptr;
- char *s;
- #if defined(CONFIG_VFD) || defined(CONFIG_LCD)
- unsigned long addr;
- #endif
- gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
- //配置設定一段記憶體.在cpu存儲控制器初始化之前,是不能通路外部ram的,是以需要一小段
- //記憶體來運作最初的初始化函數,這段記憶體一般是cpu内部ram
- __asm__ __volatile__("": : :"memory");
- memset ((void*)gd, 0, sizeof (gd_t));
- gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
- memset (gd->bd, 0, sizeof (bd_t));
- gd->flags |= GD_FLG_RELOC;
- 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 ();
- }
- }
注意看這裡init_sequence的定義
檢視文本 列印 ?
- init_fnc_t *init_sequence[] = {
- #if defined(CONFIG_ARCH_CPU_INIT)
- arch_cpu_init,
- #endif
- board_init,
- #if defined(CONFIG_USE_IRQ)
- interrupt_init,
- #endif
- timer_init,
- env_init,
- init_baudrate,
- serial_init,
- console_init_f,
- display_banner,
- #if defined(CONFIG_DISPLAY_CPUINFO)
- print_cpuinfo,
- #endif
- #if defined(CONFIG_DISPLAY_BOARDINFO)
- checkboard,
- #endif
- #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)
- init_func_i2c,
- #endif
- dram_init,
- #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
- arm_pci_init,
- #endif
- display_dram_config,
- NULL,
- };
這個是一個函數指針的數組,涉及到cpu的最後的一些初始化。到了這裡cpu的所有初始化都完成了
我們繼續看闆子的其他配置
檢視文本 列印 ?
- #ifdef CONFIG_LCD //lcd緩存設定
- if (!gd->fb_base) {
- # ifndef PAGE_SIZE
- # define PAGE_SIZE 4096
- # endif
- addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
- lcd_setmem (addr);
- gd->fb_base = addr;
- }
- #endif
- env_relocate ();//設定環境變量 也就是printenv 列印出來的那些
- #ifdef CONFIG_VFD
- drv_vfd_init(); //空函數
- #endif
- #ifdef CONFIG_SERIAL_MULTI
- serial_initialize();//序列槽初始化
- #endif
- gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
- #if defined CONFIG_SPLASH_SCREEN && defined CONFIG_VIDEO_MX5
- setup_splash_image();//lcd顯示log
- #endif
- //重新定義stdio的位置,在本環境中被定義到了序列槽上
- stdio_init ();
- jumptable_init ();//把一些初始化函數的指針放到gd中,為以後調用
- #if defined(CONFIG_API)
- api_init ();
- #endif
- console_init_r ();
- #if defined(CONFIG_ARCH_MISC_INIT)
- arch_misc_init ();//空函數
- #endif
- #if defined(CONFIG_MISC_INIT_R)
- misc_init_r ();//空函數
- #endif
- enable_interrupts ();//使能中斷
- #ifdef CONFIG_DRIVER_TI_EMAC
- extern void davinci_eth_set_mac_addr (const u_int8_t *addr);//不編譯
- if (getenv ("ethaddr")) {
- uchar enetaddr[6];
- eth_getenv_enetaddr("ethaddr", enetaddr);
- davinci_eth_set_mac_addr(enetaddr);
- }
- #endif
- #ifdef CONFIG_DRIVER_CS8900
- cs8900_get_enetaddr ();//不編譯
- #endif
- #if defined(CONFIG_DRIVER_SMC91111) || defined (CONFIG_DRIVER_LAN91C96)
- if (getenv ("ethaddr")) {
- uchar enetaddr[6];
- eth_getenv_enetaddr("ethaddr", enetaddr);//不編譯
- smc_set_mac_addr(enetaddr);
- }
- #endif
- #if defined(CONFIG_ENC28J60_ETH) && !defined(CONFIG_ETHADDR)
- extern void enc_set_mac_addr (void);//不編譯
- enc_set_mac_addr ();
- #endif
- 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 (); //初始化i2c,pmic等
- #endif
接下來涉及到了我們最關心的地方, 啟動模式 和 按鍵 響應
檢視文本 列印 ?
- #ifdef CONFIG_ANDROID_RECOVERY
- check_recovery_mode(); //檢測是否進入recovery
- #endif
- #if defined(CONFIG_CMD_NET)
- #if defined(CONFIG_NET_MULTI)
- puts ("Net: ");
- #endif
- eth_initialize(gd->bd); //根據gd的配置初始化以太網
- #if defined(CONFIG_RESET_PHY_R)
- debug ("Reset Ethernet PHY\n");
- reset_phy();
- #endif
- #endif
- #ifdef CONFIG_FASTBOOT
- check_fastboot_mode(); //檢測是否進入fastboot
- #endif
從代碼裡可以看出我們是首先檢測recovery,然後才檢測fastboot模式。
我們先來看原版是怎麼做的,首先是recovery
檢視文本 列印 ?
- void check_recovery_mode(void)
- {
- if (check_key_pressing())
- setup_recovery_env();
- else if (check_recovery_cmd_file()) {
- puts("Recovery command file founded!\n");
- setup_recovery_env();
- }
- }
這裡首先檢測是否有合法的 按鍵 按下,如果有的話就配置環境變量進入recovery
沒有按鍵就檢測uboot指令檔案,看是不是主系統要求進入recovery
是以我們這裡的重點是check_key_pressing()這個函數,仔細研究發現這個函數用的是uboot
标準的gpio驅動,官方給我們移植的uboot裡面并沒有初始化這個驅動,而是自己另外寫的。也就是說
我們調用check_key_pressing()這個函數永遠都傳回0值而執行else if (check_recovery_cmd_file())這一句
我們來看 check_recovery_cmd_file()
檢視文本 列印 ?
- int check_recovery_cmd_file(void)
- {
- int button_pressed = 0;
- int recovery_mode = 0;
- recovery_mode = check_and_clean_recovery_flag();//讀取kernel的recovery标志位,如果有的話就要進入recovery
- mxc_iomux_v3_setup_pad(MX6X_IOMUX(PAD_GPIO_5__GPIO_1_5)); //初始化vol down的gpio
- gpio_direction_input(GPIO_VOL_DN_KEY);
- if (gpio_get_value(GPIO_VOL_DN_KEY) == 0) { //如果vol down已經按下
- button_pressed = 1;
- printf("Recovery key pressed\n");
- }
- return recovery_mode || button_pressed; //傳回進入recovery
- }
也就是說官方修改的uboot走了偷懶的方法,直接在check_recovery_cmd_file()增加了一個按鍵的盤定。很不正規
是以我們下一步要修改它,從官方的基礎上走,我們也不走标準uboot 按鍵驅動,而是自己寫。
修改之前先來看原版fastboot怎麼進入的
檢視文本 列印 ?
- void check_fastboot_mode(void)
- {
- if (fastboot_check_and_clean_flag())
- do_fastboot(NULL, 0, 0, 0);
- }
這裡調用fastboot_check_and_clean_flag()來判定是否進入fastboot
檢視文本 列印 ?
- int fastboot_check_and_clean_flag(void)
- {
- int flag_set = 0;
- u32 reg;
- reg = readl(SRC_BASE_ADDR + SRC_GPR10);
- flag_set = !!(reg & ANDROID_FASTBOOT_BOOT);
- if (flag_set) {
- reg &= ~ANDROID_FASTBOOT_BOOT;
- writel(reg, SRC_BASE_ADDR + SRC_GPR10);
- }
- return flag_set;
- }
從這裡看出,要進入進入fastboot,隻能檢測(SRC_BASE_ADDR + SRC_GPR10)寄存器的
ANDROID_FASTBOOT_BOOT位是否被kernel置位,并沒有按鍵,是以我們的闆子不可能靠
按鍵進入fastboot的實際情況也确實這樣。是以我們要修改這一塊,由于我們的cpu在power鍵按住5s
以後會強制關機。是以開機後我們必須松開power鍵,我們闆子檢測的按鍵隻能是1個。開機時vol up鍵進入
recovery,按住vol down進入fastboot模式。我們修改代碼如下
建立個按鍵檢測函數check_key()
檢視文本 列印 ?
- int check_key(void)
- {
- #define PRESSED_VOLUP 1
- #define PRESSED_VOLDOWN 2
- #define KEY_MASK (PRESSED_VOLUP|PRESSED_VOLDOWN)
- #define RECOVERY_KEY_MASK (PRESSED_VOLUP)
- #define FASTBOOT_KEY_MASK (PRESSED_VOLDOWN)
- int state = 0;
- mxc_iomux_v3_setup_pad(MX6X_IOMUX(PAD_GPIO_5__GPIO_1_5));//vol down
- mxc_iomux_v3_setup_pad(MX6X_IOMUX(PAD_GPIO_5__GPIO_1_4));//vol up
- gpio_direction_input(GPIO_VOL_DN_KEY);
- gpio_direction_input(GPIO_VOL_UP_KEY);
- if (gpio_get_value(GPIO_VOL_UP_KEY) == 0)
- state |= PRESSED_VOLUP;
- if (gpio_get_value(GPIO_VOL_DN_KEY) == 0)
- state |= PRESSED_VOLDOWN;
- //如果摁下power+voldown就進入fastboot 這個的優先級要比recovery高。
- //就算同時按下power+volup+voldown三個鍵也要進入fastboot<strong>模式</strong>
- if ((state & KEY_MASK) == FASTBOOT_KEY_MASK)
- return 1;
- if(((state & KEY_MASK) == FASTBOOT_KEY_MASK))
- return 2;
- return 0;
- }
主函數判定的代碼段修改為
檢視文本 列印 ?
- if (check_key()==1)
- do_fastboot(NULL, 0, 0, 0);
- if (check_key()==2)
- setup_recovery_env();
- if (check_and_clean_recovery_flag()) {
- setup_recovery_env();
- }
- if (fastboot_check_and_clean_flag())
- do_fastboot(NULL, 0, 0, 0);
這樣我們的啟動模式按鍵就修改完成了,編譯後測試成功。
下面我們還有代碼沒有分析完:uboot的主循環:main_loop()
代碼在:\bootable\bootloader\uboot-imx\common\main.c
檢視文本 列印 ?
- void main_loop (void)
- {
- #ifndef CONFIG_SYS_HUSH_PARSER
- static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
- int len;
- int rc = 1;
- int flag;
- #endif
- #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- char *s;
- int bootdelay;
- #endif
- #ifdef CONFIG_PREBOOT
- char *p;
- #endif
- #ifdef CONFIG_BOOTCOUNT_LIMIT
- unsigned long bootcount = 0;
- unsigned long bootlimit = 0;
- char *bcs;
- char bcs_set[16];
- #endif
- #if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)
- ulong bmp = 0;
- extern int trab_vfd (ulong bitmap);
- #ifdef CONFIG_MODEM_SUPPORT
- if (do_mdm_init)
- bmp = 1;
- #endif
- trab_vfd (bmp);
- #endif
- #if defined(CONFIG_UPDATE_TFTP)
- update_tftp ();
- #endif
- #ifdef CONFIG_BOOTCOUNT_LIMIT
- bootcount = bootcount_load();
- bootcount++;
- bootcount_store (bootcount);
- sprintf (bcs_set, "%lu", bootcount);
- setenv ("bootcount", bcs_set);
- bcs = getenv ("bootlimit");
- bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
- #endif
- #ifdef CONFIG_MODEM_SUPPORT
- debug ("DEBUG: main_loop: do_mdm_init=%d\n", do_mdm_init);
- if (do_mdm_init) {
- char *str = strdup(getenv("mdm_cmd"));
- setenv ("preboot", str);
- if (str != NULL)
- free (str);
- mdm_init();
- }
- #endif
- #ifdef CONFIG_VERSION_VARIABLE
- {
- extern char version_string[];
- setenv ("ver", version_string);
- }
- #endif
- #ifdef CONFIG_SYS_HUSH_PARSER
- u_boot_hush_start ();
- #endif
- #if defined(CONFIG_HUSH_INIT_VAR)
- hush_init_var ();
- #endif
- #ifdef CONFIG_AUTO_COMPLETE
- install_auto_complete();
- #endif
- #ifdef CONFIG_PREBOOT
- if ((p = getenv ("preboot")) != NULL) {
- # ifdef CONFIG_AUTOBOOT_KEYED
- int prev = disable_ctrlc(1);
- # endif
- # ifndef CONFIG_SYS_HUSH_PARSER
- run_command (p, 0);
- # else
- parse_string_outer(p, FLAG_PARSE_SEMICOLON |
- FLAG_EXIT_FROM_LOOP);
- # endif
- # ifdef CONFIG_AUTOBOOT_KEYED
- disable_ctrlc(prev);
- # endif
- }
- #endif
- #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
- s = getenv ("bootdelay");
- bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;//計算bootdelay
- debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
- # ifdef CONFIG_BOOT_RETRY_TIME
- init_cmd_timeout ();
- # endif
- #ifdef CONFIG_POST
- if (gd->flags & GD_FLG_POSTFAIL) {
- s = getenv("failbootcmd");
- }
- else
- #endif
- #ifdef CONFIG_BOOTCOUNT_LIMIT
- if (bootlimit && (bootcount > bootlimit)) {
- printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
- (unsigned)bootlimit);
- s = getenv ("altbootcmd");
- }
- else
- #endif
- s = getenv ("bootcmd");//得到bootcmd指令
- debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
- //每10ms從控制台讀取一個字元,并且顯示倒計時。如果讀取成功的話就繼續執行main_loop代碼,
- //如果失敗的話就執行下面的run_command(s,0)
- if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
- # ifdef CONFIG_AUTOBOOT_KEYED
- int prev = disable_ctrlc(1);
- # endif
- # ifndef CONFIG_SYS_HUSH_PARSER
- run_command (s, 0);//執行 bootcmd指令
- # else
- parse_string_outer(s, FLAG_PARSE_SEMICOLON |
- FLAG_EXIT_FROM_LOOP);
- # endif
- # ifdef CONFIG_AUTOBOOT_KEYED
- disable_ctrlc(prev);
- # endif
- }
- # ifdef CONFIG_MENUKEY
- if (menukey == CONFIG_MENUKEY) {
- s = getenv("menucmd");
- if (s) {
- # ifndef CONFIG_SYS_HUSH_PARSER
- run_command (s, 0);
- # else
- parse_string_outer(s, FLAG_PARSE_SEMICOLON |
- FLAG_EXIT_FROM_LOOP);
- # endif
- }
- }
- #endif
- #endif
- #ifdef CONFIG_AMIGAONEG3SE
- {
- extern void video_banner(void);
- video_banner();
- }
- #endif
- #ifdef CONFIG_SYS_HUSH_PARSER
- parse_file_outer();
- for (;;);
- #else
- for (;;) { //如果bootdelay時候有<strong>按鍵</strong> 就進入指令處理<strong>模式</strong>
- #ifdef CONFIG_BOOT_RETRY_TIME
- if (rc >= 0) {
- reset_cmd_timeout();
- }
- #endif
- len = readline (CONFIG_SYS_PROMPT);//從控制台讀取一行資料,以回車為标志
- flag = 0;
- if (len > 0)
- z (lastcommand, console_buffer);
- else if (len == 0)
- flag |= CMD_FLAG_REPEAT;
- #ifdef CONFIG_BOOT_RETRY_TIME
- else if (len == -2) {
- puts ("\nTimed out waiting for command\n");
- # ifdef CONFIG_RESET_TO_RETRY
- do_reset (NULL, 0, 0, NULL);
- # else
- return;
- # endif
- }
- #endif
- if (len == -1)
- puts ("<INTERRUPT>\n");
- else
- rc = run_command (lastcommand, flag);//處理這條指令
- if (rc <= 0) {
- lastcommand[0] = 0;
- }
- }
- #endif
- }
到了這裡整個的uboot流程已經走完了。從這裡可以知道,uboot正式運作以後,實作的所有功能都是通過指令實作的,要繼續分析的話,就要分析uboot的指令的實作了。
我們在下一篇文章裡面講述uboot指令是怎麼實作的,kernel是怎麼啟動的。