Uboot source code analysis (based on S5PV210) uboot command system and environment variables


  In actual project development, we often need to add our own uboot commands and environment variables to realize customized products, so it is very important to understand the working mechanism of the two in uboot. This article will introduce these two aspects of knowledge. Hope to bring you some help!


# 1. Basics of the uboot command system## 1. Use the uboot command to enter the command line environment after uboot starts, enter the command here and press Enter to end, uboot will collect the command, analyze it, and then execute it. To break out of the whole infinite loop, you need to execute the bootm command to start the kernel. ## 2. Where is the implementation code of the uboot command system? The implementation code of the uboot command system is in uboot/common/cmd_xxx.c. There are several .c files related to the command system. (There are also command.c and main.c related to commands) ![Insert picture description here](https://img-blog.csdnimg.cn/1c6bca389a0b4e09a96522bdf1dad296.png) ## 3. Each command corresponds to one Function (1) Each uboot command corresponds to a function. This is an idea and method for uboot to implement the command system.

(2) We need to find the corresponding function behind each command, and analyze how this function corresponds to this command. (via function pointer)

4. The command parameter is passed to the function with argc&argv

(1) Some uboot commands also support passing parameters. That is to say, the parameter list received by the corresponding function behind the command has argc and argv, and then the command system will pass the command + parameters when we execute the command to the function that executes the command in the form of argc and argv.

md 30000000 10
argv[0]=md, argv[1]=30000000 argv[2]=10

For example analysis, take the help command as an example:

help命令背后对应的函数名叫:do_help,在uboot/common/command.c的236

int do_help (cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])

cmd_tbl_t * cmdtp 命令相关的数据结构,重点关注int argc, char *argv[]

