上篇文章介绍了字符设备的开发模板,但那是一种旧版本的驱动开发模式,设备驱动需要手动分配设备号再使用 register_chrdev进行注册,加载成功以后还需要手动使用mknod命令创建设备节点,比较麻烦。
目前Linux内核推荐的新字符设备驱动API函数,使得驱动的使用更加自动化,本篇就来一起研究下。
使用register_chrdev函数注册字符设备,需要指定一个设备号,这就造成:
需要事先确定好哪些主设备号没有使用
会将一个主设备号下的所有次设备号都使用掉,比如主设备号为200,那么 0~1048575(2^20-1)这个区间的次设备号就全部都被占用了
回顾上一篇的操作,先是加载驱动:
加载完,还有手动使用mknod指令来手动创建该设备节点,并且指定驱动程序中写死的设备号:
本篇,就要使用一种新的字符驱动编写方式,实现设备号的自动分配,省去mknod指令操作。
使用设备号的时候向Linux内核申请,需要几个就申请几个,由Linux内核分配设备可以使用的设备号。
使用如下函数来申请设备号(该函数在上篇提到过):
/*
* dev:保存申请到的设备号
* baseminor:次设备号起始地址,一般baseminor为0 (次设备号以baseminor为起始地址地址开始递)
* count:要申请的设备号数量
* name:设备名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
/*
* from:要申请的起始设备号
* count:要申请的设备号数量
* name:设备名字
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注销字符设备之后要释放设备号,不管是通过alloc_chrdev_region函数的动态分配还是register_chrdev_region函数手动指定的设备号,统一使用(和上篇使用的一样)的释放函数:
/*
* from:要释放的设备号
* count:表示从from开始,要释放的设备号数量
*/
void unregister_chrdev_region(dev_t from, unsigned count)
新字符设备驱动下,设备号分配示例代码如下:
int major; /*主设备号*/
int minor; /*次设备号*/
dev_t devid; /*设备号*/
?
/*定义了主设备号*/
if (major)
{
devid = MKDEV(major, 0); /*大部分驱动次设备号都选择0*/
register_chrdev_region(devid, 1, "test");
}
/*没有定义设备号*/
else
{
alloc_chrdev_region(&devid, 0, 1, "test"); /*申请设备号*/
major = MAJOR(devid); /*获取分配号的主设备号*/
minor = MINOR(devid); /*获取分配号的次设备号*/
}
在Linux中使用cdev结构体表示一个字符设备,其定义在include/linux/cdev.h文件中:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; /*文件操作函数集合*/
struct list_head list;
dev_t dev; /*设备号*/
unsigned int count;
};
定义好cdev变量以后就要使用cdev_init函数对其进行初始化:
/*
* cdev:要初始化的cdev结构体变量
* fops:字符设备文件操作函数集合
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
该函数的使用示例如下:
/*要初始化的cdev结构体*/
struct cdev testcdev;
?
/* 设备操作函数 */
static struct file_operations test_fops = {
.owner = THIS_MODULE,
/* 其他具体的初始项 */
};
?
testcdev.owner = THIS_MODULE;
?
/* 初始化cdev*/
cdev_init(&testcdev, &test_fops);
该函数用于向Linux系统添加字符设备,即cdev结构体变量:
/*
* cdev:要初始化的cdev结构体变量
* dev:字符设备所使用的设备号
* count:要添加的设备数量
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
卸载驱动的时候要使用cdev_del函数从Linux内核中删除字符设备:
/*
* p:要删除的字符设备
*/
void cdev_del(struct cdev *p)
上篇的Linux驱动实验中,在使用modprobe加载驱动程序以后还需要使用“mknod”命令手动创建设备节点,比较麻烦,这里就来研究一下如何实现自动创建设备节点。
在Linux下通过udev来实现设备文件的自动创建与删除。使用busybox构建根文件系统的时候,busybox会创建一个udev的简化版本mdev。
所以,在嵌入式开发中使用mdev来实现设备节点文件的自动创建与删除。Linux系统中的热插拔事件也由mdev 管理,在/etc/init.d/rcS 文件中如下语句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在cdev_add函数后面添 加自动创建设备节点相关代码。
首先要创建一个class类,其实是个结构体,定义在include/linux/device.h里面。class_create是类创建函数(宏定义):
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
?
struct class *__class_create(struct module *owner,
const char *name,
struct lock_class_key *key)
卸载驱动程序的时候需要使用函数为class_destroy删除掉类:
/*
* cls:要删除的类
*/
void class_destroy(struct class *cls);
创建好类以后还不能实现自动创建设备节点,还需要在这个类下创建一个设备。使用device_create函数创建设备:
/*
* class:设备要创建哪个类下面
* parent:父设备, 一般为 NULL
* devt:设备号
* drvdata:设备可能会使用的一些数据,一般为 NULL
* fmt:设备名字
*/
struct device *device_create(struct clas *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
参数最后的...表示这在是一个可变参数的函数。
每个硬件设备都有一些属性, 比如主设备号(dev_t),类(class)、设备(device)、开关状态(state)等等,在编写驱动的时候你可以将这些属性全部写成变量的形式:
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
可以将所有属性信封装到结构体中, 并在编写驱动open函数的时候将其作为私有数据添加到设备文件中:
/*设备结构体*/
struct test_dev{
dev_t devid; /*设备号*/
struct cdev cdev; /*cdev*/
struct class *class; /*类*/
struct device *device; /*设备*/
int major; /*主设备号*/
int minor; /*次设备号*/
};
?
struct test_dev testdev;
?
/*open函数*/
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /*设置私有数据*/
return 0;
}
【i.MX6ULL】驱动开发系列文章汇总入口:
【i.MX6ULL】驱动开发——by DDZZ669 - ARM技术 - 电子工程世界-论坛 (eeworld.com.cn)
分目录:
【i.MX6ULL】驱动开发6——Pinctrl子系统与GPIO子系统点亮LED