linux系统移植:u-boot启动流程(下)(代码片段)

嵌入式up笔记 嵌入式up笔记     2022-12-02     703

关键词:

目录

Linux系统移植:U-Boot 启动流程(下)

一、run_main_loop 函数详解

uboot 启动以后会进入 3 秒倒计时,如果在 3 秒倒计时结束之前按下按下回车键,那么就会进入 uboot 的命令模式,如果倒计时结束以后都没有按下回车键,那么就会自动启动 Linux 内核,这段过程就是由 run_main_loop 函数实现,函数代码如下:

static int run_main_loop(void)

#ifdef CONFIG_SANDBOX
	sandbox_main_loop_init();
#endif
	/* main_loop() can return to retry autoboot, if so just run it again */
	for (;;)
		main_loop();
	return 0;

函数执行在 main_loop() 死循环,函数代码如下:

/* We come here after U-Boot is initialised and ready to process commands */
void main_loop(void)

	const char *s;

	bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");

#ifndef CONFIG_SYS_GENERIC_BOARD
	puts("Warning: Your board does not use generic board. Please read\\n");
	puts("doc/README.generic-board and take action. Boards not\\n");
	puts("upgraded by the late 2014 may break or be removed.\\n");
#endif

#ifdef CONFIG_VERSION_VARIABLE
	setenv("ver", version_string);  /* set version variable */
#endif /* CONFIG_VERSION_VARIABLE */

	cli_init();

	run_preboot_environment_command();

#if defined(CONFIG_UPDATE_TFTP)
	update_tftp(0UL, NULL, NULL);
#endif /* CONFIG_UPDATE_TFTP */

	s = bootdelay_process();
	if (cli_process_fdt(&s))
		cli_secure_boot_cmd(s);

	autoboot_command(s);

	cli_loop();

其中 bootstage_mark_name 函数,打印出启动进度

定义了宏 CONFIG_VERSION_VARIABLE 的话就会执行函数 setenv,设置换将变量 ver 的值为 version_string,也就是设置版本号环境变量

cli_init 函数,初始化 shell 相关的变量

run_preboot_environment_command 函数,获取环境变量 perboot 的内容,perboot是一些预启动命令,一般不使用这个环境变量

bootdelay_process 函数,此函数会读取环境变量 bootdelay 和 bootcmd 的内容,然后将 bootdelay 的值赋值给全局变量 stored_bootdelay,返回值为环境变量 bootcmd 的值

如果 定义了 CONFIG_OF_CONTROL 的话函数 cli_process_fdt 就会实现,如果没有定义 CONFIG_OF_CONTROL 的话函数 cli_process_fdt 直接返回一个 false。在本 uboot 中没有定义 CONFIG_OF_CONTROL,因此 cli_process_fdt 函数返回值为 false

autoboot_command 函数,此函数就是检查倒计时是否结束,如果倒计时自然结束那么就执行函数
run_command_list,此函数会执行参数 s 指定的一系列命令,也就是环境变量 bootcmd 的命令,bootcmd 里面保存着默认的启动命令,因此 linux 内核启动如果倒计时结束之前按下按键,那么就会执行 cli_loop 函数,这个就是命令处理函数,负责接收好处理输入的命令,执行对应的指令

二、cli_loop 函数详解

cli_loop 函数是 uboot 的命令行处理函数,在 uboot 中输入各种命令就是由 cli_loop 来处理的,函数原型如下:

void cli_loop(void)

#ifdef CONFIG_SYS_HUSH_PARSER
	parse_file_outer();
	/* This point is never reached */
	for (;;);
#else
	cli_simple_loop();
#endif /*CONFIG_SYS_HUSH_PARSER*/

定义宏 CONFIG_SYS_HUSH_PARSER,后面调用函数 parse_file_outer,后面是个死循环,不会执行到

parse_file_outer 函数如下:

#ifndef __U_BOOT__
static int parse_file_outer(FILE *f)
#else
int parse_file_outer(void)
#endif

	int rcode;
	struct in_str input;
#ifndef __U_BOOT__
	setup_file_in_str(&input, f);
#else
	setup_file_in_str(&input);
#endif
	rcode = parse_stream_outer(&input, FLAG_PARSE_SEMICOLON);
	return rcode;