struct cmd_tbl_s {
    
    
	char		*name;		/* Command Name			*/
	int		maxargs;	/* maximum number of arguments	*/
	int		repeatable;	/* autorepeat allowed?为1时当无命令输入时,自动重复上一个命令,为0则关闭		*/
					/* Implementation function	*/
	int		(*cmd)(struct cmd_tbl_s *, int, int, char *[]);//定义了一个函数指针类型
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CFG_LONGHELP
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s	cmd_tbl_t;

Two, uboot command analysis and execution process analysis

1. Starting from main_loop (in main.c)

(1) In the second stage of uboot startup, after initializing all the things that should be initialized, it enters an infinite loop, and the loop body of the infinite loop is main_loop.

(2) The main_loop function is executed once, which is a process of obtaining commands, parsing commands, and executing commands.

#ifndef CFG_HUSH_PARSER
	run_command (p, 0);//执行命令的函数
#else
	parse_string_outer(p, FLAG_PARSE_SEMICOLON |
			    FLAG_EXIT_FROM_LOOP);//解析字符串
#endif

(3) The run_command/parse_string_outer function is two functions used to execute commands. One of them can be selected for command processing.

参考学习:https://blog.csdn.net/yakehy/article/details/39318299

parse_string_outer内部的函数通过一层层调用,最终会去执行run_list_real函数

2. Detailed explanation of run_command function

/****************************************************************************
 * returns:
 *	1  - command executed, repeatable
 *	0  - command executed but not repeatable, interrupted commands are
 *	     always considered not repeatable
 *	-1 - not executed (unrecognized, bootd recursion or too many args)
 *           (If cmd is NULL or "" or longer than CFG_CBSIZE-1 it is
 *           considered unrecognized)
 *
 * WARNING:
 *
 * We must create a temporary copy of the command since the command we get
 * may be the result from getenv(), which returns a pointer directly to
 * the environment data, which may change magicly when the command we run
 * creates or modifies environment variables (like "bootp" does).
 */

int run_command (const char *cmd, int flag)
{
    
    
	cmd_tbl_t *cmdtp;
	char cmdbuf[CFG_CBSIZE];	/* working copy of cmd		*/
	char *token;			/* start of token in cmdbuf	*/
	char *sep;			/* end of token (separator) in cmdbuf */
	char finaltoken[CFG_CBSIZE];
	char *str = cmdbuf;
	char *argv[CFG_MAXARGS + 1];	/* NULL terminated	*/
	int argc, inquotes;
	int repeatable = 1;
	int rc = 0;

#ifdef DEBUG_PARSER
	printf ("[RUN_COMMAND] cmd[%p]=\"", cmd);
	puts (cmd ? cmd : "NULL");	/* use puts - string may be loooong */
	puts ("\"\n");
#endif

	clear_ctrlc();		/* forget any previous Control C */

	if (!cmd || !*cmd) {
    
    
		return -1;	/* empty command */
	}

	if (strlen(cmd) >= CFG_CBSIZE) {
    
    //CFG_CBSIZE,规定命令码的长度
		puts ("## Command too long!\n");
		return -1;
	}

	strcpy (cmdbuf, cmd);

	/* Process separators and check for invalid
	 * repeatable commands
	 */

#ifdef DEBUG_PARSER
	printf ("[PROCESS_SEPARATORS] %s\n", cmd);
#endif
	while (*str) {
    
    

		/*
		 * Find separator, or string end
		 * Allow simple escape of ';' by writing "\;"
		 */
		for (inquotes = 0, sep = str; *sep; sep++) {
    
    
			if ((*sep=='\'') &&
			    (*(sep-1) != '\\'))
				inquotes=!inquotes;

			if (!inquotes &&
			    (*sep == ';') &&	/* separator		*/
			    ( sep != str) &&	/* past string start	*/
			    (*(sep-1) != '\\'))	/* and NOT escaped	*/
				break;
		}

		/*
		 * Limit the token to data between separators
		 */
		token = str;
		if (*sep) {
    
    
			str = sep + 1;	/* start of command for next pass */
			*sep = '\0';
		}
		else
			str = sep;	/* no more commands for next pass */
#ifdef DEBUG_PARSER
		printf ("token: \"%s\"\n", token);
#endif

		/* find macros in this token and replace them */
		process_macros (token, finaltoken);

		/* Extract arguments */
		if ((argc = parse_line (finaltoken, argv)) == 0) {
    
    
			rc = -1;	/* no command at all */
			continue;
		}

		/* Look up command in command table */
		if ((cmdtp = find_cmd(argv[0])) == NULL) {
    
    //cmdtp结构体记录了命令的相关属性及信息
			printf ("Unknown command '%s' - try 'help'\n", argv[0]);
			rc = -1;	/* give up after bad command */
			continue;
		}

		/* found - check max args */
		if (argc > cmdtp->maxargs) {
    
    //传的参数过多则会打印使用说明
			printf ("Usage:\n%s\n", cmdtp->usage);//段说明
			rc = -1;
			continue;
		}

#if defined(CONFIG_CMD_BOOTD)
		/* avoid "bootd" recursion */
		if (cmdtp->cmd == do_bootd) {
    
    
#ifdef DEBUG_PARSER
			printf ("[%s]\n", finaltoken);
#endif
			if (flag & CMD_FLAG_BOOTD) {
    
    
				puts ("'bootd' recursion detected\n");
				rc = -1;
				continue;
			} else {
    
    
				flag |= CMD_FLAG_BOOTD;
			}
		}
#endif

		/* OK - call function to do the command */
		if ((cmdtp->cmd) (cmdtp, flag, argc, argv) != 0) {
    
    //调用函数指针cmdtp->cmd执行命令
			rc = -1;
		}

		repeatable &= cmdtp->repeatable;

		/* Did the user stop this? */
		if (had_ctrlc ())
			return -1;	/* if stopped then not repeatable */
	}

	return rc ? rc : repeatable;
}

3. Analysis of key points

(1) Console command acquisition (lines 453-459 or 439-440 of main.c)
insert image description here
(2) Command analysis. The parse_line function parses "md 30000000 10" into argv[0]=md, argv[1]=30000000 argv[2]=10;

(3) In the run_command function, search for commands in the command set. The find_cmd(argv[0]) function searches for the argv[0] command in the uboot command set.

(4) Execute the command in the run_command function. Finally, the corresponding function is called and executed by means of a function pointer.

