• Linux驱动——platform设备驱动实验



    1. 驱动的分离和分层

    1.1 驱动的分离

    在这里插入图片描述
        假设平台A、B、C都有MPU6050这个IIC接口的六轴传感器,那么驱动框架应该如上图所示。主机驱动是必须的,但设备驱动都是相同的,没必要每个平台都写一个驱动。
    在这里插入图片描述
        那么最好的做法就是每个平台的I2C控制器都提供一个统一的接口,每个设备也只提供一个驱动程序,每个设备通过统一的I2C接口驱动来访问,这样就大大方便了。
    在这里插入图片描述
        实际的I2C驱动设备肯定有很多种,所以实际的架构如上图所示。
        这就是驱动的分离,就是将主机驱动和设备驱动分离开来,相当于将设备信息从设备驱动中剥离开来,驱动使用标准方法获取到设备信息(比如从设备树中获取设备信息),根据获取到的信息来初始化设备。
        这就相当于驱动只负责驱动,设备只负责设备,想办法将两者匹配即可,这就是Linux中的总线(bus)、设备(device)、驱动(driver)模型。总线就是帮助设备和驱动之间进行牵线搭桥。
    在这里插入图片描述

    1.3 驱动的分层

        Linux下的驱动也是分层的,不同层会处理不同的内容。以input为例介绍一下驱动的分层。input子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸屏等,最底层的就是设备的原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给input核心层,input核心层会处理各种IO模型,并且提供file_operations操作集合,我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。

    2. 实验程序编写

    2.1 leddevice.c编写

        首先我们来编写platform设备程序。

    /*
     * platform设备结构体 
     */
    static struct platform_device leddevice = {
    	.name = "imx6ul-led",
    	.id = -1,
    	.dev = {
    		.release = &led_release,
    	},
    	.num_resources = ARRAY_SIZE(led_resources),
    	.resource = led_resources,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

        需要定义一个platform设备结构体,其中存放有设备的名称、id、资源、资源数量等相关设备信息。

    /*
     * @description	: 设备模块加载 
     * @param 		: 无
     * @return 		: 无
     */
    static int __init leddevice_init(void)
    {
    	return platform_device_register(&leddevice);
    }
    
    /*
     * @description	: 设备模块注销
     * @param 		: 无
     * @return 		: 无
     */
    static void __exit leddevice_exit(void)
    {
    	platform_device_unregister(&leddevice);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

        在init中完成设备的注册,在exit注销设备。

    2.2 leddriver.c编写

        接下来开始编写platform驱动文件,首先是定义一个设备结构体。

    struct leddev_dev{
    	dev_t devid;			/* 设备号	*/
    	struct cdev cdev;		/* cdev		*/
    	struct class *class;	/* 类 		*/
    	struct device *device;	/* 设备		*/
    	int major;				/* 主设备号	*/		
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

        还需要定义file_operations结构体,存放设备操作函数。

    /* 设备操作函数 */
    static struct file_operations led_fops = {
    	.owner = THIS_MODULE,
    	.open = led_open,
    	.write = led_write,
    };
    
    /*
     * @description		: 向设备写数据 
     * @param - filp 	: 设备文件,表示打开的文件描述符
     * @param - buf 	: 要写给设备写入的数据
     * @param - cnt 	: 要写入的数据长度
     * @param - offt 	: 相对于文件首地址的偏移
     * @return 			: 写入的字节数,如果为负值,表示写入失败
     */
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
    	int retvalue;
    	unsigned char databuf[1];
    	unsigned char ledstat;
    
    	retvalue = copy_from_user(databuf, buf, cnt);
    	if(retvalue < 0) {
    		return -EFAULT;
    	}
    
    	ledstat = databuf[0];		/* 获取状态值 */
    	if(ledstat == LEDON) {
    		led0_switch(LEDON);		/* 打开LED灯 */
    	}else if(ledstat == LEDOFF) {
    		led0_switch(LEDOFF);	/* 关闭LED灯 */
    	}
    	return 0;
    }
    
    /*
     * @description		: 打开设备
     * @param - inode 	: 传递给驱动的inode
     * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
     * 					  一般在open的时候将private_data指向设备结构体。
     * @return 			: 0 成功;其他 失败
     */
    static int led_open(struct inode *inode, struct file *filp)
    {
    	filp->private_data = &leddev; /* 设置私有数据  */
    	return 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
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

        紧接着,编写led_probe函数,当驱动与设备匹配后,就会调用函数,它的功能如下:

    • 获取设备资源;
    • 进行管脚映射,完成硬件方面的工作;
    • 注册字符设备驱动,对第一步中定义的设备结构体leddev中的成员进行初始化,如创建设备号、创建cdev结构体、创建类、创建设备。
    /*
     * @description		: flatform驱动的probe函数,当驱动与设备匹配以后此函数就会执行
     * @param - dev 	: platform设备
     * @return 			: 0,成功;其他负值,失败
     */
    static int led_probe(struct platform_device *dev)
    {	
    	int i = 0;
    	int ressize[5];
    	u32 val = 0;
    	struct resource *ledsource[5];
    
    	printk("led driver and device has matched!\r\n");
    	/* 1、获取资源 */
    	for (i = 0; i < 5; i++) {
    		ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
    		if (!ledsource[i]) {
    			dev_err(&dev->dev, "No MEM resource for always on\n");
    			return -ENXIO;
    		}
    		ressize[i] = resource_size(ledsource[i]);	
    	}	
    
    	/* 2、初始化LED */
    	/* 寄存器地址映射 */
     	IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);
    	SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);
      	SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);
    	GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);
    	GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);
    	
    	val = readl(IMX6U_CCM_CCGR1);
    	val &= ~(3 << 26);				/* 清除以前的设置 */
    	val |= (3 << 26);				/* 设置新值 */
    	writel(val, IMX6U_CCM_CCGR1);
    
    	/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */
    	writel(5, SW_MUX_GPIO1_IO03);
    	writel(0x10B0, SW_PAD_GPIO1_IO03);
    
    	/* 设置GPIO1_IO03为输出功能 */
    	val = readl(GPIO1_GDIR);
    	val &= ~(1 << 3);			/* 清除以前的设置 */
    	val |= (1 << 3);			/* 设置为输出 */
    	writel(val, GPIO1_GDIR);
    
    	/* 默认关闭LED1 */
    	val = readl(GPIO1_DR);
    	val |= (1 << 3) ;	
    	writel(val, GPIO1_DR);
    	
    	/* 注册字符设备驱动 */
    	/*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 */
    	leddev.cdev.owner = THIS_MODULE;
    	cdev_init(&leddev.cdev, &led_fops);
    	
    	/* 3、添加一个cdev */
    	cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
    
    	/* 4、创建类 */
    	leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
    	if (IS_ERR(leddev.class)) {
    		return PTR_ERR(leddev.class);
    	}
    
    	/* 5、创建设备 */
    	leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);
    	if (IS_ERR(leddev.device)) {
    		return PTR_ERR(leddev.device);
    	}
    
    	return 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
    • 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

        编写platform驱动的remove函数,它会取消地址映射,删除cedv结构体,注销设备号,完成设备的卸载、类的卸载。

    /*
     * @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行
     * @param - dev 	: platform设备
     * @return 			: 0,成功;其他负值,失败
     */
    static int led_remove(struct platform_device *dev)
    {
    	iounmap(IMX6U_CCM_CCGR1);
    	iounmap(SW_MUX_GPIO1_IO03);
    	iounmap(SW_PAD_GPIO1_IO03);
    	iounmap(GPIO1_DR);
    	iounmap(GPIO1_GDIR);
    
    	cdev_del(&leddev.cdev);/*  删除cdev */
    	unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */
    	device_destroy(leddev.class, leddev.devid);
    	class_destroy(leddev.class);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

        剩下的就是编写platform的驱动结构体和init以及exit函数。

    /* platform驱动结构体 */
    static struct platform_driver led_driver = {
    	.driver		= {
    		.name	= "imx6ul-led",			/* 驱动名字,用于和设备匹配 */
    	},
    	.probe		= led_probe,
    	.remove		= led_remove,
    };
    		
    /*
     * @description	: 驱动模块加载函数
     * @param 		: 无
     * @return 		: 无
     */
    static int __init leddriver_init(void)
    {
    	return platform_driver_register(&led_driver);
    }
    
    /*
     * @description	: 驱动模块卸载函数
     * @param 		: 无
     * @return 		: 无
     */
    static void __exit leddriver_exit(void)
    {
    	platform_driver_unregister(&led_driver);
    }
    
    • 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

    3. 测试

        测试APP的编写过程与前面LED驱动的APP基本一致,就不过多赘述。直接进行测试。

    depmod //第一次加载驱动的时候需要运行此命令
    modprobe leddevice.ko //加载设备模块
    modprobe leddriver.ko //加载驱动模块
    
    • 1
    • 2
    • 3

        在/sys/bus/platform/devices/目录下查询有没有设备文件,下面的情况说明设备加载成功。
    在这里插入图片描述
        紧接着在/sys/bus/platform/drivers/查询是否有驱动文件,有的话说明驱动模块加载成功。
    在这里插入图片描述
        加载成功后platfomr总线就会进行匹配,匹配成功如下图所示。
    在这里插入图片描述
        输入以下命令进行测试:

    ./ledApp /dev/platled 1 //打开 LED 灯
    ./ledApp /dev/platled 0 //关闭 LED 灯
    rmmod leddevice.ko
    rmmod leddriver.ko
    
    • 1
    • 2
    • 3
    • 4

    4. 总结

    leddevice.c:

    • 定义platform设备结构体,存放名字、id、设备资源等;
    • init函数完成设备的注册,exit函数完成设备的注销。

    leddrivers.c:

    • 定义一个led设备结构体,存放设备号、类、cdev结构体等;
    • 定义file_operations结构体存放操作函数如open、write;
    • 编写led_probe函数,匹配成功后,进行管脚的映射,设备资源的加载,并进行字符设备驱动的注册,就是对第一步中led设备结构体的初始化;
    • 编写led_remove函数,取消地址映射,进行字符设备的卸载,类的卸载;
    • 编写驱动结构体,并编写驱动模块卸载函数、驱动模块加载函数。
  • 相关阅读:
    【haproxy】haproxy七层代理
    ExoPlayer架构详解与源码分析(8)——Loader
    metersphere 创建场景, 自动动态变换参数值,实现接口自动化测试。
    SpringCloud
    实现像 creat-astro 一样在终端中实现动态输出内容
    springboot中自定义JavaBean返回的json对象属性名称大写变小写问题
    反爬虫技术和策略
    RTI-DDS在VS+QT使用记录
    MySQL---JDBC编程
    Set集合源码分析
  • 原文地址:https://blog.csdn.net/m0_56561130/article/details/124946861