學習目標: 1、分析u-boot-1.1.6環境變數,瞭解環境變數初始化、設置以及過程 2、為後面能夠掌握u-boot-1.1.6如何啟動內核過程打下基礎 1、環境變數的概念 在分析uboot環境變數的源碼實現之前,先介紹一下環境變數的概念。u-boot通過環境變數為用戶提供一定程度的可配置性,在不 ...
學習目標:
1、分析u-boot-1.1.6環境變數,瞭解環境變數初始化、設置以及過程
2、為後面能夠掌握u-boot-1.1.6如何啟動內核過程打下基礎
1、環境變數的概念
在分析uboot環境變數的源碼實現之前,先介紹一下環境變數的概念。u-boot通過環境變數為用戶提供一定程度的可配置性,在不改變源碼、不重新編譯的情況下,可以通過設置環境變數的值來改變uboot的一些設置,如bootdelay時間,內核啟動命令參數等。可配置性意味著環境變數是可以添加、刪除和修改的,也就是說環境變數的內容可能會頻繁變化,為了不讓這種變化對u-boot的代碼和數據造成破壞,通常的選擇是在FLASH中預留一個專門用來存儲環境變數的塊。開發者在串口終端輸入setenv命令可以設置環境量值,設置完成後使用saveenv命令將setenv命令設置好的環境變數保存在非易失存儲器中。如下圖所示,使用uboot時, 在串口終端輸入printenv命令便能夠列印uboot中的環境變數值。
2、環境變數的數據結構
#define ENV_SIZE (CFG_ENV_SIZE - ENV_HEADER_SIZE) typedef struct environment_s { unsigned long crc; /* CRC32 over data bytes */ #ifdef CFG_REDUNDAND_ENVIRONMENT unsigned char flags; /* active/obsolete flags */ #endif unsigned char data[ENV_SIZE]; /* Environment data */ } env_t;
crc變數保存上一次環境變數數據寫入flash時做crc運算的結果,當重新從flash中讀取環境變數值時,代碼會對讀取數據進行crc校驗,並將新的校驗的值與上次保存的值進行比較,如果兩次crc校驗值相同,表示存放在flash中的環境變數正常,否則,表示存放環境變數數據損壞。
數組data[ENV_SIZE]保存環境變數的值,環境變數名已經環境變數值以字元形式存放在記憶體中
3、環境變數的初始化
int env_init(void) { /* 未定義CONFIG_OMAP2420H4,此處代碼不被編譯 */ #ifdef CONFIG_OMAP2420H4 int flash_probe(void); if(flash_probe() == 0) goto bad_flash; #endif /* env_t *env_ptr = (env_t *)CFG_ENV_ADDR, 此處執行嗎?,如果此處執行 */ /* 先進入nor flash讀取環境變數,通過CRC檢測讀取環境變數是否正確 */ if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) { gd->env_addr = (ulong)&(env_ptr->data); gd->env_valid = 1; return(0); } /* 未定義CONFIG_OMAP2420H4,此處代碼不被編譯 */ #ifdef CONFIG_OMAP2420H4 bad_flash: #endif /* CRC校驗後讀取環境變數不正確,此處執行,使用預設環境變數 */ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 0; return (0); }
int env_inti(void)函數在uboot代碼第二階段入口函數start_armboot開始處被調用。首先,env_init函數從存放環境變數的flash記憶體地址中讀取環境變數的值,並且對讀出的環境變數數據進行crc校驗,將新校驗的crc值與寫入環境變數時的crc校驗值進行比較。如果兩個值相等,將讀取數據的地址賦值給gd指針執行的全局數據結構中的env_addr成員,並將env_valid標誌位置位。如果讀取環境變數校驗值和寫入時的校驗值不同,存儲在flash中的環境變數值損壞,將預設環境變數值賦給gd指針執行的全局數據結構中的env_addr成員。
env_ptr指針指向環境變數存放初始地址,env_t *env_ptr = (env_t *)CFG_ENV_ADDR。CFG_ENV_ADDR是一個巨集,代表環境變數在flash中存放的地址,由於我配置uboot時目標板是smdk2410,所以該巨集存放在include/configs/smdk2410.h頭文件中。預設環境變數值如下所示:
uchar default_environment[] = { #ifdef CONFIG_BOOTARGS "bootargs=" CONFIG_BOOTARGS "\0" #endif #ifdef CONFIG_BOOTCOMMAND "bootcmd=" CONFIG_BOOTCOMMAND "\0" #endif #ifdef CONFIG_RAMBOOTCOMMAND "ramboot=" CONFIG_RAMBOOTCOMMAND "\0" #endif #ifdef CONFIG_NFSBOOTCOMMAND "nfsboot=" CONFIG_NFSBOOTCOMMAND "\0" #endif #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) "bootdelay=" MK_STR(CONFIG_BOOTDELAY) "\0" #endif #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0) "baudrate=" MK_STR(CONFIG_BAUDRATE) "\0" #endif #ifdef CONFIG_LOADS_ECHO "loads_echo=" MK_STR(CONFIG_LOADS_ECHO) "\0" #endif #ifdef CONFIG_ETHADDR "ethaddr=" MK_STR(CONFIG_ETHADDR) "\0" #endif #ifdef CONFIG_ETH1ADDR "eth1addr=" MK_STR(CONFIG_ETH1ADDR) "\0" #endif #ifdef CONFIG_ETH2ADDR "eth2addr=" MK_STR(CONFIG_ETH2ADDR) "\0" #endif #ifdef CONFIG_ETH3ADDR "eth3addr=" MK_STR(CONFIG_ETH3ADDR) "\0" #endif #ifdef CONFIG_IPADDR "ipaddr=" MK_STR(CONFIG_IPADDR) "\0" #endif #ifdef CONFIG_SERVERIP "serverip=" MK_STR(CONFIG_SERVERIP) "\0" #endif #ifdef CFG_AUTOLOAD "autoload=" CFG_AUTOLOAD "\0" #endif #ifdef CONFIG_PREBOOT "preboot=" CONFIG_PREBOOT "\0" #endif #ifdef CONFIG_ROOTPATH "rootpath=" MK_STR(CONFIG_ROOTPATH) "\0" #endif #ifdef CONFIG_GATEWAYIP "gatewayip=" MK_STR(CONFIG_GATEWAYIP) "\0" #endif #ifdef CONFIG_NETMASK "netmask=" MK_STR(CONFIG_NETMASK) "\0" #endif #ifdef CONFIG_HOSTNAME "hostname=" MK_STR(CONFIG_HOSTNAME) "\0" #endif #ifdef CONFIG_BOOTFILE "bootfile=" MK_STR(CONFIG_BOOTFILE) "\0" #endif #ifdef CONFIG_LOADADDR "loadaddr=" MK_STR(CONFIG_LOADADDR) "\0" #endif #ifdef CONFIG_CLOCKS_IN_MHZ "clocks_in_mhz=1\0" #endif #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0) "pcidelay=" MK_STR(CONFIG_PCI_BOOTDELAY) "\0" #endif #ifdef CONFIG_EXTRA_ENV_SETTINGS CONFIG_EXTRA_ENV_SETTINGS #endif "\0" };
4、環境變數的重定位
env_inti函數在初始化時對環境變數保存位置進行識別,並將環境變數存放的初始地址賦值給了全局數據結構gt_t成員env_addr。讀取存放在flash中的環境變數數據,進行crc校驗後有效,便使用flash中的環境變數,否則使用預設環境變數。我們知道flash操作速度記憶體慢(若環境變數存放在nand flash中,進行讀取時還需要相應的指令),對flash中環境變數進行直接讀寫不僅會影響uboot性能,而且多次擦除還會影響flash的使用壽命,因此,需要將flash中的環境變數進行重定位操作。
void env_relocate (void) { DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__, gd->reloc_off); /* 未定義CONFIG_AMIGAONEG3SE巨集,此處不被編譯 */ #ifdef CONFIG_AMIGAONEG3SE enable_nvram(); #endif /* 未定義ENV_IS_EMBEDDED巨集,此處不被編譯 */ #ifdef ENV_IS_EMBEDDED /* * The environment buffer is embedded with the text segment, * just relocate the environment pointer */ env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off); DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr); /* ifdef語句為假,此處被編譯 */ #else /* * We must allocate a buffer for the environment */ /* 在malloc區,為環境變數分配存儲空間 */ env_ptr = (env_t *)malloc (CFG_ENV_SIZE); DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr); #endif /* * After relocation to RAM, we can always use the "memory" functions */ env_get_char = env_get_char_memory; if (gd->env_valid == 0) { #if defined(CONFIG_GTH) || defined(CFG_ENV_IS_NOWHERE) /* Environment not changable */ puts ("Using default environment\n\n"); #else puts ("*** Warning - bad CRC, using default environment\n\n"); SHOW_BOOT_PROGRESS (-1); #endif if (sizeof(default_environment) > ENV_SIZE) { puts ("*** Error - default environment is too large\n\n"); return; } memset (env_ptr, 0, sizeof(env_t)); memcpy (env_ptr->data, default_environment, sizeof(default_environment)); #ifdef CFG_REDUNDAND_ENVIRONMENT env_ptr->flags = 0xFF; #endif env_crc_update (); gd->env_valid = 1; } else { env_relocate_spec (); } gd->env_addr = (ulong)&(env_ptr->data); /* 未定義CONFIG_AMIGAONEG3SE巨集,此處代碼不被編譯 */ #ifdef CONFIG_AMIGAONEG3SE disable_nvram(); #endif }
void env_relocate (void)函數功能是對flash中環境變數的數據進行重定位,首先在uboot自定義的堆區為環境變數分配相應的記憶體空間,env_ptr指針指向這塊記憶體的初始地址,然後根據gd->env_valid值執行不同的操作(gd->env_valid在env_init函數中設置)。gd->env_valid=0代表flash中環境變數無效,使用預設環境變數,將env_ptr指針指向的記憶體地址清零,再將存放預設環境變數default_environment數組內容拷貝到env_ptr指針指向的記憶體空間中;gd->env_valid=1時flash中存放環境變數有效,調用env_relocate_spec ()函數,將flash中存放的環境變數數據複製到env_ptr指針指向的記憶體空間。最後,將環境變數中的數據在重定位記憶體中的地址賦給gd->env_addr。
5、讀取環境變數
前面介紹過在串口終端輸入printenv,執行回車後,列印環境變數值。執行printenv命令其實底層調用的是do_printenv函數,這個函數源碼如下:
int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { int i, j, k, nxt; int rcode = 0; if (argc == 1) { /* 列印所有的環境變數 */ for (i=0; env_get_char(i) != '\0'; i=nxt+1) { for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) ; for (k=i; k<nxt; ++k) putc(env_get_char(k)); putc ('\n'); if (ctrlc()) { puts ("\n ** Abort\n"); return 1; } } printf("\nEnvironment size: %d/%d bytes\n", i, ENV_SIZE); return 0; } /* 遍歷輸入的參數,分別列印其環境變數值 */ for (i=1; i<argc; ++i) { /* print single env variables */ char *name = argv[i]; k = -1; for (j=0; env_get_char(j) != '\0'; j=nxt+1) { for (nxt=j; env_get_char(nxt) != '\0'; ++nxt) /* uchar (*env_get_char)(int) = env_get_char_init,env_get_char_init函數獲取相對環境變數記憶體起始地址偏移nxt長度字元 */ ; k = envmatch((uchar *)name, j); /* 檢測輸入參數名和記憶體中環境變數參數名是否匹配 */ if (k < 0) { continue; } puts (name); putc ('='); while (k < nxt) putc(env_get_char(k++)); putc ('\n'); break; } if (k < 0) { printf ("## Error: \"%s\" not defined\n", name); rcode ++; } } return rcode; }
1、當argc==1時(例如執行printenv命令,argc=1),列印uboot所有的環境變數
2、當argc>1時(例如執行printenv bootcmd baudrate命令,argc=3),列印環境變數bootcmd、baudrate
6、設置環境變數
同讀取環境變數一樣,執行setenv命令其實底層調用的是_do_setenv函數,這個函數源碼如下:
int do_setenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { if (argc < 2) {
printf ("Usage:\n%s\n", cmdtp->usage);
return 1;
}
return _do_setenv (flag, argc, argv); } int _do_setenv (int flag, int argc, char *argv[]) { int i, len, oldval; int console = -1; uchar *env, *nxt = NULL; char *name; bd_t *bd = gd->bd;
/* env_data獲取環境變數的數據部分在記憶體的初始地址 */ uchar *env_data = env_get_addr(0); if (!env_data) /* need copy in RAM */ return 1; name = argv[1];
/* 檢測輸入的第二個參數為'=',列印相關錯誤信息 */ if (strchr(name, '=')) { printf ("## Error: illegal character '=' in variable name \"%s\"\n", name); return 1; }
/* 遍歷記憶體中的環境變數數據數組,檢測配置環境變數名是否存在 */ oldval = -1; for (env=env_data; *env; env=nxt+1) { for (nxt=env; *nxt; ++nxt) ; if ((oldval = envmatch((uchar *)name, env-env_data)) >= 0) break; } /* * Delete any existing definition */ if (oldval >= 0) { #ifndef CONFIG_ENV_OVERWRITE
/* 網卡地址和串口號是固定的,如果設置是網卡地址和串口號,列印"Can't overwrite \"%s\"\n", name,代碼返回 */ if ( (strcmp (name, "serial#") == 0) || ((strcmp (name, "ethaddr") == 0) #if defined(CONFIG_OVERWRITE_ETHADDR_ONCE) && defined(CONFIG_ETHADDR) && (strcmp ((char *)env_get_addr(oldval),MK_STR(CONFIG_ETHADDR)) != 0) #endif /* CONFIG_OVERWRITE_ETHADDR_ONCE && CONFIG_ETHADDR */ ) ) { printf ("Can't overwrite \"%s\"\n", name); return 1; } #endif /* Check for console redirection */ if (strcmp(name,"stdin") == 0) { console = stdin; } else if (strcmp(name,"stdout") == 0) { console = stdout; } else if (strcmp(name,"stderr") == 0) { console = stderr; } if (console != -1) { if (argc < 3) { /* Cannot delete it! */ printf("Can't delete \"%s\"\n", name); return 1; } /* Try assigning specified device */ if (console_assign (console, argv[2]) < 0) return 1; #ifdef CONFIG_SERIAL_MULTI if (serial_assign (argv[2]) < 0) return 1; #endif } /* 如果是參數=波特率,重新設置相應波特率值 */ if (strcmp(argv[1],"baudrate") == 0) { int baudrate = simple_strtoul(argv[2], NULL, 10); int i;
/* 檢測輸入波特率值是否合法,如果不合法,列印錯誤,返回函數 */ for (i=0; i<N_BAUDRATES; ++i) { if (baudrate == baudrate_table[i]) break; } if (i == N_BAUDRATES) { printf ("## Baudrate %d bps not supported\n", baudrate); return 1; } printf ("## Switch baudrate to %d bps and press ENTER ...\n", baudrate); udelay(50000); gd->baudrate = baudrate; #ifdef CONFIG_PPC gd->bd->bi_baudrate = baudrate; #endif serial_setbrg (); udelay(50000); for (;;) { if (getc() == '\r') break; } } if (*++nxt == '\0') { if (env > env_data) { env--; } else { *env = '\0'; } } else { for (;;) { *env = *nxt++; if ((*env == '\0') && (*nxt == '\0')) break; ++env; } } *++env = '\0'; } #ifdef CONFIG_NET_MULTI if (strncmp(name, "eth", 3) == 0) { char *end; int num = simple_strtoul(name+3, &end, 10); if (strcmp(end, "addr") == 0) { eth_set_enetaddr(num, argv[2]); } } #endif /* Delete only ? */ if ((argc < 3) || argv[2] == NULL) { env_crc_update (); return 0; } /* * Append new definition at the end */ for (env=env_data; *env || *(env+1); ++env) ; if (env > env_data) ++env; /* * Overflow when: * "name" + "=" + "val" +"\0\0" > ENV_SIZE - (env-env_data) */ len = strlen(name) + 2; /* add '=' for first arg, ' ' for all others */ for (i=2; i<argc; ++i) { len += strlen(argv[i]) + 1; } if (len > (&env_data[ENV_SIZE]-env)) { printf ("## Error: environment overflow, \"%s\" deleted\n", name); return 1; } while ((*env = *name++) != '\0') env++; for (i=2; i<argc; ++i) { char *val = argv[i]; *env = (i==2) ? '=' : ' '; while ((*++env = *val++) != '\0') /* 將設置的新的參數寫入到重定位的環境變數數據中 */ ; } /* end is marked with double '\0' */ *++env = '\0'; /* Update CRC */ env_crc_update (); /* 如果設置環境變數是ethaddr、ipaddr、loadaddr、bootfile、vga_fg_color 根據新的環境值,立即更新相應全局變數、更新底層硬體 */ if (strcmp(argv[1],"ethaddr") == 0) { char *s = argv[2]; /* always use only one arg */ char *e; for (i=0; i<6; ++i) { bd->bi_enetaddr[i] = s ? simple_strtoul(s, &e, 16) : 0; if (s) s = (*e) ? e+1 : e; } #ifdef CONFIG_NET_MULTI eth_set_enetaddr(0, argv[2]); #endif return 0; } if (strcmp(argv[1],"ipaddr") == 0) { char *s = argv[2]; /* always use only one arg */ char *e; unsigned long addr; bd->bi_ip_addr = 0; for (addr=0, i=0; i<4; ++i) { ulong val = s ? simple_strtoul(s, &e, 10) : 0; addr <<= 8; addr |= (val & 0xFF); if (s) s = (*e) ? e+1 : e; } bd->bi_ip_addr = htonl(addr); return 0; } if (strcmp(argv[1],"loadaddr") == 0) { load_addr = simple_strtoul(argv[2], NULL, 16); return 0; } #if (CONFIG_COMMANDS & CFG_CMD_NET) if (strcmp(argv[1],"bootfile") == 0) { copy_filename (BootFile, argv[2], sizeof(BootFile)); return 0; } #endif /* CFG_CMD_NET */ #ifdef CONFIG_AMIGAONEG3SE if (strcmp(argv[1], "vga_fg_color") == 0 || strcmp(argv[1], "vga_bg_color") == 0 ) { extern void video_set_color(unsigned char attr); extern unsigned char video_get_attr(void); video_set_color(video_get_attr()); return 0; } #endif /* CONFIG_AMIGAONEG3SE */ return 0; }
7、保存設置的環境變數
串口輸入setenv命令,系統是將新的環境變數保存到重定位的記憶體中,我們知道記憶體在系統掉電後數據丟失,如果想要將設置的環境變數永久保存,還是要將新的環境變數寫入到存儲環境變數的flash地址中。執行saveenv命令就是將分配記憶體中的環境變數保存到flash中,執行saveenv命令其實底層調用的是do_printenv函數,這個函數源碼如下:
int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { extern char * env_name_spec; printf ("Saving Environment to %s...\n", env_name_spec); return (saveenv() ? 1 : 0); }
/* 由於saveenv函數過長,下麵刪去不被包含的巨集內的代碼 */ int saveenv(void) { int len, rc; ulong end_addr; ulong flash_sect_addr; uchar *env_buffer = (uchar *)env_ptr; int rcode = 0;
/* static env_t *flash_addr = (env_t *)CFG_ENV_ADDR; */ flash_sect_addr = (ulong)flash_addr; /* flash_sect_addr 保持環境變數在flash中起始地址 */ len = CFG_ENV_SIZE; end_addr = flash_sect_addr + 0x20000 - 1; debug ("Protect off %08lX ... %08lX\n", (ulong)flash_sect_addr, end_addr); if (flash_sect_protect (0, flash_sect_addr, end_addr)) return 1; puts ("Erasing Flash..."); if (flash_sect_erase (flash_sect_addr, end_addr)) return 1; puts ("Writing to Flash... "); rc = flash_write((char *)env_buffer, flash_sect_addr, len); if (rc != 0) { flash_perror (rc); rcode = 1; } else { puts ("done\n"); } /* try to re-protect */ (void) flash_sect_protect (1, flash_sect_addr, end_addr); return rcode; }
1、env_buffer指針存放環境變數在記憶體中的初始位置
2、寫入flash之前應該擦除想要寫入的扇區,調用flash_sect_erase函數擦除環境變數所做flash的扇區
3、再調用flash_write,寫入新的環境變數