  Thinking: The key point is how the find_cmd function finds out whether this command is a legally supported command of uboot?
  It depends on uboot's command system mechanism (how uboot completes the design of commands, how commands are registered, stored, managed, and indexed).

Three, how uboot handles the command set

1. Possible management methods

(1) array. Structure array, each structure member in the array is all the information of a command. but the size is fixed

(2) linked list. The data segment of each node in the linked list is a command structure, and all commands are placed on a linked list. This solves the inflexibility of the array method. The disadvantage is that additional memory overhead is required, and various algorithms (traversal, insertion, deletion, etc.) require code execution with a certain complexity.

(3) Is there a third type? Uboot does not use arrays or linked lists, but uses a new way to achieve this function. If you want to know the answer, continue to look down.

2. Command structure cmd_tbl_t

struct cmd_tbl_s {
    
    
	char		*name;		/* Command Name			*/
	int		maxargs;	/* maximum number of arguments	*/
	int		repeatable;	/* autorepeat allowed?		*/
					/* Implementation function	*/
	int		(*cmd)(struct cmd_tbl_s *, int, int, char *[]);
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CFG_LONGHELP
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s	cmd_tbl_t;
(1)name:命令名称,字符串格式。
(2)maxargs:命令最多可以接收多少个参数
(3)repeatable:指示这个命令是否可重复执行。重复执行是uboot命令行的一种工作机制,就是直接按回车则执行上一条执行的命令。
(4)cmd:函数指针,命令对应的函数的函数指针,将来执行这个命令的函数时使用这个函数指针来调用。初始化这个结构体时让其指向真正对应的哪个函数
(5)usage:命令的短帮助信息。对命令的简单描述。
(6)help:命令的长帮助信息。细节的帮助信息。
(7)complete:函数指针,指向这个命令的自动补全的函数。

