第1阶段——uboot分析之启动函数bootm命令 (9)

本节主要学习: 详细分析UBOOT中"bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0" 中怎么实现bootm命令启动内核.

其中bootm要做的事情: a 读取头部,把内核拷贝到合适的地方(0X30008000) b 在do_boom_linux()中把参数给内核准备好,并告诉内核参数的首地址 c 在do_boom_linux()中最后使用theKernel () 引导内核. {注意:当在cmd_bootm.C中没有定义宏CONFIG_PPC时, 系统使用./lib_arm/armlinux.C下的do_bootm_linux()函数(本uboot使用的是这个函数). 若定义了该宏,系统会使用./common/cmd_bootm.C下的do_bootm_linux()函数.} 1. bootm 0x30007FC0 为什么这里是从0x30007FC0启动?

因为Flash上存的内核格式是:uImage   而uiamge由: 头部(header) + 真正的内核 组成 在下面1.1节中讲到头部占用了64B字节,用来存放各个参数变量,所以真正的内核加载地址是在: 真正的内核开始地址=0x30007FC0+64=0X30008000,所以bootm启动内核地址刚好位于nand命令加载的地址后面,不需要移动

1.1 uImage头部结构体分析 头部:由结构体image_header_t定义,该结构体大小为64B,位于./include/image.h

typedef struct image_header {
uint32_t    ih_magic;    /* Image Header Magic Number(镜像头部幻数,为#define IH_MAGIC    0x27051956    )    */ //幻数:用来标记文件的格式 
uint32_t    ih_hcrc;    /* Image Header CRC Checksum(镜像头部CRC校验码)    */
uint32_t    ih_time;    /* Image Creation Timestamp(镜像创建时间戳)*/
uint32_t    ih_size;    /* Image Data Size(镜像数据大小(不算头部) )    */
uint32_t    ih_load;    /* Data    Load Address(镜像数据将要载入的内存地址)    */ 
uint32_t    ih_ep;      /* Entry Point Address(镜像入口地址)    */
uint32_t    ih_dcrc;    /* Image Data CRC Checksum(镜像数据CRC校验码)    */
uint8_t    ih_os;       /* Operating System(操作系统类型)    */
uint8_t    ih_arch;     /* CPU architecture(CPU架构)    */
uint8_t    ih_type;     /* Image Type(镜像类型)    */
uint8_t    ih_comp;     /* Compression Type(压缩类型)    */
uint8_t    ih_name[IH_NMLEN];    /* Image Name(镜像名字ih_name,共32字节 #define IH_NMLEN    32)    */
} image_header_t;

1.2 bootm命令之do_bootm函数分析 (bootm命令位于./common/cmd_bootm.c,其中nand命令执行时调用的是do_bootm()函数)

int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
ulong    iflag;
ulong    addr;
ulong    data, len, checksum;
ulong *len_ptr;
uint    unc_len = CFG_BOOTM_LEN; 
int    i, verify;
char    *name, *s;
int    (*appl)(int, char *[]);
image_header_t *hdr = &header; //定义头部结构体指针hdr等于header的地址.

s = getenv ("verify"); //读取uboot环境变量verify
verify = (s && (*s == 'n')) ? 0 : 1; //如果verify==n,局部变量verify=0,否则verify=1.

if (argc < 2) { //如果argc==1(只输入了bootm),则使用缺省加载地址load_addr 
addr = load_addr;
} else { //否则使用argv[1](0x30007FC0)为加载地址
addr = simple_strtoul(argv[1], NULL, 16);
}
SHOW_BOOT_PROGRESS (1);
printf ("## Booting image at %08lx ...\n", addr); //打印"## Booting image at 0x30007FC0 ...\n" 

#ifdef CONFIG_HAS_DATAFLASH
if (addr_dataflash(addr)){
read_dataflash(addr, sizeof(image_header_t), (char *)&header);
} else
#endif
memmove (&header, (char *)addr, sizeof(image_header_t)); 
//在加载地址中前64B大小的头部结构体提取到image_header_t结构变量header中,为下面的分析校验做准备

if (ntohl(hdr->ih_magic) != IH_MAGIC) //判断幻数Magic number 是否匹配,不匹配说明下载过程中错误.
{
...
} else
#endif    /* __I386__ */
{
puts ("Bad Magic Number\n");
SHOW_BOOT_PROGRESS (-1);
return 1;
}
}
SHOW_BOOT_PROGRESS (2);

data = (ulong)&header; 
len = sizeof(image_header_t);

checksum = ntohl(hdr->ih_hcrc);
hdr->ih_hcrc = 0;

if (crc32 (0, (uchar *)data    , len) != checksum) { //判断校验和
puts ("Bad Header Checksum\n");
SHOW_BOOT_PROGRESS (-2);
return 1;
}
SHOW_BOOT_PROGRESS (3);

....

#if defined(__PPC__) //判断体系结构,校验CPU类型是否正确
if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type //没有找到CPU类型
#endif
...
switch (hdr->ih_type) //判断镜像image类型
{ ...}