先调用函数 setup_file_in_str 初始化变量 input 的成员变量,之后调用函数 parse_stream_outer,这个函数就是 hush shell 的命令解释器,负责接收命令行输入,然后解析并执行相应的命令

函数 parse_stream_outer 中使用 do-while 循环就是处理输入命令,在循环中调用函数 parse_stream 进行命令解析之后调用 run_list 函数来执行解析出来的命令而函数 run_list 会经过一系列的函数调用,最终通过调用 cmd_process 函数来处理命令

三、 cmd_process 函数详解

cmd_process 函数用来处理命令行的命令,uboot 使用宏 U_BOOT_CMD 来定义命令,宏 U_BOOT_CMD 定义在文件 include/command.h 中,

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)

宏 U_BOOT_CMD_COMPLETE 如下

#define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \\
	ll_entry_declare(cmd_tbl_t, _name, cmd) =			\\
		U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd,	\\
						_usage, _help, _comp);

ll_entry_declar 定义在文件 include/linker_lists.h 中,定义如下

#define ll_entry_declare(_type, _name, _list)				\\
	_type _u_boot_list_2_##_list##_2_##_name __aligned(4)		\\
			__attribute__((unused,				\\
			section(".u_boot_list_2_"#_list"_2_"#_name)))

_type 为 cmd_tbl_t 结构体,ll_entry_declare 就是定义了一个 cmd_tbl_t 变量,这里用到了 C 语言中的 “##” 连接符。其中的 “##_list” 表示用 _list 的值来替换,“##_name” 就是用 _name 的值来替换

宏 U_BOOT_CMD_MKENT_COMPLETE 定义在文件 include/command.h 中,内容如下:

#define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \\
								  _usage, _help, _comp) \\
	 #_name, _maxargs, _rep, _cmd, _usage, \\
	  _CMD_HELP(_help) _CMD_COMPLETE(_comp) 

上面代码中的 # 就是将 _name 传递过来的值字符串化,U_BOOT_CMD_MKENT_COMPLETE 又用到了_CMD_HELP 和 _CMD_COMPLETE,这两个宏的定义如下

#ifdef CONFIG_AUTO_COMPLETE
# define _CMD_COMPLETE(x) x,
#else
# define _CMD_COMPLETE(x)
#endif
#ifdef CONFIG_SYS_LONGHELP
# define _CMD_HELP(x) x,
#else
# define _CMD_HELP(x)
#endif

_CMD_COMPLETE 和 _CMD_HELP 就 是 取 自 身 的 值 , 然 后 在 加 上 一 个 ‘ , ‘,

以一个具体的命令为例,来看一下 U_BOOT_CMD 经过展开以后究竟是个什么模样的,比如 dhcp 命令:

U_BOOT_CMD(
    dhcp, 3, 1, do_dhcp,
    "boot image via network using DHCP/TFTP protocol",
    "[loadAddress] [[hostIPaddr:]bootfilename]"
);

将其每一步运行函数都展开

U_BOOT_CMD(
    dhcp, 3, 1, do_dhcp,
    "boot image via network using DHCP/TFTP protocol",
    "[loadAddress] [[hostIPaddr:]bootfilename]"
);
//1、将 U_BOOT_CMD 展开后为:
U_BOOT_CMD_COMPLETE(dhcp, 3, 1, do_dhcp,
    "boot image via network using DHCP/TFTP protocol",
    "[loadAddress] [[hostIPaddr:]bootfilename]",
    NULL)
//2、将 U_BOOT_CMD_COMPLETE 展开后为:
ll_entry_declare(cmd_tbl_t, dhcp, cmd) = \\
U_BOOT_CMD_MKENT_COMPLETE(dhcp, 3, 1, do_dhcp, \\
    "boot image via network using DHCP/TFTP protocol", \\
    "[loadAddress] [[hostIPaddr:]bootfilename]", \\
    NULL);
//3、将 ll_entry_declare 和 U_BOOT_CMD_MKENT_COMPLETE 展开后为:
cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \\
__attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \\
  "dhcp", 3, 1, do_dhcp, \\
    "boot image via network using DHCP/TFTP protocol", \\
    "[loadAddress] [[hostIPaddr:]bootfilename]",\\
    NULL

dhcp 命令最终展开结果:

cmd_tbl_t _u_boot_list_2_cmd_2_dhcp __aligned(4) \\
    __attribute__((unused,section(.u_boot_list_2_cmd_2_dhcp))) \\
     "dhcp", 3, 1, do_dhcp, \\
    "boot image via network using DHCP/TFTP protocol", \\
    "[loadAddress] [[hostIPaddr:]bootfilename]",\\
    NULL

先定义了一个 cmd_tbl_t 类型的变量,变量名为_u_boot_list_2_cmd_2_dhcp,此变量 4字节对齐

使用 _attribute_ 关键字设置变量 _u_boot_list_2_cmd_2_dhcp 存储在 .u_boot_list_2_cmd_2_dhcp 段中

u-boot.lds 链接脚本中有一个名为 “.u_boot_list” 的段,所有 .u_boot_list 开头的段都存放到 .u_boot.list 中

cmd_tbl_t 是个结构体

     "dhcp", 3, 1, do_dhcp, \\
    "boot image via network using DHCP/TFTP protocol", \\
    "[loadAddress] [[hostIPaddr:]bootfilename]",\\
    NULL

就是初始化这结构体,cmd_tbl_t 结构体定义在文件 include/command.h 中:

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 * const []);
	char		*usage;		/* Usage message	(short)	*/
#ifdef	CONFIG_SYS_LONGHELP
	char		*help;		/* Help  message	(long)	*/
#endif
#ifdef CONFIG_AUTO_COMPLETE
	/* do auto completion on the arguments */
	int		(*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]);
