本节讲述uboot怎么把设备树(dtb)传给内核。
uboot只要把dtb写到内存,然后在启动内核时,把这块内存的起始地址传给内核(通过r2寄存器)。
uboot中内核启动命令
在uboot中,启动内核的命令是bootm。
在有设备树和无设备树时,bootm命令的使用是不同的。
- bootm <uImage_addr> //无设备树
- bootm <uImage_addr> <initrd_addr> <dtb_addr> //有设备树
比如,没有使用设备树时,通过指令将内核uImage读到内存0x3000 7FC0后,就可以使用bootm命令启动内核。
nand read.jffs2 0x30007FC0 kernel
bootm 0x30007FC0
使用设备树时,则还需要将dtb读到内存,假设读到起始地址为0x3200 0000的内存区域,然后再使用bootm命令启动内核,启动内核的时候,还要将dtb在内存中的起始地址也当做参数写入。
nand read.jffs2 0x30007FC0 kernel
nand read.jffs2 0x32000000 device_tree
bootm 0x30007FC0 - 0x32000000
需要注意的是,此时中间的参数代表的是<initrd_addr>,内存文件系统的地址。
当没有使用内存文件系统时,可以将它简写为一个减号(-)。
bootm命令怎么把dtb_addr写入r2寄存器传给内核?
uboot使用bootm命令启动,那么bootm命令要怎么把dtb_addr写入r2寄存器传给内核呢?
根据ARM程序调用规则(ATPCS),当一个函数被调用的时候,它的第一个参数使用r0寄存器来传递,第二个参数使用r1寄存器来传递,传参的数量超过一定值之后(一般来说,该值为4),才使用栈来传参。
假设,有一个函数:
int __add(int a, int b, int c);
调用这个函数:
add(1, 2, 3);
那么,1,2,3分别会通过r0,r1,r2寄存器来传递。
所以,可以创建一个C函数,传入三个参数,在第三个参数中传入dtb_addr。
这样,当这个C函数被调用时,这三个传参就会被写到r0,r1,r2寄存器,dtb_add就会被传递到函数内部。
c_function(p0, p1, p2) // p0 => r0, p1 => r1, p2 => r2
实际上,是定义个一个函数指针th_kernel,指向内核的入口地址。
然后,执行 th_kernel(0, machine_id, 0x32000000); 即可(使用设备时,machine_id未使用到,可以随便设置)。
看一下代码,bootm命令最终会调用到do_bootm_linux函数(\u-boot-1.1.6\lib_arm\armlinux.c)。
在do_bootm_linux函数中,会定义一个函数指针theKernel,它指向内核的入口地址。
函数的简单分析如下,最终通过theKernel调用函数启动内核。
dtb_addr可以随便选吗?
问:dtb可以随便存放在内核的任意区域吗?
答:不可以,需要遵循两个原则:
- 不要破坏uboot本身(dtb放到内存时,uboot还在运行,当然不能被破坏);
- 不要和内核使用的内存空间重合(dtb是给内核使用的,当然也不能破坏内核):
- 内核本身的空间不能占用;
- 内核要用到的内存区域也不能占用;(内核启动时一般会在它所处位置的下边放置页表, 这块空间(一般是0x4000即16K字节)不能被占用)
JZ2440内存使用情况:
------------------------------
0x33f80000 ->| u-boot |
------------------------------
| u-boot所使用的内存(栈等)|
------------------------------
| |
| |
| 空闲区域 |
| |
| |
| |
| |
------------------------------
0x30008000 ->| zImage |
------------------------------ uImage = 64字节的头部+zImage
0x30007FC0 ->| uImage头部 |
------------------------------
0x30004000 ->| 内核创建的页表 | head.S
------------------------------
| |
| |
-----> ------------------------------
|
|
--- (内存基址 0x30000000)
其中,内核的加载地址可以通过mkimage工具来读取。
根据JZ2440内存使用情况,可以将dtb放到中间的空闲区域,或者0x30000000-0x30004000的空闲区域,只要不会影响到uboot和内核的运行即可。
另外,在内核启动之后,dtb所在的内存区域会被保留,不会有其他程序来使用这块内存区域,即不会有内存泄漏发生。