uboot linux内核传递参数,linux驱动之uboot启动过程及参数传递

前言

一直以来都在学习和开发嵌入式linux,但对于一些常用的工具和机制却不甚了解,包括今天要说的uboot引导和启动linux内核,最近打算启动技术博客来学习和记录探索这些过程中所获得的知识。从事嵌入式linux开发的人应该都知道uboot,支持多种操作系统,多种硬件平台的uboot在嵌入式linux界可是大名鼎鼎,我们今天就来谈一谈uboot如何启动内核。这里我们不提供代码,仅给出相关的关键结构体,其余的请各位自行查看uboot代码

过程讲解

do_bootm

在uboot引导Linux启动时,使用的是bootm的命令。这个命令执行的函数就是do_bootm, 这个函数的地址在cmd/bootm.c中。

代码如下:

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

{

#ifdef CONFIG_NEEDS_MANUAL_RELOC

static int relocated = 0;

if (!relocated) {

int i;

/* relocate names of sub-command table */

for (i = 0; i < ARRAY_SIZE(cmd_bootm_sub); i++)

cmd_bootm_sub[i].name += gd->reloc_off;

relocated = 1;

}

#endif

/* determine if we have a sub command */

argc--; argv++;

if (argc > 0) {

char *endp;

simple_strtoul(argv[0], &endp, 16);

/* endp pointing to NULL means that argv[0] was just a

* valid number, pass it along to the normal bootm processing

*

* If endp is ':' or '#' assume a FIT identifier so pass

* along for normal processing.

*

* Right now we assume the first arg should never be '-'

*/

if ((*endp != 0) && (*endp != ':') && (*endp != '#'))

return do_bootm_subcommand(cmdtp, flag, argc, argv);

}

return do_bootm_states(cmdtp, flag, argc, argv, BOOTM_STATE_START |

BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER |

BOOTM_STATE_LOADOS |

#ifdef CONFIG_SYS_BOOT_RAMDISK_HIGH

BOOTM_STATE_RAMDISK |

#endif

#if defined(CONFIG_PPC) || defined(CONFIG_MIPS)

BOOTM_STATE_OS_CMDLINE |

#endif

BOOTM_STATE_OS_PREP | BOOTM_STATE_OS_FAKE_GO |

BOOTM_STATE_OS_GO, &images, 1);

}

参数

我们来说说这个函数的参数

cmd_tbl_t *cmdtp:目前笔者也不清楚它的来历,从命名方式中可以看出大约是命令表之类结构体

int flag:该参数笔者跟踪了一下其传入位置,目前并没有发现需要它的地方

int argc:不用说了,相信大家都知道这个就是bootm传入参数的个数

char * const argv[]:同上,这个就是传入的参数了

函数讲解

这里先略过前面的CONFIG_NEEDS_MANUAL_RELOC宏定义的部分,这里笔者也不甚了解

然后如果在命令有传入参数,则使用simple_strtoul对参数进行字符串到长整型数据类型的转换,这里解析的是传入的第一个参数,并将其赋值给endp,其实该参数就是在存储介质中内核的地址,但这个变量似乎并没有传入到函数里面去,仅用作判断。

执行函数do_bootm_subcommand,这个函数中执行了do_bootm_states,uboot分阶段启动,每一个阶段称之为subcommand

而do_bootm_states执行的就是不同阶段的subcommand

在这里我们可以见到,如果没有传入do_bootm参数,也就是参数argc为0,那么do_bootm_states的state参数将会是一大堆的标志宏,这些标志宏就是uboot启动时需要的阶段,每个阶段都有一个宏来表示

do_bootm_states

我们现在假设没有给bootm命令传入参数,那么我们现在进入do_bootm_states函数了

代码如下,有点长,节选部分出来

int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],

int states, bootm_headers_t *images, int boot_progress)

