目录
前言
一、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.o
build: kernel_modules
kernel_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知识共享】,后续精彩内容及时收看了解。