字符设备驱动程序框架
- 定义字符设备结构体变量
- 创建设备号,也可以让内核分配alloc_chrdev_region
- 定义自己的file_operations结构体
- 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
- 初始化cdev把file_operations结构体告诉内核
- 添加注册cdev
- 创建类
- 创建设备
- 提供入口函数
- 提供出口函数
用到的函数说明:
自动分配设备号并注册: |
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) |
参数dev是设备ID;baseminor次设备号开始编号;参数count是要申请的数量,一般都是一个;参数name是设备名字。 |
|
注册设备号: |
int register_chrdev_region(dev_t from, unsigned count, const char *name) |
参数from是要申请的起始设备号,也就是给定的设备号;参数count是要申请的数量,一般都是一个;参数name是设备名字。 |
|
释放设备号: |
void unregister_chrdev_region(dev_t from, unsigned count) |
|
cdev结构体初始化函数: |
void cdev_init(struct cdev *cdev, const struct file_operations *fops) |
|
注册字符设备: |
int cdev_add(struct cdev *p, dev_t dev, unsigned count) |
参数p指向要添加的字符设备(cdev结构体变量),参数dev就是设备所使用的设备号,参数count是要添加的设备数量。完善示例代码 |
|
删除字符设备: |
void cdev_del(struct cdev *p) |
参数p就是要删除的字符设备。 |
|
创建类: |
struct class *class_create (struct module *owner, const char *name) |
class_create一共有两个参数,参数owner一般为THIS_MODULE,参数name是类名字。返回值是个指向结构体class的指针,也就是创建的类。 |
|
删除类: |
void class_destroy(struct class *cls); |
参数cls就是要删除的类。 |
|
创建设备: |
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...) |
device_create是个可变参数函数,参数class就是设备要创建哪个类下面;参数parent是父设备,一般为NULL,也就是没有父设备;参数devt是设备号;参数drvdata是设备可能会使用的一些数据,一般为NULL;参数fmt是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件。返回值就是创建好的设备。 |
|
卸载设备: |
void device_destroy(struct class *class, dev_t devt) |
参数classs是要删除的设备所处的类,参数devt是要删除的设备号。 |
|
|
设备号相关示例代码:
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); /* 获取分配号的次设备号 */
}
cdev初始化和注册示例代码:
cdev结构体说明
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
在cdev中有两个重要的成员变量:ops和dev,这两个就是字符设备文件操作函数集合file_operations以及设备号dev_t。编写字符设备驱动之前需要定义一个cdev结构体变量。
示例代码:
struct cdev testcdev;
/* 设备操作函数 */
static struct file_operations test_fops = {
.owner = THIS_MODULE,
/* 其他具体的初始项 */
};
testcdev.owner = THIS_MODULE;
cdev_init(&testcdev, &test_fops); /* 初始化cdev结构体变量 */
cdev_add(&testcdev, devid, 1); /* 添加字符设备 */
自动创建设备节点示例代码:
struct class *class; /* 类 */
struct device *device; /* 设备 */
dev_t devid; /* 设备号 */
/* 驱动入口函数 */
static int __init xxx_init(void)
{
/* 创建类 */
class = class_create(THIS_MODULE, "xxx");
/* 创建设备 */
device = device_create(class, NULL, devid, NULL, "xxx");
return 0;
}
/* 驱动出口函数 */
static void __exit led_exit(void)
{
/* 删除设备 */
device_destroy(newchrled.class, newchrled.devid);
/* 删除类 */
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
完整的字符设备驱动程序框架示例代码:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*设备结构体 */
struct chr_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct chr_dev chrtest; /* 字符设备 */
/*
* @description : 打开设备
* @param – inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &chrtest; /* 设置私有数据 */
return 0;
}
static ssize_t test_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
static ssize_t test_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
static int test_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations chr_fops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.write = test_write,
.release = test_release,
};
/*
* @description : 驱动入口函数
*/
static int __init test_init(void)
{
u32 val = 0;
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (chrtest.major) { /* 定义了设备号 */
chrtest.devid = MKDEV(chrtest.major, 0);
register_chrdev_region(chrtest.devid,1,”test”);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&chrtest.devid, 0, 1,”test”); /* 申请设备号 */
chrtest.major = MAJOR(chrtest.devid); /* 获取主设备号 */
chrtest.minor = MINOR(chrtest.devid); /* 获取次设备号 */
}
/* 2、初始化cdev */
chrtest.cdev.owner = THIS_MODULE;
cdev_init(&chrtest.cdev, &chr_fops);
/* 3、添加一个cdev */
cdev_add(&chrtest.cdev, chrtest.devid, 1);
/* 4、创建类 */
chrtest.class = class_create(THIS_MODULE,”test”);
/* 5、创建设备 */
chrtest.device = device_create(chrtest.class, NULL,
chrtest.devid, NULL,”test”);
return 0;
}
/*
* @description : 驱动出口函数
*/
static void __exit test_exit(void)
{
/* 注销字符设备 */
cdev_del(&chrtest.cdev);/* 删除cdev */
unregister_chrdev_region(chrtest.devid, 1);
device_destroy(chrtest.class, chrtest.devid);
class_destroy(chrtest.class);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wb");