Uboot environment variable implementation analysis, uboot environment variable implementation
U-boot environment variables are used to store frequently used parameter variables. uboot wants to store environment variables in static storage (such as nand nor eeprom mmc ).
Some of them are also frequently used, and some are defined by users. Changing these names may cause errors. The following table lists some common environment variables:
Number of seconds to wait for bootdelay to start automatically
Baud Rate of the baudrate serial port console
Netmask Ethernet interface mask
Physical address of the ethaddr Ethernet Card
Default bootfile download file
Boot parameters passed by bootargs to the kernel
Command executed when bootcmd is automatically started
Server IP Address
Ipaddr local ip Address
Stdin standard input device
Stdout standard output device
Stderr standard error device
These are the default environment variables of uboot. uboot uses these environment variables for configuration. We can define some environment variables for our own uboot driver.
The design logic of Uboot environment variables is to read env from static memory into RAM during startup, and then operate on env under uboot (for example, printenv editenv setenv) all operations on the env in RAM. Only when saveenv is executed will the env in RAM be re-written to the static storage.
This design logic can accelerate the read/write speed of env.
Based on this design logic, uboot 2014.4 implements the saveenv command to save the env to static storage, but does not implement the command to read the env to RAM.
Let's take a look at how to implement the env Data Structure initialization operation in uboot.
1. env Data Structure
Env_t is defined in include/environment. h, as follows:
#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT# define ENV_HEADER_SIZE (sizeof(uint32_t) + 1)# define ACTIVE_FLAG 1# define OBSOLETE_FLAG 0#else# define ENV_HEADER_SIZE (sizeof(uint32_t))#endif#define ENV_SIZE (CONFIG_ENV_SIZE - ENV_HEADER_SIZE)typedef struct environment_s { uint32_t crc; /* CRC32 over data bytes */#ifdef CONFIG_SYS_REDUNDAND_ENVIRONMENT unsigned char flags; /* active/obsolete flags */#endif unsigned char data[ENV_SIZE]; /* Environment data */} env_t;
CONFIG_ENV_SIZE is the total length of the environment variable we need to configure in the configuration file.
Here we use nand as a static storage, and one block of nand is 128 K. Therefore, we use a block to store the env. The CONFIG_ENV_SIZE is 128 K.
The first four bytes of the Env_t struct are the crc check code for data, and CONFIG_SYS_REDUNDAND_ENVIRONMENT is not defined. Therefore, the array is followed by the data array, and the array size is ENV_SIZE.
ENV_SIZE is CONFIG_ENV_SIZE, which is 4 bytes,
Therefore, the "env_t" struct contains the entire storage area with the specified length of CONFIG_ENV_SIZE.
The first 4 bytes is the crc check code, and the remaining space is used to store environment variables.
Note that the crc verification code is calculated during saveenv in uboot and then written to nand. Therefore, crc verification will fail when uboot is started for the first time,
Because the block data read by uboot from nand is random and meaningless, the crc check is correct after the saveenv is executed and uboot is restarted.
The data field stores the actual environment variables. Env of u-boot is stored in name = value "\ 0" mode, and "\ 0 \ 0" indicates the end of the entire env at the end of all env.
The new name = value pair is always added to the end of the env data block. When a name = value pair is deleted, the subsequent environment variables are moved forward, you can delete an existing environment variable before inserting it.
U-boot saves the Data Pointer of env_t in another place.
It is a gd_t structure (different platforms have different gd_t structures). Here we use ARM as an example to list only the content related to env.
typedef struct global_data { … unsigned long env_off; /* Relocation Offset */ unsigned long env_addr; /* Address of Environment struct ??? */ unsigned long env_valid /* Checksum of Environment valid */ … } gd_t;
Ii. env Initialization
In uboot, the entire env architecture can be divided into three layers:
(1) The command layer, such as the implementation of the saveenv and setenv editenv commands, and the env_relocate function called at startup.
(2) The intermediate encapsulation layer uses different static storage features to encapsulate some common functions required for the command layer, such as env_init, env_relocate_spec, and saveenv. The implementation file is in common/env_xxx.c
(3) The driver layer is used to perform read/write operations on different static memories. These are required by different subsystems in uboot.
First, analyze the env initialization process started by uboot according to the execution flow sequence.
First, env_init of init_sequence is called in board_init_f. This function is implemented by different memory. The implementation in nand is as follows:
<span style="font-size:14px;">/* * This is called before nand_init() so we can't read NAND to * validate env data. * * Mark it OK for now. env_relocate() in env_common.c will call our * relocate function which does the real validation. * * When using a NAND boot image (like sequoia_nand), the environment * can be embedded or attached to the U-Boot image in NAND flash. * This way the SPL loads not only the U-Boot image from NAND but * also the environment. */int env_init(void){ gd->env_addr = (ulong)&default_environment[0]; gd->env_valid = 1; return 0;}</span>
From the annotations, we can basically see the function's role. Because env_init is earlier than static memory initialization, env read/write cannot be performed. Here, the env variables in gd are configured,
By default, env is set to valid. The env_relocate function is convenient for the subsequent env from nand to ram relocate.
Run the following command in board_init_r:
/* initialize environment */ if (should_load_env()) env_relocate(); else set_default_env(NULL);
This is executed after all memory initialization is complete.
First, call should_load_env, as shown below:
/* * Tell if it's OK to load the environment early in boot. * * If CONFIG_OF_CONFIG is defined, we'll check with the FDT to see * if this is OK (defaulting to saying it's not OK). * * NOTE: Loading the environment early can be a bad idea if security is * important, since no verification is done on the environment. * * @return 0 if environment should not be loaded, !=0 if it is ok to load */static int should_load_env(void){#ifdef CONFIG_OF_CONTROL return fdtdec_get_config_int(gd->fdt_blob, "load-environment", 1);#elif defined CONFIG_DELAY_ENVIRONMENT return 0;#else return 1;#endif}
From the annotations, we can see that CONFIG_OF_CONTROL is not defined. Considering the security issue, if we want to postpone the load of env, we can define CONFIG_DELAY_ENVIRONMENT. If 0 is returned here, set_default_env is called to use the default env, by default, env is set in CONFIG_EXTRA_ENV_SETTINGS in the configuration file.
We can call env_relocate to load env somewhere later. Here we choose to load env directly here. Therefore, CONFIG_DELAY_ENVIRONMENT is not defined and 1 is returned. Call env_relocate.
In common/env_common.c:
void env_relocate(void){#if defined(CONFIG_NEEDS_MANUAL_RELOC) env_reloc(); env_htab.change_ok += gd->reloc_off;#endif if (gd->env_valid == 0) {#if defined(CONFIG_ENV_IS_NOWHERE) || defined(CONFIG_SPL_BUILD) /* Environment not changable */ set_default_env(NULL);#else bootstage_error(BOOTSTAGE_ID_NET_CHECKSUM); set_default_env("!bad CRC");#endif } else { env_relocate_spec(); }}
Gd-> env_valid is set to 1 in the previous env_init, so env_relocate_spec is called here,
This function is also provided by the Intermediate encapsulation layer of different storage. For nand in common/env_nand.c, the following is the function:
void env_relocate_spec(void){ int ret; ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE); ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf); if (ret) { set_default_env("!readenv() failed"); return; } env_import(buf, 1);}
First define a buf with the length of CONFIG_ENV_SIZE, then call readenv,
CONFIG_ENV_OFFSET is the offset position of the env defined in the configuration file in nand. Here we define a 4 m position.
Readenv is also in env_nand.c, as follows:
int readenv(size_t offset, u_char *buf){ size_t end = offset + CONFIG_ENV_RANGE; size_t amount_loaded = 0; size_t blocksize, len; u_char *char_ptr; blocksize = nand_info[0].erasesize; if (!blocksize) return 1; len = min(blocksize, CONFIG_ENV_SIZE); while (amount_loaded < CONFIG_ENV_SIZE && offset < end) { if (nand_block_isbad(&nand_info[0], offset)) { offset += blocksize; } else { char_ptr = &buf[amount_loaded]; if (nand_read_skip_bad(&nand_info[0], offset, &len, NULL, nand_info[0].size, char_ptr)) return 1; offset += blocksize; amount_loaded += len; } } if (amount_loaded != CONFIG_ENV_SIZE) return 1; return 0;}
The Readenv function uses nand_info [0] to read the nand and read the data at the specified position and length to the buf. Nand_info [0] is a global variable used to represent the first nand device. This variable is initialized in nand_init. Nand_init must be before env_relocate.
Return to env_relocate_spec, and call env_import after the buf reads back, as shown below:
/* * Check if CRC is valid and (if yes) import the environment. * Note that "buf" may or may not be aligned. */int env_import(const char *buf, int check){ env_t *ep = (env_t *)buf; if (check) { uint32_t crc; memcpy(&crc, &ep->crc, sizeof(crc)); if (crc32(0, ep->data, ENV_SIZE) != crc) { set_default_env("!bad CRC"); return 0; } } if (himport_r(&env_htab, (char *)ep->data, ENV_SIZE, '\0', 0, 0, NULL)) { gd->flags |= GD_FLG_ENV_READY; return 1; } error("Cannot import environment: errno = %d\n", errno); set_default_env("!import failed"); return 0;}
First, the buf is forcibly converted to the env_t type, and then crc verification is performed on the data. Compared with the original crc verification in the buf, the default env is used for inconsistency.
Finally, call himport_r. This function splits the given data into the hash table of env_htab according to '\ 0.
Subsequent operations on the env, such as printenv setenv editenv, are performed on the hash table.
Env_relocate execution is complete, and env Initialization is complete.
Three env operation implementation
Uboot's env operation commands are implemented in common/cmd_nvedit.c.
For the setenv printenv editenv commands, the implementation code is all about the env_htab operations from relocate to RAM. Here we will not analyze them in detail. Let's take a look at the savenv implementation.
static int do_env_save(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]){ printf("Saving Environment to %s...\n", env_name_spec); return saveenv() ? 1 : 0;}U_BOOT_CMD( saveenv, 1, 0, do_env_save, "save environment variables to persistent storage", "");
In do_env_save, saveenv is called. This function is an encapsulation layer function implemented by different storage devices. For nand, in common/env_nand.c:
int saveenv(void){ int ret = 0; ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1); ssize_t len; char *res; int env_idx = 0; static const struct env_location location[] = { { .name = "NAND", .erase_opts = { .length = CONFIG_ENV_RANGE, .offset = CONFIG_ENV_OFFSET, }, },#ifdef CONFIG_ENV_OFFSET_REDUND { .name = "redundant NAND", .erase_opts = { .length = CONFIG_ENV_RANGE, .offset = CONFIG_ENV_OFFSET_REDUND, }, },#endif }; if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE) return 1; res = (char *)&env_new->data; len = hexport_r(&env_htab, '\0', 0, &res, ENV_SIZE, 0, NULL); if (len < 0) { error("Cannot export environment: errno = %d\n", errno); return 1; } env_new->crc = crc32(0, env_new->data, ENV_SIZE);#ifdef CONFIG_ENV_OFFSET_REDUND env_new->flags = ++env_flags; /* increase the serial */ env_idx = (gd->env_valid == 1);#endif ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);#ifdef CONFIG_ENV_OFFSET_REDUND if (!ret) { /* preset other copy for next write */ gd->env_valid = gd->env_valid == 2 ? 1 : 2; return ret; } env_idx = (env_idx + 1) & 1; ret = erase_and_write_env(&location[env_idx], (u_char *)env_new); if (!ret) printf("Warning: primary env write failed," " redundancy is lost!\n");#endif return ret;}
Define the env_t type variable env_new and prepare to store the env.
Use the hexport_r function to operate on env_htab and read the env content to env_new-> data,
Verify data and obtain the verification code env_new-> crc.
Finally, erase_and_write_env is called to wipe env_new first and then write it into the nand area with the offset and length defined by location.
This completes the env write to nand operation.
Himport_r hexport_r hdelete_r hmatch_r involved in the savenv readenv function and printenv setenv implementation function are basic operation functions for the env_htab hash table.
These functions are encapsulated in the lib/hashtable. c of uboot. These functions are not analyzed carefully here.
How to analyze uboot code
Then, in the linux environment, we should first learn the bare metal program development of the board. After learning, the underlying interfaces can basically be implemented by ourselves. Then we will analyze the Uboot code, starting with makefile, it is best to build a souceInsignt project for ease of reading. Let's take a look at the integration of various functions of the Code, as well as the implementation of advanced functions, and the network aspects involved, I 'd better know about the network protocol.
How to Use variables in uboot Environment Variables
Setenv
Print the environment variable printenv
Save the environment variable saveenv
It's hard to say