#endif
;

typedef struct cmd_tbl_s	cmd_tbl_t;

上面的代码初始化后,结构体的数值如下:

_u_boot_list_2_cmd_2_dhcp.name = "dhcp"
_u_boot_list_2_cmd_2_dhcp.maxargs = 3
_u_boot_list_2_cmd_2_dhcp.repeatable = 1
_u_boot_list_2_cmd_2_dhcp.cmd = do_dhcp
_u_boot_list_2_cmd_2_dhcp.usage = "boot image via network using DHCP/TFTP protocol"
_u_boot_list_2_cmd_2_dhcp.help = "[loadAddress] [[hostIPaddr:]bootfilename]"
_u_boot_list_2_cmd_2_dhcp.complete = NULL

在 uboot 的命令行中输入“dhcp”这个命令的时候,最终经过一系列处理后执行的就是 do_dhcp 这个函数

总结:uboot 中使用 U_BOOT_CMD 来定义一个命令,最终的目的就是为了定义一个 cmd_tbl_t 类型的变量,并初始化这个变量的各个成员。uboot 中的每个命令都存储在.u_boot_list段中,每个命令都有一个名为 do_xxx(xxx 为具体的命令名)的函数,这个 do_xxx 函数就是具体的命令处理函数

下面看一下 cmd_process 具体代码:

enum command_ret_t cmd_process(int flag, int argc, char * const argv[],
			       int *repeatable, ulong *ticks)

	enum command_ret_t rc = CMD_RET_SUCCESS;
	cmd_tbl_t *cmdtp;

	/* Look up command in command table */
	cmdtp = find_cmd(argv[0]);
	if (cmdtp == NULL) 
		printf("Unknown command '%s' - try 'help'\\n", argv[0]);
		return 1;
	

	/* found - check max args */
	if (argc > cmdtp->maxargs)
		rc = CMD_RET_USAGE;

#if defined(CONFIG_CMD_BOOTD)
	/* avoid "bootd" recursion */
	else if (cmdtp->cmd == do_bootd) 
		if (flag & CMD_FLAG_BOOTD) 
			puts("'bootd' recursion detected\\n");
			rc = CMD_RET_FAILURE;
		 else 
			flag |= CMD_FLAG_BOOTD;
		
	
#endif

	/* If OK so far, then do the command */
	if (!rc) 
		if (ticks)
			*ticks = get_timer(0);
		rc = cmd_call(cmdtp, flag, argc, argv);
		if (ticks)
			*ticks = get_timer(*ticks);
		*repeatable &= cmdtp->repeatable;
	
	if (rc == CMD_RET_USAGE)
		rc = cmd_usage(cmdtp);
	return rc;

