Exynos4412 内核移植(六)—— 设备树解析_知秋一叶-CSDN博客
第三课:linux内核对设备树的处理_韦东山嵌入式专栏-CSDN博客_linux内核设备数
通过大神的文章,结合自己的理解写写加深印象.其实设备树会用既可以。
1.内核对设备树的解析需要做三件事,从大到小分别是:
板级平台的识别-->运行参数的解析-->描述所有设备(device population)
2.不使用设备树的时候,板级平台的识别和运行参数的解析由bootloader设置好机器识别ID以及ATAGS,通过theKernel函数指针传给内核
描述所有设备(device population)则在根文件系统运行后,在内核中由驱动程序注册生成。
3.如今要使用设备树实现板级平台的识别、运行参数的解析和描述所有设备(device population)这三步,在theKernel()函数指针传递的第三个值从ATAGS换成DTB的首地址,DTB内含有上述三步的所有信息,theKernel()的第二个参数板子ID就没有用,theKernel(0,板子ID(无效),DTB首地址)。
4.theKernel的三个参数在内核中会分别赋予三个寄存器,r0、r1和r2
r0一般设置为0;
r1一般设置为machine id (在使用设备树时该参数没有被使用);
r2一般设置ATAGS或DTB的开始地址;
内核第一个函数head.s会对这三个寄存器进行简单处理
a. __lookup_processor_type : 使用汇编指令读取CPU ID, 根据该ID找到对应的proc_info_list结构体(里面含有这类CPU的初始化函数、信息)
b. __vet_atags : 判断是否存在可用的ATAGS或DTB
c. __create_page_tables : 创建页表, 即创建虚拟地址和物理地址的映射关系
d. __enable_mmu : 使能MMU, 以后就要使用虚拟地址了
e. __mmap_switched : 上述函数里将会调用__mmap_switched
f. 把bootloader传入的r2参数, 保存到变量__atags_pointer中
g. 调用C函数start_kernel
head.S/head-common.S :
把bootloader传来的r1值, 赋给了C变量: __machine_arch_type
把bootloader传来的r2值, 赋给了C变量: __atags_pointer // dtb首地址
5.板级平台的识别
r2参数保存在__atags_pointer了,启动了start_kernel函数了,start_kernel函数运行如下
内核中存在很多个不同板子的meachin_desc,跟目录下的compatible属性进行匹配,一致则会调用对应meachin_desc下的初始化启动函数。compatible属性可以是一连串的单板描述字符串,匹配度从左到右依次降低。优先使用最左边的单板描述字符串。
下面是其中一个单板例子
设别树对应的
6.运行时参数的提取
运行参数不用进行特殊处理,只需要从DTB文件中提取出来赋给固定的全局变量即可。提取是在板子匹配成功后执行。
a. /chosen节点中bootargs属性的值, 存入全局变量: boot_command_line
b. 确定根节点的这2个属性的值: #address-cells, #size-cells
存入全局变量: dt_root_addr_cells, dt_root_size_cells
c. 解析/memory中的reg属性, 提取出"base, size", 最终调用memblock_add(base, size);
7.描述所有设备(device population)
device population我也不知道怎么翻译,大致就是生成平台需要的device,然后和drive进行匹配
7.1 dtb文件所在的内存是会被内核自动保存起来,内核时不会去占用,即使没有指定
7.2 首先是把所有设备的描述提取出来构造节点,从根节点开始,每个大括号{}对应一个device_node结构体,device_node结构体有父指针、兄弟指针,子指针,通过父子兄弟指针的指向,把设备树所有节点关系在内存中立体成一棵树
struct device_node {
const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
phandle phandle;
const char *full_name; // 节点的名字, node-name[@unit-address]
struct fwnode_handle fwnode;
struct property *properties; // 节点的属性
struct property *deadprops;
struct device_node *parent; // 节点的父亲
struct device_node *child; // 节点的孩子(子节点)
struct device_node *sibling; // 节点的兄弟(同级节点)
#if defined(CONFIG_OF_KOBJ)
struct kobject kobj;
#endif
unsigned long _flags;
void *data;
#if defined(CONFIG_SPARC)
const char *path_component_name;
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif
};
device_node结构中的property结构体记录节点的信息,如长度、值等,相当于树叶
struct property {
char *name; // 属性名字, 指向dtb文件中的字符串
int length; // 属性值的长度
void *value; // 属性值, 指向dtb文件中value所在位置, 数据仍以big endian存储
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) || defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
通过这两个结构体,一个完整的设备树就描述完成
7.3_device_node转换为platform_device
1.根节点下的子节点中有compatible属性的会转换成platform_device
2.如果一个结点的compatile属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus")之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device
根节点的子节点外的节点,一般交给其父节点的platform_drive来处理。
platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
platform_device.dev.of_node指向device_node, 可以通过它获得其他属性
生成的platform_device就会跟 platform_driver去匹配
8.platform_device和platform_drive的匹配
匹配的顺序比较重要
匹配函数是platform_bus_type.match, 即platform_match,
匹配过程按优先顺序罗列如下:
比较 platform_dev.driver_override 和 platform_driver.drv->name
比较 platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table(设备树用这个)
比较 platform_dev.name 和 platform_driver.id_table(以前常用这个)
比较 platform_dev.name 和 platform_driver.drv->name
有一个成功, 即匹配成功



