本文uboot代碼版本: 2016.05
跑完spl之后,如果選擇了引導uboot啟動,最后會進入board_init_r函數,該函數中的數組init_sequence_r中的最后一個元素run_main_loop函數包含我們要介紹的main_loop函數。
位於common/board_r.c下的board_init_r函數, 會按順序調用數組init_seqence_r里面的函數
void board_init_r(gd_t *new_gd, ulong dest_addr) { #ifdef CONFIG_NEEDS_MANUAL_RELOC int i; #endif #ifdef CONFIG_AVR32 mmu_init_r(dest_addr); #endif #if !defined(CONFIG_X86) && !defined(CONFIG_ARM) && !defined(CONFIG_ARM64) gd = new_gd; #endif #ifdef CONFIG_NEEDS_MANUAL_RELOC for (i = 0; i < ARRAY_SIZE(init_sequence_r); i++) init_sequence_r[i] += gd->reloc_off; #endif if (initcall_run_list(init_sequence_r)) hang(); /* NOTREACHED - run_main_loop() does not return */ hang(); }
位於common/board_r.c 的數組 init_sequence_r,數組最后為run_main_loop函數
init_fnc_t init_sequence_r[] = {
initr_trace,
initr_reloc,
/* TODO: could x86/PPC have this also perhaps? */ #ifdef CONFIG_ARM initr_caches, /* Note: For Freescale LS2 SoCs, new MMU table is created in DDR. * A temporary mapping of IFC high region is since removed, * so environmental variables in NOR flash is not availble * until board_init() is called below to remap IFC to high * region. */ #endif initr_reloc_global_data, #if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500) initr_unlock_ram_in_cache, #endif initr_barrier, initr_malloc, initr_console_record, #ifdef CONFIG_SYS_NONCACHED_MEMORY initr_noncached, #endif bootstage_relocate, #ifdef CONFIG_DM initr_dm, #endif initr_bootstage, #if defined(CONFIG_ARM) || defined(CONFIG_NDS32) board_init, /* Setup chipselects */ #endif /* * TODO: printing of the clock inforamtion of the board is now * implemented as part of bdinfo command. Currently only support for * davinci SOC's is added. Remove this check once all the board * implement this. */ #ifdef CONFIG_CLOCKS set_cpu_clk_info, /* Setup clock information */ #endif #ifdef CONFIG_EFI_LOADER efi_memory_init, #endif stdio_init_tables, initr_serial, initr_announce, INIT_FUNC_WATCHDOG_RESET #ifdef CONFIG_NEEDS_MANUAL_RELOC initr_manual_reloc_cmdtable, #endif #if defined(CONFIG_PPC) || defined(CONFIG_M68K) initr_trap, #endif #ifdef CONFIG_ADDR_MAP initr_addr_map, #endif #if defined(CONFIG_BOARD_EARLY_INIT_R) board_early_init_r, #endif INIT_FUNC_WATCHDOG_RESET #ifdef CONFIG_LOGBUFFER initr_logbuffer, #endif #ifdef CONFIG_POST initr_post_backlog, #endif INIT_FUNC_WATCHDOG_RESET #ifdef CONFIG_SYS_DELAYED_ICACHE initr_icache_enable, #endif #if defined(CONFIG_PCI) && defined(CONFIG_SYS_EARLY_PCI_INIT) /* * Do early PCI configuration _before_ the flash gets initialised, * because PCU ressources are crucial for flash access on some boards. */ initr_pci, #endif #ifdef CONFIG_WINBOND_83C553 initr_w83c553f, #endif #ifdef CONFIG_ARCH_EARLY_INIT_R arch_early_init_r, #endif power_init_board, #ifndef CONFIG_SYS_NO_FLASH initr_flash, #endif INIT_FUNC_WATCHDOG_RESET #if defined(CONFIG_PPC) || defined(CONFIG_M68K) || defined(CONFIG_X86) || \ defined(CONFIG_SPARC) /* initialize higher level parts of CPU like time base and timers */ cpu_init_r, #endif #ifdef CONFIG_PPC initr_spi, #endif #ifdef CONFIG_CMD_NAND initr_nand, #endif #ifdef CONFIG_CMD_ONENAND initr_onenand, #endif #ifdef CONFIG_GENERIC_MMC initr_mmc, #endif #ifdef CONFIG_HAS_DATAFLASH initr_dataflash, #endif initr_env, #ifdef CONFIG_SYS_BOOTPARAMS_LEN initr_malloc_bootparams, #endif INIT_FUNC_WATCHDOG_RESET initr_secondary_cpu, #if defined(CONFIG_ID_EEPROM) || defined(CONFIG_SYS_I2C_MAC_OFFSET) mac_read_from_eeprom, #endif INIT_FUNC_WATCHDOG_RESET #if defined(CONFIG_PCI) && !defined(CONFIG_SYS_EARLY_PCI_INIT) /* * Do pci configuration */ initr_pci, #endif stdio_add_devices, initr_jumptable, #ifdef CONFIG_API initr_api, #endif console_init_r, /* fully init console as a device */ #ifdef CONFIG_DISPLAY_BOARDINFO_LATE show_board_info, #endif #ifdef CONFIG_ARCH_MISC_INIT arch_misc_init, /* miscellaneous arch-dependent init */ #endif #ifdef CONFIG_MISC_INIT_R misc_init_r, /* miscellaneous platform-dependent init */ #endif INIT_FUNC_WATCHDOG_RESET #ifdef CONFIG_CMD_KGDB initr_kgdb, #endif interrupt_init, #if defined(CONFIG_ARM) || defined(CONFIG_AVR32) initr_enable_interrupts, #endif #if defined(CONFIG_MICROBLAZE) || defined(CONFIG_AVR32) || defined(CONFIG_M68K) timer_init, /* initialize timer */ #endif #if defined(CONFIG_STATUS_LED) initr_status_led, #endif /* PPC has a udelay(20) here dating from 2002. Why? */ #ifdef CONFIG_CMD_NET initr_ethaddr, #endif #ifdef CONFIG_BOARD_LATE_INIT board_late_init, #endif #if defined(CONFIG_CMD_AMBAPP) ambapp_init_reloc, #if defined(CONFIG_SYS_AMBAPP_PRINT_ON_STARTUP) initr_ambapp_print, #endif #endif #ifdef CONFIG_CMD_SCSI INIT_FUNC_WATCHDOG_RESET initr_scsi, #endif #ifdef CONFIG_CMD_DOC INIT_FUNC_WATCHDOG_RESET initr_doc, #endif #ifdef CONFIG_BITBANGMII initr_bbmii, #endif #ifdef CONFIG_CMD_NET INIT_FUNC_WATCHDOG_RESET initr_net, #endif #ifdef CONFIG_POST initr_post, #endif #if defined(CONFIG_CMD_PCMCIA) && !defined(CONFIG_CMD_IDE) initr_pcmcia, #endif #if defined(CONFIG_CMD_IDE) initr_ide, #endif #ifdef CONFIG_LAST_STAGE_INIT INIT_FUNC_WATCHDOG_RESET /* * Some parts can be only initialized if all others (like * Interrupts) are up and running (i.e. the PC-style ISA * keyboard). */ last_stage_init, #endif #ifdef CONFIG_CMD_BEDBUG INIT_FUNC_WATCHDOG_RESET initr_bedbug, #endif #if defined(CONFIG_PRAM) || defined(CONFIG_LOGBUFFER) initr_mem, #endif #ifdef CONFIG_PS2KBD initr_kbd, #endif #if defined(CONFIG_SPARC) prom_init, #endif run_main_loop, };
位於同一目錄下的run_main_loop函數,可以看到在一個死循環里面執行main_loop函數,也就是說run_main_loop永遠不會返回。
static int run_main_loop(void) { #ifdef CONFIG_SANDBOX sandbox_main_loop_init(); #endif /* main_loop() can return to retry autoboot, if so just run it again */ for (;;) main_loop(); return 0; }
以下為位於common/main.c下面的main_loop函數,bootstage_mark_name函數最終調用了show_boot_progress函數,該函數為空函數
/* We come here after U-Boot is initialised and ready to process commands */ void main_loop(void) { const char *s; bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop"); #ifndef CONFIG_SYS_GENERIC_BOARD puts("Warning: Your board does not use generic board. Please read\n"); puts("doc/README.generic-board and take action. Boards not\n"); puts("upgraded by the late 2014 may break or be removed.\n"); #endif #ifdef CONFIG_VERSION_VARIABLE setenv("ver", version_string); /* set version variable */ #endif /* CONFIG_VERSION_VARIABLE */ cli_init(); run_preboot_environment_command(); #if defined(CONFIG_UPDATE_TFTP) update_tftp(0UL, NULL, NULL); #endif /* CONFIG_UPDATE_TFTP */ s = bootdelay_process(); if (cli_process_fdt(&s)) cli_secure_boot_cmd(s); autoboot_command(s); cli_loop(); panic("No CLI available"); }
setenv函數如下所示,用於設置環境變量ver為version_string
version_string在common/cmd_version.c定義為
const char __weak version_string[] = U_BOOT_VERSION_STRING;
U_BOOT_VERSION_STRING在include/version.h中定義為
#define U_BOOT_VERSION_STRING U_BOOT_VERSION " (" U_BOOT_DATE " - " \ U_BOOT_TIME " " U_BOOT_TZ ")" CONFIG_IDENT_STRING
U_BOOT_VERSION, U_BOOT_DATE, U_BOOT_TIME, U_BOOT_TZ這幾個宏在include/generated/version_autogenerated.h和include/generated/timestamp_autogenerated.h中被定義,CONFIG_IDENT_STRING在include/version.h中被定義為空("")
//nclude/generated/version_autogenerated.h 這個頭文件在編譯的時候自動生成 define PLAIN_VERSION "2016.05-00297-ge4b7ffa-dirty" #define U_BOOT_VERSION "U-Boot " PLAIN_VERSION #define CC_VERSION_STRING "arm-linux-gnueabihf-gcc (Linaro GCC 5.3-2016.02) 5.3.1 20160113" #define LD_VERSION_STRING "GNU ld (GNU Binutils) 2.25.0 Linaro 2015_10"
//include/generated/timestamp_autogenerated.h 這個頭文件在編譯的時候自動生成 #define U_BOOT_DATE "Sep 11 2019" #define U_BOOT_TIME "21:00:25" #define U_BOOT_TZ "+0800" #define U_BOOT_DMI_DATE "09/11/2019"
uboot起來之后可以通過printenv命令查看當前所設置的環境變量,其中就有ver這個環境變量
varname = "ver" varvalue = "U-Boot 2016.05-00297-ge4b7ffa-dirty (Sep 11 2019 - 19:24:32 +0800)"
int setenv(const char *varname, const char *varvalue) { const char * const argv[4] = { "setenv", varname, varvalue, NULL }; /* before import into hashtable */ if (!(gd->flags & GD_FLG_ENV_READY)) return 1; if (varvalue == NULL || varvalue[0] == '\0') return _do_env_set(0, 2, (char * const *)argv, H_PROGRAMMATIC); else return _do_env_set(0, 3, (char * const *)argv, H_PROGRAMMATIC); }
varname和varvalue都不為空,所以接着執行 _do_env_set(0, 3, (char * const *)argv, H_PROGRAMMATIC);
/* * Set a new environment variable, * or replace or delete an existing one. */ static int _do_env_set(int flag, int argc, char * const argv[], int env_flag) { int i, len; char *name, *value, *s; ENTRY e, *ep; debug("Initial value for argc=%d\n", argc); while (argc > 1 && **(argv + 1) == '-') { //argv[1] = "ver", 跳過此循環 char *arg = *++argv; --argc; while (*++arg) { switch (*arg) { case 'f': /* force */ env_flag |= H_FORCE; break; default: return CMD_RET_USAGE; } } } debug("Final value for argc=%d\n", argc); name = argv[1];//"ver" value = argv[2];//"U-Boot 2016.05-00297-ge4b7ffa-dirty (Sep 11 2019 - 20:31:15 +0800)" if (strchr(name, '=')) {//判斷name中是否有 “=”這個字符,如沒有返回NULL,此處條件不成立 printf("## Error: illegal character '='" "in variable name \"%s\"\n", name); return 1; } env_id++; /* Delete only ? */ if (argc < 3 || argv[2] == NULL) {//如果第3個參數為NULL,就刪除這變量 int rc = hdelete_r(name, &env_htab, env_flag); return !rc; } /* * Insert / replace new value */ for (i = 2, len = 0; i < argc; ++i) len += strlen(argv[i]) + 1; value = malloc(len); if (value == NULL) { printf("## Can't malloc %d bytes\n", len); return 1; } for (i = 2, s = value; i < argc; ++i) { char *v = argv[i]; while ((*s++ = *v++) != '\0') ; printf("s: [%s]\n", s); *(s - 1) = ' '; } if (s != value) *--s = '\0'; e.key = name; e.data = value; hsearch_r(e, ENTER, &ep, &env_htab, env_flag); free(value); if (!ep) { printf("## Error inserting \"%s\" variable, errno=%d\n", name, errno); return 1; } return 0; }
接下來執行cli_init函數,在common/cli.c中對函數的定義如下,如果定義了CONFIG_SYS_HUSH_PARSER則支持hush shell,一個據說來自busybox的命令解析器
void cli_init(void) { #ifdef CONFIG_SYS_HUSH_PARSER u_boot_hush_start(); #endif #if defined(CONFIG_HUSH_INIT_VAR) hush_init_var(); #endif }
run_preboot_environment_command函數從環境變量中獲取preboot的定義,該變量包含了一些預啟動命令,一般環境變量中不包含該項配置。我目前所用的系統也沒有定義該配置,因為沒有定義CONFIG_UPDATE_TFTP,所以先不管update_tftp這個函數
static void run_preboot_environment_command(void) { #ifdef CONFIG_PREBOOT char *p; p = getenv("preboot"); if (p != NULL) { # ifdef CONFIG_AUTOBOOT_KEYED int prev = disable_ctrlc(1); /* disable Control C checking */ # endif run_command_list(p, -1, 0); # ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ # endif } #endif /* CONFIG_PREBOOT */ }
函數bootdelay_process在common/autoboot.c中定義,用於獲取環境變量bootdelay的值並轉成整數后保存在全局變量stored_bootdelay中,最后獲取bootcmd的配置值作為函數的返回值。
const char *bootdelay_process(void) { char *s; int bootdelay; #ifdef CONFIG_BOOTCOUNT_LIMIT unsigned long bootcount = 0; unsigned long bootlimit = 0; #endif /* CONFIG_BOOTCOUNT_LIMIT */ #ifdef CONFIG_BOOTCOUNT_LIMIT bootcount = bootcount_load(); bootcount++; bootcount_store(bootcount); setenv_ulong("bootcount", bootcount); bootlimit = getenv_ulong("bootlimit", 10, 0); #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv("bootdelay"); bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY; #ifdef CONFIG_OF_CONTROL bootdelay = fdtdec_get_config_int(gd->fdt_blob, "bootdelay", bootdelay); #endif debug("### main_loop entered: bootdelay=%d\n\n", bootdelay); #if defined(CONFIG_MENU_SHOW) bootdelay = menu_show(bootdelay); #endif bootretry_init_cmd_timeout(); #ifdef CONFIG_POST if (gd->flags & GD_FLG_POSTFAIL) { s = getenv("failbootcmd"); } else #endif /* CONFIG_POST */ #ifdef CONFIG_BOOTCOUNT_LIMIT if (bootlimit && (bootcount > bootlimit)) { printf("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n", (unsigned)bootlimit); s = getenv("altbootcmd"); } else #endif /* CONFIG_BOOTCOUNT_LIMIT */ s = getenv("bootcmd"); process_fdt_options(gd->fdt_blob); stored_bootdelay = bootdelay; return s; }
函數cli_process_fdt,在這里一直是false,所以不會回執行cli_secure_boot_cmd函數
往下執行定義在common/autoboot.c的autoboot_command函數
void autoboot_command(const char *s) { debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>"); if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) { #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) int prev = disable_ctrlc(1); /* disable Control C checking */ #endif run_command_list(s, -1, 0); #if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC) disable_ctrlc(prev); /* restore Control C checking */ #endif } #ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { s = getenv("menucmd"); if (s) run_command_list(s, -1, 0); } #endif /* CONFIG_MENUKEY */ }
上面第一個if中的函數abortboot函數如下,由於沒有定義CONFIG_AUTOBOOT_KEYED,所以會調用abortboot_normal函數,在bootdelay時間內如果沒有用戶干預,沒有按任何鍵盤鍵,則這個函數
順利執行完,往下執行run_command_list函數,執行環境變量bootcmd里面的一大堆命令,嘗試啟動並計入linux內核;
假若在bootdelay時間按下了任何鍵,abortboot函數返回1。
tatic int abortboot(int bootdelay) { #ifdef CONFIG_AUTOBOOT_KEYED return abortboot_keyed(bootdelay); #else return abortboot_normal(bootdelay); #endif }
則繼續往下執行cli_loop運行hush shell解釋器:
void cli_loop(void) { #ifdef CONFIG_SYS_HUSH_PARSER parse_file_outer(); /* This point is never reached */ for (;;); #elif defined(CONFIG_CMDLINE) cli_simple_loop(); #else printf("## U-Boot command line is disabled. Please enable CONFIG_CMDLINE\n"); #endif /*CONFIG_SYS_HUSH_PARSER*/ }
parse_file_outer進行必要的初始化之后,將調用parse_strea_outer函數;
/* most recursion does not come through here, the exeception is * from builtin_source() */ static int parse_stream_outer(struct in_str *inp, int flag) { struct p_context ctx; o_string temp=NULL_O_STRING; int rcode; #ifdef __U_BOOT__ int code = 1; #endif do { ctx.type = flag; initialize_context(&ctx); update_ifs_map(); if (!(flag & FLAG_PARSE_SEMICOLON) || (flag & FLAG_REPARSING)) mapset((uchar *)";$&|", 0); inp->promptmode=1; rcode = parse_stream(&temp, &ctx, inp, flag & FLAG_CONT_ON_NEWLINE ? -1 : '\n'); #ifdef __U_BOOT__ if (rcode == 1) flag_repeat = 0; #endif if (rcode != 1 && ctx.old_flag != 0) { syntax(); #ifdef __U_BOOT__ flag_repeat = 0; #endif } if (rcode != 1 && ctx.old_flag == 0) { done_word(&temp, &ctx); done_pipe(&ctx,PIPE_SEQ); #ifndef __U_BOOT__ run_list(ctx.list_head); #else code = run_list(ctx.list_head); if (code == -2) { /* exit */ b_free(&temp); code = 0; /* XXX hackish way to not allow exit from main loop */ if (inp->peek == file_peek) { printf("exit not allowed from main input shell.\n"); continue; } break; } if (code == -1) flag_repeat = 0; #endif } else { if (ctx.old_flag != 0) { free(ctx.stack); b_reset(&temp); } #ifdef __U_BOOT__ if (inp->__promptme == 0) printf("<INTERRUPT>\n"); inp->__promptme = 1; #endif temp.nonnull = 0; temp.quote = 0; inp->p = NULL; free_pipe_list(ctx.list_head,0); } b_free(&temp); /* loop on syntax errors, return on EOF */ } while (rcode != -1 && !(flag & FLAG_EXIT_FROM_LOOP) && (inp->peek != static_peek || b_peek(inp))); #ifndef __U_BOOT__ return 0; #else return (code != 0) ? 1 : 0; #endif /* __U_BOOT__ */ }
上面的do-while循環執行並解釋uboot的命令,也就是我們看到的命令行模式