嵌入式linux开发uboot移植(六)——uboot环境变量
一、uboot环境变量简介
u-boot的缺省情况下会有一些基本的环境变量,当执行saveenv时,环境变量会保存到flash存储设备中。如果环境变量的值为空,则uboot会使用uboot代码中的值;如果环境变量不为空,则优先使用环境变量的值。默认环境变量在uboot源码中common/Env_common.c文件中。
uchar default_environment[CFG_ENV_SIZE] = {
#ifdef CONFIG_BOOTARGS
"bootargs="CONFIG_BOOTARGS"\0"
#endif
#ifde fCONFIG_BOOTCOMMAND
"bootcmd="CONFIG_BOOTCOMMAND"\0"
#endif
#ifdef CONFIG_MTDPARTITION
"mtdpart="CONFIG_MTDPARTITION"\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_ETHADDR
"ethaddr="MK_STR(CONFIG_ETHADDR)"\0"
#endif
#ifdef CONFIG_IPADDR
"ipaddr="MK_STR(CONFIG_IPADDR)"\0"
#endif
#ifdef CONFIG_SERVERIP
"serverip="MK_STR(CONFIG_SERVERIP)"\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_EXTRA_ENV_SETTINGS
CONFIG_EXTRA_ENV_SETTINGS
#endif
"\0"
};
所有的环境变量存储在一个16KB大小的一维数组中,每个环境变量以"\0"结束。uboot在启动时,在遍历调用执行init_sequence函数指针数组中的env_init函数时已经对环境变量进行校验,通过调用env_relocate函数,将环境变量从Flash启动设备重定位到SDRAM中。uboot启动时环境变量的初始化如下:
环境变量的使用示例:
setenv bootcmd "nand read 0x20008000 0x80000 0x500000;bootm 0x30008000"
saveenv
常见的uboot环境变量:
bootdelay 执行自动启动的等候秒数
baudrate 串口控制台的波特率
netmask 以太网接口的掩码
ethaddr 以太网卡的网卡物理地址
bootfile 缺省的下载文件
bootargs 传递给内核的启动参数
bootcmd 自动启动时执行的命令
serverip 服务器端的ip地址
ipaddr 本地ip 地址
stdin 标准输入设备
stdout 标准输出设备
stderr 标准出错设备
二、环境变量操作的源码分析
在对环境变量的操作中,u-boot调用的对环境变量操作的通用函数接口位于common/env_common.c文件,环境变量存储在不同的启动设备时的操作函数接口存放在不同的文件,如common/env_epprom.c、common/env_flash.c、common/env_nand.c,对环境变量的操作函数接口存放在common/env_nvedit.c。
1、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) {//printenv命令没有参数时打印出所有变量信息
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/%ld bytes\n",
i, (ulong)ENV_SIZE);
return 0;
}
for (i=1; i<argc; ++i) {//遍历打印单个或多个变量的信息
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)//遍历计算变量的字符数
;
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;
}
2、setenv
int do_setenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
if (argc < 2) {//setenv命令无参数输入处理情况
printf ("Usage:\n%s\n", cmdtp->usage);
return 1;
}
return _do_setenv (flag, argc, argv);
}
3、saveenv
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);
}
int saveenv(void)
{
movi_write_env(virt_to_phys((ulong)env_ptr));//将所列环境变量写入SD卡
puts ("done\n");
return 1;
}
4、字符串解析
uboot中对环境变量的操作充分利用了对环境变量字符串的解析。本部分将字符串的解析提取出来进行分析。
环境变量存储在default_environment[CFG_ENV_SIZE]数组中,每个环境变量以"\0"结束。通过对环境变量数组default_environment[CFG_ENV_SIZE]进行解析,将环境变量分别打印出来。
实验源码:
#include <stdio.h>
unsigned char environment[] =
{
"bootdelay=" "10" "\0"
"ipaddr=" "192.168.1.210" "\0"
"serverip=" "192.168.1.200" "\0"
"bootcmd=" "tftp 0x20008000 uImage;bootm 0x20008000" "\0"
"baudrate=" "115200" "\0"
};
int main(int argc, char **argv)
{
int i, s,k;
for(i = 0; *(environment+i) != '\0'; i = s + 1) //按照变量遍历环境变量数组
{ //
for(s = i; *(environment + s) != '\0'; s++)//计算出变量的字符数量
;
for(k = i; k < s; k++)//打印出变量的信息
putc( *(environment + k), stdout);
putc('\n', stdout);
}
return 0;
}
编译运行结果:
bootdelay=10
ipaddr=192.168.1.210
serverip=192.168.1.200
bootcmd=tftp 0x20008000 uImage;bootm 0x20008000
baudrate=115200
三、uboot中的SD/MMC设备驱动
1、uboot中的SD/MMC设备的初始化
uboot在启动的第二阶段BL2的start_armboot函数中在对开发板初始化部分使用mmc_initialize函数对SD/MMC设备进行初始化,初始化分为两部分:
A、cpu_mmc_init
cpu_mmc_init函数完成对SoC内部SD/MMC控制器的初始化,包括使用setup_hsmmc_clock函数初始化SD/MMC控制器的时钟,setup_hsmmc_cfg_gpio函数初始化SoC中SD/MMC控制器的GPIO引脚。
返回时调用了smdk_s3c_hsmmc_init函数,smdk_s3c_hsmmc_init根据SD/MMC通道调用函数s3c_hsmmc_initialize初始化struct mmc结构体实例的属性和操作方法。struct mmc结构体实例的操作方法指定了SD/MMC设备操作硬件设备的方法函数。
B、mmc_init
mmc_init函数完成对SD/MMC硬件设备的初始化。mmc_init函数内部通过调用mmc_go_idle、mmc_send_if_cond、mmc_send_app_op_cond、mmc_send_cmd等函数最终调用struct mmc结构体实例中操作方法send_cmd、set_ios、init挂接的s3c_hsmmc_send_command、s3c_hsmmc_set_ios、s3c_hsmmc_init硬件操作函数,最终完成对SD/MMC硬件设备的初始化。
SD/MMC设备驱动stuct mmc结构体封装了SD/MMC设备的属性和操作方法。
struct mmc {
struct list_head link;
char name[32];
void *priv;
uint voltages;
uint version;
uint f_min;
uint f_max;
int high_capacity;
uint bus_width;
uint clock;
uint card_caps;
uint host_caps;
uint ocr;
uint scr[2];
uint csd[4];
uint cid[4];
ushort rca;
uint tran_speed;
uint read_bl_len;
uint write_bl_len;
u32 capacity;
struct mmc_ext_csdext_csd;/* mmc v4 extended card specific */
block_dev_desc_t block_dev;
int (*send_cmd)(struct mmc *mmc,
struct mmc_cmd *cmd, struct mmc_data *data);
void (*set_ios)(struct mmc *mmc);
int (*init)(struct mmc *mmc);
};
SD/MMC设备驱动构建就是对SD/MMC设备结构体struct mmc类型实例的初始化。
2、uboot中对SD/MMC设备的操作
saveenv命令保存环境变量到Flash设备时需要调用驱动程序操作Flash设备。uboot硬件驱动程序是从linux kernel移植的。saveenv的命令函数为do_saveenv。saveenv命令保存环境变量的工作流程如下:
A、调用common/cmd_nvedit.c中do_saveenv函数
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);
}
B、调用common/env_auto.c文件中saveenv函数:
int saveenv(void)
{
#if defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
if (INF_REG3_REG == 2)
saveenv_nand();//环境变量保存到nand设备
else if (INF_REG3_REG == 3)
saveenv_movinand();//环境变量保存到SD/MMC设备
else if (INF_REG3_REG == 1)
saveenv_onenand();
else if (INF_REG3_REG == 4)
saveenv_nor();
#elifdefined(CONFIG_SMDK6440)
if (INF_REG3_REG == 3)
saveenv_nand();
else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
saveenv_nand_adv();
else if (INF_REG3_REG == 0 || INF_REG3_REG == 1 || INF_REG3_REG == 7)
saveenv_movinand();
#else // others
if (INF_REG3_REG == 2 || INF_REG3_REG == 3)
saveenv_nand();
else if (INF_REG3_REG == 4 || INF_REG3_REG == 5 || INF_REG3_REG == 6)
saveenv_nand_adv();
else if (INF_REG3_REG == 0 || INF_REG3_REG == 7)
saveenv_movinand();
else if (INF_REG3_REG == 1)
saveenv_onenand();
#endif
else
printf("Unknown boot device\n");
return 0;
}
C、调用common/env_auto.c文件中saveenv_movinand函数
int saveenv_movinand(void)
{
#if defined(CONFIG_CMD_MOVINAND)
movi_write_env(virt_to_phys((ulong)env_ptr));
puts("done\n");
return 1;
#else
return 0;
#endif/* CONFIG_CMD_MOVINAND */
}
E、调用cpu/s5pc11x/movi.c文件中的movi_write_env函数
void movi_write_env(ulong addr)
{
movi_write(raw_area_control.p_w_picpath[2].start_blk,
raw_area_control.p_w_picpath[2].used_blk, addr);
}
F、调用drivers/mmc/mmc.c文件中movi_write函数
ulong movi_write(ulong start, lbaint_t blkcnt, void *src)
{
return mmc_bwrite(0, start, blkcnt, src);
}
G、调用drivers/mmc/mmc.c文件中mmc_bwrite函数,最终调用drivers/mmc/s3c_hsmmc.c文件中s3c_hsmmc_send_command函数完成数据保存写入。
3、uboot中的驱动分层
uboot中与SD/MMC设备有关的操作接口函数位于drivers/mmc/mmc.c文件中,这些接口函数最终指向了struct mmc结构体的函数指针成员。与SD/MMC设备硬件有关的操作函数接口位于drivers/mmc/s3c_hsmmc.c文件中,主要是SoC内部的SD/MMC控制器对SD/MMC设备的操作,如SD/MMC设备的命令发送函数s3c_hsmmc_send_command、SD/MMC设备的数据读写函数s3c_hsmmc_set_ios。SD/MMC设备驱动在驱动构建初始化函数s3c_hsmmc_initialize中将struct mmc结构体实例中的函数指针与对相对应的SD/MMC设备操作方法接口进行了挂接。
uboot将对SoC内部与SD/MMC设备有关的初始化操作函数如setup_hsmmc_clock、setup_hsmmc_cfg_gpio放在了与SoC相关的文件目录cpu/s5pc11x/setup_hsmmc.c文件中。
uboot中对于linux设备驱动分层的继承移植极大方便了开发者对uboot的移植。