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);
            }
              
原文地址:https://www.cnblogs.com/-glb/p/12364448.html