{

boot_os_fn *boot_fn;

ulong iflag = 0;

int ret = 0, need_boot_fn;

images->state |= states;

/*

* Work through the states and see how far we get. We stop on

* any error.

*/

if (states & BOOTM_STATE_START)

ret = bootm_start(cmdtp, flag, argc, argv);

if (!ret && (states & BOOTM_STATE_FINDOS))

ret = bootm_find_os(cmdtp, flag, argc, argv);

if (!ret && (states & BOOTM_STATE_FINDOTHER))

ret = bootm_find_other(cmdtp, flag, argc, argv);

/* Load the OS */

if (!ret && (states & BOOTM_STATE_LOADOS)) {

ulong load_end;

iflag = bootm_disable_interrupts();

ret = bootm_load_os(images, &load_end, 0);

if (ret == 0)

lmb_reserve(&images->lmb, images->os.load,

(load_end - images->os.load));

else if (ret && ret != BOOTM_ERR_OVERLAP)

goto err;

else if (ret == BOOTM_ERR_OVERLAP)

ret = 0;

}

........

/* From now on, we need the OS boot function */

if (ret)

return ret;

boot_fn = bootm_os_get_boot_func(images->os.os);

need_boot_fn = states & (BOOTM_STATE_OS_CMDLINE |

BOOTM_STATE_OS_BD_T | BOOTM_STATE_OS_PREP |

BOOTM_STATE_OS_FAKE_GO | BOOTM_STATE_OS_GO);

if (boot_fn == NULL && need_boot_fn) {

if (iflag)

enable_interrupts();

printf("ERROR: booting os '%s' (%d) is not supported\n",

genimg_get_os_name(images->os.os), images->os.os);

bootstage_error(BOOTSTAGE_ID_CHECK_BOOT_OS);

return 1;

}

/* Call various other states that are not generally used */

if (!ret && (states & BOOTM_STATE_OS_CMDLINE))

ret = boot_fn(BOOTM_STATE_OS_CMDLINE, argc, argv, images);

if (!ret && (states & BOOTM_STATE_OS_BD_T))

ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);

if (!ret && (states & BOOTM_STATE_OS_PREP)) {

#if defined(CONFIG_SILENT_CONSOLE) && !defined(CONFIG_SILENT_U_BOOT_ONLY)

if (images->os.os == IH_OS_LINUX)

fixup_silent_linux();

#endif

ret = boot_fn(BOOTM_STATE_OS_PREP, argc, argv, images);

}

#ifdef CONFIG_TRACE

/* Pretend to run the OS, then run a user command */

if (!ret && (states & BOOTM_STATE_OS_FAKE_GO)) {

char *cmd_list = getenv("fakegocmd");

ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_FAKE_GO,

images, boot_fn);

if (!ret && cmd_list)

ret = run_command_list(cmd_list, -1, flag);

}

#endif

/* Check for unsupported subcommand. */

if (ret) {

puts("subcommand not supported\n");

return ret;

}

/* Now run the OS! We hope this doesn't return */

if (!ret && (states & BOOTM_STATE_OS_GO))

ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,

images, boot_fn);

/* Deal with any fallout */

}

参数

cmd_tbl_t *cmdtp:同上

int flag:同上

int argc:同上

char * const argv[]:同上

int states:这个参数就是那一大堆的标志宏

bootm_headers_t *images:这个数据结果就重要了,他传入的是一个全局的结构体,这个结构体用于保存从存储介质中读到的linux内核头部信息,同时这个全局结构体的也被命名为images

int boot_progress:似乎无作用

函数讲解

那么我们最先看到的是images的成员被赋值为states

往下看是会将参数states跟宏BOOTM_STATE_START进行与操作,如果通过则执行

bootm_start,那么这里就可以知道上面所说的每个阶段都有一个宏来表示

接下来就会进入bootm_start这个函数了

1、这里我们先设置一个断点,直接跳到下面看bootm_start,看完我们再回来

好,我们看完bootm_start后,接下来往下面,接着执行bootm_find_os,同样,我们先跳到后面去查看它的函数讲解,等下再回来

2、执行完bootm_find_os,接着执行bootm_find_other,这里不细讲,主要是查询是否有ramdisk

3、然后关闭中断,执行bootm_load_os,我们继续跳到后面去看这个函数

