设备树DTS

本文详细介绍了设备驱动的三种写法,重点讲解了设备树DTS的引入及其作用,包括DTS语法、内核处理流程以及DTS在内核中的转换。DTS使得硬件资源的指定更加灵活,简化了驱动程序的编写。内核通过解析dtb文件,构造device_node并转换成platform_device,然后进行驱动匹配。同时,文章还阐述了平台设备和驱动的匹配规则,以及i2c和spi节点的处理方式。
摘要由CSDN通过智能技术生成

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
有一个成功, 即匹配成功

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值