zoukankan      html  css  js  c++  java
  • device_node转换为platform_device

    以前在写驱动程序的时候,需要把驱动分为平台device和平台driver两部分。在平台device中会放入硬件所使用的资源,使用C代码来指定platform_device,当需要修改硬件资源时,比如说想去修改led的引脚时,需要重新修改C文件,重新编译内核。再后来我们使用了设备树,可以在设备树中指定硬件资源。设备树是dts文件,它会转换成dtb文件,最终给内核使用。内核会来解析dtb文件得到一系列的device_node,现在终于来到了最后一步,将device_node转换成platform_device.

    在分析如何转换之前,先来看下面的两个问题。

    1、哪些device_node可以转换成platform_device?

    根节点也会对应一个device_node,它并不对应什么设备,也就是说不对应什么硬件,因此根节点对应的device_node应该不会转换成platform_device。

    再看一下memory,它虽然是一个硬件,但是我们并不需要什么驱动程序。内存所对应的device_node应该不会转换成platform_device

    再看一些chosen,它只是用来设置一些运行时的信息,它并不对应真实的硬件,它也不应该转换成platform_device.

    因此,并非所有的device_node都会转换为platform_device。 只有以下的device_node会转换:

    1.1 该节点必须含有compatible属性

    1.2 根节点的子节点(节点必须含有compatible属性)

    1.3 含有特殊compatible属性的节点的子节点(子节点必须含有compatible属性):

      这些特殊的compatilbe属性为: "simple-bus","simple-mfd","isa","arm,amba-bus"

    1.4 比如以下的节点,
    /mytest会被转换为platform_device,
    因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device

    /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。

    / {
              mytest {
                  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>;
                      };
              };
          };

    对于根目录下的第一级子节点,比如说I2c,它应该转换成platform_device,它会对应有一个platform_driver,当匹配时,driver中的probe函数就会被调用,对于I2c下面的这些子节点,比如说AT24C02应该交给probe函数来处理。如果仍然把这些子节点转成platfrom_device的话,就不太合适了。
    2. device_node如何转换成platform_device

    首先来看一下,platform_device结构体的定义:

    struct platform_device {
        const char    *name;
        int        id;
        bool        id_auto;
        struct device    dev;
        u32        num_resources;
        struct resource    *resource;   //指向一个动态生成的数组,数组的大小由num_resources决定。意味着平台设备中可以有0项,或1项,或多项资源。
                                     //这些资源来自设备树中的reg属性,如果设备树中设有reg属性,那么在对应的platform_device中就会有一项资源,
                                     //用来表示它所占用的内存空间。还可以指定一些中断属性,那么在platform_device就会表示它占据哪个中断号。
                                     //资源的类别:I/O资源,内存资源,中断资源,这3中资源都可以在设备树中指定。这些资源会从device_node转换得到。
                                     
        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;
    };

    platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;

    在设备树中还有一些属性,它们并不对应什么资源,那么这些属性保存在哪里?

    platform_device结构体中有struct device结构体,在它里面有一项of_node,它指向device_node结构体,因此以后想得到某个属性时,可以从platform_device中先找到dev,再找到of_node,从of_node中读取那些属性值。

    struct device {

      struct device_node *of_node; /* associated device tree node */

    };
    platform_device.dev.of_node指向device_node, 可以通过它获得其他属性

    2.分析代码

    函数调用过程: 

    2.1

    a. of_platform_default_populate_init (drivers/of/platform.c) 被调用过程分析
    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是在哪被调用的。在代码中根本找不到of_platform_default_populate_init函数被调用的地方,只是发现了下面的代码。

    arch_initcall_sync(of_platform_default_populate_init);
    arch_initcall_sync是一个宏,将该宏展开。
    #define arch_initcall_sync(fn)        __define_initcall(fn, 3s)
    #define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id)
    #define ___define_initcall(fn, id, __sec) 
        static initcall_t __initcall_##fn##id __used 
            __attribute__((__section__(#__sec ".init"))) = fn;

    因此:

    arch_initcall_sync(of_platform_default_populate_init)
        __define_initcall(of_platform_default_populate_init, 3s)
            ___define_initcall(of_platform_default_populate_init, 3s, .initcall##3s)
                #define ___define_initcall(of_platform_default_populate_init, 3s, .initcall##3s) 
                    static initcall_t __initcall_of_platform_default_populate_init3s __used 
                        __attribute__((__section__(.initcall3s.init))) = of_platform_default_populate_init;

    定义了一个函数指针of_platform_default_populate_init,这个函数指针有什么特殊的地方吗?
    特殊之处就在于,它有一个段属性,段属性的名字是.initcall3s.init。我们在编译内核时,有这些段属性的变量会被集中放在一起。

    打开arch/arm/kernel/vmlinux.ld,搜索.initcall3s.init

    所有文件的段属性initcall3s.init的那些变量都会放在__initcall3s_start到__initcall4s_start这个区域之间,这两个区域之间的函数就是位于initcall第3阶段要被调用的函数。内核启动时,会从__initcall3S_start到__initcall4s_start里面把它取出来,依次调用存放在该区间的函数。调用过程如下:

    do_initcall_level(level);  // 比如 do_initcall_level(3)
        for (fn = initcall_levels[3]; fn < initcall_levels[3+1]; fn++)//执行到3的时候,就会从initcall3s到initcall4s这段空间中的函数指针取出来依次执行
            do_one_initcall(initcall_from_entry(fn));  // 就是调用"arch_initcall_sync(fn)"中定义的fn函数

    2.2

     of_platform_default_populate_init  (drivers/of/platform.c) 生成platform_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;
                    }
                }
    const struct of_device_id of_default_bus_match_table[] = {
        { .compatible = "simple-bus", },
        { .compatible = "simple-mfd", },
        { .compatible = "isa", },
    #ifdef CONFIG_ARM_AMBA
        { .compatible = "arm,amba-bus", },
    #endif /* CONFIG_ARM_AMBA */
        {} /* Empty terminated list */
    };

    根据之前的分析,如果compatible属性里面含有simple-bus, simple-mfd, isa之一,那么它的子节点就可以被转换为platform_device.

     of_platform_default_populate_init  (drivers/of/platform.c) 生成platform_device的过程:
    of_platform_default_populate_init
        of_platform_default_populate(NULL, NULL, NULL);
            of_platform_populate(NULL, of_default_bus_match_table, NULL, NULL)
                root = root ? of_node_get(root) : of_find_node_by_path("/"); //首先得到根节点
                //对根节点中的每个子节点,都调用of_platform_bus_create。当做一种总线进行处理。即在根节点下面
                //的所有子节点,都默认是一种总线节点,比如说i2c,spi都是总线节点。led也将其作为一种总线节点来处理,不过该节点是虚有其表。
                
                for_each_child_of_node(root, child) {
                    rc = of_platform_bus_create(child, matches, lookup, parent, true);  // 调用过程看下面
                                /* Make sure it has a compatible property */
                                //如果总线节点,即根节点下面的子节点不含有compatible属性,将会立即返回。这就是为什么memory,chosen节点不能创建platform_device的原因了。
                                if (strict && (!of_get_property(bus, "compatible", NULL))) {
                                    return 0;  
                                }
                                
                                //该函数主要是对根目录下符合条件的根节点创建出platform_device,它是如何创建的呢?
                                //之前说过,会从device_node中根据它的reg属性或中断属性构造出那些资源。
                                         dev=of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
                                
                                    构造资源的过程
                                    dev = of_device_alloc(np, bus_id, parent);   // 根据device_node节点的属性设置platform_device的resource
                                                   if (rc) {
                                            of_node_put(child);
                                            break;
                                        }
                }

    3.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
                        }

    4.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);
                }
                  
  • 相关阅读:
    怎么查看京东店铺的品牌ID
    PPT编辑的时候很卡,放映的时候不卡,咋回事?
    codevs 1702素数判定2
    codevs 2530大质数
    codevs 1488GangGang的烦恼
    codevs 2851 菜菜买气球
    hdu 5653 Bomber Man wants to bomb an Array
    poj 3661 Running
    poj 1651 Multiplication Puzzle
    hdu 2476 String Painter
  • 原文地址:https://www.cnblogs.com/-glb/p/12364448.html
Copyright © 2011-2022 走看看