4、跳过ramdisk的代码,我们直接查看bootm_os_get_boot_func,这个函数很简单,直接查看boot_os变量,直接获取我们使用的操作系统的启动函数,uboot为每个不同的操作系统都编写了不同的启动函数。将其返回并赋值给变量boot_fn。

终于要接近尾声了,继续跳过一些可选代码。我们直接看boot_selected_os,这函数里面就执行do_bootm_linux跳转到我们的内核去运行了,如无意外,到了这里一般情况下就不返回了。

这里我们使用的是linux,所以它的启动函数是do_bootm_linux

这里会根据不同的阶段去执行boot_fn,需要执行的阶段有以下这些

BOOTM_STATE_OS_CMDLINE

BOOTM_STATE_OS_BD_T

BOOTM_STATE_OS_PREP

BOOTM_STATE_OS_GO

5、在这里,我们讲解的是ARM架构,在这种结构中前2个阶段是不用的,我们跳到文章后面查看do_bootm_linux的BOOTM_STATE_OS_PREP 和 BOOTM_STATE_OS_GO实现吧

bootm_start

代码如下:

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc,

char * const argv[])

{

memset((void *)&images, 0, sizeof(images));

images.verify = getenv_yesno("verify");

boot_start_lmb(&images);

bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");

images.state = BOOTM_STATE_START;

return 0;

}

参数

cmdtp、flag、argc、argv[]这几个参数相信不用我讲大家也都知道他们是什么了

函数讲解

最先看到的是清空images结构体,然后获取uboot的环境变量verify,并复制给images的成员

然后执行boot_start_lmb,这个函数看起来想是初始化镜像结构体的lmb成员,

然后获取环境变量中的某些变量并复制到images->lmb中,具体其作用目前暂不明白

最后执行bootstage_mark_name,大致就是记录启动阶段的名字和记录此时的一些数据

好,我们回去刚刚的do_bootm_states

bootm_find_os

代码如下:

static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,

char * const argv[])

{

const void *os_hdr;

bool ep_found = false;

int ret;

/* get kernel image header, start address and length */

os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,

&images, &images.os.image_start, &images.os.image_len);

if (images.os.image_len == 0) {

puts("ERROR: can't get kernel image!\n");

return 1;

}

/* get image parameters */

switch (genimg_get_format(os_hdr)) {

images.os.type = image_get_type(os_hdr);

images.os.comp = image_get_comp(os_hdr);

images.os.os = image_get_os(os_hdr);

images.os.end = image_get_image_end(os_hdr);

images.os.load = image_get_load(os_hdr);

images.os.arch = image_get_arch(os_hdr);

}

......

if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {

images.os.load = images.os.image_start;

images.ep += images.os.load;

}

images.os.start = map_to_sysmem(os_hdr);

}

参数

同bootm_start

函数讲解

这里需要说一下函数,该函数主要参数有images,os_data,os_len

boot_get_kernel函数先执行genimg_get_kernel_addr_fit来获取内核镜像在存储介质中的位置,如果没有传入命令参数,则默认使用全局变量load_addr并返回,该变量由每个硬件平台自己定义宏并赋值,可能这里是移植需要做的工作之一。另外它似乎有扫描多个内核镜像并找到启动镜像的功能,但这里暂且不表。

获取到内核的存储地址后,使用genimg_get_image读取内核到内存中,这个函数将内核头部的64字节信息和内核全部读到指定地址CONFIG_SYS_LOAD_ADDR,然后返回内核所在的内存地址。

genimg_get_image获取头部信息指针后,然后根据头部指针来获取内核的大小和内核目前所在的内存地址os_len和os_data,这2个指针指向的其实就是在imges结构体中的成员。到了这里,boot_get_kernel执行完毕,我们返回内核头部信息指针

接着从内核头部信息指针中获取内核的格式,格式有传统格式,FIT格式和安卓格式等,这里我们使用传统格式来讲解。我们回到bootm_find_os。

根据返回的头部信息指针,我们去获取到内核想信息并复制给images.os的各个成员,包括内核类型type,内核压缩方式comp,内核是什么操作系统os,内核要装载到内存的哪个位置load,内核是什么体系架构arch,为以后的工作做准备,这里要说明一下,现在内核所在的内存地址是uboot所指定,而内核启动的内存地址不一定在这里,是在laod成员所执行的地址,后面需要把整个镜像拷贝到这里。