  When uboot's command system is working, a command corresponds to an instance of a cmd_tbl_t structure, and then as many commands as uboot supports, as many structure instances are needed. The uboot command system manages these structure instances. When the user enters a command, uboot will search for these structure instances (the search method is related to the storage management method). Execute the command if found, or prompt unknown command if not found.

3. The idea of ​​uboot to realize command management

(1) Fill in a structure instance to form a command

(2) Attach a specific segment attribute (user-defined segment, not a code segment or a data segment) to the command structure instance, and link and arrange the content with this segment attribute together (next to each other, not mixed with other stuff, and won't drop one with such a segment attribute, but the order is out of order) .
insert image description here
(3) When uboot is relocated, load the uboot image into DDR. The section with specific section attributes in the uboot image loaded into DDR is actually a collection of command structures, a bit like an array of command structures. These commands can be traversed in a manner similar to traversing an array, and can be understood as an array

(4) The segment start address and end address (link address, defined in u-boot.lds) determine the start and end addresses of these command sets.

	__u_boot_cmd_start = .;//自定义的段,起始地址
	.u_boot_cmd : {
    
     *(.u_boot_cmd) }
	__u_boot_cmd_end = .;//结束地址

  When uboot was first written, its size was determined, and it was designed well at that time, so there was no need to use linked lists for dynamic addition, and the efficiency of linked lists was relatively low.

4. uboot command definition specific implementation analysis

Command example: version command (do_version function starting from line 33 in command.c)
(1) Basic analysis of U_BOOT_CMD macro

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {
      
      #name, maxargs, rep, cmd, usage, help}

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))
这个宏定义在uboot/common/command.h中。
U_BOOT_CMD(
	version,	1,		1,	do_version,
	"version - print monitor version\n",
	NULL
);

This macro becomes:

cmd_tbl_t __u_boot_cmd_version __attribute__ ((unused,section (".u_boot_cmd"))) = {
    
    #name, maxargs, rep, cmd, usage, help}

__attribute__ :gcc扩展语法,用于给前边这个变量cmd_tbl_t __u_boot_cmd_version
赋予一个段属性,贴一个标签(.u_boot_cmd,类似于.text,.data),将其放在
.u_boot_cmd这个自定义段中

#name:#的作用是让version变成字符串“version”,可以赋值给cmd_tbl_t __u_boot_cmd_version成员字符串name

  The key to the understanding of this U_BOOT_CMD macro lies in the name and segment attributes of the structure variable. The name uses ## as a hyphen (##name will be replaced by the passed name to ensure that the variable name will not be duplicated. Two commands with the same name are not allowed in uboot, even if the parameters are different), and the user Customize segment attributes to ensure that these data structures are linked together and arranged when linking.

  After the function corresponding to each command, there is a macro like this

5. Detailed explanation of find_cmd function

(1) The task of the find_cmd function is to find out whether there is a certain command from the current uboot command set. Returns a pointer to the command structure if found, or NULL if not found.

(2) The implementation idea of ​​the function is very simple, and it will be even simpler if the command with a dot (such as md.b md.w) is not considered. The idea of ​​the search command is actually the idea of ​​for loop traversing the array, the difference is that the start address and end address of the array are given by the address value, and the number of elements in the array is a structure variable type.

6. Detailed explanation of U_BOOT_CMD macro

  This macro actually defines a structure variable corresponding to a command. This variable name is related to the first parameter of the macro. Therefore, as long as the first parameter of the parameter is different when the macro is called, the defined structure variable will not have the same name.

4. Add custom commands in uboot

1. Add commands directly in the existing c file

(1) Add a command in uboot/common/command.c, such as: mycmd

(2) It is relatively simple to add commands in the existing .c file. You can directly use the U_BOOT_CMD macro to add commands, and provide a corresponding function of do_xxx to the command, and the command will work.

(3) After the addition is completed, recompile the project (make distclean; make x210_sd_config; make), and then burn a new uboot to run to experience the new command.

(4) You can also use argc and argv in the function to verify the parameters passed.

2. Create a c file and add commands

(1) Create a new command file in the uboot/common directory, called cmd_aston.c (the corresponding command name is called aston, and the corresponding function is called the do_aston function), and then add the U_BOOT_CMD macro and function corresponding to the command in the c file. Note that the header file includes not to be missed.

  The things done in the do_aston function are the things to be done by the aston command, and the corresponding functions are completed according to your actual needs.

(2) Add aston.o in uboot/common/Makefile, the purpose is to let Make compile and link cmd_aston.c into it when compiling.

(3) Recompile and burn. The recompilation steps are: make distclean; make x210_sd_config; make

3. Experience: the advantages of the uboot command system

(1) The uboot command system itself is a little complicated, but after it is written, it does not need to be moved. When we transplant uboot, we will not touch the command system of uboot. The most we can do is to add commands to uboot.

(2) Adding commands to uboot is very simple.

Five, uboot environment variable basis

1. The role of environment variables

  Let us not modify the source code of uboot, but modify some data and characteristics of uboot runtime by modifying environment variables. For example, by modifying the bootdelay environment variable, you can change the countdown seconds when the system starts automatically at boot.

2. Priority of environment variables

(1) There is an environment variable value in the uboot code, and there is also a value in the environment variable partition. The actual runtime rule of the uboot program is: if the environment variable partition is empty, use the value in the code; if the environment variable partition is not empty, use the corresponding value in the environment variable partition first.

(2) For example machid (machine code). In uboot, a machine code 2456 is defined in x210_sd.h, which is hard-coded in the program and cannot be changed. If you want to modify the machine code configured in uboot, you can modify the machine code in x210_sd.h, but after modifying the source code, you need to recompile and burn, which is very troublesome; the simpler method is to use the environment variable machid. set machid 0x998 is similar to this. After the machid environment variable is available, the environment variable corresponding to machid will be used first when the system starts. This is the priority issue.

3. How environment variables work in uboot

(1) The default environment variable , default_environment in uboot/common/env_common.c, this thing is essentially a character array, the size is CFG_ENV_SIZE (16KB), the content inside is composed of many continuous distribution of environment variables, each environment variable is the most Terminated with '\0'.

(2) The environment variable partition in the SD card is in the raw partition of uboot . The SD card is actually given a partition, which is specially used for storage. When storing, the environment variables in the DDR are actually written into the partition of the SD card as a whole. So when we saveenv, all the environment variables are actually saved, not just the changed ones.

(3) The environment variable in DDR, in default_environment, is essentially a character array. In uboot, it is actually a global variable. When linking, it is in the data segment. When relocating, default_environment is relocated to a memory address in DDR. The global character array at this address is the environment variable in the DDR of our uboot runtime.

Summary: The environment variable partition in the newly burned system is blank. When uboot runs for the first time, it loads a copy of the environment variable in the uboot code, which is called the default environment variable. When we saveenv, the environment variables in DDR will be updated to the environment variables in the SD card, and then they can be saved. The environment variables in the SD card will be loaded into the DDR when the environment variables are relocated next time. .

  Although the content in default_environment is initialized to a certain value by the uboot source code (this value is our default environment variable), but in the second stage of uboot startup, the code will judge whether the crc of the env partition in the SD card is passed during env_relocate . If the crc check passes, it means that there are correct environment variables stored in the SD card, then the relocate function will read the environment variables from the SD card to overwrite the default_environment character array, so that the last changed environment variables can be maintained every time the device is turned on .

6. Source code analysis of environment variable related commands

The following three functions are in: uboot/common/cmd_nvedit.c

1、printenv

(1) Find the function corresponding to the printenv command. As can be seen from the help of printenv, there are two ways to use this command. The first is to print all environment variables directly without parameters; the second is printenv name to print only the value of the environment variable name.

(2) Analyze the do_printenv function.

/************************************************************************
 * Command interface: print one or all environment variables
 */

int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    
    
	int i, j, k, nxt;
	int rcode = 0;

	if (argc == 1) {
    
    		/* Print all env variables	*/
		for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
    
    //i=nxt+1跳过了env_get_char(nxt) = '\0'
			for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)//确定命令在数组的的开始结束序号
				;
			for (k=i; k<nxt; ++k)//打印出命令
				putc(env_get_char(k));
			putc  ('\n');

			if (ctrlc()) {
    
    //用户可使用ctrl C随时终止
				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) {
    
    	/* 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)
				;
			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;
}
uchar env_get_char_memory (int index)
{
    
    
	if (gd->env_valid) {
    
    
		return ( *((uchar *)(gd->env_addr + index)) );//这个与else下的那个效果是相同的
	} else {
    
                                    //都是返回环境变量数组中的元素,在env_init()函数中
		return ( default_environment[index] );//有赋值,start_armboot()中调用过该函数
	}
}

uchar env_get_char (int index)//在存放环境变量的数组中查找第I个元素等不等于'\0'
{
    
    
	uchar c;

	/* if relocated to RAM */
	if (gd->flags & GD_FLG_RELOC)//default environment是否被加载到DDR中
		c = env_get_char_memory(index);//故而使用这个
	else
		c = env_get_char_init(index);//让内存那份环境变量有效,我们工作时DDR中的环境变量一直有效

	return (c);
}

(3) The do_printenv function first distinguishes whether argc=1 or not equal to 1. If argc=1, then print all the environment variables in a loop; if argc is not equal to 1, the following parameters are the environment variables to be printed. Just print whichever.

(4) When argc=1, use double for loops to process the printing of all environment variables in sequence. The first for loop is to process each environment variable. So how many environment variables there are, how many circles are executed in the first step.

(5) To understand this function, you must first understand how the entire environment variable is stored in memory.

(6) Key points: First, you must understand how environment variables are stored in memory; second, you must have a good foundation in C language processing strings.

2、setenv

(1) The command definition and corresponding function are in uboot/common/cmd_nvedit.c, and the corresponding function is do_setenv.
insert image description here
insert image description here
(2) The idea of ​​setenv is: first go to the environment variable in the DDR to find out if there is such an environment variable. If it exists, it needs to overwrite the original environment variable. If it does not exist, add an environment variable at the end.

1步:遍历DDR中环境变量的数组,找到原来就有的那个环境变量对应的地址。168-174行。2步:擦除原来的环境变量,259-2653步:写入新的环境变量,266-273行。

(3) Originally setenv is finished after finishing the above, but there are some additional issues to consider.

问题一:环境变量太多超出DDR中的字符数组,溢出的解决方法。

问题二:有些环境变量如baudrate、ipaddr等,在gd中有对应的全局变量。这种环境变量
在set更新的时候要同时去更新对应的全局变量,否则就会出现在本次运行中环境变量和全
局变量不一致的情况。

3、saveenv

(1) In uboot/common/cmd_nvedit.c, the corresponding function is 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);
}
env_auto.c文件:
char * env_name_spec = "SMDK bootable device";

insert image description here
(2) From the output of the saveenv command actually executed by uboot, and the configuration in x210_sd.h (#define CFG_ENV_IS_IN_AUTO), it can be analyzed that what we actually use is the relevant content in env_auto.c.

  There is no chip called auto. Env_auto.c uses macro definitions to conditionally compile various common flash chips (such as movinand, norflash, nand, etc.). Then read INF_REG (the corresponding register inside OMpin) in the program to know our boot medium, and then call the operation function corresponding to this boot medium to operate.

int saveenv(void)
{
    
    
#if defined(CONFIG_S5PC100) || defined(CONFIG_S5PC110) || defined(CONFIG_S5P6442)
	if (INF_REG3_REG == 2)
		saveenv_nand();
	else if (INF_REG3_REG == 3)
		saveenv_movinand();
	else if (INF_REG3_REG == 1)
		saveenv_onenand();
	else if (INF_REG3_REG == 4)
		saveenv_nor();
#elif	defined(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;
}

s5pc110.h文件:
#define INF_REG3_REG			__REG(INF_REG_BASE+INF_REG3_OFFSET)
#define INF_REG_BASE			0xE010F000
#define INF_REG3_OFFSET			0x0c

(3) do_saveenv internally calls the saveenv function in env_auto.c to perform the actual environment variable saving operation.

(4) Register address: E010F000+0C=E010_F00C, which means user-defined data.

  We have mentioned in the previous article: #BOOT_MMCSD (which is 3, defined in x210_sd.h) is written into this register after judging the boot medium in start.S (lines 259-260, 277-278), so read here The result must be 3, after judgment it is movinand. So the function actually executed is: saveenv_movinand

int saveenv_movinand(void)
{
    
    
#if defined(CONFIG_CMD_MOVINAND)//virt_to_phys,虚拟地址映射到物理地址,虚拟地址转换为物理地址
        movi_write_env(virt_to_phys((ulong)env_ptr));//真正的写的命令,将环境变量写入inand
        puts("done\n");//打印信息,表示保存完毕

        return 1;
#else
	return 0;
#endif	/* CONFIG_CMD_MOVINAND */
}

(5) The actual operation of saving environment variables is: the movi_write_env function in cpu/s5pc11x/movi.c, this function must write to the sd card, and the environment variable array in DDR (in fact, it is the array of default_environment, the size is 16KB, just right 32 sectors) into the ENV partition in iNand.

void movi_write_env(ulong addr)
{
    
    
	movi_write(raw_area_control.image[2].start_blk,//所传参数开始地址,扇区数,源
		   raw_area_control.image[2].used_blk, addr);
}

raw_area_t raw_area_control;

/*
 * magic_number: 0x24564236
 * start_blk: start block number for raw area
 * total_blk: total block number of card
 * next_raw_area: add next raw_area structure
 * description: description for raw_area
 * image: several image that is controlled by raw_area structure
 * by scsuh
 */
typedef struct raw_area {
    
    
	uint magic_number; /* to identify itself */
	uint start_blk; /* compare with PT on coherency test */
	uint total_blk;
	uint next_raw_area; /* should be sector number */
	char description[16];
	member_t image[15];
} raw_area_t; /* 512 bytes */

(6) raw_area_control is the original partition table of iNnad/SD card planned in uboot, which records our partition of iNand, env partition is also here, the subscript is 2, chasing this layer (movi_write() function) Enough, and then it is to call the underlying function of writing SD card/iNand in the driver part.

(7) If you want to know the original partition table of iNnad/SD card planned in uboot, you should study this function:
uboot/common/cmd_movi.c

int init_raw_area_table (block_dev_desc_t * dev_desc)
{
    
    
	struct mmc *host = find_mmc_device(dev_desc->dev);
	
	/* when last block does not have raw_area definition. */
	if (raw_area_control.magic_number != MAGIC_NUMBER_MOVI) {
    
    
		int i = 0;
		member_t *image;
		u32 capacity;
	
		if (host->high_capacity) {
    
    
			capacity = host->capacity;
		#ifdef CONFIG_S3C6410
			if(IS_SD(host))
				capacity -= 1024;
		#endif
		} else {
    
    
			capacity = host->capacity;
		}

		dev_desc->block_read(dev_desc->dev,
			capacity - (eFUSE_SIZE/MOVI_BLKSIZE) - 1,
			1, &raw_area_control);
		if (raw_area_control.magic_number == MAGIC_NUMBER_MOVI) {
    
    
			return 0;
		}
		
		dbg("Warning: cannot find the raw area table(%p) %08x\n",
			&raw_area_control, raw_area_control.magic_number);
		/* add magic number */
		raw_area_control.magic_number = MAGIC_NUMBER_MOVI;

		/* init raw_area will be 16MB */
		raw_area_control.start_blk = 16*1024*1024/MOVI_BLKSIZE;
		raw_area_control.total_blk = capacity;
		raw_area_control.next_raw_area = 0;
		strcpy(raw_area_control.description, "initial raw table");

		image = raw_area_control.image;

#if defined(CONFIG_EVT1)
	#if defined(CONFIG_FUSED)
		/* image 0 should be fwbl1 */
		image[0].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);
		image[0].used_blk = MOVI_FWBL1_BLKCNT;
		image[0].size = FWBL1_SIZE;
		image[0].attribute = 0x0;
		strcpy(image[0].description, "fwbl1");
		dbg("fwbl1: %d\n", image[0].start_blk);
	#endif
#endif

		/* image 1 should be bl2 */
#if defined(CONFIG_EVT1)
	#if defined(CONFIG_FUSED)
		image[1].start_blk = image[0].start_blk + MOVI_FWBL1_BLKCNT;
	#else
		image[1].start_blk = (eFUSE_SIZE/MOVI_BLKSIZE);
	#endif
#else
		image[1].start_blk = capacity - (eFUSE_SIZE/MOVI_BLKSIZE) -
				MOVI_BL1_BLKCNT;
#endif
		image[1].used_blk = MOVI_BL1_BLKCNT;
		image[1].size = SS_SIZE;

		image[1].attribute = 0x1;
		
		strcpy(image[1].description, "u-boot parted");
		dbg("bl1: %d\n", image[1].start_blk);

		/* image 2 should be environment */
#if defined(CONFIG_EVT1)
		image[2].start_blk = image[1].start_blk + MOVI_BL1_BLKCNT;
#else
		image[2].start_blk = image[1].start_blk - MOVI_ENV_BLKCNT;
#endif
		image[2].used_blk = MOVI_ENV_BLKCNT;
		image[2].size = CFG_ENV_SIZE;
		image[2].attribute = 0x10;
		strcpy(image[2].description, "environment");
		dbg("env: %d\n", image[2].start_blk);

		/* image 3 should be bl2 */
#if defined(CONFIG_EVT1)
		image[3].start_blk = image[2].start_blk + MOVI_ENV_BLKCNT;
#else
		image[3].start_blk = image[2].start_blk - MOVI_BL2_BLKCNT;
#endif
		image[3].used_blk = MOVI_BL2_BLKCNT;
		image[3].size = PART_SIZE_BL;
		image[3].attribute = 0x2;
		strcpy(image[3].description, "u-boot");
		dbg("bl2: %d\n", image[3].start_blk);

		/* image 4 should be kernel */
#if defined(CONFIG_EVT1)
		image[4].start_blk = image[3].start_blk + MOVI_BL2_BLKCNT;
#else
		image[4].start_blk = image[3].start_blk - MOVI_ZIMAGE_BLKCNT;
#endif
		image[4].used_blk = MOVI_ZIMAGE_BLKCNT;
		image[4].size = PART_SIZE_KERNEL;
		image[4].attribute = 0x4;
		strcpy(image[4].description, "kernel");
		dbg("knl: %d\n", image[4].start_blk);

		/* image 5 should be RFS */
#if defined(CONFIG_EVT1)
		image[5].start_blk = image[4].start_blk + MOVI_ZIMAGE_BLKCNT;
#else
		image[5].start_blk = image[4].start_blk - MOVI_ROOTFS_BLKCNT;
#endif
		image[5].used_blk = MOVI_ROOTFS_BLKCNT;
		image[5].size = PART_SIZE_ROOTFS;
		image[5].attribute = 0x8;
		strcpy(image[5].description, "rfs");
		dbg("rfs: %d\n", image[5].start_blk);

		for (i=6; i<15; i++) {
    
    
			raw_area_control.image[i].start_blk = 0;
			raw_area_control.image[i].used_blk = 0;
		}
	}
}

7. Obtain environment variables inside uboot

The following two functions are in: uboot\common\cmd_nvedit.c

1、eventv

(1) should be non-reentrant.

char *getenv (char *name)
{
    
    
	int i, nxt;

	WATCHDOG_RESET();

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

		for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
    
    
			if (nxt >= CFG_ENV_SIZE) {
    
    
				return (NULL);
			}
		}
		if ((val=envmatch((uchar *)name, i)) < 0)
			continue;
		return ((char *)env_get_addr(val));
	}

	return (NULL);
}

