u-boot環境變量

u-boot通過環境變量爲用戶提供一定程度的可配置性,如波特率,啓動參數等。環境變量固化在非易失性存儲介質中,通過saveenv來保存。可配置性意味着環境變量是可以添加、刪除和修改的,也就是說環境變量的內容可能會頻繁變化,爲了不讓這種變化對u-boot的代碼和數據造成破壞,通常的選擇是在FLASH中預留一個專門用來存儲環境變量的塊。
U-boot環境變量大致分爲四個部分:環境變量的結構,初始化,環境變量的保存,環境變量的讀取。根據這個思路,依次學習各個部分。
本例中環境變量存儲在NAND FLASH上,且環境變量沒有嵌入到u-boot中,即ENV_IS_EMBEDDED宏狀態是undefined。NAND FLASH擦除以塊爲單位,一塊的大小時128K,下圖是本例u-boot和環境變量在NAND FLASH上的存儲, 單獨爲環境變量分配了一個塊的存儲空間。
這裏寫圖片描述

一、環境變量的結構

環境變量是一個結構體

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保存的是整個環境變量值做CRC運算的結果,用於校驗環境變量是否合法。
元素data[ENV_SIZE] 保存了環境變量的值,下面是默認的環境變量的值,可以看出環境變量其實是一個字符串,由”\0”將每個變量分開,環境變量的末尾是”\0\0”

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"
};

二、環境變量的初始化

u-boot首先調用common/env_nand.c下的int env_init(void)函數。由於ENV_IS_EMBEDDED是未定義的,故初始化函數執行了兩條語句,這裏暫時認爲CRC是正確的,在環境變量拷貝到SDRAM上之後再做校驗。

int env_init(void)
{
#if defined(ENV_IS_EMBEDDED)
    ulong total;
    int crc1_ok = 0, crc2_ok = 0;
    env_t *tmp_env1, *tmp_env2;

    total = CFG_ENV_SIZE;

    tmp_env1 = env_ptr;
    tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);

    crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
    crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);

    if (!crc1_ok && !crc2_ok)
        gd->env_valid = 0;
    else if(crc1_ok && !crc2_ok)
        gd->env_valid = 1;
    else if(!crc1_ok && crc2_ok)
        gd->env_valid = 2;
    else {
        /* both ok - check serial */
        if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
            gd->env_valid = 2;
        else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
            gd->env_valid = 1;
        else if(tmp_env1->flags > tmp_env2->flags)
            gd->env_valid = 1;
        else if(tmp_env2->flags > tmp_env1->flags)
            gd->env_valid = 2;
        else /* flags are equal - almost impossible */
            gd->env_valid = 1;
    }

    if (gd->env_valid == 1)
        env_ptr = tmp_env1;
    else if (gd->env_valid == 2)
        env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
    gd->env_addr  = (ulong)&default_environment[0];
    gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */

    return (0);
}

環境變量原本是存放在NAND FLASH上的,由於環境變量經常被用到,且NAND FLASH的擦寫速度太慢,需要將環境變量從FLASH上拷貝到SDRAM上,u-boot通過調用env_relocate()完成這部分的操作。


void env_relocate (void)
{
    DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
        gd->reloc_off);

#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);
#else
    /*
     * We must allocate a buffer for the environment
     */
     //在malloc區分配大小爲CFG_ENV_SIZE的空間,並將env_ptr指向該空間的起始地址。
    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;
    // gd->env_valid的值爲1
    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 {
        // 從FLASH中讀取環境變量的內容到SDRAM上。
        env_relocate_spec ();
    }
    // gd->env_addr指向環境變量的第一個參數。
    gd->env_addr = (ulong)&(env_ptr->data);
}

通過env_relocate_spec ()讀取環境變量到SDRAM上。請注意,剛燒寫的板子上僅僅給環境變量預留了一定大小的空間,並沒有環境變量的內容。根據重定位函數,我們知道板子第一次開機時,讀出來的環境變量進行CRC校驗時肯定是失敗的,會使用預存的默認環境變量。