最后将images.os.load赋值给images.ep,其实就是内核的启动地址了

下面内核头部信息结构体,到了这里,我们就可以返回到do_bootm_states。

typedef struct image_header {

__be32 ih_magic; /* Image Header Magic Number */

__be32 ih_hcrc; /* Image Header CRC Checksum */

__be32 ih_time; /* Image Creation Timestamp */

__be32 ih_size; /* Image Data Size */

__be32 ih_load; /* Data Load Address */

__be32 ih_ep; /* Entry Point Address */

__be32 ih_dcrc; /* Image Data CRC Checksum */

uint8_t ih_os; /* Operating System */

uint8_t ih_arch; /* CPU architecture */

uint8_t ih_type; /* Image Type */

uint8_t ih_comp; /* Compression Type */

uint8_t ih_name[IH_NMLEN]; /* Image Name */

} image_header_t;

bootm_load_os

主要参数

bootm_headers_t *images:就在这一行字的上边

代码如下:

static int bootm_load_os(bootm_headers_t *images, unsigned long *load_end,

int boot_progress)

{

image_info_t os = images->os;

ulong load = os.load;

ulong blob_start = os.start;

ulong blob_end = os.end;

ulong image_start = os.image_start;

ulong image_len = os.image_len;

bool no_overlap;

void *load_buf, *image_buf;

int err;

load_buf = map_sysmem(load, 0);

image_buf = map_sysmem(os.image_start, image_len);

err = bootm_decomp_image(os.comp, load, os.image_start, os.type,

load_buf, image_buf, image_len,

CONFIG_SYS_BOOTM_LEN, load_end);

if (err) {

bootstage_error(BOOTSTAGE_ID_DECOMP_IMAGE);

return err;

}

flush_cache(load, ALIGN(*load_end - load, ARCH_DMA_MINALIGN));

debug(" kernel loaded at 0x%08lx, end = 0x%08lx\n", load, *load_end);

bootstage_mark(BOOTSTAGE_ID_KERNEL_LOADED);

no_overlap = (os.comp == IH_COMP_NONE && load == image_start);

if (!no_overlap && (load < blob_end) && (*load_end > blob_start)) {

debug("images.os.start = 0x%lX, images.os.end = 0x%lx\n",

blob_start, blob_end);

debug("images.os.load = 0x%lx, load_end = 0x%lx\n", load,

*load_end);

/* Check what type of image this is. */

if (images->legacy_hdr_valid) {

if (image_get_type(&images->legacy_hdr_os_copy)

== IH_TYPE_MULTI)

puts("WARNING: legacy format multi component image overwritten\n");

return BOOTM_ERR_OVERLAP;

} else {

puts("ERROR: new format image overwritten - must RESET the board to recover\n");

bootstage_error(BOOTSTAGE_ID_OVERWRITTEN);

return BOOTM_ERR_RESET;

}

}

return 0;

}

函数讲解

首先调用bootm_decomp_image,来解压内核。查看其传入参数,我们知道都是从上一步中获取得到的各种数据,包括装载地址,解压类型等等。os.image_start是内核未解压时所在的地址,load_buf是内核的启动位置也就是解压后内核所在的地址了。

我们继续返回到do_bootm_states

do_bootm_linux

不同的硬件平台有不同的实现,我们这里查看的ARM架构的实现代码

代码如下

int do_bootm_linux(int flag, int argc, char * const argv[],

bootm_headers_t *images)

{

/* No need for those on ARM */

if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)

return -1;

if (flag & BOOTM_STATE_OS_PREP) {

boot_prep_linux(images);

return 0;

}

if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {

boot_jump_linux(images, flag);

return 0;

}

boot_prep_linux(images);

boot_jump_linux(images, flag);

return 0;

}

boot_prep_linux

首先先执行BOOTM_STATE_OS_PREP的代码,这里调用的是boot_prep_linux,这个函数跟内核传递参数有关系,uboot向内核传递参数就是在这里做的准备