(2) The implementation method is to traverse the default_environment array, take out all the environment variables one by one, compare the names, and return the first address of the environment variable if they are equal.

2、get v_r

(1) Reentrant version.

知识补充:
	可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,
也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而
返回控制时不会出现什么错误;而不可重入的函数由于使用了一些系统资源,比如全局变量
区,中断向量表等,所以它如果被中断的话,可能会出现问题,这类函数是不能运行在多任
务环境下的。
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);
}

(2) The getenv function directly returns the address of the found environment variable in the environment variable in DDR, while the method of getenv_r function is to find the address of the environment variable in DDR, copy the environment variable to the provided buf, Instead of moving the environment variables in the original DDR.

  So the difference is: the address returned in getenv can only be read and cannot be written casually, while the environment variable returned in getenv_r is in the buf provided by itself, which can be rewritten and processed at will.

3. Summary

(1) The functions are the same, but the reentrant version will be safer and recommended.

(2) All operations related to environment variables, mainly understand the storage method of environment variables in DDR, understand the association and priority of environment variables and gd global variables, and understand the storage methods of environment variables in storage media (dedicated raw partition), the entire environment variable is clear.

Note: Most of this information is compiled from the course notes of Mr. Zhu's Internet of Things Lecture, citing Baidu Encyclopedia, some other people's blog content and combining with his own actual development experience. If there is any infringement, please contact to delete! The level is limited, if there are mistakes, welcome to communicate in the comment area.

Guess you like

Origin blog.csdn.net/weixin_45842280/article/details/126899689