T10 字符设备驱动补充
1 linux字符驱动设备开发流程
1.1 流程
- 封装自己的字符设备结构体类
- 实现
struct file_operations
结构体 - 模块的入口函数
- 实例化自己的字符设备结构体类(实例化对象),使用
kmalloc
- 初始化通用字符设备(
cdev_init
)/* 功能:初始化字符设备 参数: @cdev:cdev结构体 @fops:操作字符设备的函数接口地址 返回值:无 */ void cdev_init(struct cdev *cdev, const struct file_operation *fops)
- 申请设备号(
register_chrdev_region
)/* 功能:注册设备号 参数: @from:设备号 @count:注册的设备个数 @name:设备名字 返回值:注册成功返回0,失败返回错误码(负数) */ int register_chrdev_region(dev_t from, unsigned count, const char *name)
- 添加字符设备到操作系统(
cdev_add
)/* 功能:添加一个字符设备驱动到操作系统 参数: @p:cdev结构体地址 @dev:设备号 @count:次设备号个数 返回值:注册成功返回0,失败返回错误码(负数) */ int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- 模块的出口函数
- 释放给字符设备类分配的空间(
kfree
)- 移除注册的字符设备(
cdev_del
)/* 功能:从系统中删除一个字符设备驱动 参数: @p:cdev结构体地址 返回值:无 */ void cdev_del(struct cdev *p)
- 释放申请的设备号(
unregister_chrdev_region
)/* 功能:释放申请的设备号 参数: @from:设备号 @count:次设备号个数 返回值:无 */ void unregister_chrdev_region(dev_t from, unsigned count)
1.2 代码
- Makefile文件
obj-m := cdev_test.o
KDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
- 源文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
dev_t dev_no; //device major number
char buffer[BUF_SIZR]; //my private memory
struct cdev cdev; //char device class
};
/* step1: malloc memory for char device */
static struct demo_cdev demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
/* step2: init for demo char device*/
cdev_init(&demo_dev.cdev, &demo_operation);
/* step3: apply a major device number for char device */
demo_dev.dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
/* step4: register char device number */
ret = register_chrdev_region(demo_dev.dev_no, 1, "demo_chrdev");
if (ret < 0) {
printk(KERN_ERR "faild to register device number\n");
return ret;
}
/* step4: add char device to OS */
ret = cdev_add(&demo_dev.cdev, demo_dev.dev_no, 1);
if (ret < 0) {
printk(KERN_ERR "cdev add failed\n");
/* failed to add cdev, we should unregister cdev before return*/
unregister_chrdev_region(demo_dev.dev_no, 1);
return ret;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
cdev_del(&demo_dev.cdev);
unregister_chrdev_region(demo_dev.dev_no, 1);
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
1.3 装载驱动
- 创建设备节点(命令方式)
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo mknod /dev/cdev_test c 168 0
- 查看节点是否创建成功
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -l /dev/cdev_test
crw-r--r-- 1 root root 168, 0 3月 7 15:53 /dev/cdev_test
- 装载驱动
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test.ko
- 查看信息
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[24936.071335] Enter: demo_init
[24936.071337] demo char device init done
- 测试file_operation功能
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[24936.071335] Enter: demo_init
[24936.071337] demo char device init done
[25542.812769] Enter: demo_open
[25542.812777] Enter: demo_read
[25542.812784] Enter: demo_release
1.4 改进
- 不足
- 不足1:上述驱动中,在注册设备号的时候,我们是直接指定一个特定的主设备号,如果系统中已经存在这个设备号,那么将会导致驱动注册失败
- 改进1:当指定的设备号注册失败的时候,我们可以采用动态注册,让linux系统给我们分配一个未使用过的设备号
/* 功能:让系统自动分配一个设备号,之后再注册设备 参数: @dev:用来获取系统分配的设备号 @baseminor:第一个次设备号 @count:次设备号的个数 @name:设备名称 返回值:成功返回0,失败返回错误码 */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) /* 功能:创建一个类 参数: @owner:一般写为THIS_MODULE @name:字符串名字,自己写 返回值:class结构体类型指针,可通过IS_ERR(cls)判断是否创建失败,成功返回0,失败返回非0(可通过PTR_ERR(cls)来获取失败的返回码) */ struct class *class_create(owner, name); /* 功能:创建一个设备文件 参数: @class:class_create创建的class结构体类型指针 @parent:父亲,一般直接写NULL @devt:设备号,通过MDKEV()宏创建 @drvdata:私有数据,一般写NULL @fmt:可变参数,一般为字符串,表示设备节点名字 返回值:device类型指针 */ struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...);
不足2:每次装载完驱动之后都需要手动去创建设备节点,麻烦
改进2:当我们注册完设备之后,让linux系统自动在/dev目录下创建设备节点
不足3:字符设备读写函数接口没有实际数据流向
改进3:使用xxx_read函数将设备里面的内容拷贝到用户空间;使用xxx_write函数将用户空间的数据写入设备内部buffer
/* 功能:从用户空间拷贝数据到内核空间(大量数据) 参数: @to:内核空间地址 @from:用户空间地址 @n:数据大小 返回值:成功返回0,失败返回未被拷贝的字节数 注意:此处__user是一个空的宏,主要用来显示的告诉程序员它修饰的指针变量存放的是用户空间的地址,这是一个好习惯 */ int copy_from_user(void *to, const void __user *from, int n) /* 功能:从内核空间拷贝数据到用户空间(大量数据) 参数: @to:用户空间地址 @from:内核空间地址 @n:数据大小 返回值:成功返回0,失败返回未被拷贝的字节数 */ int copy_to_user(void __user *to, const void *from, int n) /* 宏功能:检查用户空间传递到内核空间的地址是否合法 参数: @type:VERIFY_READ:用户空间的地址是否可读 VERIFY_WRITE:用户空间的地址是否可写 @addr:用户空间传递的地址 @szie:检查的空间大小 返回值:成功返回非0,失败返回0 */ access_ok(type, addr, size) /* 宏功能:拷贝单个数据到用户空间 参数: @x:需要拷贝到用户空间的地址 @ptr:用户空间的地址 返回值:成功返回0,失败返回-EFAULT 注意:此宏使用前需要使用access_ok宏检查ptr是否有效 */ put_user(x, ptr) / __put_user(x, ptr) /* 宏功能:拷贝单个数据到内核空间 参数: @x:需要拷贝到用户空间的地址 @ptr:用户空间的地址 返回值:成功返回0,失败返回-EFAULT 注意:此宏使用前需要使用access_ok宏检查ptr是否有效 */ get_user(x, ptr) / __get_user(x, ptr)
- 不足4:设备的空间是在data段分配的,假如设备内部buff过大,将占用data段很大一部分内存
- 改进4:分配堆内存给设备
/* 功能:分配内存 参数: @size:内存大小 @flags:常用GFP_KERNEL 返回值:成功返回分配的内存首地址,失败返回NULL */ static __always_inline void *kmalloc(size_t size, gfp_t flags)
- 内核读写函数API
/*
功能:对应用户空间的read,将内核空间数据发送给用户空间
参数:
@filp:file结构体指针,里面包含文件描述符fd
@buff:用户空间地址,用来保存内核发送过去的数据
@count:用户空间要读的数据长度
@fops:用户空间读取数据的当前偏移量
注意:关于fops的一些解释
假设用户空间调用read(fd, buf, 10)函数,此时内核空间一共给用户空间发送1024个字节,但是用户空间此刻只读 取了10个字节,故此刻偏移量为10,那么下次用户再次读取10字节的时候就需要从11的偏移量开始。
1.所以在编写内核的read函数时候,我们需要先判断当前偏移量是否已经超过了内核发送给用户数据的最大值,此处不 能大于等于1024。
2.因为用户是按10字节为单位进行读取,那么读到最后肯定不是10的倍数,假设此时内核还剩余4字节数据没被用户读 取,那么此刻用户再读10字节的时候内核就应该只返回4字节数据
*/
ssize_t xxx_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
/*
功能:
*/
ssize_t xxx_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
- 有关于
struct file
结构体描述,该结构体描述如下,其中有一个变量void *private_data
,这个私有数据可以用来保存我们的私有变量,所以我们可以将我们创建的驱动保存到这个变量下面,当使用xxx_read
,xxx_write
等函数的时候就可以通过private来访问我们驱动内部的数据
struct file {
...
/* needed for tty driver, and maybe others */
void *private_data;
...
}
/*用法示例*/
struct demo_cdev {
dev_t dev_no; //device major number
char *buffer; //my private memory
struct cdev cdev; //char device class
struct class *cls;
struct device *device;
};
struct demo_cdev *demo_dev;
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
}
- 改进后代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
dev_t dev_no; //device major number
char *buffer; //my private memory
struct cdev cdev; //char device class
struct class *cls;
struct device *device;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
};
static int __init demo_init(void)
{
int ret = -1;
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "Enter: %s\n", __func__);
/* step2: init for demo char device*/
cdev_init(&demo_dev->cdev, &demo_operation);
/* step3: apply a major device number for char device */
demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
/* step4: register char device number */
ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev");
if (ret < 0) {
/* if we faild to register c_dev number,we can let OS to register cdev number */
ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev");
if (ret < 0) {
printk(KERN_ERR "faild to register device number\n");
goto ERROR_CHRDEV_REGION;
}
}
/* step5: add char device to OS */
ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1);
if (ret < 0) {
printk(KERN_ERR "cdev add failed\n");
/* failed to add cdev, we should unregister cdev before return*/
goto ERROR_CDEV_ADD;
}
/* create a cdev class in /sys/class/cdev_test/ */
demo_dev->cls = class_create(THIS_MODULE, "cdev_test");
if (IS_ERR(demo_dev->cls)) {
ret = PTR_ERR(demo_dev->cls);
goto ERR_CLASS_CREATE;
}
/* create a cdev device in /sys/class/cdev_test/cdev_mem */
demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem");
if (IS_ERR(demo_dev->device)) {
ret = PTR_ERR(demo_dev->device);
goto ERROR_DEVICE_CREATE;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_DEVICE_CREATE:
class_destroy(demo_dev->cls);
ERR_CLASS_CREATE:
cdev_del(&demo_dev->cdev);
ERROR_CDEV_ADD:
unregister_chrdev_region(demo_dev->dev_no, 1);
ERROR_CHRDEV_REGION:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
cdev_del(&demo_dev->cdev);
unregister_chrdev_region(demo_dev->dev_no, 1);
kfree(demo_dev);
demo_dev = NULL;
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 调试
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls
cdev_test_v1.c cdev_test_v3.c cdev_test_v3.mod cdev_test_v3.mod.o Makefile Module.symvers
cdev_test_v2.c cdev_test_v3.ko cdev_test_v3.mod.c cdev_test_v3.o modules.order
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v3.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/cdev_mem
crw------- 1 root root 168, 0 3月 8 15:48 /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq" -> /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem
hq -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem
hanqi -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
2 ioctl
2.1 定义
ioctl
是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。
2.2 相关函数
- 用户层
#include <sys/ioctl.h>
/*
功能:实现IO控制
参数:
@fd:文件描述符
@cmd:交互协议,设备驱动将根据 cmd 执行对应操作
@...:可变参数 arg,依赖 cmd 指定长度以及类型
返回值:执行成功时返回0,失败则返回-1并设置全局变量errorno值
*/
int ioctl(int fd, int cmd, ...) ;
- 内核层:当通过ioctl调用xxx_ioctl时候有3种情况
- 不传递参数给xxx_ioctl
- 传递参数给xxx_ioctl,并希望能将指令写入设备(如设置串口波特率)
- 调用xxx_ioctl函数获取硬件信息(如获取当前串口波特率)
/*
功能:unlocked_ioctl,在无大内核锁(BKL)的情况下调用;compat_ioctl,compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用
参数:
@file:file结构体,其中保存了文件描述符
@cmd:用户空间传递给内核的指令
@arg:可变参数,可供用户空间与内核空间之间传参
*/
long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
long (*compat_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
2.3 协议
- ioctl函数中的cmd不应该随意设置,需要做到大统一,好linux为我们提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段
| 设备类型 | 序列号 | 方向 |数据尺寸|
|----------|--------|-------|-----------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|-------|-----------|
- 设备类型:代表一类设备,一般用一个字母或者一个8bit的数字表示。在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
- 序列号:表示这个设备的第几个命令
- 方向:这个命令用来操作的方式,如:只读,只写,读写或者其他; _IOC_NONE(无数据)、_IOC_READ(读数据)、_IOC_WRITE(写数据)、_IOC_READ | _IOC_WRITE(读写数据)
- 数据尺寸:即用户传递的数据大小
- 貌似挺麻烦,不过linux系统已经为我们封装好系列宏,故我们只需要调用宏来设计命令而不用自己一个位一个位地去设计,只需要指定设备类型、命令序列、数据类型三个字段即可
/*生成命令*/
/*
@type:设备类型
@nr:命令序号
@size:用户传递的数据类型(int,char,struct name...)
*/
#define _IO(type,nr) //没有数据传递的命令
#define _IOR(type,nr,size) //从驱动中读取数据
#define _IOW(type,nr,size) //向驱动中写入数据
#define _IOWR(type,nr,size) //双向传输数据
-----------------------------------------------------------------------------------------------------
_IOC_NONE //值为0,表示无数据传输
IOC_READ //值为1,表示从驱动中读取数据
_IOC_WRITE //值为2,向驱动中写入数据
IOC_READ | _IOC_WRITE //双向数据传输
-----------------------------------------------------------------------------------------------------
/*解析命令*/
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) //从命令中提取数据流向
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) //从命令中提取设备类型
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) ///从命令中提取序列号
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) ///从命令中提取数据大小
- 命令生成示例
#define DEV_FIFO_TYPE 'k'
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE, 0x10)
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE, 0x11, int)
#define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE, 0x12, int)
2.4 代码
- 驱动代码头文件
cdev_test_v4.h
#ifndef __CDEV_TEST_V4_H__
#define __CDEV_TEST_V4_H__
#include <linux/ioctl.h>
#define CDEV_TEST_V4_TYPE 'h'
#define CDEV_TEST_V4_CLEAN _IO(CDEV_TEST_V4_TYPE, 0x10)
#define CDEV_TEST_V4_SETVAL _IOW(CDEV_TEST_V4_TYPE, 0x11, int)
#define CDEV_TEST_V4_GETVAL _IOR(CDEV_TEST_V4_TYPE, 0x12, int)
#endif
- 驱动代码源文件
cdev_test_v4.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
dev_t dev_no; //device major number
char *buffer; //my private memory
int value;
struct cdev cdev; //char device class
struct class *cls;
struct device *device;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static int __init demo_init(void)
{
int ret = -1;
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "Enter: %s\n", __func__);
/* step2: init for demo char device*/
cdev_init(&demo_dev->cdev, &demo_operation);
/* step3: apply a major device number for char device */
demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM);
/* step4: register char device number */
ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev");
if (ret < 0) {
/* if we faild to register c_dev number,we can let OS to register cdev number */
ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev");
if (ret < 0) {
printk(KERN_ERR "faild to register device number\n");
goto ERROR_CHRDEV_REGION;
}
}
/* step5: add char device to OS */
ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1);
if (ret < 0) {
printk(KERN_ERR "cdev add failed\n");
/* failed to add cdev, we should unregister cdev before return*/
goto ERROR_CDEV_ADD;
}
/* create a cdev class in /sys/class/cdev_test/ */
demo_dev->cls = class_create(THIS_MODULE, "cdev_test");
if (IS_ERR(demo_dev->cls)) {
ret = PTR_ERR(demo_dev->cls);
goto ERR_CLASS_CREATE;
}
/* create a cdev device in /sys/class/cdev_test/cdev_mem */
demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem");
if (IS_ERR(demo_dev->device)) {
ret = PTR_ERR(demo_dev->device);
goto ERROR_DEVICE_CREATE;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_DEVICE_CREATE:
class_destroy(demo_dev->cls);
ERR_CLASS_CREATE:
cdev_del(&demo_dev->cdev);
ERROR_CDEV_ADD:
unregister_chrdev_region(demo_dev->dev_no, 1);
ERROR_CHRDEV_REGION:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
device_destroy(demo_dev->cls, MKDEV(MAJOR_NUM, MINOR_NUM));
class_destroy(demo_dev->cls);
cdev_del(&demo_dev->cdev);
unregister_chrdev_region(demo_dev->dev_no, 1);
kferr(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
- 用户层测试源文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "cdev_test_v4.h"
#define DEVICE_PATH "/dev/cdev_mem"
int main(void)
{
int fd;
int ret;
int n;
int value;
char buf[10] = "hanqi";
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
printf("failed to open\n");
return -1;
}
n = write(fd, buf, sizeof(buf));
if (n < 0) {
printf("failed to write\n");
close(fd);
return -1;
}
printf("write to cdev %d bytes\n", n);
ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value);
if (ret < 0) {
printf("failed to ioctl\n");
close(fd);
return -1;
}
printf("value = %d\n", value);
ret = ioctl(fd, CDEV_TEST_V4_SETVAL, 520);
if (ret < 0) {
printf("failed to ioctl\n");
close(fd);
return -1;
}
ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value);
if (ret < 0) {
printf("failed to ioctl\n");
close(fd);
return -1;
}
printf("value = %d\n", value);
close(fd);
return 0;
}
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v4.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ gcc test_cdev4.h -o test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
write to cdev 10 bytes
value = 1
value = 520
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem
hanqi
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 1970.376549] Enter: demo_init
[ 1970.376612] demo char device init done
[ 3117.086360] Enter: demo_open
[ 3117.086368] Enter: demo_write
[ 3117.086515] cmd: getval
[ 3117.086521] cmd: setval
[ 3117.086522] cmd: getval
[ 3117.086527] Enter: demo_release
[ 3263.306387] Enter: demo_open
[ 3263.306403] Enter: demo_read
[ 3263.306523] Enter: demo_read
[ 3263.306536] Enter: demo_release
3 Misc设备驱动
上述过程创建一个字符设备驱动流程比较复杂,为此引入了Misc设备驱动,简化字符设备驱动的创建流程。其主设备号是固定的为10。
- Misc设备驱动结构体
struct miscdevice {
int minor; //次设备号,若为MISC_DYNAMIC_MINOR就自动分配
const char *name; //设备名
const struct file_operations *fops; //设备文件操作结构体
struct list_head list; //misc_list链表头
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
3.1 API
- 注册/卸载Misc设备驱动
/* 在加载模块的时候会自动创建设备文件,其主设备号为10 */
int misc_register(struct miscdevice* misc);
/* 在卸载模块的时候自动删除设备文件 */
int misc_dergister(struct miscdevice* misc);
- 结构体初始化
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
}
3.2 代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include "cdev_test_v4.h"
#define BUF_SIZR 1024
#define MAJOR_NUM 168
#define MINOR_NUM 0
struct demo_cdev {
char *buffer; //my private memory
int value;
struct miscdevice *mdev;
};
/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;
static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
int ret = -1;
int read_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
/* determine whether user has finished reading data */
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
read_bytes = BUF_SIZR - *pos;
} else {
read_bytes = size;
}
ret = copy_to_user(buf, kbuf, read_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += read_bytes;
return read_bytes;
}
static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
int ret;
int write_bytes;
struct demo_cdev *demo = file->private_data;
char *kbuf = demo->buffer + *pos;
printk(KERN_INFO "Enter: %s\n", __func__);
if (*pos >= BUF_SIZR) {
return 0;
}
if (size > (BUF_SIZR - *pos)) {
write_bytes = BUF_SIZR - *pos;
} else {
write_bytes = size;
}
ret = copy_from_user(kbuf, buf, write_bytes);
if (ret != 0) {
return -EFAULT;
}
*pos += write_bytes;
return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
file->private_data = (void *)demo_dev;
return 0;
}
static int demo_release(struct inode *node, struct file *file)
{
printk(KERN_INFO "Enter: %s\n", __func__);
return 0;
}
static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
struct demo_cdev *demo = file->private_data;
switch(cmd) {
case CDEV_TEST_V4_CLEAN:
memset(demo->buffer, 0x00, BUF_SIZR);
printk(KERN_INFO "cmd: clean\n");
break;
case CDEV_TEST_V4_GETVAL:
put_user(demo->value, (int *)arg);
printk(KERN_INFO "cmd: getval\n");
break;
case CDEV_TEST_V4_SETVAL:
demo->value = (int)arg;
printk(KERN_INFO "cmd: setval\n");
break;
default:
break;
}
return (long)ret;
}
static struct file_operations demo_operation= {
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
.unlocked_ioctl = demo_ioctl,
};
static struct miscdevice misc_struct = {
.minor = MISC_DYNAMIC_MINOR,
.name = "misc_dev",
.fops = &demo_operation,
};
static int __init demo_init(void)
{
int ret = -1;
printk(KERN_INFO "Enter: %s\n", __func__);
demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
if (!demo_dev) {
printk(KERN_ERR "failed to malloc demo_dev\n");
ret = -ENOMEM;
goto ERROR_MALLOC_DEMODEV;
}
demo_dev->value = 1;
demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
if (!demo_dev->buffer) {
printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
ret = -ENOMEM;
goto ERROR_MALLOC_BUFFER;
}
memset(demo_dev->buffer, 0x00, BUF_SIZR);
demo_dev->mdev = &misc_struct;
ret = misc_register(demo_dev->mdev);
if (ret < 0) {
printk(KERN_ERR "failed to register misc\n");
goto ERROR_MISC;
}
printk(KERN_INFO "demo char device init done\n");
return 0;
ERROR_MISC:
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
kfree(demo_dev);
demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
return ret;
}
static void __exit demo_exit(void)
{
printk(KERN_INFO "Enter: %s\n", __func__);
misc_deregister(demo_dev->mdev);
kfree(demo_dev->buffer);
demo_dev->buffer = NULL;
kfree(demo_dev);
demo_dev = NULL;
printk(KERN_INFO "demo char device exit done\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
3.3 测试
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod misc_dev.ko
[sudo] hq 的密码:
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/misc_dev
crw------- 1 root root 10, 56 3月 10 12:12 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq666" -> /dev/misc_dev
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev
hq666 -
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test
write to cdev 10 bytes
value = 1
value = 520
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg
[ 4161.949170] Enter: demo_init
[ 4161.951759] demo char device init done
[ 4213.359167] Enter: demo_open
[ 4213.359182] Enter: demo_write
[ 4213.359187] Enter: demo_release
[ 4220.892937] Enter: demo_open
[ 4220.892945] Enter: demo_read
[ 4220.892974] Enter: demo_read
[ 4220.892983] Enter: demo_release
[ 4394.397552] Enter: demo_open
[ 4394.397556] Enter: demo_write
[ 4394.397648] cmd: getval
[ 4394.397651] cmd: setval
[ 4394.397652] cmd: getval
[ 4394.397656] Enter: demo_release
- 我们可以进入
/sys/class
文件夹下面查看misc设备,其中就包含了我们刚才创建的设备
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cd /sys
hq@hq-virtual-machine:/sys$ ls
block bus class dev devices firmware fs hypervisor kernel module power
hq@hq-virtual-machine:/sys$ cd class/
hq@hq-virtual-machine:/sys/class$ ls
ata_device devcoredump extcon input mmc_host ppdev regulator sound tty
ata_link devfreq firmware intel_scu_ipc nd ppp remoteproc spi_host vc
ata_port devfreq-event gpio iommu net pps rfkill spi_master vfio
backlight dma graphics leds pci_bus printer rtc spi_slave virtio-ports
bdi dma_heap hidraw lirc pci_epc ptp scsi_device spi_transport vtconsole
block dmi hwmon mdio_bus phy pwm scsi_disk thermal wakeup
bsg drm i2c-adapter mem powercap rapidio_port scsi_generic tpm watchdog
dax drm_dp_aux_dev i2c-dev misc power_supply rc scsi_host tpmrm
hq@hq-virtual-machine:/sys/class$ cd misc/
hq@hq-virtual-machine:/sys/class/misc$ ls
agpgart cpu_dma_latency ecryptfs hpet lightnvm mcelog psaux snapshot udmabuf vfio vmci
autofs device-mapper fuse hw_random loop-control misc_dev rfkill tun uinput vga_arbiter vsock
hq@hq-virtual-machine:/sys/class/misc$ ls -l
总用量 0
lrwxrwxrwx 1 root root 0 3月 10 12:23 agpgart -> ../../devices/virtual/misc/agpgart
lrwxrwxrwx 1 root root 0 3月 10 12:23 autofs -> ../../devices/virtual/misc/autofs
lrwxrwxrwx 1 root root 0 3月 10 12:23 cpu_dma_latency -> ../../devices/virtual/misc/cpu_dma_latency
lrwxrwxrwx 1 root root 0 3月 10 12:23 device-mapper -> ../../devices/virtual/misc/device-mapper
lrwxrwxrwx 1 root root 0 3月 10 12:23 ecryptfs -> ../../devices/virtual/misc/ecryptfs
lrwxrwxrwx 1 root root 0 3月 10 12:23 fuse -> ../../devices/virtual/misc/fuse
lrwxrwxrwx 1 root root 0 3月 10 12:23 hpet -> ../../devices/virtual/misc/hpet
lrwxrwxrwx 1 root root 0 3月 10 12:23 hw_random -> ../../devices/virtual/misc/hw_random
lrwxrwxrwx 1 root root 0 3月 10 12:23 lightnvm -> ../../devices/virtual/misc/lightnvm
lrwxrwxrwx 1 root root 0 3月 10 12:23 loop-control -> ../../devices/virtual/misc/loop-control
lrwxrwxrwx 1 root root 0 3月 10 12:23 mcelog -> ../../devices/virtual/misc/mcelog
lrwxrwxrwx 1 root root 0 3月 10 12:23 misc_dev -> ../../devices/virtual/misc/misc_dev
lrwxrwxrwx 1 root root 0 3月 10 12:23 psaux -> ../../devices/virtual/misc/psaux
lrwxrwxrwx 1 root root 0 3月 10 12:23 rfkill -> ../../devices/virtual/misc/rfkill
lrwxrwxrwx 1 root root 0 3月 10 12:23 snapshot -> ../../devices/virtual/misc/snapshot
lrwxrwxrwx 1 root root 0 3月 10 12:23 tun -> ../../devices/virtual/misc/tun
lrwxrwxrwx 1 root root 0 3月 10 12:23 udmabuf -> ../../devices/virtual/misc/udmabuf
lrwxrwxrwx 1 root root 0 3月 10 12:23 uinput -> ../../devices/virtual/misc/uinput
lrwxrwxrwx 1 root root 0 3月 10 12:23 vfio -> ../../devices/virtual/misc/vfio
lrwxrwxrwx 1 root root 0 3月 10 12:23 vga_arbiter -> ../../devices/virtual/misc/vga_arbiter
lrwxrwxrwx 1 root root 0 3月 10 12:23 vmci -> ../../devices/virtual/misc/vmci
lrwxrwxrwx 1 root root 0 3月 10 12:23 vsock -> ../../devices/virtual/misc/vsock
hq@hq-virtual-machine:/sys/class/misc$ cd misc_dev
hq@hq-virtual-machine:/sys/class/misc/misc_dev$ ls
dev power subsystem uevent
hq@hq-virtual-machine:/sys/class/misc/misc_dev$ cat uevent
MAJOR=10
MINOR=56
DEVNAME=misc_dev
hq@hq-virtual-machine:/sys/class/misc/misc_dev$