uboot以tag结构体的方式给内核传参,cmdline,bootargs,以及uboot如何启动内核
一、u-boot启动流程
1、第一阶段
cpu/s3c24xx/start.S
主要是一些依赖于 CPU 体系结构的代码,比如硬件设备初始化代码 等。这一阶段的代码主要是通过汇编来实现的,已达到短小精悍和高效的目的。为位置无关代码,通常在 Flash 中运行。所以有的指令为相对寻址,可 以在任何位置运行。
1)、soc内部cpu硬件初始化
关闭 Watchdog、关闭中断、设置 CPU 的速度和时钟 频率、配置 SDRAM 存储控制器及 IO、关闭处理器内部指令/数据
Cache 等;
mmu映射
第一阶段的东西我认为只要能知道个大概就行,但是mmu还是必须得单独拿出来说一下
sdram物理地址
顺便提一下,为啥uboot的烧录地址是0x3000000,就是这个sram的寻址地址空间,是如图。
1、第二阶段
lib_arm/board.c
通常用 C 语言来实现,这样可以实现更复杂的功能,而且代码 会具有更好的可读性和可移植性。
初始使到的硬件设备(如串口、Flash 和网卡等)
检测系统内存映射
把内核从 Flash 读到 RAM 空间中
为内核设置启动参数
调用内核
第二阶段主要是对开发板级别的硬件、软件数据结构进行初始化。
(2)
init_sequence
cpu_init 空的
board_init 网卡、机器码、内存传参地址
dm9000_pre_init 网卡
gd->bd->bi_arch_number 机器码
gd->bd->bi_boot_params 内存传参地址
interrupt_init 定时器
env_init
init_baudrate gd数据结构中波特率
serial_init 空的
console_init_f 空的
display_banner 打印启动信息
print_cpuinfo 打印CPU时钟设置信息
checkboard 检验开发板名字
dram_init gd数据结构中DDR信息
display_dram_config 打印DDR配置信息表
mem_malloc_init 初始化uboot自己维护的堆管理器的内存
mmc_initialize inand/SD卡的SoC控制器和卡的初始化
env_relocate 环境变量重定位
gd->bd->bi_ip_addr gd数据结构赋值
gd->bd->bi_enetaddr gd数据结构赋值
devices_init 空的
jumptable_init 不用关注的
console_init_r 真正的控制台初始化
enable_interrupts 空的
loadaddr、bootfile 环境变量读出初始化全局变量
board_late_init 空的
eth_initialize 空的
x210_preboot_init LCD初始化和显示logo
check_menu_update_from_sd 检查自动更新
main_loop 主循环
2.6.17.2、启动过程特征总结
(1)第一阶段为汇编阶段、第二阶段为C阶段
(2)第一阶段在SRAM中、第二阶段在DRAM中
(3)第一阶段注重SoC内部、第二阶段注重SoC外部Board内部
在系统上电后,片子自动将U-boot的前4k拷到内部ram中开始执行,但不知什么时候开始转到外部ram执行代码?
前4k代码负责将U-Boot剩余的部分复制到外部ram。然后通过下面这一句:ldr pc, _start_armboot ,跳转到lib_arm/board.c中定义的start_armboot函数。这就已经转到外部ram执行了。
void start_armboot (void)
gd = (gd_t*)gd_base;
分配gd
global data gd_t结构体包括了u-boot中所有重要全局变量(也包括了bd变量)
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
include/asm-arm/global_data.h
声明一个寄存器变量gd占用r8。这个宏在所有需要引用全局数据指针gd_t *gd的源码中都有声明,这个声明也避免编译器把r8分配给其他的变量,所以gd就是r8,这个指针变量不占用内存。
定义gd为gd_t类型指针,存储在寄存器r8中
register 表示变量对于执行速度非常重要,因此应该放在机器的寄存器中(寄存器独立于内存,通常在处理器芯片上)
volatile 用于指定变量的值可以由外部过程异步修改,例如中断例程
asm__ volatile(“”: : :“memory”);
告诉编译器内存地址已经修改过了
asm 指示编译器在此插入汇编语句。
volatile 告诉编译器,严禁将此处的汇编语句与其它的语句重组合优化。即:原原本本按原来的样子处理这这里
的汇编。
“”::: 空指令。barrier()不用在此插入一条串行化汇编指令。
memory 强制gcc编译器假设RAM所有内存单元均被汇编指令修改,这样cpu中的registers和cache中已缓存的内存单
元中的数据将作废。
cpu将不得不在需要的时候重新读取内存中的数据。这就阻止了cpu又将registers,cache中的数据用于去
优化指令,而避免去访问内存。
init_fnc_t *init_sequence
init_fnc_t结构体里面的函数都在board/samsung/smdk2416/smdk2416.c内被定义
size = flash_init ();
nand_init();
函数原型
int run_command (const char *cmd, int flag)
内核启动流程
启动内核
操作系统的内核映像一般是存储在 Flash 上的,当 Bootloader 将内核复制到 RAM 里之后可能还需要对其解压。
但是对于有自解压能力的内核而言是不需要 Bootloader 来解压的。
根据前面调用流程中的分析我们只要看common/cmd_bootm.c中的do_bootm
uboot(universal boot)是通用的启动代码,支持多种架构的CPU,并且是开源的。uboot是高度定制的,大致分为Soc级资源管理和板级资源管理。不同的CPU或者同款CPU不同的开发板,uboot都是不同的,要根据硬件电路进行移植。
bootLoader是嵌入式设备中用来引导内核启动的一段代码。内核启动是需要一定条件的,当设备上电后会首先运行BootLoader,BootLoader会初始化必要的硬件,比如DDR、Flash、串口等,相关初始化完成后就会去启动内核。我的理解,BootLoader是一个概念并不是具体的代码,只要满足在设备启动初期启动内核功能的代码都可以叫做BootLoader。
uboot和bootLoader的关系
uboot是BootLoader的一个具象化的表现,uboot是BootLoader,但BootLoader不仅仅指uboot,比如bios也是属于BootLoader。但是在我们日常的嵌入式开发中,经常把BootLoader和uboot混在一起,很多时候BootLoader和uboot都指的是uboot,因为在嵌入式开发中,使用的BootLoader基本都是uboot。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_42031299/article/details/123452708
cmdline是uboot引导内核启动时传递给内核的,作用是指导内核启动。内核启动阶段会去解析cmdline,并根据cmdline去指导内核启动。
uboot启动内核的大致步骤
(1)首先uboot要通过读取SD卡/flash等外存 或者 通过tftp、nfs等网络下载方式,将内核加载到内存的链接地址处;
(2)解析加载的内核的头信息,区分出当前启动方式是zImage、uImage还是设备树方式,构建出描述该内核的image_header_t结构体;
(3)将上一步得到的image_header_t结构体和内核所在地址传入do_bootm_linux函数,启动Linux内核;
(4)boot->bootm->do_bootm_linux->内核启动,uboot结束
2、加载内核镜像到DDR的链接地址处
2.1、内核有三种状态:
(1)静态:没有启动内核时,内核以镜像的方式存放在外存中;
(2)动态:内核在DDR中运行时;
(3)静态->动态:把内核从外存加载到DDR,并启动内核;
2.2、内核的重定位
内核从外存加载到DDR的过程就叫做重定位,必须加载DDR的特定地址(链接地址),因为启动的时候就是去链接地址启动。内核的重定位是uboot完成的,根据启动方式的不同,uboot可能从flash等外存去读取内核,也可能通过tftp、nfs等网络下载方式读取内核,但不管何种读取内核的方式,最终内核都是被加载到链接地址。链接地址在编译脚本、环境变量bootcmd、配置文件的CONFIG_BOOTCOMMAND宏定义可以查到。
3、启动内核的相关命令
(1)boot:该命令会先将内核重定位,然后调用bootm命令启动内核;
(2)bootm:这是直接启动内核的命令,只能启动已经加载到DDR的内核,在调用时传入内核在DDR中的地址(一般是内核的链接地址)即可启动内核。在bootm命令的实现代码里,其实主要完成的是启动方式的判断,判断出启动的操作系统类型后,完成初始化就会去调用相关操作系统的启动函数。
(2)do_bootm_linux函数:这是启动linux系统的函数,功能包括:准备给内核的传参、找到内核程序入口、启动内核。
start_armboot()
env_init()--init_sequence[]
env_relocate()
env_relocate_spec ()
env_relocate_spec_movinand()
movi_read_env()
use_default()
mmc_bread()
1)环境变量的地址存放在变量gd->env_addr中,gd全局变量参见博客:《uboot中重要的全局变量——gd》;
(2)实际上gd->env_addr == (ulong)&(env_ptr->data),但是env_ptr->data可以指向默认环境变量default_environment[]或者从SD卡读取的环境变量;
2、saveenv命令的代码调用关系
do_saveenv
saveenv
saveenv_movinand
movi_write_env
movi_write
mmc_bwrite
#define __REG(x) (*(vu_long *)(x))
#define INF_REG3_REG __REG(INF_REG_BASE+INF_REG3_OFFSET)
//路径:./common/env_auto.c
int saveenv(void)
{
if (INF_REG3_REG == 2)
saveenv_nand();
else if (INF_REG3_REG == 3) //开发板接的iNand/SD卡,所以INF_REG3_REG == 3
saveenv_movinand();
else if (INF_REG3_REG == 1)
saveenv_onenand();
else if (INF_REG3_REG == 4)
saveenv_nor();
else
printf("Unknown boot device\n");
return 0;
}
//从外存中读取环境变量保存到env_ptr变量中
int saveenv_movinand(void)
{
movi_write_env(virt_to_phys((ulong)env_ptr));
puts("done\n");
return 1;
}
void movi_write_env(ulong addr)
{
movi_write(raw_area_control.image[2].start_blk,
raw_area_control.image[2].used_blk, addr);
}
ulong movi_write(ulong start, lbaint_t blkcnt, void *src)
{
//第一个参数是0,说明从SD卡通道0去读环境变量,这在代码里写死了
//start:开始读取的扇区数
//blkcnt:要写入的扇区个数
//src:将从SD卡读取到的数据加载到该地址处
return mmc_bwrite(0, start, blkcnt, src);
}
uboot以tag方式给内核传参
(1)struct tag,tag是一个数据结构,在uboot和linux kernel中都有定义tag数据机构,而且定义是一样的。
(2)tag_header和tag_xxx。tag_header中有这个tag的size和类型编码,kernel拿到一个tag后先分析tag_header得到tag的类型和大小,然后将tag中剩余部分当作一个tag_xxx来处理。
(3)tag_start与tag_end。kernel接收到的传参是若干个tag构成的,这些tag由ATAG_CORE类型的tag起始,到ATAG_NONE类型的tag结束。
(4)tag传参的方式是由linux kernel发明的,kernel定义了这种向我传参的方式,uboot只是实现了这种传参方式从而可以支持给kernel传参。
、内核如何接收tag参数
启动内核的代码:theKernel (0, machid, bd->bi_boot_params);其中bd->bi_boot_params就是所有tag结构体所在的首地址,这个地址是保存在全局变量gd->bd中的,在uboot启动的前期会指定内存地址用于存放tag结构体,然后在启动内核的时候传给内核,内核拿到地址就会从该地址去遍历tag结构体,内核会判断tag的类型,如果是ATAG_CORE类型的tag则是起始的tag,如果是ATAG_NONE则是最后一个tag结构体,不用再往后遍历。
tag结构体
struct tag_header {
u32 size; //结构体的大小
u32 tag; //结构体的类型
};
struct tag {
struct tag_header hdr;
union { //此枚举体包含了uboot传给内核参数的所有类型
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
struct tag_mtdpart mtdpart_info;
} u;
};
构建tag结构体
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
static struct tag *params;
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params;//bd->bi_boot_params是专门用于保存tag结构体的内存首地址
params->hdr.tag = ATAG_CORE; //ATAG_CORE类型是tag结构体的开始
params->hdr.size = tag_size (tag_core); //结构体的大小
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next (params); //将指针偏移params->hdr.size个字节,让params指向下一个可用的内存地址
}
`````````中间省略掉其他类型tag结构体的构建
static void setup_end_tag (bd_t *bd)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
内核中解析cmdline的函数调用关系
start_kernel()
parse_early_param()
parse_early_options()
parse_args();
next_arg()
parse_one()
do_early_param()
parse_early_param()函数
void __init parse_early_param(void)
{
static __initdata int done = 0;
static __initdata char tmp_cmdline[COMMAND_LINE_SIZE];
if (done)
return;
/* All fall through to do_early_param. */
strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
parse_early_options()函数
void __init parse_early_options(char *cmdline)
{
parse_args(“early options”, cmdline, NULL, 0, do_early_param);
}
1
2
3
4
调用parse_args()函数并将cmdline和do_early_param函数指针传入。
parse_args()函数
int parse_args(const char *name,
char *args,
struct kernel_param *params,
unsigned num,
int (*unknown)(char *param, char *val))
{
char *param, *val;
/* Chew leading spaces */
args = skip_spaces(args);
while (*args) {
int ret;
int irq_was_disabled;
args = next_arg(args, ¶m, &val);//解析cmdline的格式解析出cmdline的每个单元
irq_was_disabled = irqs_disabled();
ret = parse_one(param, val, params, num, unknown);
if (irq_was_disabled && !irqs_disabled()) {
······
}
switch (ret) {
······
}
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
(1)next_arg()函数:按照cmdline的格式逐个解析;
比如cmdline=“root=/dev/mmcblk0p2 rootfstype=ext3”
第一次解析结果:param=“root”,val=“/dev/mmcblk0p2”
第二次解析结果:param=“rootfstype”,val=“ext3”
(2)parse_one()函数:
对每个从cmdline解析出的单元进行处理;
parse_one()函数
static int parse_one(char *param,
char *val,
struct kernel_param *params,
unsigned num_params,
int (*handle_unknown)(char *param, char *val))
{
······
//handle_unknown函数指针就是do_early_param()函数
if (handle_unknown) {
DEBUGP("Unknown argument: calling %p\n", handle_unknown);
return handle_unknown(param, val);
}
DEBUGP("Unknown argument `%s'\n", param);
return -ENOENT;
}
对next_arg()函数解析出的param和val进行处理,handle_unknown函数指针就是do_early_param()函数。具体的解析工作都是do_early_param()函数做的。
do_early_param()函数
static int __init do_early_param(char *param, char *val)
{
struct obs_kernel_param *p;
for (p = __setup_start; p < __setup_end; p++) { //遍历".init.setup"段,寻找匹配的obs_kernel_param结构体
if ((p->early && strcmp(param, p->str) == 0) ||
(strcmp(param, "console") == 0 &&
strcmp(p->str, "earlycon") == 0)
) {
if (p->setup_func(val) != 0) //匹配到obs_kernel_param结构体就执行绑定的处理函数
printk(KERN_WARNING
"Malformed early option '%s'\n", param);
}
}
/* We accept everything at this stage. */
return 0;
}
(1)__setup_start和__setup_end是".init.setup"段的起始/结束地址;
(2)".init.setup"段的遍历结合__setup宏和链接脚本进行理解;
cmdline常用参数
例如:mem=1408M console=ttyS0,115200 root=/dev/mmcblk0p7 rootfstype=squashfs mtdparts=xxx
(1)mem=
用来告诉内核当前系统的内存有多少;
(2)console=
指定控制台使用的串口已经波特率;
(3)root=
根文件系统的位置,比如上面就是指定根文件系统在mmcblk0p7分区,内核挂载根文件系统时会用到;
根文件系统也可以通过nfs远程挂载:
root=/dev/nfs nfsroot=192.168.1.141:/root/s3c2440/build_rootfs/aston_rootfs ip=192.168.1.10:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off
(4)rootfstype=
指明文件系统的格式和权限;
(5)mtdparts=
指明存储设备的分区情况;
如:mtdparts=nand0:0x140000@0x000000(param),4m(uboot),2m(env),49408k(romfs),2m(custom),25m(web),2m(logo),512k(dgs),31m(ext_usr),2m(config_fw),-(config)