• u-boot-1.1.6环境变量


    学习目标:

    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            ""
    #endif
    #ifdef    CONFIG_BOOTCOMMAND
        "bootcmd="    CONFIG_BOOTCOMMAND        ""
    #endif
    #ifdef    CONFIG_RAMBOOTCOMMAND
        "ramboot="    CONFIG_RAMBOOTCOMMAND        ""
    #endif
    #ifdef    CONFIG_NFSBOOTCOMMAND
        "nfsboot="    CONFIG_NFSBOOTCOMMAND        ""
    #endif
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
        "bootdelay="    MK_STR(CONFIG_BOOTDELAY)    ""
    #endif
    #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
        "baudrate="    MK_STR(CONFIG_BAUDRATE)        ""
    #endif
    #ifdef    CONFIG_LOADS_ECHO
        "loads_echo="    MK_STR(CONFIG_LOADS_ECHO)    ""
    #endif
    #ifdef    CONFIG_ETHADDR
        "ethaddr="    MK_STR(CONFIG_ETHADDR)        ""
    #endif
    #ifdef    CONFIG_ETH1ADDR
        "eth1addr="    MK_STR(CONFIG_ETH1ADDR)        ""
    #endif
    #ifdef    CONFIG_ETH2ADDR
        "eth2addr="    MK_STR(CONFIG_ETH2ADDR)        ""
    #endif
    #ifdef    CONFIG_ETH3ADDR
        "eth3addr="    MK_STR(CONFIG_ETH3ADDR)        ""
    #endif
    #ifdef    CONFIG_IPADDR
        "ipaddr="    MK_STR(CONFIG_IPADDR)        ""
    #endif
    #ifdef    CONFIG_SERVERIP
        "serverip="    MK_STR(CONFIG_SERVERIP)        ""
    #endif
    #ifdef    CFG_AUTOLOAD
        "autoload="    CFG_AUTOLOAD            ""
    #endif
    #ifdef    CONFIG_PREBOOT
        "preboot="    CONFIG_PREBOOT            ""
    #endif
    #ifdef    CONFIG_ROOTPATH
        "rootpath="    MK_STR(CONFIG_ROOTPATH)        ""
    #endif
    #ifdef    CONFIG_GATEWAYIP
        "gatewayip="    MK_STR(CONFIG_GATEWAYIP)    ""
    #endif
    #ifdef    CONFIG_NETMASK
        "netmask="    MK_STR(CONFIG_NETMASK)        ""
    #endif
    #ifdef    CONFIG_HOSTNAME
        "hostname="    MK_STR(CONFIG_HOSTNAME)        ""
    #endif
    #ifdef    CONFIG_BOOTFILE
        "bootfile="    MK_STR(CONFIG_BOOTFILE)        ""
    #endif
    #ifdef    CONFIG_LOADADDR
        "loadaddr="    MK_STR(CONFIG_LOADADDR)        ""
    #endif
    #ifdef  CONFIG_CLOCKS_IN_MHZ
        "clocks_in_mhz=1"
    #endif
    #if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
        "pcidelay="    MK_STR(CONFIG_PCI_BOOTDELAY)    ""
    #endif
    #ifdef  CONFIG_EXTRA_ENV_SETTINGS
        CONFIG_EXTRA_ENV_SETTINGS
    #endif
        ""
    };

    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
    ", __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
    ", __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
    ", __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
    
    ");
    #else
            puts ("*** Warning - bad CRC, using default environment
    
    ");
            SHOW_BOOT_PROGRESS (-1);
    #endif
    
            if (sizeof(default_environment) > ENV_SIZE)
            {
                puts ("*** Error - default environment is too large
    
    ");
                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) != ''; i=nxt+1) {
                for (nxt=i; env_get_char(nxt) != ''; ++nxt)
                    ;
                for (k=i; k<nxt; ++k)
                    putc(env_get_char(k));
                putc  ('
    ');
    
                if (ctrlc()) {
                    puts ("
     ** Abort
    ");
                    return 1;
                }
            }
    
            printf("
    Environment size: %d/%d bytes
    ", 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) != ''; j=nxt+1) {
    
                for (nxt=j; env_get_char(nxt) != ''; ++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 ('
    ');
                break;
            }
            if (k < 0) {
                printf ("## Error: "%s" not defined
    ", 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: %s ", 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" ", 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" ", 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" ", 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" ", 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 ", baudrate); return 1; } printf ("## Switch baudrate to %d bps and press ENTER ... ", baudrate); udelay(50000); gd->baudrate = baudrate; #ifdef CONFIG_PPC gd->bd->bi_baudrate = baudrate; #endif serial_setbrg (); udelay(50000); for (;;) { if (getc() == ' ') break; } } if (*++nxt == '') { if (env > env_data) { env--; } else { *env = ''; } } else { for (;;) { *env = *nxt++; if ((*env == '') && (*nxt == '')) break; ++env; } } *++env = ''; } #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" +"" > 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 ", name); return 1; } while ((*env = *name++) != '') env++; for (i=2; i<argc; ++i) { char *val = argv[i]; *env = (i==2) ? '=' : ' '; while ((*++env = *val++) != '')  /* 将设置的新的参数写入到重定位的环境变量数据中 */ ; } /* end is marked with double '' */ *++env = ''; /* 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...
    ", 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 ", (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 "); } /* 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,写入新的环境变量

  • 相关阅读:
    Linux下文件的基本操作
    conpot_usage简要说明
    const声明常量以及特点
    let变量声明以及声明特性
    盒子模型
    文本样式
    行间距
    字体的其他样式
    字体分类
    字体样式
  • 原文地址:https://www.cnblogs.com/053179hu/p/10619313.html
Copyright © 2020-2023  润新知