本章(第二章)内容是设备树讲解中最重要的一章。
设备树文件里面描述的信息可以分为以下三部分:
- platform identification(平台信息);
- runtime configuration(运行时的配置信息);
- device population(设备的特性);
实际上,内核对于设备树的处理也会分为三部分:
bootloader在启动内核时,会设置r0,r1,r2三个寄存器,通过这三个寄存器将一些参数传给内核。其中:
- r0一般设置为0;
- r1一般设置为machine id(在使用设备树时该参数没有用到);
- r2一般设置为ATAGS或DTB的开始地址;
问:machine id的作用是什么呢?
答:一个内核镜相uImage通常可以支持多种单板,如smdk2410,smdk2440,jz2440,这三种单板之间都有一些微小的差别,也就是说它们分别需要不同的初始化函数。
在内核代码中,它们都有一个用于描述自己的machine_desc结构体,里面有对应的初始化函数,还有一个nr(number)。
那么,当内核启动时,内核怎么知道它在哪个板子上运行,需要调用哪个初始化参数呢?
这就需要用uboot了,uboot需要把machine id传给内核,内核再根据uboot传入的machine id来匹配machine_desc结构体中的nr,如果两者相等就是匹配成功,就会调用对应machine_desc结构体中的初始化函数。
上面是没有使用设备树时的情况,在使用设备树时,r1可以不设置了,也就是不传入machine id,此时,r1可以随便设置。
r2是设备树或ATAGS的起始地址,ATAGS就是之前说的uboot向内核传参数的tag的起始地址。这里需要注意的就是,r2传入的可能是设备树的起始地址,也可能是tag的起始地址。
tag就是uboot对内核传入的启动参数, 对于tag,可以从下列课程处了解更多的相关信息。
下面大致看一下head.S。
首先会获取processor id,然后跳到__lookup_processor_type,看看这款内核能不能支持这款CPU。
如果内核能够支持这款CPU的话,就会有相应的结构体被调用,在之后调用相应的初始化函数。
在代码里面,有很多.S文件,这些文件里面包含proc_info_list结构体(里面含有这类CPU的初始化函数、信息)。
然后往下执行,有一个__vet_atags函数,这个函数就会判断r2传入的是tag的首地址还是dtb文件的首地址。
然后是__create_page_tables,这个主要是创建页表,也就是创建虚拟地址与物理地址的映射关系,这个先不深入。
继续向下,会使能MMU,使能MMU之后就要使用虚拟地址了。
进入__mmap_switched。
进入 __mmap_switched_data,可以看到里面都是一些变量和段的地址(C变量在汇编文件中出现时,变量名表示的该变量的地址)。
继续往下,会从__mmap_switched_data中读值,经过第4步后会将C变量__atags_pointer的地址读到r2。
最后,再将之前保存到r8的tag或dtb首地址赋给地址为r2的内存空间,也就是__atags_pointer变量。
综上所述,在__mmap_switched中,经过一系列的汇编操作,最终会将:
- 把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
- 把bootloader传来的r2值, 赋给了C变量: __atags_pointer // dtb首地址
整个head.S的大致流程如下:
- __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
- __vet_atags : 判断是否存在可用的ATAGS或DTB
- __create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
- __enable_mmu : 使能MMU, 以后就要使用虚拟地址了
- __mmap_switched : 上述函数里将会调用__mmap_switched
- 把bootloader传入的r2参数, 保存到变量__atags_pointer中
- 调用C函数start_kernel
总的来说,head.S中所做的操作就是,将r2中保存的tag或dtb的首地址赋给了C变量__atags_pointer。