设备树DTS
DTS引入
一个设备驱动程序的几种写法:
1、最开始一般传统方法:直接在程序中写死硬件资源,代码简单,不易扩展,裸机小项目可以使用
2、总线设备驱动模型:是将驱动程序分为platform_device和platform_driver,在平台设备中指定硬件资源,在平台驱动中做驱动程序的核心:分配、设置、注册file_operations结构体。易于扩展,但是硬件资源变动了就要重新编译使用
3、设备树dts使用:在dts中指定硬件资源,驱动程序分为platfrm_driver和dts文件,dts被编译成dtb,启动时会将dtb文件传给内核,内核根据dtb文件做驱动程序的核心:分配、设置、注册platform)devices
__这三种的主要区别__就是硬件资源的使用,如何指定使用的硬件资源。其他驱动核心都是一样的:分配、设置、注册file_operations结构体,这个结构体中有.open/.read/.write/.ioctl等,驱动程序使用这些成员函数去操作硬件。
一个驱动程序的一般写法:
1、分配file_operations结构体
2、设置file_operations结构体:该结构体中有.open,.read,.write等成员,在这些成员函数中去操作硬件
3、注册file_operations结构体:register_chrdev(major, name, &fops)
4、入口函数:register_chrdev
5、出口函数:unregister_chrdev
如何去知道一个设备和驱动匹配呢
1 platform_device含有name
2 platform_driver.id_table"可能"指向一个数组, 每个数组项都有name, 表示该platform_driver所能支持的platform_device
3 platform_driver.driver含有name, 表示该platform_driver所能支持的platform_device
4 优先比较1, 2两者的name, 若相同则表示互相匹配
5 如果platform_driver.id_table为NULL, 则比较1, 3两者的name, 若相同则表示互相匹配
dts的使用:platform_device来自设备树文件,dts文件被编译成dtb文件,dtb文件传给内核,内核解析dtb文件,构造出一系列的device_node结构体,device_node结构体会转换成platfrm_device结构体,所以我们直接在dts中指定硬件资源,不需要再在.c文件中设置platform_device
来自dts的platform_device结构体"里面有成员".dev.of_node", 它里面含有各种属性, 比如 compatible, reg, pin,我们写的platform_driver"里面有成员".driver.of_match_table", 它表示能支持哪些来自于dts的platform_device,如果"of_node中的compatible" 跟 “of_match_table中的compatible” 一致, 就表示匹配成功, 则调用 platform_driver中的probe函数,在probe函数中, 可以继续从of_node中获得各种属性来确定硬件资源
DTS语法
Devicetree node格式:
[label:] node-name[@unit-address] {
[properties definitions]
[child nodes]
};
引用其他节点:
1、__phandle__ : // 节点中的phandle属性, 它的取值必须是唯一的(不要跟其他的phandle值一样)
pic@10000000 {
phandle = <1>;
interrupt-controller;
};
another-device-node {
interrupt-parent = <1>; // 使用phandle值为1来引用上述节点
};
2、 __label__:
PIC: pic@10000000 {
interrupt-controller;
};
another-device-node {
interrupt-parent = <&PIC>; // 使用label来引用上述节点,
// 使用lable时实际上也是使用phandle来引用,
// 在编译dts文件为dtb文件时, 编译器dtc会在dtb中插入phandle属性
};
DTS在内核中的处理
dts在编译被翻译成dtb文件——》dtb文件在启动时传递给内核——》解析处理dtb文件中的信息——》将信息转换成deveice_node——》device_node转换成platform_device——》platform_device和platform_driver匹配使用驱动程序
内核开始head.s对dtb文件的处理
bootloader启动内核时,有设置r0、r1、r2上寄存器,
r0:一般设置0 r1:一般设置是machine id r2:一般设置是ATAGS或是DTS首地址
1、__lookup_processor_type:
2、__vet_atags:判断是否存在可用的atags或者dtb
3、__create_page_tables:创建页表,创建物理地址和虚拟地址的映射关系
4、__enable_mmu:使能MMU,开始使用虚拟地址
5、__mmap_switched:上述函数里将会调用__mmap_switched
6、将bootlaoder传入的r2参数,保存到变量__atags_pointer中
7、调用C函数start_kernel
dts中的信息处理
设备树中根节点属性compatible列出兼容的machine,内核中有很多machine_desc,里面有个dt_compat数组成员来表示支持那些machine。通过比较来选择machine。
调用:
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;
dtb转换device_node
start_kernel // init/main.c
setup_arch(&command_line); // arch/arm/kernel/setup.c
arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
early_init_fdt_reserve_self();
/* Reserve the dtb region */
// 把DTB所占区域保留下来, 即调用: memblock_reserve
early_init_dt_reserve_memory_arch(__pa(initial_boot_params),
fdt_totalsize(initial_boot_params),
0);
early_init_fdt_scan_reserved_mem(); // 根据dtb中的memreserve信息, 调用memblock_reserve
unflatten_device_tree(); // arch/arm/kernel/setup.c
__unflatten_device_tree(initial_boot_params, NULL, &of_root,
early_init_dt_alloc_memory_arch, false); // drivers/of/fdt.c
/* First pass, scan for size */
size = unflatten_dt_nodes(blob, NULL, dad, NULL);
/* Allocate memory for the expanded device tree */
mem = dt_alloc(size + 4, __alignof__(struct device_node));
/* Second pass, do actual unflattening */
unflatten_dt_nodes(blob, mem, dad, mynodes);
populate_node
np = unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl,
__alignof__(struct device_node));
np->full_name = fn = ((char *)np) + sizeof(*np);
populate_properties
pp = unflatten_dt_alloc(mem, sizeof(struct property),
__alignof__(struct property));
pp->name = (char *)pname;
pp->length = sz;
pp->value = (__be32 *)val;
每一个节点转换成为一个device_node结构体,里面有properties,用来表示节点的属性,每一个属性对应一个property结构体。这些device_node构成一棵树,根节点是of_root
device_node转换成platform_device
内核函数of_platform_default_populate_init, 遍历device_node树, 生成platform_device
1、 并非所有的device_node都会转换为platform_device
只有以下的device_node会转换:
该节点必须含有compatible属性
根节点的子节点(节点必须含有compatible属性)
含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):这些特殊的compatilbe属性为: “simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”
2、__ of_platform_default_populate_init调用__
start_kernel // init/main.c
rest_init();
pid = kernel_thread(kernel_init, NULL, CLONE_FS);
kernel_init
kernel_init_freeable();
do_basic_setup();
do_initcalls();
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level); // 比如 do_initcall_level(3)
for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)
do_one_initcall(initcall_from_entry(fn)); // 就是调用"arch_initcall_sync(fn)"中定义的fn函数
__ of_platform_default_populate_init __生成platf-rm_device过程:
of_platform_default_populate_init
of_platform_default_populate(NULL, NULL, NULL);
of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
for_each_child_of_node(root, child) {
rc = of_platform_bus_create(child, matches, lookup, parent, true); // 调用过程看下面
dev = of_device_alloc(np, bus_id, parent); // 根据device_node节点的属性设置platform_device的resource
if (rc) {
of_node_put(child);
break;
}
}
__ of_platform_bus_create __的调用:
dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); // 生成bus节点的platform_device结构体
if (!dev || !of_match_node(matches, bus)) // 如果bus节点的compatile属性不吻合matches成表, 就不处理它的子节点
return 0;
for_each_child_of_node(bus, child) { // 取出每一个子节点
pr_debug(" create child: %pOF\n", child);
rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); // 处理它的子节点, of_platform_bus_create是一个递归调用
if (rc) {
of_node_put(child);
break;
}
}
3、__ i2c和spi总线节点的处理过程 __:
/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
/i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。
类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
/spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。
/i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
platform_driver的probe函数中会调用i2c_add_numbered_adapter:
i2c_add_numbered_adapter // drivers/i2c/i2c-core-base.c
__i2c_add_numbered_adapter
i2c_register_adapter
of_i2c_register_devices(adap); // drivers/i2c/i2c-core-of.c
for_each_available_child_of_node(bus, node) {
client = of_i2c_register_device(adap, node);
client = i2c_new_device(adap, &info); // 设备树中的i2c子节点被转换为i2c_client
}
/spi节点一般表示spi控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
platform_driver的probe函数中会调用spi_register_master, 即spi_register_controller:
spi_register_controller // drivers/spi/spi.c
of_register_spi_devices // drivers/spi/spi.c
for_each_available_child_of_node(ctlr->dev.of_node, nc) {
spi = of_register_spi_device(ctlr, nc); // 设备树中的spi子节点被转换为spi_device
spi = spi_alloc_device(ctlr);
rc = of_spi_parse_dt(ctlr, spi, nc);
rc = spi_add_device(spi);
}
4、__ 注册platform_driver和platform_device __ :
platform_driver_register
__platform_driver_register
drv->driver.probe = platform_drv_probe;
driver_register
bus_add_driver
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); // 把 platform_driver 放入 platform_bus_type 的driver链表中
driver_attach
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 对于plarform_bus_type下的每一个设备, 调用__driver_attach
__driver_attach
ret = driver_match_device(drv, dev); // 判断dev和drv是否匹配成功
return drv->bus->match ? drv->bus->match(dev, drv) : 1; // 调用 platform_bus_type.match
driver_probe_device(drv, dev);
really_probe
drv->probe // platform_drv_probe
platform_drv_probe
struct platform_driver *drv = to_platform_driver(_dev->driver);
drv->probe
platform_device_register
platform_device_add
device_add
bus_add_device
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); // 把 platform_device 放入 platform_bus_type的device链表中
bus_probe_device(dev);
device_initial_probe
__device_attach
ret = bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); // // 对于plarform_bus_type下的每一个driver, 调用 __device_attach_driver
__device_attach_driver
ret = driver_match_device(drv, dev);
return drv->bus->match ? drv->bus->match(dev, drv) : 1; // 调用platform_bus_type.match
driver_probe_device
匹配函数是platform_bus_type.match, 即platform_match,
匹配过程按优先顺序罗列如下:
1、比较 platform_dev.driver_override 和 platform_driver.drv->name
2、比较 platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
3、比较 platform_dev.name 和 platform_driver.id_table
4、比较 platform_dev.name 和 platform_driver.drv->name
有一个成功, 即匹配成功