zoukankan      html  css  js  c++  java
  • Linux设备树(3)——Linux内核对设备树的处理

    一、内核head.S对dtb的简单处理

    Documentation/devicetree/usage-model.txt 中指定:

    Linux uses DT data for three major purposes:
    1) platform identification,
    2) runtime configuration, and
    3) device population.

    内核对设备树的处理也是分这三部分的。

    1. 对于32bit的arm处理器,bootloader启动内核时,会设置r0,r1,r2三个寄存器,
    r0一般设置为0;
    r1一般设置为machine id (在使用设备树时该参数没有被使用);
    r2一般设置ATAGS(使用设备树之前)或DTB的开始地址(使用设备树之后)

    bootloader给内核传递的参数时有2种方法:ATAGS 或 DTB

    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首地址

    对于32bit的arm处理器,machine_desc 使用 MACHINE_START 初始化,其 dt_compat 存储的是 compatible 属性数组,用于表示支持的单板。

    u-boot 中也提供的对dtb文件进行操作的命令,为 fdt, uboot 中所有的命令都是使用U_BOOT_CMD()来定义的,对应文件中有命令的使用注释。

    二、内核对设备树中平台信息的处理

    a. 设备树根节点的compatible属性列出了一系列的字符串,表示它兼容的单板名,从“最兼容”到次之(体现在匹配顺序上)。

    b. 内核中有多个machine_desc,其中有 dt_compat 成员,它指向一个字符串数组,里面表示该 machine_desc 支持哪些单板。

    c. 使用设备树根节点的 compatile 属性的值,跟每一个 machine_desc.dt_compat 比较,成绩为"吻合的compatile属性值的位置",成绩越低越匹配, 对应的 machine_desc 即被选中。

    函数调用过程(arm平台适合,但是不适合arm64平台了):

    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
                /*将dtb文件占用的内存预留起来(之后通过sysfs文件可以dump)*/
                fixmap_remap_fdt(dt_phys);
                    __fixmap_remap_fdt(dt_phys, &size, PAGE_KERNEL_RO);
                    memblock_reserve(dt_phys, size);
                early_init_dt_verify(phys_to_virt(dt_phys)  // 判断是否有效的dtb, drivers/of/ftd.c
                    /*保存设备树dtb文件起始地址*/
                    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;

    三、内核对设备树中运行时配置信息的处理

    1. 函数调用过程

    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_scan_nodes();      // drivers/of/ftd.c
                            /*从“/chosen”(根节点下的chosen节点)下检索各种信息*/
                            of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
    
                            /* Initialize {size,address}-cells info */
                            of_scan_flat_dt(early_init_dt_scan_root, NULL);
    
                            /* Setup memory, calling early_init_dt_add_memory_arch */
                            of_scan_flat_dt(early_init_dt_scan_memory, NULL);

    a. /chosen 节点中 bootargs 属性的值, 存入全局变量 boot_command_line
    b. 将根节点的 #address-cells, #size-cells 属性的值,存入全局变量 dt_root_addr_cells, dt_root_size_cells
    c. 解析 /memory 中的 reg 属性, 提取出"base, size", 最终调用 memblock_add(base, size);

    2. dts文件中的每一个 “{};” 都表示一个节点,都会对应构造一个 device_node 结构体,根节点也不例外。同级不同节点之间是兄弟的关系,包含节点是父子关系。

    四、dtb转换为device_node(unflatten)的过程

    1. 函数调用过程

    setup_arch(char **cmdline_p)
        arm_memblock_init(mdesc); // arch/arm/kernel/setup.c
            early_init_fdt_reserve_self();
                //把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
           /*非扁平化,也就是把dtb构造成树的结构*/
        unflatten_device_tree(void)
            /*
             * 遍历设备树中每一个node节点,然后构造device_node结构体,并生成其树状关系,
             * 根节点存在struct device_node of_root参数中。
             */
            __unflatten_device_tree(initial_boot_params, NULL, &of_root, early_init_dt_alloc_memory_arch, false);
                unflatten_dt_nodes(blob, NULL, dad, NULL);
                    /*对设备树中的所有节点都执行这个填充节点的函数*/
                    populate_node
                        /*
                         * 这里一块把节点的名字长度allocl一起分配了,节点的名字在device_node的末尾,
                         * 由device_node中的full_name指向。
                         */
                        unflatten_dt_alloc(mem, sizeof(struct device_node) + allocl, __alignof__(struct device_node));
                        np->full_name = fn = ((char *)np) + sizeof(*np);
    
                        /* 处理属性,为节点中的每一个属性都构造一个struct property结构 */
                        populate_properties(blob, offset, mem, np, pathp, dryrun);

    2. 在DTB文件中,
    dtb文件中,每一个节点都以 TAG(FDT_BEGIN_NODE 即 0x00000001) 开始, 节点内部可以嵌套其他节点, 每一个属性都以 TAG(FDT_PROP即0x00000003)开始

    3. dtb文件中的每一个节点都转换为一个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
    };

    4. device_node 结构体中有 properties, 用来表示该节点的属性,每一个属性对应一个 property 结构体

    struct property {
        char    *name;    // 属性名字, 指向dtb文件中的字符串,比如reg, compatible等。
        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
    };

    5. 这些device_node构成一棵树, 根节点为: of_root

    五、device_node 转换为 platform_device

    1. 转换过程

    dts -> dtb -> device_node -> platform_device

    (1) 哪些 device_node 可以转换为 platform_device

    ① 根节点下的含有 compatile 属性的子节点。
    ② 如果一个结点的 compatile 属性含有这些特殊的值("simple-bus","simple-mfd","isa","arm,amba-bus",定义在 of_default_bus_match_table 中)之一, 那么它的子结点(需含compatile属性)也可以转换为platform_device。

    (2) 怎么转换 ?
    platform_device 中含有 resource 数组, 它来自 device_node 的 reg, interrupts 属性;
    platform_device.dev.of_node 指向 device_node,可以通过它获得其他属性。

    2. 根节点下的i2c, spi等节点会转换为平台设备的设备端,i2c, spi等总线节点下的子节点, 应该交给对应的总线驱动程序来处理, 它们不应该被转换为 platform_device。

    3. 小结:
    (1) 内核函数 of_platform_default_populate_init, 遍历 device_node 树, 生成 platform_device。
    (2) 并非所有的 device_node 都会转换为 platform_device,只有以下的device_node会转换:
    ① 该节点必须含有 compatible 属性
    ② 需要是根节点的子节点(节点必须含有compatible属性)才会转换
    ③ 含有特殊 compatible 属性的节点的子节点(子节点必须含有compatible属性,子节点的子节点才会转换),这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus",定义在全局of_default_bus_match_table中。

    4. 示例

    / {
          mytest {
                /*父节点中的"simple-bus"属性会导致其子节点也会构建出平台设备的设备端*/
              compatile = "mytest", "simple-bus";
              mytest@0 {
                    compatile = "mytest_0";
              };
          };
          
          i2c {
              compatile = "samsung,i2c";
              at24c02 {
                    compatile = "at24c02";                      
              };
          };
    
          spi {
              compatile = "samsung,spi";              
              flash@0 {
                    compatible = "winbond,w25q32dw";
                    spi-max-frequency = <25000000>;
                    reg = <0>;
                  };
          };
    };

    (1) 节点 /mytest 会被转换为 platform_device。由于它兼容 "simple-bus", 它的子节点 /mytest/mytest@0 也会被转换为platform_device. 也就是说一个节点的 compatible 属性中只要有 "simple-bus",其子节也会转换为 platform_device,若没有是不会转换的。
    simple-bus 在usage-model中有介绍。

    (2) /i2c 节点一般表示i2c控制器,它会被转换为platform_device,在内核中有对应的 platform_driver; 其子节点 /i2c/at24c02 节点不会被转换为 platform_device,它被如何处理完全由父节点的 platform_driver 决定, 一般是被创建为一个i2c_client。

    (3) /spi 节点也类似, 它一般也是用来表示 SPI 控制器, 它会被转换为 platform_device, 在内核中有对应的 platform_driver; 其子节点 /spi/flash@0 不会被转换为 platform_device, 它被如何处理完全由父节点的 platform_driver 决定, 一般是被创建为一个spi_device。

    5. device_node 节点转化为 platform_device 的函数调用过程

    负责转化的函数为:

    static int __init of_platform_default_populate_init(void) 
    arch_initcall_sync(of_platform_default_populate_init);

    内核中没有直接对它的调用,而是使用放在".initcall3s.init"代码段中,在内核启动时进行 do_initcall_level 时调用这个函数。

    arch_initcall_sync(of_platform_default_populate_init) 宏等效于:

    static initcall_t __initcall_of_platform_default_populate_init3s __used
    __attribute__((__section__(".initcall3s.init"))) = of_platform_default_populate_init;
    start_kernel //init/main.c
        rest_init();
            pid = kernel_thread(kernel_init, NULL, CLONE_FS); /*创建kernel_init内核线程*/
                        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函数

    生成platform_device的过程:

    of_platform_default_populate_init //drivers/of/platform.c
        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); //调用过程看下面
                        of_platform_device_create_pdata
                            /*根据device_node节点的属性设置platform_device的resource*/
                            dev = of_device_alloc(np, bus_id, parent);
                                if (num_irq || num_reg) {
                                    res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
                                    dev->num_resources = num_reg + num_irq;
                                    dev->resource = res;
                                    for (i = 0; i < num_reg; i++, res++) {
                                        rc = of_address_to_resource(np, i, res);
                                    }
                                }
                        /*若父设备设备树中的节点中compatible中没有这个table里面的字符,就不为其子节点创建平台设备了*/
                        if (!dev || !of_match_node(of_default_bus_match_table, bus))
                            return 0;

    of_platform_bus_create的详细调用过程(处理bus节点生成platform_devie, 并决定是否处理它的子节点):

    of_platform_bus_create(bus, matches, ...)
        dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);  // 生成bus节点的platform_device结构体
        /*
         * 如果父设备树节点(一级节点)的compatile属性不吻合of_default_bus_match_table表, 
         * 就不为它的子节点(二级节点)创建platform_device结构。(这是个递归调用,也可能是
         * 三、四级节点)。
         */
        if (!dev || !of_match_node(matches, bus))  
            return 0;
        
        for_each_child_of_node(bus, child) {    //取出每一个子节点
            pr_debug("   create child: %pOF
    ", child);
            /*
             * 若上面吻合就为它的子节点也创建平台设备的设备端, 这是一个递归调用。
             */
            rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
        }

    6. I2C总线节点的处理过程:

    /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
                                device_register(&client->dev);
                    }

    i2c触摸板驱动(client驱动)的注册过程:

    mxt_init(void) //atmel_mxt_ts.c
        i2c_add_driver(&mxt_driver); //也就是i2c_register_driver(THIS_MODULE, driver)
            /*注册返回时,驱动程序核心将为所有匹配但未绑定的设备调用了probe()。*/
            driver_register(&driver->driver);
            /*遍历已经存在的i2c适配器(每一个i2c主机控制器都会注册成一个i2c适配器)*/
            i2c_for_each_dev(driver, __process_new_driver);
                i2c_do_add_adapter(data, to_i2c_adapter(dev));s
                    /*检测该总线上支持的设备,并实例化它们*/
                    i2c_detect(adap, driver);
                        i2c_detect_address(temp_client, driver);
                            client = i2c_new_device(adapter, &info);

    7. SPI总线节点的处理过程:
    /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);
            }

    8. 小结

      并不是所有的 device_node 结构都能转换成 platform_device 结构,比如根节点、choosen、memory 等对应的 device_node 就不会转换为platform_device 结构。一般只有根节点的子节点(一级子节点)才能转化为 platform_device 结构,子节点的子节点一般情况下是不可以转换为 platform_device 结构的,但是若其父节点的compatible属性中指定了of_default_bus_match_table中的字符串,其子节点也是可以转化为 platform_device 的。

    /i2c 下的子节点是交由根节点下的i2c节点对应的 platform_driver 驱动的,子节点注册成为 i2c_client。spi总线的类似,其子节点注册为spi_device。

    根节点下的子节点会注册成为平台设备的设备端,根节点的子节点的子节点不会注册成平台设备,而是由其父节点的平台驱动负责处理。

    六、platform_device跟platform_driver的匹配

    1. 注册 platform_driver 的过程

    platform_driver_register //drivers/base/platform.c
        __platform_driver_register
            drv->driver.probe = platform_drv_probe;
            driver_register
                //最终调用的是这个函数,i2c的和spi的也都是它
                bus_add_driver
                    //把 platform_driver 放入 platform_bus_type 的driver链表中
                    klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
                    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

    2. 注册 platform_device 的过程

    platform_device_register
        platform_device_add
            device_add
                bus_add_device
                    //把 platform_device 放入 platform_bus_type 的device链表中
                    klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices); 
                bus_probe_device(dev);
                    device_initial_probe
                        __device_attach
                            //对于plarform_bus_type下的每一个driver, 调用 __device_attach_driver
                            ret = bus_for_each_drv(dev->bus, NULL, &data, __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,由这个函数可知匹配过程按优先顺序罗列如下:
    a. 比较 platform_dev.driver_override 和 platform_driver.drv->name
    b. 比较 platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
    c. 比较 platform_dev.name 和 platform_driver.id_table
    d. 比较 platform_dev.name 和 platform_driver.drv->name
    有一个成功, 即匹配成功,之后dev不再参与匹配了。


    3. 范围更广一些的总线设备模型:

    设备端注册或驱动端注册都会触发匹配,匹配是使用 bus_type.match(),匹配上后会调用 bus_type.probe(),bus的probe()中会调用
    驱动的probe().

    (1) 驱动端注册触发的匹配过程:

    driver_register(struct device_driver *drv)
        bus_add_driver(struct device_driver *drv) //drivers/base/bus.c
            driver_attach(struct device_driver *drv) //drivers/base/dd.c
                bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
                    __driver_attach(struct device *dev, void *data)
                        /*调用drv->bus->match(dev, drv)来匹配dev和drv,匹配上后调用下面的probe()流程*/
                        driver_match_device(drv, dev);
                        /*
                         * 只有当这个dev->driver为空的时候才进行匹配,也就是说一旦dev匹配到drv后就
                         * 不再参与匹配过程了。
                         */
                        if (!dev->driver)
                        driver_probe_device(struct device_driver *drv, struct device *dev)    //drivers/base/dd.c
                            really_probe    //drivers/base/dd.c
                                if (dev->bus->probe) {
                                    /*先回去调用bus的probe,在bus的probe中再调用驱动的probe.*/
                                    ret = dev->bus->probe(dev);
                                } else if (drv->probe) {
                                    /*若bus的probe不存在,则直接调用驱动的probe.*/
                                    ret = drv->probe(dev);
                                }

    对于i2c平台设备,其bus为 i2c_bus_type,i2c_bus_type.probe = i2c_device_probe:

    i2c_device_probe
        if (driver->probe)
                status = driver->probe(client, i2c_match_id(driver->id_table, client));

    只要 device_driver 在注册的时候指定好 bus,之后引用的都是这个bus里面的函数.


    (2) 设备端注册触发匹配过程:

    device_register(struct device *dev) //drivers/base/core.c
        device_add(dev);
            bus_add_device(dev);
                /*将device添加到bus的klist_devices链表中*/
                klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
                kobject_uevent(&dev->kobj, KOBJ_ADD);
                bus_probe_device(dev);
                    if (bus->p->drivers_autoprobe)
                        device_initial_probe(dev); //drivers/base/dd.c
                            __device_attach(dev, true);
                                /*对bus上的每一个驱动都与device匹配*/
                                bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver);
                                    __device_attach_driver
                                        driver_match_device(drv, dev);
                                        driver_probe_device(drv, dev); /*接下来的处理流程和drv匹配dev是一样的了*/

    七、内核中设备树的操作函数

    dtb -> device_node -> platform_device

    1. include/linux/目录下有很多of开头的头文件:

    // (1) 处理DTB的
    of_fdt.h           // dtb文件的相关操作函数, 我们一般用不到, 因为dtb文件在内核中已经被转换为device_node树(它更易于使用)
    
    // (2) 处理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的相关函数
    
    // (3) 处理 platform_device的,在将 device_node --> 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

    2. 设备树中的下面的操作使用的应该是handle,有两个值,表示可以引用两类中断。
    interrupts = <0xA 0x8>

    八、在根文件系统中查看设备树(有助于调试)

    1. /sys/firmware/fdt // 原始dtb文件

    hexdump -C /sys/firmware/fdt    //打印出原始的dtb文件

    2. /sys/firmware/devicetree // 以目录结构程现的dtb文件

    根节点对应其中的base目录, 每一个节点对应一个目录, 每一个属性对应一个文件。

    3. /sys/devices/platform // 显示系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的。
    对于来自设备树的platform_device,其sys文件路径下有of_node节点(反向也成立)。可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性

    可以根据这个目录判断平台设备的设备端有没有被创建。也可以通过有无 of_node 节点来判断是不是通过设备树节点产生的平台设备。

    4. /proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base

  • 相关阅读:
    非GUI运行Jmeter,jtl文件没有响应数据的解决办法
    Fiddler抓取APP接口
    CentOS 7.x关闭/开启防火墙出现Unit iptables.service failed to load: No such file or directory问题解决
    Jmeter+Ant+Jenkins接口自动化持续集成环境搭建(Linux)
    Jenkins持续集成环境部署
    性能测试流程介绍
    MySQL性能优化
    Linux监控命令之==>ps
    Linux监控命令之==>lsof
    Zabbix监控基础
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/10999432.html
Copyright © 2011-2022 走看看