void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
    ulong total = CFG_ENV_SIZE;
    int ret;
    // 讀取FLASH上環境變量到env_ptr所指向的地址,若讀取失敗,則使用默認的環境變量。
    ret = nand_read(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
    if (ret || total != CFG_ENV_SIZE)
        return use_default();
    // 校驗CRC,若失敗,則使用默認的環境變量。
    if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
        return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
static void use_default()
{
    puts ("*** Warning - bad CRC or NAND, using default environment\n\n");

    if (default_environment_size > CFG_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,
            default_environment_size);
    // 計算CRC,並賦給SDRAM上的env_ptr->crc
    env_ptr->crc = crc32(0, env_ptr->data, ENV_SIZE);
    gd->env_valid = 1;
}

至此,環境變量的初始化就結束了,環境變量的值被拷貝到SDRAM,全局變量
gd->env_addr = (ulong)&(env_ptr->data); 指向了環境變量的第一個參數。
gd->env_valid = 1;

三、環境變量的保存

int saveenv(void)用來保存環境變量的。由於NAND FLASH的每個位只能從1->0,而不能相反,所以我們需要先對其擦除,然後再進行寫操作。

int saveenv(void)
{
    ulong total;
    int ret = 0;

    puts ("Erasing Nand...");
    // 擦除FLASH,&nand_info[0]是NAND的起始地址,擦除操作從CFG_ENV_OFFSET開始,大小爲CFG_ENV_SIZE。
    if (nand_erase(&nand_info[0], CFG_ENV_OFFSET, CFG_ENV_SIZE))
        return 1;

    puts ("Writing to Nand... ");
    total = CFG_ENV_SIZE;
    // 寫FLASH,&nand_info[0]是NAND的起始地址,將env_ptr所指向的內容寫到CFG_ENV_OFFSET開始的位置,大小爲CFG_ENV_SIZE。
    ret = nand_write(&nand_info[0], CFG_ENV_OFFSET, &total, (u_char*)env_ptr);
    if (ret || total != CFG_ENV_SIZE)
        return 1;

    puts ("done\n");
    return ret;
}

關於環境變量的存儲偏移量和大小相關的宏定義是下面這樣。由於板子上用的NAND FLASH的塊擦除的大小爲128K (0x20000),下面的宏定義保證了環境變量保存在單獨的一個NAND 塊上。

#define CFG_ENV_OFFSET      0x40000
#define CFG_ENV_SIZE        0x20000 /* Total Size of Environment Sector */

四、環境變量的讀取

在u-boot運行時,我們可以使用U-boot命令配置環境變量而不是修改代碼來控制U-boot的行爲,如我們可以修改環境變量來配置開發板的IP地址。u-boot配置環境變量就是配置存儲器上的某個地址的內容,讀取這個地址的內容再進行接下來的操作。u-boot中讀取環境變量的函數是common/cmd_nvedit.c下的
int getenv_r (char *name, char *buf, unsigned len)。
@para1: 需要讀取的環境變量名
@para2: 若找到了該環境變量,將值存放到buf。
@para3: buf的長度
@return value: 若有該環境變量,返回值爲環境變量參數的長度。否則返回-1


int getenv_r (char *name, char *buf, unsigned len)
{
    int i, nxt;

    for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
        int val, n;

        for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
            if (nxt >= CFG_ENV_SIZE) {
                return (-1);
            }
        }
        if ((val=envmatch((uchar *)name, i)) < 0)
            continue;
        /* found; copy out */
        n = 0;
        while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
            ;
        if (len == n)
            *buf = '\0';
        return (n);
    }
    return (-1);
}

其中 env_get_char指向函數env_get_char_memory。
功能是獲取環境變量中,索引爲index處的字符。

uchar env_get_char_memory (int index)
{
    if (gd->env_valid) {
        // gd->env_addr是環境變量的首地址。
        return ( *((uchar *)(gd->env_addr + index)) );
    } else {
        return ( default_environment[index] );
    }
}
發佈了31 篇原創文章 · 獲贊 47 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章