• 【linux kernel】linux的platform设备驱动框架分析



    🔺【linux内核系列文章】

    👉对一些文章内容进行了勘误,本系列文章长期不定时更新,希望能分享出优质的文章!


    一、简介

    相关文件;

    • /include/linux/platform_device.h
    • /drivers/base/platform.c

    在linux设备驱动中,有许多没有特定总线的外设驱动,在实际开发中,又需要使用到总线、驱动和设备模型这三个概念,故而linux提供了platform这个虚拟总线,挂接在platform总线上的驱动称为platform驱动,由struct platform_driver描述,挂接在platorm总线上的设备称为platform设备,由struct platform_device描述。

    在linux内核的驱动源码中,可以看见很多基于platform驱动框架的驱动案例实现。

    二、platform总线

    在linux内核中,使用struct bus_type描述一个总线,为了抽象出platform这个虚拟总线,其定义如下(/drivers/base/platform.c):

    struct bus_type platform_bus_type = {
    	.name		= "platform",
    	.dev_groups	= platform_dev_groups,
    	.match		= platform_match,
    	.uevent		= platform_uevent,
    	.pm		= &platform_dev_pm_ops,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    platform总线的注册由platform_bus_init()完成:

    int __init platform_bus_init(void)
    {
    	int error;
    
    	early_platform_cleanup();
    
    	error = device_register(&platform_bus);
    	if (error)
    		return error;
    	error =  bus_register(&platform_bus_type);
    	if (error)
    		device_unregister(&platform_bus);
    	of_platform_register_reconfig_notifier();
    	return error;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    该函数在linux内核启动过程中,在driver_init()中被调用,从而向linux内核注册了platform总线。

    三、platform设备和驱动的匹配过程

    在定义platform总线的时候就定了该总线下设备和驱动的具体匹配过程,由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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    从上述代码可知,platform设备和驱动的匹配分为了四种方式处理:

    • 1、基于设备树的匹配方式。

    struct device_driver结构中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,
    在设备树中的每个设备节点的compatible属性会和of_match_table表中的所有成员比较,查看是否存在相同的条目,如果存在则表示设备和此驱动匹配,设备和驱动匹配成功以后probe函数就会执行(这个过程是由linux设备驱动模型中的总线去完成)。

    • 2、ACPI的匹配方式。

    • 3、id_table 匹配。

    每个struct platform_driver有一个id_table成员变量,用于保存很多id信息,这些id信息存放着这个platform驱动所支持的驱动类型。

    • 4、比较name字段

    如果第三种匹配方式的id_table不存在,就直接比较驱动和设备的name字段是否相等,如果相等则匹配成功;反之匹配不成功。

    一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般
    用的最多的还是第四种,也就是直接比较驱动和设备的name字段,因为这种方式最简单了。

    四、platrom驱动和platform设备

    前文已经提到:挂接在platform总线上的驱动称为platform驱动,由struct platform_driver描述,挂接在platorm总线上的设备称为platform设备,由struct platform_device描述。要想开发基于platform设备驱动驱动框架的驱动程序,一定离不开这两个数据结构。首先来看看platform驱动的描述者struct platform_driver,该结构定义如下(/include/linux/platform_device.h):

    struct platform_driver {
    	int (*probe)(struct platform_device *);
    	int (*remove)(struct platform_device *);
    	void (*shutdown)(struct platform_device *);
    	int (*suspend)(struct platform_device *, pm_message_t state);
    	int (*resume)(struct platform_device *);
    	struct device_driver driver;
    	const struct platform_device_id *id_table;
    	bool prevent_deferred_probe;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • probe:当驱动与设备匹配成功以后.probe函数就会执行,这是一个非常重要的函数,一般驱动的提供者都会设计该函数。
    • remove:当platform驱动移除的时候,.remove指向的函数将执行。
    • shutdown、suspend和resume:与电源管理相关的函数。
    • driver:为device_driver结构体变量,相当于C++中的基类,提供了最基础的驱动框架。plaform_driver继承了这个基类,然后在此基础上又添加了一些特有的成员变量。
    • id_table:描述platform设备的id_table表,platform总线匹配驱动和设备的时候会使用。
    • prevent_deferred_probe:布尔类型变量(内部参数),用于防止驱动程序请求延迟probe,以避免进一步的徒劳的探测尝试。

    再看看platform设备的描述者struct platform_device,定义如下(/include/linux/platform_device.h):

    struct platform_device {
    	const char	*name;
    	int		id;
    	bool		id_auto;
    	struct device	dev;
    	u32		num_resources;
    	struct resource	*resource;
    
    	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;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • name :name表示设备名字,该参数要和所使用的platform驱动的name字段相同,否则设备就无法匹配到对应的驱动。
    • id:设备id。
    • dev:linux内核面向对象的具体体现,用于描述platform_device的基类。
    • num_resources:表示资源的数量。
    • resource:表示资源,也就是设备的信息,比如外设寄存器等。Linux内核使用struct resource结构体表示资源。
    • id_entry:platform设备对应的id匹配表实例,在platform总线匹配驱动和设备的时候会使用到。

    五、platform驱动设计

    platform驱动设计的总体思路分为两种:

    • (1)使用【struct platform_device + struct platform_driver】的方式实现。

    在这种实现方式中,需要实现描述设备信息的struct platform_device结构,并需要使用platform_device来描述具体的设备信息,然后使用platform_device_register()函数将设备信息注册到 Linux 内核中;如果不再使用platform了,可以通过platform_device_unregister()函数注销相应的platform设备。

    这种方式在不支持设备树的linux内核中使用!

    • (2)使用【struct platform_driver + 设备树】的方式来实现。

    在编写 platform 驱动的时候,首先定义一个struct platform_driver结构体变量,然后实现结构体中的各个成员变量,重点是实现匹配方法以及probe函数。当驱动和设备匹配成功以后.probe函数就会执行,具体的驱动程序在 probe 函数里面编写。当定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register()函数向Linux内核注册一个platform驱动。

    注意,如果linux内核支持设备树,就可以不需要再使用struct platform_device来描述设备,直接使用设备树去描述设备的信息。当然,如果
    一定要用struct platform_device来描述设备信息也是可以的。
    基于新版的linux内核的platform驱动的开发,通常是通过设备树来描述设备信息,我们只需要实现对应的platform驱动即可。

    六、代码示例

    本小节基于【struct platform_driver + 设备树】给出一个基本的platform驱动的设计结构。

    首先使用设备树描述设备的信息:

    	debug_device_node {
    		compatible = "iriczhao_debug";
    		pinctrl-0 = <&pinctrl_usdhc2_8bit>;
    		pinctrl-1 = <&pinctrl_usdhc2_8bit_100mhz>;
    		pinctrl-2 = <&pinctrl_usdhc2_8bit_200mhz>;
    		bus-width = <8>;
    		non-removable;
    		status = "okay";
    	};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    上述代码描述了一个名为debug_device_node的设备节点,给出了compatible属性值。

    platform驱动设计:

    #include 
    #include 
    #include 
    #include 
    
    #include 
    
    #include 
    
    #include 
    
    static int platform_demo_probe(struct platform_device *dev)
    {   printk("\r\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r\n");
        printk("do platform_demo_probe\r\n");
        printk("\r\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\r\n");
    
        return 0;
    }
    
    static int platform_demo_remove(struct platform_device *dev)
    {
        printk("do platform_demo_remove\r\n");
        return 0;
    }
    
    static const struct of_device_id platform_demo_id[] = {
        { .compatible = "iriczhao_debug" },
        { /* Sentinel */ }
    };
    
    MODULE_DEVICE_TABLE(of, platform_demo_id);
    
    static struct platform_driver platform_demo_driver = {
        .probe = platform_demo_probe,
        .remove = platform_demo_remove,
        .driver = {
            .name = "dd",
            .of_match_table = platform_demo_id,
        }
    };
    
    
    static int __init platform_demo_init(void)
    {
        printk("do platform_demo_init\r\n");
    
        return platform_driver_register(&platform_demo_driver);
    }
    
    static void __exit platform_demo_exit(void)
    {
        printk("do platform_demo_exit\r\n");
        
        platform_driver_unregister(&platform_demo_driver);
    }
    
    module_init(platform_demo_init);
    module_exit(platform_demo_exit);
    
    MODULE_AUTHOR("IRIC");
    MODULE_LICENSE("GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    以模块方式构建上述代码,运行后结果如下:


    从上述结果可知:
    platform驱动和对应的设备匹配成功,且.probe指向的函数得以执行,当模块退出时,platform驱动将被移除,这时候.remove指向的函数得以执行。结果符合程序预期效果!

  • 相关阅读:
    Java面试题大全(整理版)1000+面试题附答案详解最全面看完稳了
    SpringMVC之自定义注解
    C# Onnx DIS高精度图像二类分割
    微信公众号关注/取消关注事件推送开发记录
    数据库连接池(C3P0、Druid的使用)java
    JavaScript · 9:数据类型转换 & 隐式转换
    2021年SpringBoot面试题30道
    互联网扭蛋机小程序:打破传统扭蛋机的局限,提高销量
    聚类性能度量
    SQL常见问题之如何分析周活跃率?(每周的活跃率)——步骤具体且明确
  • 原文地址:https://blog.csdn.net/iriczhao/article/details/133845137