switch (hdr->ih_comp) //根据镜像压缩(compression)类型把内核镜像解压到指定的地址
{
case IH_COMP_NONE: //使用的是没有压缩,执行该段case
if(ntohl(hdr->ih_load) == data) //该data内核地址刚好位于ih_load加载地址,不需要移动,直接运行
{ 
printf (" XIP %s ... ", name); //打印
} 
else //else执行内核移动,将内核data地址移到 hdr->ih_load (加载地址)中
{ ...
memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len); 
...}    
break;
case IH_COMP_GZIP:
....
}
...
switch (hdr->ih_os) //根据不同的操作系统类型来启动内核
{ 
case IH_OS_LINUX: //LINUX系统,执行该段case
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux(cmdtp, flag, argc, argv,addr, len_ptr, verify); //执行do_bootm_linux()函数启动内核
break;
case IH_OS_NETBSD: //NETBSD系统 
....
....
}

do_bootm()函数若执行无误,最终会执行do_bootm_linux()函数

1.3 bootm命令之do_bootm_linux函数分析 进入do_bootm_linux()函数(位于./lib_arm/armlinux.C) :

void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],ulong addr, ulong *len_ptr, int verify) 
{
void (*theKernel)(int zero, int arch, uint params); //定义一个函数指针theKernel
... ...
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); //1.设置theKernel地址=ih_ep镜像入口地址,用于后面启动内核
... ...
char *commandline = getenv ("bootargs"); 
//commandline指向"bootargs"命令环境参数. 用于后面setup_commandline_tag的形参
//在本uboot界面中输入print指令就能得到"bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0" 
//root=/dev/mtdblock3:表示根文件系统root位于第4个flsh分区(mtdblock3), mtdblock0=bootloader,mtd1=参数,mtd2=内核 
//init=/linuxrc:指定内核启动后运行的第一个脚本是当前目录下linuxrc脚本
//console=ttySAC0:指定选择串口0(ttySAC0)来打印信息、
... ...

/*2.设置tag 参数*/ 
setup_start_tag (bd);           //在0X30000100地址保存start_tag数据,tag:用于u-boot给Linux kernel传递参数数据,因为内核启动后不能使用uboot了.
setup_memory_tags (bd);                   //保存memory_tag数据,让LINUX知道内存多大
setup_commandline_tag (bd, commandline); //保存commandline_tag数据
setup_end_tag (bd);                      //初始化tag结构体结束
....
cleanup_before_linux ();                   //3.启动内核之前需要做一些清理工作,禁止中断,关闭cache

theKernel (0, bd->bi_arch_number, bd->bi_boot_params); 
//4.通过ih_ep镜像入口地址启动内核,然后从0X30000010处读取tag参数,
//其中"bd->bi_arch_number"参数是向内核传递的机器ID,用于内核确定机器ID是否正确,    bd->bi_arch_number是在start_armboot函数中board_init里赋了值

}

从以上代码中可以看出启动内核之前主要执行了两步骤:

1.4   通过setup_...._tag函数为内核准备参数,

1.5  进入cleanup_before_linux函数清除中断和cache

1.4 tag参数函数分析: 1.4.1 d setup_start_tag (bd)函数分析如下: (在上面的tag结构体的首地址为什么在0X30000100?) 通过搜索"setup_start_tag"得到该函数位于./lib_arm/armlinux.c中:

static void setup_start_tag (bd_t *bd)
{
params = (struct tag *) bd->bi_boot_params; 

//初始化(struct tag *)型全局变量params= bd->bi_boot_params=0x30000100,
// 之后的memory_tag和commandline_tag等tag数据都保存在params后面的偏移地址. 
params->hdr.tag = ATAG_CORE; //存放srat常量:params->hdr.tag = ATAG_CORE=0x54410001, tag表示tag类型的常量。 
params->hdr.size = tag_size (tag_core); //存放srat长度:params->hdr.size=5, size表示start_tag的结构大小。
//因为tag_size (tag_core)=((sizeof(struct tag_header) + sizeof(struct tag_core)) >> 2)
//其中tag_header结构体里有2个4字节成员(size,tag),
//tag_core结构体里有3个4字节成员(flags,pagesize,rootdev)
//所以tag_size (tag_core)=(2*4+3*4)>>2=5; 单位是4字节
params->u.core.flags = 0;    //存放params的(tag_core型)结构体成员u.core.flags=0
params->u.core.pagesize = 0;//存放params的(tag_core型)结构体成员u.core.pagesize=0
params->u.core.rootdev = 0;//存放params的(tag_core型)结构体成员u.core.rootdev=0

params = tag_next (params); //params指向下一个tag(setup_memory_tags),params=(0x30000100+size*4)=0x30000114 
} 

通过上面代码,最终内存分布为:

1.4.2 do_bootm_linux函数中setup_memory_tags(bd)函数分析如下: 

