2.uboot分析二

 

第二阶段分析

我们先贴出这一阶段的程序流程图,以便理解:

 start_armboot
        cpu_init //初始化IRQ/FIQ模式的栈
        board_init
                /* 设置时钟 */
                clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);
                设置IO管脚
                gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;//机器ID
                gd->bd->bi_boot_params = 0x30000100;//传参的存放地址
                icache_enable()
                dcache_enable();
        interrupt_init //初始化定时器
        env_init        //检查flash上的环境参数是否有效
        init_baudrate //以下三个函数用于初始化串口
        serial_init
        console_init_f
        dram_init  //检测系统内存映射
                gd->bd->bi_dram[0].start = PHYS_SDRAM_1;            //内存起始地址
                gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;  //内存大小
  
        flash_init //识别norflash并初始化
        nand_init //识别nandflash
        env_relocate //将环境变量读入内存有效位置
        main_loop //根据环境变量启动内核

我们再贴出代码:

对应的初始化函数定义在一个结构体里:

init_fnc_t *init_sequence[] = {
cpu_init,/* basic cpu dependent setup */
board_init,/* basic board dependent setup */
interrupt_init,/* set up exceptions */
env_init,/* initialize environment */
init_baudrate,/* initialze baudrate settings */
serial_init,/* serial communications setup */
console_init_f,/* stage 1 init of console */
display_banner,/* say that we are here */
#if defined(CONFIG_DISPLAY_CPUINFO)
print_cpuinfo,/* display cpu info (and speed) */
#endif
#if defined(CONFIG_DISPLAY_BOARDINFO)
checkboard,/* display board info */
#endif
dram_init,/* configure available RAM banks */
display_dram_config,
NULL,
};
start_armboot函数代码如下:
void start_armboot (void)
{
………………………
}

(1)初始化本阶段要用的硬件设备

最主要的是设置系统时钟、初始化串口,只要这两个设置就好了,就可以从串口打印信息了

board_init函数设置MPLL、改变系统时钟,它是开发板相关的函数,在board/smdk2410/smdk2410.c中实现。值得注意的是,board_init函数中还保存了机器类型id,这将在调用内核时传给内核,board_init()函数代码如下:

2.int board_init (void)

{
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;

/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);

/* some delay between MPLL and UPLL */
delay (4000);

/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);

/* some delay between MPLL and UPLL */
delay (8000);

/* set up the I/O ports */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;

/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;/*传给内核的机器码*/

/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;/*启动参数*/

icache_enable();
dcache_enable();

return 0;
}

在cpu/arm920t/s3c24x0/serial.c中定义了串口初始化程序serial_int(),贴出相关代码:
3.int serial_init (void)
{
serial_setbrg ();

return (0);
}

为简化分析步骤就不具体分析了

(2)检测系统内存映射(memory map)

对于特定的开发板,其内存的分布是明确的,所以可以直接设置。board/smdk2410/smdk2410.c中的dram.init函数指定了本开发板的内存起始地址为0x30000000,大小为0x4000000,代码如下:

4.int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;//0x30000000
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;//0x4000000

return 0;
}

这些设置的参数,将在后面向内核传递参数时用到

(3)我们知道即便是内核启动,也是通过u-boot命令来实现的,u-boot中每个命令都通过U_BOOT_CMD宏来定义,格式如下:

U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")

参数意义如下:

name:命令的名字,注意,它不是一个字符串(不要用双引号括起来)

maxargs:最大的参数个数

repeatable:命令是否可重复,可重复是指运行一个命令后,下次敲回车即可再次运行

command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *,int,int,char *[])。

usage:简短的使用说明,这是个字符串。

help:叫详细的使用说明,这时个字符串。

宏U_BOOT_CMD在include/commond.h中定义,如下所示:

#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}

Struct_Section也是在include/commond.h中定义的,如下所示:

#define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

比如对于bootm命令,它如下定义:

U_BOOT_CMD(
  bootm, CFG_MAXARGS, 1, do_bootm,
  "bootm   - boot application image from memory\n",
  "[addr [arg ...]]\n    - boot application image stored in memory\n"
  "\tpassing arguments 'arg ...'; when booting a Linux kernel,\n"
  "\t'arg' can be the address of an initrd image\n"
#ifdef CONFIG_OF_FLAT_TREE
"\tWhen booting a Linux kernel which requires a flat device-tree\n"
"\ta third argument is required which is the address of the of the\n"
"\tdevice-tree blob. To boot that kernel without an initrd image,\n"
"\tuse a '-' for the second argument. If you do not pass a third\n"
"\ta bd_info struct will be passed instead\n"
#endif
);

宏U_BOOT_CMD扩展开后如下所示:

cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".u_boot_cmd")))=
{"bootm",CFG_MAXARGS,1,do_bootm."string1","string2"};

对于每个使用U_BOOT_CMD宏来定义的命令,其实都是在”.u_boot_cmd“段中定义一个cmd_tbl_t结构。连接脚本中有如下代码:

__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;

程序中就是根据命令的名字在内存段__u_boot_cmd_start~__u_boot_cmd_end 之间找到它的cmd_tbl_t结构,然后调用它的函数find_cmd,关于这个函数我们可以参考common/command.c中的find_cmd函数。

