活动地址:CSDN21天学习挑战赛
【Linux驱动开发】设备树详解(一)设备树基础介绍
【Linux驱动开发】设备树详解(二)设备树语法详解
【Linux驱动开发】设备树详解(三)设备树Kernel解析


文章目录
5、设备树的加载流程
我们知道,
dts
文件经过dtc
工具编译为dtb
,内核加载并解析dtb
文件,最终获得设备树的信息。
那么Linux
如何加载``dtb文件,并生成对应节点的呢?
5.1 设备树地址设置
我们一般通过Bootloader
引导启动Kernel
,在启动Kernel
之前,Bootloader
必须将dtb
文件的首地址传输给Kernel
,以供使用。
Bootloader
将dtb
二进制文件的起始地址写入r2
寄存器中Kernel
在第一个启动文件head.S/head-common.S
中,读取r2
寄存器中的值,获取dtb
文件起始地址- 跳转入口函数
start_kernel
执行C语言代码
5.2 获取设备树中的平台信息——machine_desc
在dts
文件中,在根节点中有一个compatible属性,该属性的值是一系列的字符串,比如compatible = “samsung,smdk2440”“samsung,smdk2410,samsung,smdk24xx”;
,该属性就是告诉内核要选择什么样的machine_desc
,因为machine_desc
结构体中有一个dt_compat
成员,该成员表示machine_desc
支持哪些单板,所以内核会把compatible
中的字符串与dt_compat
进行依次比较。
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
mdesc = setup_machine_fdt(__atags_pointer); // arch/arm/kernel/devtree.c
early_init_dt_verify(phys_to_virt(dt_phys) // 判断是否有效的dtb, drivers/of/ftd.c
initial_boot_params = params;
mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach); // 找到最匹配的machine_desc, drivers/of/ftd.c
while ((data = get_next_compat(&compat))) {
score = of_flat_dt_match(dt_root, compat);
if (score > 0 && score < best_score) {
best_data = data;
best_score = score;
}
}
machine_desc = mdesc;
5.3 获取设备树的配置信息
在前面,我们也知道设备树中的
chosen
属性,用于传输固件和Linux
之间的数据,包含一些启动参数,那么我们该如何解析出来呢?
/chosen
节点中bootargs
属性的值, 存入全局变量:boot_command_line
- 确定根节点的这2个属性的值:
#address-cells
,#size-cells
- 存入全局变量:
dt_root_addr_cells
,dt_root_size_cells
- 解析
/memory
中的reg
属性, 提取出"base, size"
, 最终调用memblock_add(base, size);
5.4 设备树节点解析
dtb文件会在内存中一直存在着,不会被内核或者应用程序占用,我们需要使用的时候可以直接使用dtb文件。dtb文件的内容会被解析生成多个device_node,然后这些device_node构成一棵树, 根节点为: of_root
每一个节点都以TAG(FDT_BEGIN_NODE, 0x00000001)开始, 节点内部可以嵌套其他节点,
每一个属性都以TAG(FDT_PROP, 0x00000003)开始
- 设备树中的每一个节点,都会被转换为
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; /* removed properties */
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
转换为platform_device
那么多的device_node,哪些会被转化为platform_device呢?
- 根节点下的子节点,且该子节点必须包含compatible属性;
- 如果一个节点的
compatile
属性含有这些特殊的值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一,那么它的子结点(需含compatile属性)也可以转换为platform_device。
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override; /* Driver name to force a match */
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata;
};
转换完成之后,
- 设备树中的
reg/irq
等属性,都存放在了platform_device->resource
结构体中 - 设备树中的其他属性,都存在在了
platform_device.dev->of_node
结构体中
- C代码获取设备树属性
转换完成之后,内核提供了一些API
来直接获取设备树中对应的属性。如:
of_property_read_u32_index
:获取设备树中某个属性的值of_property_read_string
:获取设备树中某个属性的字符串的值of_get_address
:获取设备树中的某个节点的地址信息
整体总结下来,有几个类别:
a. 处理DTB
of_fdt.h // dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)
b. 处理device_node
of.h // 提供设备树的一般处理函数, 比如 of_property_read_u32(读取某个属性的u32值), of_get_child_count(获取某个device_node的子节点数)
of_address.h // 地址相关的函数, 比如 of_get_address(获得reg属性中的addr, size值)
of_match_device(从matches数组中取出与当前设备最匹配的一项)
of_dma.h // 设备树中DMA相关属性的函数
of_gpio.h // GPIO相关的函数
of_graph.h // GPU相关驱动中用到的函数, 从设备树中获得GPU信息
of_iommu.h // 很少用到
of_irq.h // 中断相关的函数
of_mdio.h // MDIO (Ethernet PHY) API
of_net.h // OF helpers for network devices.
of_pci.h // PCI相关函数
of_pdt.h // 很少用到
of_reserved_mem.h // reserved_mem的相关函数
c. 处理 platform_device
of_platform.h // 把device_node转换为platform_device时用到的函数,
// 比如of_device_alloc(根据device_node分配设置platform_device),
// of_find_device_by_node (根据device_node查找到platform_device),
// of_platform_bus_probe (处理device_node及它的子节点)
of_device.h // 设备相关的函数, 比如 of_match_device
上述总结下来,流程为dts->dtb->device_node->platform_device
6、设备树调试
- 查看原始的
dtb
文件
ls /sys/firmware/fdt
hexdump -C /sys/firmware/fdt
- 查看设备树信息
ls /sys/firmware/devicetree
ls /proc/device-tree
以目录结构程现的dtb文件, 根节点对应
base
目录, 每一个节点对应一个目录, 每一个属性对应一个文件
/proc/device-tree
是链接文件, 指向/sys/firmware/devicetree/base
- 查看所有硬件信息
ls /sys/devices/platform
系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的。
7、 参考地址
[1]:https://elinux.org/Device_Tree_Usage
[2]:https://www.kernel.org/doc/Documentation/devicetree/usage-model.txt
[3]:https://blog.csdn.net/zj82448191/article/details/109195364