static void setup_memory_tags (bd_t *bd) 
{
int i;

for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {
params->hdr.tag = ATAG_MEM; //存放内存tag常量: params->hdr.tag =ATAG_MEM= 0x54410002 
params->hdr.size = tag_size (tag_mem32); //存放内存长度:params->hdr.size =4 (len+ATAG_MEM+u.mem.size+u.mem.start)

params->u.mem.start = bd->bi_dram[i].start; 
//存放内存(sdram)的的首地址,
// bd->bi_dram[i].start在start_armboot()函数中init_sequence->dram_init结构函数成员里被复制:
// gd->bd->bi_dram[0].start = PHYS_SDRAM_1;其中"PHYS_SDRAM_1"在./include/configs/100ask24x0.h中定义为0X30000000(bank6首地址)
//所以,这里存放内存(sdram)首地址:params->u.mem.start =0X30000000; 
params->u.mem.size = bd->bi_dram[i].size;
//同上,gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;"PHYS_SDRAM_1_SIZE"被定义为0X04000000(64Mb)
//所以,这里存放内存(sdram)长度: params->u.mem.size=0X04000000; 
params = tag_next (params); //params指向下一个tag(setup_commandline_tag),params=(0x30000114+size*4)=0x30000124 
}
}

通过上面代码,最终内存分布为:

1.4.3 do_bootm_linux函数中setup_commandline_tag(bd)函数分析如下:

static void setup_commandline_tag (bd_t *bd, char *commandline) //commandline:指向"bootargs"命令环境参数
{
char *p;

if (!commandline) // 判断bootargs是否为空,
return;


for (p = commandline; *p == ' '; p++); //去掉空格

if (*p == '\0') //判断*p是否为空
return;

params->hdr.tag = ATAG_CMDLINE; //存放命令行产量: params->hdr.tag =ATAG_MEM= 0x54410009 
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2; //存放命令行长度 params->hdr.size 
/* 其中 strlen (p) + 1 + 4: +1表示添加结束符'/0' */
/* +4 表示向上取整,比如当len=(4,5,6,7)时,size=(len+4)>>2=2; 实现4字节对齐 */

strcpy (params->u.cmdline.cmdline, p); 
//存放命令行参数:params->u.cmdline.cmdline=boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

params = tag_next (params); //params指向下一个tag(setup_end_tag)
}

通过上面代码,最终内存分布为:

1.4.4 do_bootm_linux函数中setup_end_tag (bd)函数分析如下:

static void setup_end_tag (bd_t *bd)  
{
params->hdr.tag = ATAG_NONE; //params->hdr.tag =ATAG_NONE=0
params->hdr.size = 0; //size=0
}

通过上面代码,最终内存分布为:

1.5  进入cleanup_before_linux函数清除中断和cache(./arm920t/cpu/cpu.c):

int cleanup_before_linux (void)
{
unsigned long i;

disable_interrupts (); //禁止中断
/* turn off I/D-cache */ //关闭 指令Icache和数据Dcache
asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i));
i &= ~(C1_DC | C1_IC);
asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i));

/* flush I/D-cache */
i = 0;
asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i));

return (0);
}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏代码世界

Flask之wtforms

WTForms是一个支持多个web框架的form组件,主要用于对用户请求数据进行验证。

883
来自专栏Linux驱动

第1阶段——uboot分析之启动函数bootm命令 (9)

本节主要学习: 详细分析UBOOT中"bootcmd=nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0" 中...

2099
来自专栏Charlie's Road

<Solidity学习系列四>使用编译器

Solidity存储库的一个构建目标是solc,solidity命令行编译器。 使用solc --help为您提供所有选项的解释。 编译器可以生成各种输出,范围...

732
来自专栏Aloys的开发之路

Java杂项

JDK JDK(Java Development Kit)是一个写Java程序所需的开发环境。它由一个处于操作系统层之上的运行环境,还有开发者编译、调试和运行J...

2217
来自专栏杨建荣的学习笔记

深入理解Oracle中的DBCA

但凡是学习 过Oracle的同学,DBCA都是一个必备工具,有了这个工具,创建数据库成为可能。而DBCA本身有图形和静默两种方式。静默方式看起来高大上,可以轻松...

3539
来自专栏优启梦

Chrome控制台console的基本用法

大家都有用过各种类型的浏览器,每种浏览器都有自己的特色,本人拙见,在我用过的浏览器当中,我是最喜欢Chrome的,因为它对于调试脚本及前端设计调试都有它比其它浏...

42912
来自专栏JackeyGao的博客

Django小技巧20: 使用多个settings模块

通常来说, 为了保持项目的配置简单,我们会避免使用多个配置文件。但理想很丰满, 现实是随着项目越来越大, settings.py可能也会变得相当复杂. 在那种情...

531
来自专栏禹都一只猫博客

Flask RESTful API 简单的设计一个 GET 请求接口

4135
来自专栏24K纯开源

CMake结合Visual Studio中开发Qt应用程序注意事项

2392
来自专栏后端技术探索

vim神奇高效功能--批量生成Sql实例

可以通过写代码,读取文件进行入库,无论用什么语言,这代码逻辑都很容易实现。唯一的问题是代码需要上线后执行,每次上线可是一个大工程,恐怕会被pm同学嘲笑说:“你这...

883

扫码关注云+社区