本节学习的内容主要为基于LinuxAPI函数的字符设备驱动的开发,还包括在驱动模块加载的时候如何自动创建设备节点。总结的脑图如下:
一、驱动原理
1.分配和释放设备号
申请设备号函数:
cpp
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
注册设备号函数:
cpp
int reister_chrdev_region(dev_t from, unsigned count, const char *name)//rrom起始设备号 count数量 name设备名
设备释放函数:
cpp
void unregister_chrdev_region(dev_t from, unsigned count)
设备号分配实例:
cpp
1 int major; /* 主设备号 */
2 int minor; /* 次设备号 */
3 dev_t devid; /* 设备号 */
4 5
if (major) { /* 定义了主设备号 */
6 devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */
7 register_chrdev_region(devid, 1, "test");
8 } else { /* 没有定义设备号 */
9 alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
10 major = MAJOR(devid); /* 获取分配号的主设备号 */
11 minor = MINOR(devid); /* 获取分配号的次设备号 */
12 }
大部分次设备号都选择0
2.注册方法
1)、字符设备结构
cdev结构体在/include/linux/cdev.h中定义如下:
cpp
1 struct cdev {
2 struct kobject kobj;
3 struct module *owner;
4 const struct file_operations *ops;//file_operations
5 struct list_head list;
6 dev_t dev;//cdev
7 unsigned int count;
8 };
编写字符设备驱动之前,需要定义一个cdev结构体变量,这个变量就表示一个字符设备。
cpp
struct cdev test_cdev;
2)、cdev_init
初始化函数cdev_init内容如下:
cpp
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
使用cdev_init函数初始化cdev变量代码如下:
cpp
1 struct cdev testcdev;
2
3
/* 设备操作函数 */
4 static struct file_operations test_fops = {
5 .owner = THIS_MODULE,
6 /* 其他具体的初始项 */
7 };
8
9
testcdev.owner = THIS_MODULE;
10 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
3)、cdev_add
cdev_add函数原型:
cpp
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
注册字符设备代码段内容:
cpp
1 struct cdev testcdev;
2
3
/* 设备操作函数 */
4 static struct file_operations test_fops = {
5 .owner = THIS_MODULE,
6 /* 其他具体的初始项 */
7 };
8
9testcdev.owner = THIS_MODULE;
10 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */
11 cdev_add(&testcdev, devid, 1); /* 添加字符设备 */
4)、cdev_del
cdev_del原型内容:
cpp
void cdev(struct cdev *p)
二、自动创建设备节点
自动创建设备节点以后,使用modprobe加载驱动模块成功的话就会自动在/dev目录下创建对应的设备文件。
1.modev机制
udev程序实现设备文件的创建与删除。
2.创建和删除类
class_create是类创建函数,class_create是个宏定义,内容如下:
cpp
1 #define class_create(owner, name) \//owner一般为THIS_MODULE name为类名字 返回值是指向结构体class的指针,也就是创建的类
2 ({ \
3 static struct lock_class_key __key; \
4 __class_create(owner, name, &__key); \
5 })
6
7
struct class *__class_create(struct module *owner, const char *name,
8 struct lock_class_key *key)
卸载驱动函数的时候需要删除类,class_destroy函数原型如下:
cpp
void class_destroy(struct class *cls)//cls就是要删除的类
3.创建设备
创建好类以后,还需要在类下面创建一个设备,才能实现自动创建设备节点。
device_create函数原型如下:
cpp
struct device *device_create(struct class *class,//clasd 要创建的类
struct device *parent,//parent 父设备 一般为NULL
dev_t devt,//devt 设备号
void *drvdata,//drvdata 设备可能会使用的一些数据 一般为NULL
const char *fmt, ...)//fmt 设备名字 生成/dev/xxx
卸载时,需要删掉创建的设备。device_destroy函数原型如下:
cpp
void device_destroy(struct class *class, dev_t devt)//class 删除的类 devt删除的设备号
三、设置文件私有数据
编写open函数的时候将设备结构体作为私有数据添加到设备文件中,如下:
cpp
/* 设备结构体 */
1 struct test_dev{
2 dev_t devid; /* 设备号 */
3 struct cdev cdev; /* cdev */
4 struct class *class; /* 类 */
5 struct device *device; /* 设备 */
6 int major; /* 主设备号 */
7 int minor; /* 次设备号 */
8 };
9
10 struct test_dev testdev;
11
12 /* open 函数 */
13 static int test_open(struct inode *inode, struct file *filp)
14 {
15 filp->private_data = &testdev; /* 设置私有数据 */
16 return 0;
17 }
在open函数里面设置好私有数据以后,在write、read、close等函数中直接读取private_data即可得到设备结构体。
本节学习的内容看似就几个函数而已,但是要将其替换改动到原来的驱动函数中还是需要一定的代码功力的。切记戒骄戒躁,放平心态,一遍一遍不厌其烦的去调试,去总结。相信功夫不负有心人,铁棒总能磨成针......
Linux版本号4.1.15 芯片MX6ULL
本文为参考正点原子开发板配套教程整理而得,仅用于学习交流使用,不得用于商业用途。