这里先调用char *commandline = getenv("bootargs");从uboot的环境变量中获取到我们传入的启动参数,并

使用指针指向了这串字符串,该字符串在后面会用到

再调用setup_start_tag设置启动要用到的 tag,在这里有一个全局变量params,bd->bi_boot_params的值赋给它,params的结构如下

struct tag {

struct tag_header hdr;

union {

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;

} u;

他本质是一个tag结构。该结构包括hdr和各种类型的tag_*

hdr来标志当前的tag是哪种类型。

setup_start_tag是初始化了第一个tag,类型为tag_core。

最后调用tag_next跳到第一个tag末尾,为下一个tag赋值做准备。

接下来调用setup_serial_tag,代码如下,功能笔者觉得是设置控制台串口号。其中get_board_serial是各个硬件平台的实现,其功能大概是获取环境变量中的串口号,将该串口作为控制台输出。

static void setup_serial_tag(struct tag **tmp)

{

struct tag *params = *tmp;

struct tag_serialnr serialnr;

get_board_serial(&serialnr);

params->hdr.tag = ATAG_SERIAL;

params->hdr.size = tag_size (tag_serialnr);

params->u.serialnr.low = serialnr.low;

params->u.serialnr.high= serialnr.high;

params = tag_next (params);

*tmp = params;

}

接着,再调用setup_commandline_tag,代码如下,可以看出,这里调用了strcpy来赋值字符串,赋值的字符串正是上面提到的,函数开头使用getenv获取的启动参数字符串

static void setup_commandline_tag(bd_t *bd, char *commandline)

{

char *p;

if (!commandline)

return;

/* eat leading white space */

for (p = commandline; *p == ' '; p++);

/* skip non-existent command lines so the kernel will still

* use its default command line.

*/

if (*p == '\0')

return;

params->hdr.tag = ATAG_CMDLINE;

params->hdr.size =

(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;

strcpy (params->u.cmdline.cmdline, p);

params = tag_next (params);

}

接着下面还调用了setup_revision_tag、setup_memory_tags,同理都是设置不同的tag而已。这里比较特殊的是setup_memory_tags,如果有多片内存ram,会循环为每一片的ram设置一个tag

继续调用setup_board_tags,这个是板级实现,如果没有实现则跳过

最后将最末尾的tag设置为ATAG_NONE,标志tag结束。

由此可知我们的启动参数params是一片连续的内存,这片内存有很多个tag,我们通过调用不同的程序来设置这些tag。

这样整个参数的准备就结束了,最后在调用boot_jump_linux时会将tags的首地址也就是bi_boot_params传给内核,让内核解析这些tag。当然我想也有朋友不懂内核传递的参数都是字符串,设置这些tag跟传递的参数有什么关系呢,笔者也不明白,等到后面我们再来讲解。

总结一下,uboot将参数以tag数组的形式布局在内存的某一个地址,每个tag代表一种类型的参数,首尾tag标志开始和结束,首地址传给kernel供其解析。

我们回到do_bootm_linux,其实这行到这里是跳出do_bootm_linux回到我们的do_bootm_states

boot_jump_linux

kernel_entry变量是个函数指针,我们会讲images->ep赋值给它作为跳转到内核执行的入口。寄存器r2会赋值为gd->bd->bi_boot_params,就是我们之前所有是tag启动参数

然后传入其余相关参数并执行kernel_entry启动内核

到了这里,uboot引导内核的启动过程讲解完毕

后记

总结来说,uboot启动内核就是读取内核,加载内核,解析内核头部,解压内核,装载内核到执行启动地址,准备启动参数,启动内核这几个阶段。

写完该片,笔者对uboot的理解深了一层,当然该文所讲的只是uboot引导的主要部分,还有很多细节我们跳过了(但大体上不影响)。以前笔者仅仅只是使用uboot,并没有对它进行一个系统的理解,今天算是对uboot的有了一些更深的理解。当然了,这些都是理论层面,还需要各位去根据uboot的代码进行实践。实践出真知,要了解uboot后的内核启动,我们还需要对操作系统和编译原理有一定的了解,关于我们后面有机会再继续聊吧

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值