先调用函数 find_cmd 在命令表中找到指定的命令,函数原型如下:

cmd_tbl_t *find_cmd(const char *cmd)

	cmd_tbl_t *start = ll_entry_start(cmd_tbl_t, cmd);
	const int len = ll_entry_count(cmd_tbl_t, cmd);
	return find_cmd_tbl(cmd, start, len);

传入 cmd 命令,然后通过函数 ll_entry_start 得到数组的第一个元素,也就是命令表起始地址,然后通过函数 ll_entry_count 得到数组长度,也就是命令表的长度,最终通过函数 find_cmd_tbl 在命令表中找到所需的命令,每个命令都有一个 name 成员,所以将参数 cmd 与命令表中每个成员的 name 字段都对比一下,如果相等的话就说明找到了这个命令,找到以后就返回这个命令

最后调用函数 cmd_call 来执行具体的命令,执行指令函数如下:

static int cmd_call(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

	int result;

	result = (cmdtp->cmd)(cmdtp, flag, argc, argv);
	if (result)
		debug("Command failed, result=%d\\n", result);
	return result;

所以具体的调用流程就是处理字符串,创建对应的 cmd_tbl_t 存储单元,初始化 cmd_tbl_t 变量,然后 cmd_process 中会查找检测 cmd_tbl 的返回值,然后调用 cmd_call 函数执行指令,如果返回值为 CMD_RET_USAGE 的话就会调用 cmd_usage 函数输出命令的用法,其实就是输出 cmd_tbl_t 的 usage 成员变量

以上就是 U-Boot 流程分析的最后一部分!

linux系统移植:u-boot启动流程(中)(代码片段)

Linux系统移植:U-Boot启动流程(中)一、board_init_f函数详解board_init_f函数是_main函数初始化中调用的重要函数之一,函数主要有两个工作:初始化一系列外设,比如串口、定时器,打印一些消息初始化gd... 查看详情

linux系统移植:正点原子u-boot移植(代码片段)

文章目录Linux系统移植:正点原子U-Boot移植一、What‘sU-Boot?1.1U-Boot简介1.2U-Boot选择二、正点原子U-Boot编译2.1编译环境2.2编译脚本三、U-Boot烧写与启动Linux系统移植:正点原子U-Boot移植一、What‘sU-Boot?1.1U-Boot简介Linux系统启... 查看详情

linux系统移植:u-boot顶层makefile分析(下)(代码片段)

目录Linux系统移植:U-Boot顶层Makefile分析(下)一、调用scripts/Kbuild.include二、导出交叉编译工具变量设置三、导出其他变量四、makexxx_config过程五、make过程Linux系统移植:U-Boot顶层Makefile分析(下)继续沿着... 查看详情

linux系统移植:u-boot常用指令(下)(代码片段)

文章目录Linux系统移植:U-Boot常用指令(下)一、U-Boot常用命令(上)1.1EMMC和SD卡命令1.2FAT格式文件系统命令1.3EXT格式文件系统命令1.4NAND操作命令1.5BOOT操作命令1.6其他常用命令Linux系统移植:U-Boot常用指令... 查看详情

linux系统移植:u-boot链接脚本(代码片段)

文章目录Linux系统移植:U-Boot链接脚本一、u-boot.lds介绍二、u-boot.lds分析Linux系统移植:U-Boot链接脚本一、u-boot.lds介绍前面提到的U-Boot的本质就是一个大的裸机程序,执行的时候需要先找到程序入口,而程序的链接... 查看详情

成为linux程序员需要学习啥

1第一部分:LINUX平台搭建与环境熟悉 了解Linux系统;区分各种版本的Linux系统,以便于拓展Linux视野。1、Linux简介;2、Linux系统的主要特点;3、Linux的组成;4、主要的Linux版本;5、嵌入式Linux简介与发展第二部分虚拟机安装和LINU... 查看详情

linux系统移植博文导航

Linux系统移植专栏更新很久了,博主今天就把以前的博文整理一下,希望对感兴趣的朋友有所帮助,在此感谢CSDN这个平台给出了这个一个交流的机会,也感谢大家的支持。Linux相关的网站U-BootLinux内核busyboxLinux系统移植Linux系统移... 查看详情

linux系统移植:u-boot常用指令(上)(代码片段)

文章目录Linux系统移植:U-Boot常用指令(上)一、U-Boot命令行二、U-Boot常用命令(上)2.1help帮助命令2.2信息查询命令2.3环境变量修改命令2.4内存操作命令2.5网络操作命令Linux系统移植:U-Boot常用指令(... 查看详情

u-boot-2016.07移植(第一篇)初步分析

U-BOOT-2016.07移植 (第一篇) 初步分析目录U-BOOT-201607移植 第一篇 初步分析目录编译和移植环境更新交叉编译工具1下载arm-linux-gcc4432安装arm-linux-gcc443安装环境Ubuntu910下载u-boot-201607并解压分析顶层Makefile1找出目标依赖关系2总结... 查看详情

linux系统移植:nxp官板uboot移植(代码片段)

文章目录Linux系统移植:NXP官板uboot移植一、获取官板U-Boot二、编译下载U-Boot源码三、官方驱动验证Linux系统移植:NXP官板uboot移植移植NXP的IMX6ULL官板U-Boot到原子的开发板一、获取官板U-Boot去NXP官网下载评估板的U-Boot,... 查看详情

正点原子i.mx6u-mini移植篇u-boot移植过程详解(代码片段)

...以后Linux还不能正常启动,还需要再移植一个根文件系统(rootfs),根文件系统里面包含了一些最常用的命令和文件。所以U-Boot、Linuxkernel和rootfs这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linu... 查看详情

linux系统移植:u-boot工程创建(代码片段)

文章目录Linux系统移植:U-Boot工程创建一、U-Boot文件目录二、U-Boot目录解析2.1arch文件夹2.2board文件夹2.3config文件夹2.4.u-boot.xxx_cmd文件2.5顶层Makefile文件2.6u-boot.xxx文件2.7.config文件2.8README文件三、U-Boot过程创建3.1打开工程文件夹3... 查看详情

linux学习:uboot移植

...nbsp;2)第二阶段的功能初始化本阶段使用的硬件设备检测系统内存映射将内核从Flash读取到RAM中为内核设置启动参数启动内核关于uboot启动分析以下这条链接写的不错,使用的是MINI2440平台: http://w 查看详情

怎样移植u-boot和linux到s3c2440开发板

...启动。本文就介绍如何实现该功能,并组成一个最简单的系统,这不仅要移植uboot,还要移植linux内核及创建一个根文件系统。首先我们对nandflash进行分区,规划好每个文件存放在nandflash的位置。下面是nandflash的分区:第0分区:0... 查看详情

u-boot移植---代码修改---时钟修改sdram

...u-boot。一、时钟修改  在代码流程分析中,我们知道,系统的启动是:设置CPU为管理员模式关闭看门狗屏蔽中断设置启动参数:时钟 FCLK:HCLK:PCLK=1:2:4   FCLK=120MHZflushv4I/DcachesdisableMMUstuffandc 查看详情

嵌入式linux第二部分-裸机开发/系统移植/驱动开发/内核开发

...。主要涉及Linux环境配置,嵌入式Linux裸机开发,Linux文件系统及系统移植,驱动开发等部分。目前持续更新中,更新时间:2022年11月13日【嵌入式Linux】裸机开发篇LinuxC语言及Makefile基础【嵌入式Linux】1.shell概念及常用命令行【嵌... 查看详情

uboot启动流程

...号为例,简要介绍一下u-boot在smdk2410上的启动流程。首先系统是从arch/arm/cpu/arm920t文件夹下的start.s文件開始运行,而且实际開始运行的代码是从第117行開始:117:start_code:118:/*119:*setthecputoSVC32mode120:*/12 查看详情

linux系统移植:u-boot顶层makefile分析(上)(代码片段)

目录Linux系统移植:U-Boot顶层Makefile分析(上)一、版本号二、传递变量到子make三、命令输出四、静默输出五、编译输出目录六、代码检查七、模块编译八、获取主机架构和系统九、设置目标架构、交叉编译器和配置... 查看详情