设备树传参中的platform device的匹配

设备树传参中的platform device的匹配在高版本的Linux内核中使用了设备树进行传参,之前购买一块nanopi的板子使用的是linux4.11.2版本的内核(使用的友善之臂的Mainlinelinux)就是这种情况,并且使用设备树传参过后,原来硬编码在mach-xxx.c文件中的platformdevice全部都放入了设备树中,而原来使用name进行platformdevice和driver进行匹配的方式也发生了变化。以

大家好,又见面了,我是你们的朋友全栈君。

问题

在高版本的Linux内核中使用了设备树进行传参,之前购买一块nanopi的板子使用的是linux 4.11.2版本的内核(使用的友善之臂的Mainline linux)就是这种情况,并且使用设备树传参过后,原来硬编码在mach-xxx.c文件中的platform device全部都放入了设备树中,而原来使用name进行platform device和driver进行匹配的方式也发生了变化。
以nanopi中PWM驱动为例,这是它的platform driver:

static struct platform_driver sun4i_pwm_driver = {
    .driver = {
        .name = "sun4i-pwm",
        .of_match_table = sun4i_pwm_dt_ids,
    },
    .probe = sun4i_pwm_probe,
    .remove = sun4i_pwm_remove,
};

在没有设备树传参时,platform device和driver的匹配是通过名字来匹配的,也就是比较platform_driver.device_driver.name和platform_device.name,而在这里整个工程中是找不到”sun4i-pwm”的字符串的,也就是说匹配方式发生了变化。

设备树的加载

设备树文件的后缀名为.dts,一般放在 /arch/arm/boot/dts 文件夹中,对内核进行编译的时候可以使用make指令将设备树编译成二进制文件.dtb,该文件在内核启动的时候会被加载到内核当中。以我的nanopi的板子为例,要求将SD卡分为boot分区和rootfs分区,编译的.dtb文件只需放入boot分区即可,uboot在环境变量中指定该.dtb文件的名字,那么内核在加载的时候就会加载相应的设备树。内核加载设备树的过程如下:

b   start_kernel                            -->arch/arm/kernel/head-common.S
    start_kernel                            -->init/main.c
        setup_arch                          -->arch/arm/kernel/setup.c
            setup_machine_fdt               -->arch/arm/kernel/devtree.c
                early_init_dt_scan_nodes    -->drivers/of/fdt.c
                    of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);  -->(1)
                    of_scan_flat_dt(early_init_dt_scan_root, NULL);  -->(2)
                    of_scan_flat_dt(early_init_dt_scan_memory, NULL);  -->(3) 
                __machine_arch_type = mdesc->nr;    -->(4)
        unflatten_device_tree               -->drivers/of/fdt.c, (5)
            __unflatten_device_tree
                unflatten_dt_nodes          // 对设备树进行展开

以下解释引用自http://blog.csdn.net/lichengtongxiazai/article/details/38941913

(1)扫描 /chosen node,保存运行时参数(bootargs) 到boot_command_line ,此外,还处理initrd相关的property ,并保存在initrd_start 和initrd_end 这两个全局变量中
(2)扫描根节点,获取 {size,address}-cells信息,并保存在dt_root_size_cells和dt_root_addr_cells全局变量中
(3)扫描DTB中的memory node,并把相关信息保存在meminfo中,全局变量meminfo保存了系统内存相关的信息
(4)Change machine number to match the mdesc we’re using
(5)unflattens a device-tree, creating the tree of struct device_node.

device和driver的匹配

以上文章还提到

系统应该会根据Device tree来动态的增加系统中的platform_device(这个过程并非只发生在platform bus上,也可能发生在其他的非即插即用的bus上,例如AMBA总线、PCI总线)。 如果要并入linux kernel的设备驱动模型,那么就需要根据device_node的树状结构(root是of_allnodes)将一个个的device node挂入到相应的总线device链表中。只要做到这一点,总线机制就会安排device和driver的约会。当然,也不是所有的device node都会挂入bus上的设备链表,比如cpus node,memory node,choose node等。

可以看到,一些设备树的device_node最终是会变为device挂载到总线上的,而这时就可以和driver进行配对了。在platform 总线中也是如此,要知道device和driver是如何配对的,就要看platform 总线的定义了,platform_bus_type定义如下:

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};

因此要看device和driver是如何配对的需要看platform_match函数是如何匹配的,platform_match函数如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* When driver_override is set, only bind to the matching driver */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* Attempt an OF style match first */
    // 匹配上之后直接返回,不进行下一步匹配
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try ACPI style match */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

可以看到和2.6版本的platform_match函数不同了,该函数在最后一步才进行pdev->name, drv->name名字的匹配,而如果在之前的if中匹配上了是直接返回1,而不会进行下一步匹配的。其中第二个if就是在设备树中进行匹配,我们追进去可以看到这个函数实际做的工作:

platform_match
    of_driver_match_device      // 利用设备树的节点进行匹配
        of_match_device             --> driver/of/device.c
            of_match_node           --> driver/of/base.c
                __of_match_node

在__of_match_node函数中才进行的真正的device和driver的匹配:

static
const struct of_device_id *__of_match_node(const struct of_device_id *matches,
                       const struct device_node *node)
{
    const struct of_device_id *best_match = NULL;
    int score, best_score = 0;

    if (!matches)
        return NULL;
    // 这里和设备树的进行匹配
    for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
        score = __of_device_is_compatible(node, matches->compatible,
                          matches->type, matches->name);
        if (score > best_score) {
            best_match = matches;
            best_score = score;
        }
    }

    return best_match;
}

这里匹配的是什么呢?在这个内核内核中匹配的是matches->compatible,为了方便说明,还是以nanopi的PWM驱动为例,这里再贴一遍它的platform_driver:

static struct platform_driver sun4i_pwm_driver = {
    .driver = {
        .name = "sun4i-pwm",
        .of_match_table = sun4i_pwm_dt_ids,
    },
    .probe = sun4i_pwm_probe,
    .remove = sun4i_pwm_remove,
};

这里匹配的就是platform_driver.driver_driver.of_match_id的内容,也就是这里的of_match_table,我们再追进去看:

static const struct of_device_id sun4i_pwm_dt_ids[] = {
    {
        .compatible = "allwinner,sun4i-a10-pwm",
        .data = &sun4i_pwm_data_a10,
    }, {
        .compatible = "allwinner,sun5i-a10s-pwm",
        .data = &sun4i_pwm_data_a10s,
    }, {
        .compatible = "allwinner,sun5i-a13-pwm",
        .data = &sun4i_pwm_data_a13,
    }, {
        .compatible = "allwinner,sun7i-a20-pwm",
        .data = &sun4i_pwm_data_a20,
    }, {
        .compatible = "allwinner,sun8i-h3-pwm",
        .data = &sun4i_pwm_data_h3,
    }, {
        /* sentinel */
    },
};

这就和__of_match_node函数匹配上了,该函数最终使用的就是platform_driver.driver_driver.of_device_id->compatible和设备树中的compatible进行比较,再工程中使用全局搜索”allwinner,sun8i-h3-pwm”(因为nanopi使用的是全志h3),可以找到对应的设备树节点在sunxi-h3-h5.dtsi中:

pwm: pwm@01c21400 {
            compatible = "allwinner,sun8i-h3-pwm";
            reg = <0x01c21400 0x8>;
            clocks = <&osc24M>;
            #pwm-cells = <3>;
            status = "disabled";
        };

总结

使用设备树传参时的platform device已经没有硬编码再内核当中了,而是使用一个设备树文件,将所有板级相关信息写在了里面,在platform总线进行匹配时,使用的是platform_driver.driver_driver.of_device_id->compatible和设备树中的compatible进行匹配,所以想要添加驱动,只需要在设备树文件中添加节点,指定device的compatible,然后在代码中再指定driver的compatible即可完成匹配,执行probe函数。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/163818.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 解释afterPropertiesSet

    在spring的bean的生命周期中,实例化->生成对象->属性填充后会进行afterPropertiesSet方法,这个方法可以用在一些特殊情况中,也就是某个对象的某个属性需要经过外界得到,比如说查询数据库等方式,这时候可以用到spring的该特性,只需要实现InitializingBean即可:@Component(“a”)publicclassAimplementsInitializingBean{privateBb;publicA(Bb){

  • docker打开2375监听端口「建议收藏」

    docker打开2375监听端口「建议收藏」由于在使用caliper时,需要用到Docker的监听端口,所以此步骤如下:1、修改/usr/lib/systemd/system/docker.service,在[service]的ExecStart,添加-Htcp://0.0.0.0:2375ExecStart=/usr/bin/dockerd-Htcp://0.0.0.0:2375-Hfd://–containerd=/run/containerd/containerd.sock2、刷新配置文件,重启docker

  • ubuntu命令chmod755

    ubuntu命令chmod755使用方式:在终端切换到文件目录输入chmod775hello.py这样就将hello.py变成了可执行文件当然作为python文件还需要再开头加上#!/usr/bin/envpython用于适应linux环境。chmod是Linux下设置文件权限的命令,后面的数字表示不同用户或用户组的权限。一般是三个数字:第一个数字表示文件所有者的权限第二个数字表示与文…

  • C语言中的void的理解

    C语言中的void的理解1初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说,并详述void及void指针类型的使用方法与技巧。2.void的含义void的字面意思是“无类型”,void*则为“无类型指针”,void*可以指向任何类型的数据。void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,

  • setScale,preScale 和 postScale 的区别

    setScale,preScale 和 postScale 的区别setScale,preScale和postScale的区别上面讲到,Matrix由3*3矩阵中9个值来决定。而我们对Matrix的所有设置,也是对这9个值的各种不同的改变,来达到我们想要的效果。下面是Matrix3*3的矩阵结构{MSCALE_X,MSKEW_X,MTRANS_X,MSKEW_Y,MSCALE_Y,MTRANS_Y,MPERSP_0,MPERSP_1,MPERSP_2}一、首先介绍Scale缩放的控制scale就是缩放,我们调用Matrix的setScale、preSc

    2022年10月20日
  • Shell循环读取文件

    Shell循环读取文件注:部分概念介绍来源于网络方法1:while循环中执行效率最高,最常用的方法。functionwhile_read_LINE(){whilereadLINEdoecho$LINEdone<$FILENAME}注释:这种方式在结束的时候需要执行文件,就好像是执行完的时候再把文件读进去一样。方法2:重定向法;管道法:cat$FILENAME|whilereadLINEfunctionWhile_read_LINE(){cat$FILENAME|..

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号