Linux解析设备树生成设备的过程

  1. 设备树的populate过程大致有如下几个阶段
    一、根据设备树创建device node链表
    
    start_kernel
    
        ---> setup_arch
    
                ---> unflatten_device_tree
    
    在u-boot或者lk引导内核的时候,会将设备树在物理内存中的物理起始地址传递给Linux内核,然后Linux内核在函数unflatten_device_tree中会解析设备树镜像,并利用扫描到的信息创建由device node构成的链表,全局变量of_root指向链表的根节点,设备树的每个节点都会有一个struct device_node与之对应。
    
    二、遍历device node链表,创建并注册platform_device
    
    start_kernel
    
        ---> rest_init
    
                ---> kernel_init
    
                        ---> kernel_init_freeable
    
                                ---> do_basic_setup
    
                                        ---> do_initcalls
    
    在do_initcalls函数中,kernel会依次执行各个initcall函数,在这个过程中,会调用 customize_machine,具体如下:
    
    static int __init customize_machine(void)
    {
        /*
         * customizes platform devices, or adds new ones
         * On DT based machines, we fall back to populating the
         * machine from the device tree, if no callback is provided,
         * otherwise we would always need an init_machine callback.
         */
        of_iommu_init();
        if (machine_desc->init_machine)
            machine_desc->init_machine();
    #ifdef CONFIG_OF
        else
            of_platform_populate(NULL, of_default_bus_match_table,
                        NULL, NULL);
    #endif
        return 0;
    }
    arch_initcall(customize_machine);
    
    在arch/arm/mach-msm/board-8909.c中:
    DT_MACHINE_START(MSM8909_DT,
        "Qualcomm Technologies, Inc. MSM 8909 (Flattened Device Tree)")
        .map_io = msm8909_map_io,
        .init_machine = msm8909_init,
        .dt_compat = msm8909_dt_match,
        .reserve = msm8909_dt_reserve,
        .smp = &msm8916_smp_ops,
    MACHINE_END
    所以,machine_desc->init_machine();调用的实际是msm8909_init函数:
    static void __init msm8909_init(void)
    {
        struct of_dev_auxdata *adata = msm8909_auxdata_lookup;
    
        /*
         * populate devices from DT first so smem probe will get called as part
         * of msm_smem_init.  socinfo_init needs smem support so call
         * msm_smem_init before it.
         */
        of_platform_populate(NULL, of_default_bus_match_table, adata, NULL);
        msm_smem_init();
    
        if (socinfo_init() < 0)
            pr_err("%s: socinfo_init() failed\n", __func__);
    
        msm8909_add_drivers();
    }
    
    通过of_platform_populate函数来生成platform_device,具体调用过程:
    of_platform_populate->of_platform_bus_create->of_platform_device_create_pdata->of_device_add
    那么Linux系统是怎么知道哪些device node要注册为platform_device,哪些是用于i2c_client,哪些是用于spi_device?
    of_platform_populate根据参数of_default_bus_match_table来判断创建设备的类型,它的定义如下:
    
    const struct of_device_id of_default_bus_match_table[] = {
        { .compatible = "simple-bus", },
        { .compatible = "simple-mfd", },
    #ifdef CONFIG_ARM_AMBA
        { .compatible = "arm,amba-bus", },
    #endif /* CONFIG_ARM_AMBA */
        {} /* Empty terminated list */
    };
        如果某个device node的compatible属性的值与数组of_default_bus_match_table中的任意一个元素的compatible的值match,那么这个device node的child device node仍旧会被注册为platform_device。
    	备注:对于compatible属性的值是arm,primecell的节点有些特殊,它是单独处理的。
    
    of_platform_populate:
    
       int of_platform_populate(struct device_node *root,
                   const struct of_device_id *matches,
                   const struct of_dev_auxdata *lookup,
                   struct device *parent)
       {
           struct device_node *child;
           int rc = 0;
           // 如果传递进来的参数root为NULL,那么需要通过of_find_node_by_path函数找到device tree中的根节点。
    	 //得到根节点之后呢,就需要通过这个根节点来遍历device tree中的节点了。
           root = root ? of_node_get(root) : of_find_node_by_path("/");  
           if (!root)
               return -EINVAL;
        
           for_each_child_of_node(root, child) { // 遍历root device node的child device node
               rc = of_platform_bus_create(child, matches, lookup, parent, true);
               if (rc) {
                   of_node_put(child);
                   break;
               }
           }
           of_node_set_flag(root, OF_POPULATED_BUS);
        
           of_node_put(root);
           return rc;
       }
    of_platform_bus_create :
    
       static int of_platform_bus_create(struct device_node *bus,
                         const struct of_device_id *matches,
                         const struct of_dev_auxdata *lookup,
                         struct device *parent, bool strict)
       {
           const struct of_dev_auxdata *auxdata;
           struct device_node *child;
           struct platform_device *dev;
           const char *bus_id = NULL;
           void *platform_data = NULL;
           int rc = 0;
        
           /* Make sure it has a compatible property */
           //strict为真时,只有包含"compatible"属性的node节点才会生成相应的platform_device结构体// 
           if (strict && (!of_get_property(bus, "compatible", NULL))) { 
               pr_debug("%s() - skipping %s, no compatible prop\n",
                    __func__, bus->full_name);
               return 0;
           }
            //通过设备节点查找名称和platform_data
           auxdata = of_dev_lookup(lookup, bus);  
           if (auxdata) {
               bus_id = auxdata->name;
               platform_data = auxdata->platform_data;
           }
           //单独处理compatible属性值是arm,primecell的节点。
           if (of_device_is_compatible(bus, "arm,primecell")) {
               /*
                * Don't return an error here to keep compatibility with older
                * device tree files.
                */
               of_amba_device_create(bus, bus_id, platform_data, parent);
               return 0;
           }                                                                                                                         //根据device node创建 platform_device并注册
           dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); 
           判断是否需要遍历该设备节点下的子节点,前提是该节点的“compatible”和of_default_bus_match_table结构体中的数据匹配                        if (!dev || !of_match_node(matches, bus)) 
               return 0;
           遍历这个device node下的child device node,递归调用节点解析函数,为子节点继续生成platform_device结构体
           for_each_child_of_node(bus, child) {
               pr_debug("   create child: %s\n", child->full_name);
               rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict);
               if (rc) {
                   of_node_put(child);
                   break;
               }
           }
           of_node_set_flag(bus, OF_POPULATED_BUS);
           return rc;
       }
      
      在of_platform_bus_create函数中,参数strict用于指明设备节点是否需要有compatible属性。如果strict属性为真,首先是需要确定节点是否有
      "compatible"属性,如果没有"compatible"属性,则直接返回,即不会创建platform设备的。这样可以把chosen、aliases、memory等没有compatible
      属性的节点排除在外。  如果"compatible"属性值有"arm,primecell",则会调用of_amba_device_create函数去创建amba_device,这个设备暂时也不知道是一个什么设备,那么这里还是先忽略。
    
    继续,调用of_platform_device_create_pdata函数:
    static struct platform_device *of_platform_device_create_pdata(  
                        struct device_node *np,  
                        const char *bus_id,  
                        void *platform_data,  
                        struct device *parent)  
    {  
        struct platform_device *dev;  
      
        if (!of_device_is_available(np) ||  
            of_node_test_and_set_flag(np, OF_POPULATED))  
            return NULL;  
      
        dev = of_device_alloc(np, bus_id, parent);  
        if (!dev)  
            goto err_clear_flag;  
      
        of_dma_configure(&dev->dev);  
        dev->dev.bus = &platform_bus_type;  
        dev->dev.platform_data = platform_data;  
      
        /* We do not fill the DMA ops for platform devices by default. 
         * This is currently the responsibility of the platform code 
         * to do such, possibly using a device notifier 
         */  
      
        if (of_device_add(dev) != 0) {  
            platform_device_put(dev);  
            goto err_clear_flag;  
        }  
      
        return dev;  
      
    err_clear_flag:  
        of_node_clear_flag(np, OF_POPULATED);  
        return NULL;  
    }  
    可以看到,这个函数才是真正创建platform_device的。
    首先调用of_device_is_available函数,这个函数主要用于检测"status"属性,如果没有"status"属性或者属性的值为"okay"或"ok",
    返回true,否则返回false。所以"status"属性就是用来检测是否可用,其实就是用来确认是否需要创建platform设备。
    
    "status"属性检测完毕了,则要调用of_device_alloc函数来为platform_device分配内存了。
    struct platform_device *of_device_alloc(struct device_node *np,  
                      const char *bus_id,  
                      struct device *parent)  
    {  
        struct platform_device *dev;  
        int rc, i, num_reg = 0, num_irq;  
        struct resource *res, temp_res;  
      
        dev = platform_device_alloc("", -1);  
        if (!dev)  
            return NULL;  
      
        /* count the io and irq resources */  
        while (of_address_to_resource(np, num_reg, &temp_res) == 0)  
            num_reg++;  
        num_irq = of_irq_count(np);  
      
        /* Populate the resource table */  
        if (num_irq || num_reg) {  
            res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);  
            if (!res) {  
                platform_device_put(dev);  
                return NULL;  
            }  
      
            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);  
                WARN_ON(rc);  
            }  
            if (of_irq_to_resource_table(np, res, num_irq) != num_irq)  
                pr_debug("not all legacy IRQ resources mapped for %s\n",  
                     np->name);  
        }  
      
        dev->dev.of_node = of_node_get(np);  
        dev->dev.parent = parent;  
      
        if (bus_id)  
            dev_set_name(&dev->dev, "%s", bus_id);  
        else  
            of_device_make_bus_id(&dev->dev);  
      
        return dev;  
    }  
    调用platform中的platform_device_alloc函数来分配内存。内存申请了之后,还会对platform_device做一些初始化,例如IO、中断资源等等。首先是调用of_address_to_resource和of_irq_count去计算io和中断资源的个数。
    
    int of_address_to_resource(struct device_node *dev, int index,  
                   struct resource *r)  
    {  
        const __be32    *addrp;  
        u64     size;  
        unsigned int    flags;  
        const char  *name = NULL;  
      
        addrp = of_get_address(dev, index, &size, &flags);  
        if (addrp == NULL)  
            return -EINVAL;  
      
        /* Get optional "reg-names" property to add a name to a resource */  
        of_property_read_string_index(dev, "reg-names", index, &name);  
      
        return __of_address_to_resource(dev, addrp, size, flags, name, r);  
    }  
    首先调用of_get_address获取地址信息。
    const __be32 *of_get_address(struct device_node *dev, int index, u64 *size,  
                unsigned int *flags)  
    {  
        const __be32 *prop;  
        unsigned int psize;  
        struct device_node *parent;  
        struct of_bus *bus;  
        int onesize, i, na, ns;  
      
        /* Get parent & match bus type */  
        parent = of_get_parent(dev);  
        if (parent == NULL)  
            return NULL;  
        bus = of_match_bus(parent);  
        bus->count_cells(dev, &na, &ns);  
        of_node_put(parent);  
        if (!OF_CHECK_ADDR_COUNT(na))  
            return NULL;  
      
        /* Get "reg" or "assigned-addresses" property */  
        prop = of_get_property(dev, bus->addresses, &psize);  
        if (prop == NULL)  
            return NULL;  
        psize /= 4;  
      
        onesize = na + ns;  
        for (i = 0; psize >= onesize; psize -= onesize, prop += onesize, i++)  
            if (i == index) {  
                if (size)  
                    *size = of_read_number(prop + na, ns);  
                if (flags)  
                    *flags = bus->get_flags(prop);  
                return prop;  
            }  
        return NULL;  
    }  
    首先是找到它的parent,然后根据parent去找bus。
    static struct of_bus *of_match_bus(struct device_node *np)  
    {  
        int i;  
      
        for (i = 0; i < ARRAY_SIZE(of_busses); i++)  
            if (!of_busses[i].match || of_busses[i].match(np))  
                return &of_busses[i];  
        BUG();  
        return NULL;  
    }  
    of_busses定义如下:
    static struct of_bus of_busses[] = {  
    #ifdef CONFIG_OF_ADDRESS_PCI  
        /* PCI */  
        {  
            .name = "pci",  
            .addresses = "assigned-addresses",  
            .match = of_bus_pci_match,  
            .count_cells = of_bus_pci_count_cells,  
            .map = of_bus_pci_map,  
            .translate = of_bus_pci_translate,  
            .get_flags = of_bus_pci_get_flags,  
        },  
    #endif /* CONFIG_OF_ADDRESS_PCI */  
        /* ISA */  
        {  
            .name = "isa",  
            .addresses = "reg",  
            .match = of_bus_isa_match,  
            .count_cells = of_bus_isa_count_cells,  
            .map = of_bus_isa_map,  
            .translate = of_bus_isa_translate,  
            .get_flags = of_bus_isa_get_flags,  
        },  
        /* Default */  
        {  
            .name = "default",  
            .addresses = "reg",  
            .match = NULL,  
            .count_cells = of_bus_default_count_cells,  
            .map = of_bus_default_map,  
            .translate = of_bus_default_translate,  
            .get_flags = of_bus_default_get_flags,  
        },  
    };  
    在of_match_bus函数中,如果前面的bus不匹配,则使用默认的"default" bus,注意它的addresses字段为"reg"。
    
    回到of_get_address函数中,调用of_get_property函数去读取哪个属性呢,就是前面的addresses值的属性,即reg属性,所以reg属性就是用来定义io地址地址信息的。而io地址的长度是通过of_get_address中的of_read_number去读取完成的,最后返回这个io地址。
    
    回到of_address_to_resource函数中,在得到这个io地址之后,调用__of_address_to_resource函数将io地址转换成struct resource资源信息。
    
    然后是中断资源。
    int of_irq_count(struct device_node *dev)  
    {  
        struct of_phandle_args irq;  
        int nr = 0;  
      
        while (of_irq_parse_one(dev, nr, &irq) == 0)  
            nr++;  
      
        return nr;  
    }  
      
    int of_irq_parse_one(struct device_node *device, int index, struct of_phandle_args *out_irq)  
    {  
        struct device_node *p;  
        const __be32 *intspec, *tmp, *addr;  
        u32 intsize, intlen;  
        int i, res = -EINVAL;  
      
        pr_debug("of_irq_parse_one: dev=%s, index=%d\n", of_node_full_name(device), index);  
      
        /* OldWorld mac stuff is "special", handle out of line */  
        if (of_irq_workarounds & OF_IMAP_OLDWORLD_MAC)  
            return of_irq_parse_oldworld(device, index, out_irq);  
      
        /* Get the reg property (if any) */  
        addr = of_get_property(device, "reg", NULL);  
      
        /* Try the new-style interrupts-extended first */  
        res = of_parse_phandle_with_args(device, "interrupts-extended",  
                        "#interrupt-cells", index, out_irq);  
        if (!res)  
            return of_irq_parse_raw(addr, out_irq);  
      
        /* Get the interrupts property */  
        intspec = of_get_property(device, "interrupts", &intlen);  
        if (intspec == NULL)  
            return -EINVAL;  
      
        intlen /= sizeof(*intspec);  
      
        pr_debug(" intspec=%d intlen=%d\n", be32_to_cpup(intspec), intlen);  
      
        /* Look for the interrupt parent. */  
        p = of_irq_find_parent(device);  
        if (p == NULL)  
            return -EINVAL;  
      
        /* Get size of interrupt specifier */  
        tmp = of_get_property(p, "#interrupt-cells", NULL);  
        if (tmp == NULL)  
            goto out;  
        intsize = be32_to_cpu(*tmp);  
      
        pr_debug(" intsize=%d intlen=%d\n", intsize, intlen);  
      
        /* Check index */  
        if ((index + 1) * intsize > intlen)  
            goto out;  
      
        /* Copy intspec into irq structure */  
        intspec += index * intsize;  
        out_irq->np = p;  
        out_irq->args_count = intsize;  
        for (i = 0; i < intsize; i++)  
            out_irq->args[i] = be32_to_cpup(intspec++);  
      
        /* Check if there are any interrupt-map translations to process */  
        res = of_irq_parse_raw(addr, out_irq);  
     out:  
        of_node_put(p);  
        return res;  
    }  
    我们看在of_irq_parse_one函数中,是查找的"interrupts"属性值。
    
    回到of_device_alloc函数,还是通过前面的of_address_to_resource函数将io地址资源赋值给平台设备,通过of_irq_to_resource_table函数将中断号转换成中断资源信息并赋值给平台设备。
    
    int of_irq_to_resource_table(struct device_node *dev, struct resource *res,  
            int nr_irqs)  
    {  
        int i;  
      
        for (i = 0; i < nr_irqs; i++, res++)  
            if (!of_irq_to_resource(dev, i, res))  
                break;  
      
        return i;  
    }  
      
    int of_irq_to_resource(struct device_node *dev, int index, struct resource *r)  
    {  
        int irq = irq_of_parse_and_map(dev, index);  
      
        /* Only dereference the resource if both the 
         * resource and the irq are valid. */  
        if (r && irq) {  
            const char *name = NULL;  
      
            memset(r, 0, sizeof(*r));  
            /* 
             * Get optional "interrupt-names" property to add a name 
             * to the resource. 
             */  
            of_property_read_string_index(dev, "interrupt-names", index,  
                              &name);  
      
            r->start = r->end = irq;  
            r->flags = IORESOURCE_IRQ | irqd_get_trigger_type(irq_get_irq_data(irq));  
            r->name = name ? name : of_node_full_name(dev);  
        }  
      
        return irq;  
    }  
    我们可以看出在of_device_alloc函数中除了为平台设备分配内存之外,还为平台设备找到了io地址资源和中断资源。
    
    回到of_platform_device_create_pdata函数中,平台设备已经申请好了,然后对平台设备继续进行赋值操作,例如平台设备的总线赋值为平台总线,平台设备的私有数据赋值为platform_data,最后调用of_device_add函数将平台设备注册到内核中。
     
    三、注册其他设备
    
    I2C设备的注册
    
    
    
    1)platform bus用于挂载和CPU通过系统总线连接的各类外设。I2C控制器直接从属于platform bus,我们在linux kernel中常说的I2C driver,都是指I2C controller driver,都是以platform driver的形式存在,当然,对应的控制器是platform device。
    2)与此同时,kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C controller通过I2C总线连接的各个I2C slave device。
    3)比较特殊的地方是,I2C core使用一个虚拟实体----I2C adapter,抽象I2C controller有关的功能(主要是数据的收发),I2C    adapter也挂载在I2C bus上。
    4)I2C adapter和I2C slave device都挂载在I2C bus上,就可以方便的进行Master(I2C adapter)和Slave之间的匹配操作,
       并通过I2C core提供的统一接口,访问I2C salve device,进行数据的收发。
        i2c控制器在i2c驱动模型中被抽象为i2c_adapter,但是i2c控制器驱动实际上是在platform_bus上,所以i2c控制器对应的是platform_device,因此会在上面调用of_platform_populate时注册,然后i2c控制器驱动的probe函数会被调用。probe函数中调用注册adapter的函数接口:i2c_add_numbered_adapter ---> i2c_add_adapter ---> i2c_register_adapter ---> of_i2c_register_devices,在函数of_i2c_register_devices中会遍历这个adapter对应的device node的child device node。然后调用of_i2c_register_device,这个函数根据每个child device node的信息构造i2c_board_info,并调用i2c_new_device,
    	在i2c_new_device中会创建并注册i2c_client,注册i2c_client的时候如果找到了对应的设备驱动程序,设备驱动程序的probe函数就会被调动。
    
    SPI设备的注册
    
        由于SPI驱动模型跟I2C类似,spi_device的注册过程也跟i2c_client的很类似。spi控制器在spi子系统中被抽象为spi_master,spi控制器驱动实际上也在platform_bus上,所以spi控制器对应的是platform_device。当调用of_platform_populate注册spi控制器对应的platform_device的时候,spi控制器驱动的probe函数会被执行,在probe函数中会向spi子系统注册spi_master。probe函数中调用devm_spi_register_master ---> spi_register_master ---> of_register_spi_devices,在of_register_spi_devices中会遍历与这个spi_master对应的device node的child device node,这些child device node就是挂在spi bus上的板级外设,如spi接口的存储器等等。然后调用of_register_spi_device,根据每个child device node的信息创建spi_device,
    	并调用spi_add_device完成注册,注册spi_device的时候如果找到了对应的设备驱动程序,设备驱动程序的probe函数就会被调动。
    
     
    
    其他platform device的注册
    
    在上面说如果在of_platform_populate的时候如果给matches传递了of_default_bus_match_table,那么跟matches匹配的device_node的直接child device node会也会自动被注册为platform_device。假如跟matches不匹配的话,这个device_node的直接child device node不会被再被处理了。比如像下面的设备树结构:
    
    
     / {
         #address-cells = <0x2>;
         #size-cells = <0x2>;
         model = "Qualcomm Technologies";
         compatible = "qcom,msm8996";
         interrupt-parent = <0x1>;
     
         soc {
             compatible = "simple-bus";
     
             qcom,msm-dai-mi2s {
                 compatible = "qcom,msm-dai-mi2s";
     
                 qcom,msm-dai-q6-mi2s-quat {
                     compatible = "qcom,msm-dai-q6-mi2s";
                 };
             };
         };
     };
    
      如上,节点"qcom,msm-dai-mi2s"会被注册为platform_device,而其child device node是"qcom,msm-dai-q6-mi2s-quat",并不会被注册为platform_device。
      如果此时需要把"qcom,msm-dai-q6-mi2s-quat"也注册为 platform_device的话,也可以在"qcom,msm-dai-mi2s"对应的platform device_driver在被probe的
      时候重新调用of_platform_populate。如下:
    
    
     static int msm_dai_mi2s_q6_probe(struct platform_device *pdev)
     {
         int rc;
         rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
         if (rc) {
             dev_err(&pdev->dev, "%s: failed to add child nodes, rc=%d\n",
                 __func__, rc);
         } else
             dev_dbg(&pdev->dev, "%s: added child node\n", __func__);
         return rc;
     }
     
     static int msm_dai_mi2s_q6_remove(struct platform_device *pdev)
     {
         return 0;
     }
     
     static const struct of_device_id msm_dai_mi2s_dt_match[] = {
         { .compatible = "qcom,msm-dai-mi2s", },
         { }
     };
     
     MODULE_DEVICE_TABLE(of, msm_dai_mi2s_dt_match);
     
     static struct platform_driver msm_dai_mi2s_q6 = {
         .probe  = msm_dai_mi2s_q6_probe,
         .remove = msm_dai_mi2s_q6_remove,
         .driver = {
             .name = "msm-dai-mi2s",
             .owner = THIS_MODULE,
             .of_match_table = msm_dai_mi2s_dt_match,
         },
     };
    
     其中,在第4行又重新调用了of_platform_populate,它的第一个参数是"qcom,msm-dai-mi2s"的device node,通过这个就可以遍历其child device node,并将其注册为platform device。
    
     
    
    
    
    


 

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值