目录
前言
一、Linux驱动的分离与分层
二、开发环境
三、驱动程序编写
3.2 platform 驱动模块程序
3.3 测试app程序
四、运行测试
4.1 编译
4.2 运行测试
前言
前面几章编写的设备驱动都非常的简单,都是对 IO进行最简单的读写操作。像 I2C、SPI、 LCD 等这些复杂外设的驱动就不能这么去写了, Linux 系统要考虑到驱动的可重用性,因此提出了驱动的分离与分层这样的软件思路,在这个思路下诞生了我们将来最常打交道的platform 设备驱动,也叫做平台设备驱动。本章我们就来学习一下 Linux 下的驱动分离与分层,以及 platform 框架下的设备驱动该如何编写。
一、Linux驱动的分离与分层
Linux驱动的分离与分层在这就不再进行详细讲解了,如果还不了解的,可以去查看之前的文章《Linux驱动之软件架构思想》,链接如下:
Linux驱动之软件架构思想
二、开发环境
-
CPU:IMX6ULL
-
内核版本:Linux-5.19
三、驱动程序编写
本次我们需要编写一个驱动模块和一个设备模块,其中驱动模块是 platform 驱动程序,设备模块是 platform 的设备信息。当这两个模块都加载成功以后就会匹配成功,然后 platform驱动模块中的 probe 函数就会执行, probe 函数中就是传统的字符设备驱动那一套。
3.1 platform 设备模块程序
在 leddevice.c 中输入如下所示内容:
/************************************************************* Copyright © toto Co., Ltd. 1998-2029. All rights reserved.* Description: * Version: 1.0* Autor: toto* Date: Do not edit* LastEditors: Seven* LastEditTime: Do not edit
*************************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>#define LEDDEV_CNT 1 /* 设备号数量 */
#define LEDDEV_NAME "platform_led" /* 设备名字 */
#define LED_ON 1
#define LED_OFF 0/* 映射后的寄存器虚拟地址指针 */
static void __iomem *VM_CCM_CCGR1;
static void __iomem *VM_SW_MUX_GPIO1_IO03;
static void __iomem *VM_SW_PAD_GPIO1_IO03;
static void __iomem *VM_GPIO1_DR;
static void __iomem *VM_GPIO1_GDIR;/* led_dev 设备结构体 */
struct led_dev {dev_t devid; /* 设备号 */struct cdev cdev; /* cdev */struct class *class; /* 类 */struct device *device; /* 设备 */int major; /* 主设备号 */
};struct led_dev leddev; /* led 设备 *//** @Brief led 打开、关闭接口* @Param sta:1打开,0关闭* @Note NOne* @RetVal NOne*/
void led_switch(u8 sta)
{u32 val = 0;if (sta == LED_ON) {val = readl(VM_GPIO1_DR);val &= ~(1 << 3);writel(val, VM_GPIO1_DR);} else if (sta == LED_OFF) {val |= (1 << 3);writel(val, VM_GPIO1_DR);}
}/** @Brief 打开设备* @Param inode:传递给驱动的inode* @Param filp:设备文件* @Note NOne* @RetVal NOne*/
static int led_open(struct inode *inode, struct file *filp)
{/* 设置私有数据 */filp->private_data = &leddev;return 0;
}/** @Brief 向设备写数据* @Param filp:设备文件* @Param buf:要写入设备的数据* @Param cnt:要写入的数据长度* @Param offt:相对于文件首地址的偏移* @Note NOne* @RetVal 写入的字节数,如果为负值,表示写入失败*/
static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{int ret;unsigned char databuf[1];unsigned char ledstat;ret = copy_from_user(databuf, buf, cnt);if (ret < 0) {return -EFAULT;}ledstat = databuf[0];led_switch(ledstat);return 0;
}/* 设备操作函数 */
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,
};/** @Brief platform 驱动的probe函数,当驱动与设备匹配后此函数就会执行* @Param platform 设备* @Note NOne* @RetVal 0成功,其他值失败*/
static int led_probe(struct platform_device *dev)
{int i = 0;int ressize[5];u32 val = 0;struct resource *ledsource[5];printk(KERN_INFO "led driver and device has matched\n");for (i = 0; i < 5; i++) {ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i);if (!ledsource[i]) {dev_err(&dev->dev, "NO MEM\n");return -ENXIO;}ressize[i] = resource_size(ledsource[i]);}/* 初始化 led *//* 寄存器映射 */VM_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);VM_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);VM_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);VM_GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);VM_GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);val = readl(VM_CCM_CCGR1);val |= (1 << 3);writel(val, VM_CCM_CCGR1);/* 设置GPIO1_IO03 复用功能 */writel(5, VM_SW_MUX_GPIO1_IO03);writel(0x10B0, VM_SW_PAD_GPIO1_IO03);/* 默认关闭 led */val = readl(VM_GPIO1_GDIR);val |= (1 << 3);writel(val, VM_GPIO1_GDIR);/* 注册字符设备驱动 *//* 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;
}/** @Brief 移除 platform 驱动函数* @Param None* @Note NOne* @RetVal NOne*/
static int led_remove(struct platform_device *dev)
{iounmap(VM_CCM_CCGR1);iounmap(VM_SW_MUX_GPIO1_IO03);iounmap(VM_SW_PAD_GPIO1_IO03);iounmap(VM_GPIO1_DR);iounmap(VM_GPIO1_GDIR);cdev_del(&leddev.cdev);unregister_chrdev_region(leddev.devid, LEDDEV_CNT);device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;
}/* platform 驱动结构体 */
static struct platform_driver led_driver = {.driver = {.name = "imx6ull-led", /* 驱动名字,用于匹配设备 */},.probe = led_probe,.remove = led_remove,
};/** @Brief 驱动模块加载函数* @Param None* @Note NOne* @RetVal NOne*/
static int __init leddriver_init(void)
{return platform_driver_register(&led_driver);
}/** @Brief 驱动模块卸载函数* @Param None* @Note NOne* @RetVal NOne*/
static void __exit leddriver_exit(void)
{platform_driver_unregister(&led_driver);
}module_init(leddriver_init);
module_exit(leddriver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
-
第 56~82 行, led_resources 数组,也就是设备资源,描述了 LED 所要使用到的寄存器信息,也就是 IORESOURCE_MEM 资源。
-
第 88~96, platform 设备结构体变量 leddevice,这里要注意 name 字段为“imx6ul-led”,所以稍后编写 platform 驱动中的 name 字段也要为“imx6ul-led”,否则设备和驱动匹配失败。
-
第 103~106 行,设备模块加载函数,在此函数里面通过 platform_device_register 向 Linux 内核注册 leddevice 这个 platform 设备。
-
第 113~116 行,设备模块卸载函数,在此函数里面通过 platform_device_unregister 从 Linux内核中删除掉 leddevice 这个 platform 设备。
3.2 platform 驱动模块程序
在 leddriver.c 里面输入如下内容:
/************************************************************ Copyright © toto Co., Ltd. 1998-2029. All rights reserved.* Description: * Version: 1.0* Autor: toto* Date: Do not edit* LastEditors: Seven* LastEditTime: Do not edit
***********************************************************/
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/irq.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>#define CCM_CCGR1_REGBASE (0x020C406C)
#define SW_MUX_GPIO1_IO03_REGBASE (0x020E0068)
#define SW_PAD_GPIO1_IO03_REGBASE (0x020E02F4)
#define GPIO1_DR_REGBASE (0x0209C000)
#define GPIO1_GDIR_REGBASE (0x0209C004)
#define REGISTER_LENGTH 4/** @Brief 释放platform设备模块的函数* @Param dev:要释放的设备* @Note NOne* @RetVal NOne*/
static void led_release(struct device *dev)
{printk("led device released\n");
}/*
*设备资源信息,LED使用的所有寄存器
*/
static struct resource led_resource[] = {[0] = {.start = CCM_CCGR1_REGBASE,.end = (CCM_CCGR1_REGBASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[1] = {.start = SW_MUX_GPIO1_IO03_REGBASE,.end = (SW_MUX_GPIO1_IO03_REGBASE + REGISTER_LENGTH -1),.flags = IORESOURCE_MEM,},[2] = {.start = SW_PAD_GPIO1_IO03_REGBASE,.end = (SW_PAD_GPIO1_IO03_REGBASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[3] = {.start = GPIO1_DR_REGBASE,.end = (GPIO1_DR_REGBASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},[4] = {.start = GPIO1_GDIR_REGBASE,.end = (GPIO1_GDIR_REGBASE + REGISTER_LENGTH -1),.flags = IORESOURCE_MEM,},
};/** platform设备结构体*/
static struct platform_device leddev = {.name = "imx6ull-led",.id = -1,.dev = {.release = led_release,},.num_resources = ARRAY_SIZE(led_resource),.resource = led_resource,
};/** @Brief 设备模块加载* @Param None* @Note NOne* @RetVal NOne*/
static int __init leddev_init(void)
{return platform_device_register(&leddev);
}/** @Brief 设备模块注销* @Param None* @Note NOne* @RetVal NOne*/
static void __exit leddev_exit(void)
{platform_device_unregister(&leddev);
}module_init(leddev_init);
module_exit(leddev_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("toto");
-
第 34~122 行,传统的字符设备驱动。
-
第 130~206 行, probe 函数,当设备和驱动匹配以后此函数就会执行,当匹配成功以后会在终端上输出“led driver and device has matched!”这样语句。在 probe 函数里面初始化 LED、注册字符设备驱动。也就是将原来在驱动加载函数里面做的工作全部放到 probe 函数里面完成。
-
第 213~226 行, remobe 函数,当卸载 platform 驱动的时候此函数就会执行。在此函数里面释放内存、注销字符设备等。也就是将原来驱动卸载函数里面的工作全部都放到 remove 函数中完成。
-
第 229~235 行, platform_driver 驱动结构体,注意 name 字段为"imx6ul-led",和我们在leddevice.c 文件里面设置的设备 name 字段一致。
-
第 242~245 行,驱动模块加载函数,在此函数里面通过 platform_driver_register 向 Linux 内核注册 led_driver 驱动。
-
第 252~255 行,驱动模块卸载函数,在此函数里面通过 platform_driver_unregister 从 Linux内核卸载 led_driver 驱动。
3.3 测试app程序
在 platform_app.c 里面输入如下内容:
/********************************************* Copyright © toto Co., Ltd. 1998-2029. All rights reserved.* Description: * Version: 1.0* Autor: toto* Date: Do not edit* LastEditors: Seven* LastEditTime: Do not edit
********************************************/
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDON 1
#define LEDOFF 0 /** @Brief* @Param None* @Note NOne* @RetVal NOne*/
int main(int argc, char *argv[])
{int fd, retval;char *filename;unsigned char databuf[1];if (argc != 3) {printf("Error argc par cnt\n");return -1;}filename = argv[1];fd = open(filename, O_RDWR);if (fd < 0) {printf("file %s open failed\n", filename);return -1;}databuf[0] = atoi(argv[2]);retval = write(fd, databuf, sizeof(databuf));if (retval < 0) {printf("led control failed\n");close(fd);return -1;}close(fd);return 0;
}
四、运行测试
4.1 编译
1.编译驱动程序 编写 Makefile 文件,Makefile 内容如下所示:
KERNELDIR := /home/toto/workspace/linux/linux-5.19
CURRENT_PATH := $(shell pwd)
obj-m := leddriver.o
obj-m += leddevice.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译命令:
make -j8
编译成功以后就会生成一个名为“leddevice.ko leddriver.ko”的驱动模块文件。
2. 编译测试app 编译命令:
arm-linux-gnueabihf-gcc platform_app.c -o platform_app
编译成功以后就会生成 platform_app 这个应用程序。
4.2 运行测试
开发板上电,将leddevice.ko、leddriver.ko 和 key_app 这三个文件拷贝到 /lib/modules/5.19.0-g794a2f7be62d-dirty/ 目录中,输入如下命令加载 leddevice.ko 设备模块和 leddriver.ko 这个驱动模块:
insmod leddevice.ko
insmod leddriver.ko
根文件系统中/sys/bus/platform/目录下保存着当前板子 platform 总线下的设备和驱动,其中devices 子目录为 platform 设备, drivers 子目录为 plartofm 驱动。查看/sys/bus/platform/devices/目录,看看我们的设备是否存在,我们在 leddevice.c 中设置 leddevice(platform_device 类型)的name 字段为“imx6ull-led”,也就是设备名字为 imx6ull-led,因此肯定在/sys/bus/platform/devices/目录下存在一个名字“imx6ull-led”的文件,否则说明我们的设备模块加载失败,结果如下所示:
同理,查看/sys/bus/platform/drivers/目录,看一下驱动是否存在,我们在 leddriver.c 中设置led_driver (platform_driver 类型)的 name 字段为“imx6ull-led”,因此会在/sys/bus/platform/drivers/目录下存在名为“imx6ull-led”这个文件,结果如下所示:
驱动模块和设备模块加载成功以后 platform 总线就会进行匹配,当驱动和设备匹配成功以后就会输出如下所示一行语句:
/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod leddevice.ko
/lib/modules/5.19.0-g794a2f7be62d-dirty # insmod leddriver.ko
[ 45.657342] led driver and device has matched
驱动和设备匹配成功以后就可以测试 LED 灯驱动了,输入如下命令打开 LED 灯:
./platform_app /dev/platform_led 1
在输入如下命令关闭 LED 灯:
./platform_app /dev/platform_led 0
卸载驱动命令如下:
rmmod leddevice.ko
rmmod leddriver.ko
关于更多嵌入式C语言、FreeRTOS、RT-Thread、Linux应用编程、linux驱动等相关知识,关注公众号【嵌入式Linux知识共享】,后续精彩内容及时收看了解。