目录
1.编译配置过程
1. 解压缩《u-boot-1.1.6.tar.bz2》
tar -xvf u-boot-1.1.6.tar.bz2
2. 打补丁:
patch -p1 < ../u-boot-1.1.6_jz2440.patch
patch -p0 file1(想被打的文件) < test.patch(补丁)
-p1:忽略补丁文件中的第一个“/”之前的参数即“u-boot-1.1.6”,如下图所示。
其中,.patch文件中--表示原来的代码,++表示修改后的代码。、
(关于linux打补丁的一篇文章https://blog.csdn.net/qq_33194301/article/details/88908630))
3. 打开补丁文件
补丁文件表示最新修改后的代码和初始代码相比在哪个位置修改了哪些部分。
4 .配置:make 100ask24x0_config
5. 编译: make
——编译完成生成u-boot.bin文件
2. U-boot功能
1)基础功能
- 关看门狗
- 初始化时钟
- 初始化sdram
- 从flash里读出内核写入sdram
- 启动内核
2)开发功能
- 烧写flash
- 网卡
- USB
- 串口
3.Makefile结构分析
转载: https://blog.csdn.net/xiaoaojianghu09/article/details/101382655
https://blog.csdn.net/xiaoaojianghu09/article/details/101311866
4.U-boot源码分析
4.1 start.S的功能
u-boot的start.S做的事情,实际跟我们之前写的start.S差不多,只不过更复杂一些。 主要完成了以下功能:
- 设为svc模式
- 关闭看门狗
- 屏蔽中断
- 初始换sdram
- 设置栈
- 设置时钟
- 代码重定位 flash=>sdram
- 清除BSS段
- 调用start_armboot C函数
U-Boot第一阶段的启动流程。这个阶段主要是初始化硬件设备,为加载U-Boot的第二阶段代码准备RAM空间最后跳转到lib_arm/board.c中start_armboot函数,这是第二阶段的入口点。
U-Boot第一阶段的存储器布局。上图中U-Boot安装在Nand Flash,启动时Nand Flash控制器前4KB的代码复制到SRAM中,这4KB的代码再将整个U-Boot复制到SDRAM中,最后跳转到SDRAM中去执行,这就进入了第二阶段。
————————————————
版权声明:本文为CSDN博主「求佛_ce123」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ce123_zhouwei/article/details/7304909
关于start.S的详细内容,可以参考这篇文章, 其中对于一些基础知识的阐述也很详细,值得一看。
4.2 start_armboot C
以上为硬件初始化,称为u-boot的第一阶段。第二阶段,更为复杂的内容则会在start_armboot C函数中实现,下图为图解。
在start_armboot中主要是做各个模块的初始化,将环境参数读入内存的指定位置。最后start_armboot()函数使用死循环调用main_loop()函数,作用是防止main_loop()函数开始的初始化代码如果调用失败后重新执行初始化操作,保证程序能进入到U-Boot的命令行。
main_loop在common/main.c中,通过这条指令读取启动内核
|
其中bootcmd为环境变量,有两个参数。 第一个参数nand read.jffs2 kernel 0x30007FC0的意为从flash中读取内核,从kernel分区读取到内存0x30007FC0这个位置。 后一个参数 bootm 0x30007FC0,意思是启动内核。 |
![]() |
4.2.1 那么U-boot是怎么生成命令的呢?为什么bootm就可以运行内核了?
U_BOOT_CMD 的命令格式如下,可以将其理解为宏定义的函数,括号里的是他的参数。
U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")
各个参数的意义如下:
name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串
maxargs:命令的最大参数个数
repeatable:是否自动重复(按Enter键是否会重复执行)
command:该命令对应的响应函数指针
usage:简短的使用说明(字符串)
help:较详细的使用说明(字符串)
U_BOOT_CMD宏在include/command.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/command.h中定义如下:
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
/* 凡是带有attribute ((unused,section (“.u_boot_cmd”))属性声明的变量都将被存放在”.u_boot_cmd”段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。
*/
在u-Boot连接脚本 u-boot.lds中定义了.u_boot_cmd
段:
. = .;
__u_boot_cmd_start = .; /*将 __u_boot_cmd_start指定为当前地址 */
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .; /* 将__u_boot_cmd_end指定为当前地址 */
这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。
这样只要将u-boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在 __u_boot_cmd_start 与 __u_boot_cmd_end 之间查找就可以了。
关于_attribute_ ()
GNU C的一大特色就是__attribute__机制。GNU C扩展的__attribute__ 机制被用来设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。__attribute__书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:__attribute__((attribute-list))。 其位置约束为: 放于声明的尾部“:”之前。
__attribute__主要用于改变所声明或定义的函数或数据的特性,它有很多子项,用于改变作用对象的特性。比如对函数,noline将禁止进行内联扩展、noreturn表示没有返回值、pure表明函数除返回值外,不会通过其它(如全局变量、指针)对函数外部产生任何影响。但这里我们比较感兴趣的是对代码段起作用子项section。
eg.
int var __attribute__((section(".xdata"))) = 0;
这样定义的变量var将被放入名为.xdata的输入段,(注意:__attribute__这种用法中的括号好像很严格,这里的几个括号好象一个也不能少。)
static int __attribute__((section(".xinit"))) functionA(void)
{
.....
}
这个例子将使函数functionA被放入名叫.xinit的输入段。
需要着重注意的是,__attribute__的section属性只指定对象的输入段,它并不能影响所指定对象最终会放在可执行文件的什么段。
有了以上的铺垫,我们可以知道,当run_command("bootm...",0)的时候,U-boot识别字符串指令,跳转到功能实现的common文件夹下的Cmd_bootm.c中。其中,有这样一个代码段
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段。
#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}
cmd_tbl_t __u_boot_cmd_bootm __attribute__ ((unused,section (".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
}
其中自定义的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;
4.3 命令执行过程
① 在u-boot控制台中输入“boot”命令执行时,u-boot控制台接收输入的字符串“boot”,传递给run_command函数。
② run_command函数调用common/command.c中实现的find_cmd函数在__u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回boot命令的cmd_tbl_t结构。
③ 然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用boot命令的响应函数do_bootd,从而完成了命令的执行。
5. U-boot启动内核
我们再回头看这个环境变量“bootcmd=nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0”。从kernel分区(代表了某个起始地址和长度)读取内核,放到内存地址(0x30007fc0)去。
在PC机上,每一个硬盘前面都有一个分区表。对于嵌入式Linux来说,flash上面没有分区表,显然这个分区就和PC机上不一样;既然没有分区表,这些分区怎么体现?只能在源码里面固定死,因此我们需要关注这些分区的地址,也就是bootloader的起始地址和各个分区的大小。
FLASH | |||
bootloader | environment parameters | kernel | root |
定义分区的源码如下:(include\configs\100ask24x0.h)
#define MTDIDS_DEFAULT "nand0=nandflash0"
#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \
"128k(params)," \
"2m(kernel)," \
"-(root)"
通过在命令里面输入mtd,可以看到各个分区的起始地址和大小。kernel的起始地址为0x00060000长度为0x00200000。
因此这两句的意思实际是一样的,kernel代表了起始地址和大小。
nand read.jffs2 0x30007FC0 kernel |
nand read.jffs2 0x30007FC0 0x00060000 0x00200000 |
Flash上存的内核——UImage,是一个头部+真正的内核。头部的结构如下,其中ih_load是加载地址,ih_ep是入口地址。
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;
bootm的工作:
- 读取头部,根据头部将内核移动到合适的地方
- 启动 do_bootm_linux :
(1)Uboot告诉内核一些参数 ==> 设置启动参数
(2)跳到入口地址启动内核
那么如何设置启动参数?U-boot和内核之间如何交互数据?
通过在某个地址按某种格式保存数据。内核启动后,在这个地址把数据读取出来按约定的格式解析出来,这个格式称为TAG(标记列表)。
static void setup_start_tag (bd_t *bd); static void setup_memory_tags (bd_t *bd); static void setup_commandline_tag (bd_t *bd, char *commandline); static void setup_initrd_tag (bd_t *bd, ulong initrd_start,ulong initrd_end); static void setup_end_tag (bd_t *bd);
其中拿setup_start_tag来举例,这个函数里完成了一系列参数的赋值。
static void setup_start_tag (bd_t *bd) { params = (struct tag *) bd->bi_boot_params; params->hdr.tag = ATAG_CORE; 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); }
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep); //theKernel是一个函数指针
......
theKernel (0, bd->bi_arch_number, bd->bi_boot_params); // 启动内核
其中,第一个参数0是固定的
通过看来自于linux-2.6.30.4\Documentation\arm\Booting:
第二个参数是机器类型ID,每种单板都有自己的机器ID
board\100ask24x0\100ask24x0.c
第三个参数是标记列表的开始地址
board\100ask24x0\100ask24x0.c