最后我们来总结一下,命令的实现与应用:

我们用U_BOOT_CMD宏定义一个命令,包括命令名、对应的函数,以及说明!定义的这个命令被存放在__u_boot_cmd_start ~__u_boot_cmd_end之间的段里面。

当我们使用命令的时候,就根据命令的名字找到其对应的函数进行操作!

 

nand read.jffs2和bootm命令

我们已nand read.jffs2和bootm命令为例来分析一下,顺便可以把我们u_boot最核心的部分(启动内核)来分析一下:

(1)首先我们在命令行里输入nand read.jffs2 0x30007fc0 kernel //这句话用来从0x30007fc0处读取kernel分区内容

(2)程序运行在common/main.c中,s = getenv("bootcmd")来获得输入的命令,然后调用run_command (s, 0)函数。在这个函数里首先对命令进行解析,并提取命令参数,然后调用函数 find_cmd() 在 __u_boot_cmd_start~__u_boot_cmd_end 之间寻找相同的命令,并返回对应的 cmd_tbl_t 结构体,find_cmd()函数代码如下:

cmd_tbl_t *find_cmd (const char *cmd)
{
cmd_tbl_t *cmdtp;
cmd_tbl_t *cmdtp_temp = &__u_boot_cmd_start; /*Init value */
const char *p;
int len;
int n_found = 0;

/*
 * Some commands allow length modifiers (like "cp.b");
 * compare command name only until first dot.
 */
len = ((p = strchr(cmd, '.')) == NULL) ? strlen (cmd) : (p - cmd);

for (cmdtp = &__u_boot_cmd_start;cmdtp != &__u_boot_cmd_end; cmdtp++)//这就是我们所说的遍历了
     {
if (strncmp (cmd, cmdtp->name, len) == 0)//名字要相同
             {
if (len == strlen (cmdtp->name))//长度要相同
return cmdtp; /* full match */

cmdtp_temp = cmdtp; /* abbreviated command ? */
n_found++;
}
}
if (n_found == 1) { /* exactly one match */
return cmdtp_temp;
}

return NULL; /* not found or ambiguous command */
}

(3)找到命令后会调用命令对应的函数,代码如下:

(cmdtp->cmd) (cmdtp, flag, argc, argv)

在分析内核启动之前我们先来补充一些知识:

分区,在嵌入式中,各个分区都在内核里写死了,具体在include/configs中的smdk2410.h中,不过我没有发现有具体的代码,估计是在移植的时候自己添加进去的!这里我们把韦东山老师的代码贴出来,分析一下:

 

 在nandflash上从0地址开始,前256k存放bootloader,接下来128k存放环境变量,然后是2M存放kernel,剩下的是root分区。

那么 nand read.jffs2 0x30007fc0 kernel 对应的函数是common/cmd_nand.c文件里的do_nand()函数,具体代码我们不分析了,只是说一下它的作用:读kernel分区内容到内存中

(4)在命令行输入:

bootm 0x30007fc0 //这是启动内核的

跟上面一样找到相应的命令,执行相应的程序 。我们也要补充一点知识:

u_boot下要用到uImage,uImage由一个头和真正的内核组成

其中头部的定义为:

typedef struct image_header {
uint32_t ih_magic; /* Image Header Magic Number */
uint32_t ih_hcrc; /* Image Header CRC Checksum */
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 */
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;

里面两个成员比较重要:

ih_load:加载地址,表示内核运行时要放在哪里

ih_ep   :内核入口地址

bootm 0x30007fc0时,如果发现内核的存放地址与加载uImage的头里面的加载地址不相符,就会把内核重新拷贝到加载地址处。这当然要花费一定的时间,所以我们一般会让它们相符,以加快内核启动速度。

对应的函数是common/cmd_bootm.c下的do_bootm()函数,关于这个函数我们不再分析具体代码,只是说一下它的功能:

  .根据头部将内核移动到合适的地方

.启动内核,启动内核会调用armlinux.c中函数:do_bootm_linux()对这个函数我们也不分析其具体代码,我们只说一下它的功能:设置启动参数并跳到入口地址处。

我们还得来说一说启动参数的问题,我们知道当内核启动起来之后,u_boot就没有用了,所以u_boot需要将一些内核参数放在跟内核约定好的某个位置,以便内核能够在无u_boot下访问,下面一些函数完成这项功能:

setup_start_tag (bd)

setup_memory_tags (bd)

setup_commandline_tag (bd, commandline)

setup_end_tag (bd)

在来谈一谈启动内核,下面一个函数完成内核的启动工作:theKernel (0, bd->bi_arch_number, bd->bi_boot_params)

它带了三个参数:bd->bi_arch_number表示机器码

                bd->bi_boot_params启动参数地址

这两个参数在board/smdk2410/smdk2410.c文件里可以找到定义:

gd->bd->bi_arch_number = MACH_TYPE_SMDK2410 //不解释,在内核分析里会详细分析

gd->bd->bi_boot_params = 0x30000100 //启动参数放在0x30000100开始的位置,要记清楚,内核分析里要用到的

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值