• Linux驱动开发(二)---驱动与设备的分离设计


    前言

    《Linux驱动开发(一)—环境搭建与hello world》
    继续宣传一下韦老师的视频

    70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

    在这里插入图片描述

    分离设计

    参考韦东山老师的代码。
    那开始的话,我们写的程序,如果要驱动一个LED,可能会写成下面的样子,

    
    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/slab.h>
    #include <linux/init.h>
    #include <linux/fs.h>
    #include <linux/delay.h>
    #include <linux/poll.h>
    #include <linux/mutex.h>
    #include <linux/wait.h>
    #include <linux/uaccess.h>
    #include <asm/io.h>
    
    static int major;
    static struct class *led_class;
    
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
    {
    	char val;
    	/* copy_from_user : get data from app */
    	copy_from_user(&val, buf, 1);
    
    	/* to set gpio register: out 1/0 */
    	if (val)
    	{
    		/* set gpio to let led on */
    	}
    	else
    	{
    
    		/* set gpio to let led off */
    	}
    	return 1;
    }
    
    static int led_open(struct inode *inode, struct file *filp)
    {
    	/* enable gpio
    	 * configure pin as gpio
    	 * configure gpio as output 
    	 */
    	return 0;
    }
    
    static struct file_operations led_fops = {
    	.owner		= THIS_MODULE,
    	.write		= led_write,
    	.open		= led_open,
    };
    
    /* 入口函数 */
    static int __init led_init(void)
    {
    	major = register_chrdev(0, "leddev", &led_fops);
    	led_class = class_create(THIS_MODULE, "myled");
    	device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */
    	return 0;
    }
    
    static void __exit led_exit(void)
    {
    	device_destroy(led_class, MKDEV(major, 0));
    	class_destroy(led_class);
    	unregister_chrdev(major, "leddev");
    }
    
    module_init(led_init);
    module_exit(led_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

    这么写没任何问题,干净独立,不牵扯任何东西
    在这里插入图片描述
    不过造成驱动每个LED,都需要开发一个模块,在led_write中操作指定的GPIO。
    由此我们就衍生出了一种仿照单片机HAL库的做法,将模块成两部分:
    一部分就是类似去HAL库的部分,我们称之为driver部分
    一部分就如同我们调用的部分,我们称之为device部分

    在这里插入图片描述

    看一个韦老师的例子:
    driver部分

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/errno.h>
    #include <linux/miscdevice.h>
    #include <linux/kernel.h>
    #include <linux/major.h>
    #include <linux/mutex.h>
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    #include <linux/stat.h>
    #include <linux/init.h>
    #include <linux/device.h>
    #include <linux/tty.h>
    #include <linux/kmod.h>
    #include <linux/gfp.h>
    #include <linux/platform_device.h>
    
    
    static int hello_probe(struct platform_device *pdev)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        return 0;
    }
    
    static int hello_remove(struct platform_device *pdev)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        return 0;
    }
    
    
    static struct platform_driver hello_driver = 
    {
    	.probe      = hello_probe,
    	.remove     = hello_remove,
    	.driver     = 
    	{
    		.name   = "100ask_led",
    	},
    };
    
    static int __init hello_drv_init(void)
    {
        int err;
        
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        err = platform_driver_register(&hello_driver); 
        
        return err;
    }
    
    static void __exit hello_drv_exit(void)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        platform_driver_unregister(&hello_driver);
    }
    
    module_init(hello_drv_init);
    module_exit(hello_drv_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

    deivce部分

    #include <linux/module.h>
    #include <linux/fs.h>
    #include <linux/errno.h>
    #include <linux/miscdevice.h>
    #include <linux/kernel.h>
    #include <linux/major.h>
    #include <linux/mutex.h>
    #include <linux/proc_fs.h>
    #include <linux/seq_file.h>
    #include <linux/stat.h>
    #include <linux/init.h>
    #include <linux/device.h>
    #include <linux/tty.h>
    #include <linux/kmod.h>
    #include <linux/gfp.h>
    #include <linux/platform_device.h>
    
    
    static void hello_dev_release(struct device *dev)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
    }
    
    
    static struct platform_device hello_dev = 
    {
    	.name = "100ask_led",
    	.dev = 
    	{
    		.release = hello_dev_release,
    	},
    };
    
    static int __init hello_dev_init(void)
    {
        int err;
        
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        err = platform_device_register(&hello_dev);   
        
        return err;
    }
    
    static void __exit hello_dev_exit(void)
    {
    	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
        platform_device_unregister(&hello_dev);
    }
    
    module_init(hello_dev_init);
    module_exit(hello_dev_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

    每一部分编译为一个ko文件。二者没有加载先后的要求,一旦都加载成功,两部分功能都会挂载到一个统一的虚拟总线上,并且通过匹配规则关联起来,最终会执行到下面driver结构中的probe函数

    static struct platform_driver hello_driver = 
    {
    	.probe      = hello_probe,
    	.remove     = hello_remove,
    	.driver     = 
    	{
    		.name   = "100ask_led",
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    probe函数如下

    static int hello_probe(struct platform_device *pdev)
    
    • 1

    它的参数,正好就是设备结构

    static struct platform_device hello_dev = 
    {
    	.name = "100ask_led",
    	.dev = 
    	{
    		.release = hello_dev_release,
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们就把这个结构,理解为传入参数,再简单一点,我们就可以认为


    driver部分就是执行函数,device部分就是传入参数
    driver部分根据device部分的参数,对不同的资源进行控制

    这就是分离的思想。
    在这里插入图片描述

    参数传递

    参数通过platform_device传递到probe函数中,

    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

    其中的resource用来传递具体参数,内部包含了各种类型的数据,字符串类型的,在ioprot.h中详细列举了flags,desc的定义,用来传递不同类型的参数。

    struct resource {
    	resource_size_t start;
    	resource_size_t end;
    	const char *name;
    	unsigned long flags;
    	unsigned long desc;
    	struct resource *parent, *sibling, *child;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们可以通过定义的方式进行传递

    
    static struct resource resources[] = {
        {
                .start = (3<<8)|(1),			
                .flags = IORESOURCE_IRQ,
        },
    };
    static struct platform_device led_dev = {
            .name = "100ask_led",
    		.num_resources = ARRAY_SIZE(resources),
    		.resource = resources,
            .dev = {
                    .release = led_dev_release,
             },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后我们在probe函数中就可以通过下面的方法,得到传递过来的参数

    	struct resource *res;
    	res = platform_get_resource(pdev, IORESOURCE_IRQ, i++);
    	if (!res)
    		return -EINVAL;
    	/* 记录引脚 */
    	minor = g_ledcnt;
    	leds_desc[minor].pin = res->start;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    整体的执行过程大概如下图所示
    在这里插入图片描述
    之后就和前一章的内容一样,在创建从设备之后,用户侧打开从设备,读写从设备的操作就会继续关联上struct file_operations中定义的读写操作了。
    在这里插入图片描述

    驱动与设备的匹配

    驱动driver的结构如下

    static struct platform_driver hello_driver = 
    {
    	.probe      = hello_probe,
    	.remove     = hello_remove,
    	.driver     = 
    	{
    		.name   = "100ask_led",
    	},
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    那么如何让设备去加载这个驱动呢,最简单的就是名字一致的匹配

    static struct platform_device hello_dev = 
    {
    	.name = "100ask_led",
    	.dev = 
    	{
    		.release = hello_dev_release,
    	},
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二者都是100ask_led,自然可以匹配上,1对1。
    如果要是多个设备用一个驱动,那么就不能这么写了,因为如果device中.name一样,就无法重复加载了。
    解决办法有几种:

    override

    在device中通过.driver_override 参数指定强制匹配的驱动

    static struct platform_device led_dev = {
            .name = "100ask_led1",
            .dev = {
                    .release = led_dev_release,
             },
    		.driver_override = "100ask_led",
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    就可以强制匹配一个驱动100ask_led。这就是属于设备点名要这个驱动,别的都不好使
    在这里插入图片描述

    id_table

    可以在驱动中指定一个设备列表

    static const struct platform_device_id led_id_table[] = {
    	{"hellodevice1",   1},
    	{"hellodevice2", 2},
    	{"hellodevice3", 3},
    	{ },
    };
    
    static struct platform_driver led_driver = {
        .probe      = led_probe,
        .remove     = led_remove,
        .driver     = {
            .name   = "100ask_led",
        },
        .id_table = led_id_table,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    然后在device中的名字,使用上面id_table中的名字,就可以匹配上了

    static struct platform_device led_dev = {
            .name = "hellodevice1",
            .dev = {
                    .release = led_dev_release,
             },
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这就是驱动支持这几个设备,别的也不好使
    在这里插入图片描述

    两种做法就好像是:

    • 设备主动去调用某个驱动,驱动不认识设备,设备主动找驱动
    • 驱动官方认证设备,驱动就认可这个设备。

    在这里插入图片描述
    想了解具体过程,可以去分析一下下面两个函数

    platform_driver_register
    platform_device_register
    
    • 1
    • 2

    分析的时候要注意分清主次,分析关键部分就行,否则,很容易生气,因为看不懂
    在这里插入图片描述

    结束语

    一晃疫情就三年了。
    在这里插入图片描述
    也不知道什么时候能放开手脚出去转转,夏天都会想去海边玩玩,现在各个景区也在开始放开政策,吸引外地游客过去,不过目前居住地还在不包含在范围内,哎……
    在这里插入图片描述
    自己种的西红柿开始红了,大城市还很难买到自然变红的西红柿,味道确实不太一样。
    在这里插入图片描述
    所以生活还是要多一些耐心,等待也许会换来值得。

  • 相关阅读:
    performance_schema
    在 SQL Server 中使用 Try Catch 处理异常
    戏说领域驱动设计(十)——杂谈
    说一说MVCC多版本并发控制器?
    求加django电影推荐系统中的推荐功能
    k8s的安装部署,详细过程展示(保姆级安装教程)
    使用Python进行机器学习:从基础到实战
    【SQL教程|01】SQL简介——什么是SQL
    结构优化软件SolidThinking Inspire的自学攻略
    Qt扫盲-QFontInfo理论
  • 原文地址:https://blog.csdn.net/baidu_19348579/article/details/125498564