• 手把手带你给你的Linux驱动程序加入platform结构体


    前言

    Linux 2.6的设备驱动模型中,关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

    一个现实的Linux设备和驱动通常都需要挂接在一种总线上,对于本身依附于PCI、USB、I2C、SPI等的设备而言,这自然不是问题,但是在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设等确不依附于此类总线。基于这一背景,Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动成为 platform_driver

    platform 总线

    platform 总线是虚拟总线,当使用这个虚拟总线是带来的好处主要有两点:

    • 使得设备被挂接在一个总线上,因此,符合 Linux 2.6 的设备模型。其结果是,配套的sysfs 结点、设备电源管理都成为可能。

    • 隔离BSP和驱动。在BSP中定义platform 设备和设备使用的资源、设备的具体配置信息,而在驱动中,只需要通过通用 API 去获取资源和数据,做到了板相关代码和驱动代码的分离,使得驱动具有更好的可扩展性和跨平台性。

    在网上看到一个非常有趣的比喻:总线是红娘,设备是男方,驱动是女方:

    • 红娘负责男方和女方的撮合
    • 男方或女方找到红娘,来登记,看有没有合适的姑娘 – 设备或驱动的注册
    • 红娘这个时候看看有没有合适的八字(二者的name字段) – match 函数进行匹配,看name是不是相同
    • 如果没有,就等着,知道匹配的了,就结婚,结完婚,男方向女方交代,我有多少存款,我的房子在哪,钱放哪等等(struct resource *resource)。
    • 女方去拿存款买菜啦,买衣服了等等(匹配成功后执行 int (*probe)(struct platform_device *) 匹配成功后执行的第一个函数),当然,如果男的跟小三跑了,女方也不会继续下去的。

    驱动的分隔与分离

    对于 Linux 这样一个成熟、庞大、复杂的操作系统,代码的重用性非常重要,否则的话就会在 Linux 内核中存在大量无意义的重复代码。

    尤其是驱动程序,因为驱动程序占用了 Linux内核代码量的大头,如果不对驱动程序加以管理,任由重复的代码肆意增加,那么用不了多久Linux 内核的文件数量就庞大到无法接受的地步。

    假如现在有三个平台 A、BC,这三个平台(这里的平台说的是 SOC)上都有 MPU6050 这个 I2C 接口的六轴传感器,按照我们写裸机 I2C 驱动的时候的思路,每个平台都有一个MPU6050的驱动,因此编写出来的最简单的驱动框架如图所示:
    在这里插入图片描述

    每种平台下都有一个主机驱动和设备驱动,主机驱动肯定是必须要的,毕竟不同的平台其 I2C 控制器不同。但是右侧的设备驱动就没必要每个平台都写一个,因为不管对于那个 SOC 来说, MPU6050 都是一样,通过 I2C 接口读取数据就行了,只需要一个MPU6050 的驱动程序即可。

    但是如果再多来几个设备呢?我们设备端的驱动又要重复编写好几次,这样会使我们的程序又变得臃肿了,这时候我们就在想如果我们可以让I2C控制器提供出来一个接口就好了,这样我们每个设备只需要也提供一个接口即可。

    按照上面的想法,每个设备的话也只提供一个驱动程序(设备驱动),每个设备通过统一的 I2C 接口驱动来访问,这样就可以大大简化驱动文件。
    在这里插入图片描述

    实际的 I2C 驱动设备肯定有很多种,不止 MPU6050这一个,那么实际的驱动架构如图所示:
    在这里插入图片描述
    这个就是驱动的分隔,也就是将主机驱动和设备驱动分隔开来,比如 I2C、 SPI 等等都会采用驱动分隔的方式来简化驱动的开发。

    在实际的驱动开发中,一般 I2C 主机控制器驱动已经由芯片厂家编写好了,而设备驱动一般也有设备器件的厂家编写好了,我们只需要提供设备信息即可,比如 I2C 设备的话提供设备连接到了哪个 I2C 接口上, I2C 的速度是多少等等。

    相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法去获取到设备信息(比如从设备树中获取到设备信息),然后根据获取到的设备信息来初始化设备。

    这样就相当于驱动只需要负责驱动,设备只需要设备,想办法将两者进行匹配即可。这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。
    在这里插入图片描述
    当我们向系统注册一个驱动的时候,总线就会在右侧的设备中查找,看看有没有与之匹配的设备,如果有的话就将两者联系起来。

    同样的,当向系统中注册一个设备的时候,总线就会在左侧的驱动中查找看有没有与之匹配的设备,有的话也联系起来。

    Linux 内核中大量的驱动程序都采用总线、驱动和设备模式,我们一会要重点讲解的 platform 驱动就是这一思想下的产物。

    编写platform结构体

    在使用设备树的时候 platform 驱动会通过 of_match_table 来保存兼容性值,也就是表明此驱动兼容哪些设备。所以 of_match_table 将会尤为重要,比如 platform 驱动中 platform_driver 就可以按照如下所示设置:

    static const struct of_device_id leds_of_match[] = {
    	{ .compatible = "atkalpha-gpioled" }, /* 兼容属性 */
    	{ /* Sentinel */ }
    };
    
    MODULE_DEVICE_TABLE(of, leds_of_match);
    
    static struct platform_driver leds_platform_driver = {
    	.driver = {
    		.name = "imx6ul-led",
    		.of_match_table = leds_of_match,
    	},
    	.probe = leds_probe,
    	.remove = leds_remove,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1~4 行: of_device_id 表,也就是驱动的兼容表,是一个数组,每个数组元素为 of_device_id类型。每个数组元素都是一个兼容属性,表示兼容的设备,一个驱动可以跟多个设备匹配。这里我们仅仅匹配了一个设备,那就是上面创建的 gpioled 这个设备。
    • 2 行: compatible 值为“atkalpha-gpioled”,驱动中的 compatible 属性和设备中的 compatible 属性相匹配。若匹配成功,则驱动中对应的 probe 函数就会执行。
    • 3 行:是一个空元素,在编写 of_device_id 的时候最后一个元素一定要为空!
    • 6 行:通过 MODULE_DEVICE_TABLE 声明一下 leds_of_match 这个设备匹配表。
    • 11 行:设置 platform_driver 中的 of_match_table 匹配表为上面创建的 leds_of_match,至此我们就设置好了 platform 驱动的匹配表了。

    编写驱动程序

    本次驱动程序的编写我们加入了设备树和platform结构体,具体代码如下:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define LEDDEV_CNT 1 /* 设备号长度 */
    #define LEDDEV_NAME "dtsplatled" /* 设备名字 */
    #define LEDOFF 0
    #define LEDON 1
    
    /* leddev 设备结构体 */
    struct leddev_dev{
    	dev_t devid; /* 设备号 */
    	struct cdev cdev; /* cdev */
    	struct class *class; /* 类 */
    	struct device *device; /* 设备 */
    	int major; /* 主设备号 */
    	struct device_node *node; /* LED 设备节点 */
    	int led0; /* LED 灯 GPIO 标号 */
    };
    
    struct leddev_dev leddev; /* led 设备 */
    
    static int led_open(struct inode *inode, struct file *filp)
    {
    	filp->private_data = &leddev; /* 设置私有数据 */
    	return 0;
    }
    
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
    	return -EFAULT;
    }
    
    /* 设备操作函数 */
    static struct file_operations led_fops = {
    	.owner = THIS_MODULE,
    	.open = led_open,
    	.write = led_write,
    };
    
    static int led_probe(struct platform_device *dev)
    {
    	printk("led driver and device was matched!\r\n");
    	/* 1、设置设备号 */
    	if (leddev.major) 
    	{
    		leddev.devid = MKDEV(leddev.major, 0);
    		register_chrdev_region(leddev.devid, LEDDEV_CNT,
    		LEDDEV_NAME);
    	} 
    
    	else 
    	{
    		alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,
    		LEDDEV_NAME);
    		leddev.major = MAJOR(leddev.devid);
    	}
    
    	/* 2、注册设备 */
    	cdev_init(&leddev.cdev, &led_fops);
    	
    	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
    	
    	/* 3、创建类 */
    	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    	if (IS_ERR(leddev.class)) 
    	{
    		return PTR_ERR(leddev.class);
    	}
    
    	/* 4、创建设备 */
    	leddev.device = device_create(leddev.class, NULL,leddev.devid,NULL, LEDDEV_NAME);
    	if (IS_ERR(leddev.device)) 
    	{
    		return PTR_ERR(leddev.device);
    	}
    	
    	/* 5、初始化 IO */
    	leddev.node = of_find_node_by_path("/gpioled");
    	if (leddev.node == NULL)
    	{
    		printk("gpioled node nost find!\r\n");
    		return -EINVAL;
    	}
    }	
    static int led_remove(struct platform_device *dev)
    {
    	gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭 LED */
    	cdev_del(&leddev.cdev); /* 删除 cdev */
    	unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
    	device_destroy(leddev.class, leddev.devid);
    	class_destroy(leddev.class);
    	return 0;
    }
    
    
    /* 匹配列表 */
    static const struct of_device_id led_of_match[] = 
    {
    	{ .compatible = "atkalpha-gpioled" },
    	{ /* Sentinel */ }
    };
    	
    /* platform 驱动结构体 */
    static struct platform_driver led_driver = 
    {
    	.driver = {
    		.name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
    		.of_match_table = led_of_match, /* 设备树匹配表 */
    	},
    	.probe = led_probe,
    	.remove = led_remove,
    };
    
    static int __init leddriver_init(void)
    {
    	return platform_driver_register(&led_driver);
    }
    
    static void __exit leddriver_exit(void)
    {
    	platform_driver_unregister(&led_driver);
    }
    
    module_init(leddriver_init);
    module_exit(leddriver_exit);
    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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146

    说明: 以上代码只保留了重要代码,功能性代码已经删除,这就是加入设备树和platform结构体的字符驱动设备的驱动代码。

    参考文献

    • 正点原子 I.MX6U Linux驱动开发指南
    👇点击下方公众号卡片获取资料👇
  • 相关阅读:
    企业信息化建设,花小钱导入开源ERP不香吗?
    NNDL 实验八 网络优化与正则化(2)批大小的调整
    Http实战之无状态协议、keep-alive分析
    OPENAI 开发者大会_观后感_231107
    数据结构与算法(Java篇)笔记--选择排序
    【Java】JDK11特性概览
    docker入门级使用
    R数据分析:扫盲贴,什么是多重插补
    Additional Features for Scripting
    els 兼容性DC、传递图片到窗口
  • 原文地址:https://blog.csdn.net/qq_45172832/article/details/126049866