在linux内核早期的时候,每个嵌入式系统的板载信息(总线,设备的寄存器地址等)都是hardcode在arch/<cpu>/match-xxx/board-xxx.c之类的文件中,即必须存在这样的c文件,里面定义了板子上的硬件的地址等信息。
这样做会造成很多很多的大量相似的没多少用的重复代码。
为了解决这个问题,而引入了DTS(device tree source)。
每个嵌入式系统的描述硬件信息的DTS文件存放在arch/<cpu>/boot/dts/目录下,比如ast2500板子的DTS文件:ast2600evb.dts
//ast2600evb.dts
//aspeed-g6.dti
这些*.dts, *.dti文件,通过工具DTC编译成二进制文件*.dtb.
dtb文件的格式如下:
boot_param_header: 就是struct fdt_header.
memory reserve map: 这段保存的是一个保留内存映射列表,每个表由一对64位的物理地址和大小组成.
device tree strings: 由于某些属性(比如compatible)在大多数节点下都会存在,为了减少dtb文件大小,就需要把这些属性字符串只指定一个存储位置即可,这样每个节点的属性只需要按照位置找到属性字符串的位置就可以得到是哪个属性,所以dtb把device-tree strings单独列出来存储.
device stree structure:
device tree structure又分为node和property两部分:
这个生成的二进制dtb文件最后通过bootloader下载到内存中,并由bootloader传递给kernel.
下面,我们来看看内核是在哪里解析这个dtb文件,以及解析后如何处理的。
start_kernel() -> setup_arch()
arch/arm/kernel/setup.c
这里主要有两个函数,一个是setup_machine_fdt(),一个是unflatten_device_tree()。其中,__atags_pointer为uboot传递给kernel的dtb文件的内存地址,即__atags_pointer指向dtb文件占据的内存。
我们先来看这个setup_machine_fdt().
arch/arm/kernel/devtree.c
setup_machine_fdt()先调用early_init_dt_verify()校验dtb文件的checksum.
之后调用of_flat_dt_match_machine()。
drivers/of/fdt.c
of_flat_dt_match_machine()首先调用get_next_compat(),即arch_get_next_mach(&compat),这个arch_get_next_mach()用于获取.arch.info.init段的数据。.arch.info.init由宏DT_MACHINE_START()和宏MACHINE_START()来声明。在linux arm引入DTS前,由各个arch/<cpu>/mach-xxx/下的文件来来预先定义,而在引入DTS后,只有在setup_machine_fdt()开始的215-222行定义了一个。
所以,对于DTS来说,返回的是setup_machine_fdt()开始的215-222行处定义的:
我们回到of_flat_dt_match_machine().
由于setup_machine_fdt()开始的215-222行处定义的machine data中的dt_compat为NULL,故826行of_flat_dt_match()返回0.
之后,850行调用of_flat_dt_get_machine_name()来获取dts文件中”/”node下的model或compatile字符串.
drivers/of/fdt.c
之后,of_flat_dt_match_machine()结束,返回到setup_machine_fdt().
arch/arm/kernel/devtree.c
setup_machine_fdt()在253行,调用early_init_dt_scan_nodes()。
drivers/of/fdt.c
early_init_dt_scan_nodes()扫描’/’节点下的‘chosen’子节点,获取它的属性值property;
然后扫描’/’节点下的其他属性值,比如’#size-cells’, ‘#address-cells’;
最后扫描‘/’节点下的子节点memory的属性,比如’device_type’,’reg’等。
setup_machine_fdt()返回后,回到了setup_arch().
arch/arm/kernel/setup.c
setup_arch()然后调用unflatten_device_tree()继续去解析剩余node/property.
drivers/of/fdt.c
unflatten_device_tree()调用__unflatten_device_tree()继续解析dts文件,并将数据保存到struct device_node结构中。每个dts节点对应一个device_node,父子关系通过device_node的指针来关联。最后将device_node链表赋给of_root,即of_root代表所有的device_node的list的root,通过它,可以遍历所有的device_node。
至此,dts文件在内核中的解析告与段落。
我们接着来看,解析后的device_node如何变成platform_device,并注册到platform_bus_type的klist_devices链表中?
这个操作在of_platform_default_populate_init()函数中进行。
drivers/of/platform.c
476行的root就是前面介绍的of_root, 即device_node list的root。
483-489行,遍历所有的child,以及child的sibling(孩子的兄弟)的device_node,并调用of_platform_bus_create()。
注意,这里设置的platform device的parent,第一个为/sys/devices/platform,子节点的,依次在对应的子目录。
到这里,dts文件描述的device_node都转换成了platform_device注册到了platform_bus_type.klist_devices上了。
后面,当platform_bus_type.klist_drivers上注册上了驱动,则会调用该驱动的match_table去匹配platform_bus_type.klist_devices上的设备,匹配到了,则调用驱动的probe函数进一步处理。