一、简介
如下是写一个字符驱动设备的一般步骤:
1、为设备定义一个设备相关的结构体(包含设备涉及的cdev,私有数据等)
2、初始化函数的定义
① 向系统申请设备号(register_chrdev_region()或alloc_chrdev_region())
② 使用kzalloc 申请设备内存(为1 中定义的结构体申请存储空间)
③ 调用cdev_init()初始化cdev
④ 调用cdev_add()向系统注册设备
3、卸载函数的定义
① 释放设备号(unregister_chrdev_region())
② 调用cdev_del()注销设备
4、定义file_operations,实现对应的open,read,write方法。
二、主要知识点介绍
1、设备号
一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设备号。主设备
号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各个设备。Linux 提供了一个名为
dev_t 的数据类型表示设备号,dev_t 定义在文件include/linux/types.h 里面,定义如下:
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
dev_t 是个32 位的变量,其中12 位用来表示主设备号,20 位用来表示次设备号。因此Linux 系统中主
设备号范围为0~4095 , 所以大家在选择主设备号的时候一定不要超过这个范围。在文件
include/linux/kdev_t.h 中提供了几个操作设备号的宏定义,如下所示:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
宏MINORBITS 表示次设备号位数,一共20 位。
宏MINORMASK 表示次设备号掩码。
宏MAJOR 用于从dev_t 中获取主设备号,将dev_t 右移20 位即可。
宏MINOR 用于从dev_t 中获取次设备号,取dev_t 的低20 位的值即可。
宏MKDEV 用于将给定的主设备号和次设备号的值组合成dev_t 类型的设备号。
2、设备号分配
① 静态分配:register_chrdev_region(dev_t from, unsigned count, const char *name)
from:设备号(主设备号与此设备号组合)
count:要申请的设备号数量
name:设备名称
② 动态分配:alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这些设备号的
主设备号一样,但是次设备号不同,次设备号以baseminor 为起始地址地址开始递增。一般baseminor 为0,
也就是说次设备号从0 开始。
其他参数含义同register_chrdev_region
当我们知道该主设备号没有被使用时,可使用静态分配方式。如果我们不清楚该主设备号是否被占用,最好使用动态分配方式,会自动进行分配这设备号。
3、简化版的注册设备
① 注册 register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
major:主设备号,0 表示动态分配
name: 设备名
fops: 操作函数
② unregister_chrdev(unsigned int major, const char *name)
major:主设备号
name: 设备名
4、自动创建设备节点
class_create()一共有两个参数,参数owner 一般为THIS_MODULE,参数name 是类名字。返回值是个指
向结构体class 的指针,也就是创建的类。
当使用class_create()创建类后,再使用device_create 函数在这个类下创建一个设备。device_create
函数原型如下:
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 这个设备文件。返回值就是创建好的设备。
三、demo
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/ide.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#define CHRDEV_MAJOR 200
#define CHRDEV_NAME "char_test"
dev_t devno;
struct class *class;
struct device *device;
struct cdev *cdev_t;
static char readbuf[100];
static char writebuf[100];
static char kerneldata[] = {"kernel data!"};
#define DEVICE_NUMBER 1
static int chartest_open (struct inode *inode, struct file *file)
{
printk("chartest_open\n");
return 0;
}
static ssize_t chartest_read (struct file *file, char __user *buf,
size_t cnt, loff_t *offt)
{
int value = 0;
memcpy(readbuf, kerneldata, sizeof(kerneldata));
value = copy_to_user(buf, readbuf, cnt);
if(!value) {
printk("kernel send data : %s\n", buf);
} else {
printk("kernel send data failed %d\n", value);
}
return 0;
}
static ssize_t chartest_write (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
int value = 0;
value = copy_from_user(writebuf, buf, cnt);
if(!value) {
printk("kernel recieve data : %s\n", writebuf);
} else {
printk("kernel send data failed %d\n", value);
}
return 0;
}
static int chartest_release (struct inode *inode, struct file *filp)
{
printk("chartest_release\n");
return 0;
}
static struct file_operations chartest_fops = {
.owner = THIS_MODULE,
.open = chartest_open,
.read = chartest_read,
.write = chartest_write,
.release = chartest_release,
};
static int chartest_init(void)
{
int ret = 0;
if (CHRDEV_MAJOR) {
devno = MKDEV(CHRDEV_MAJOR, 0);
ret = register_chrdev_region(devno, DEVICE_NUMBER, CHRDEV_NAME);
if (ret < 0) {
printk("register_chrdev_region failed\n");
return 0;
}
} else {
ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, CHRDEV_NAME);
if (ret < 0) {
printk("alloc_chrdev_region failed\n");
return 0;
}
}
cdev_t = kzalloc(sizeof(struct cdev), GFP_KERNEL);
cdev_init(cdev_t, &chartest_fops);
cdev_t->owner = THIS_MODULE;
cdev_add(cdev_t, devno, 1);
class = class_create(THIS_MODULE, CHRDEV_NAME);
device = device_create(class, NULL, devno, NULL, CHRDEV_NAME);
printk("chartest_init !\n");
return 0;
}
static void chartest_exit(void)
{
unregister_chrdev_region(devno, DEVICE_NUMBER);
cdev_del(cdev_t);
device_destroy(class, devno);
class_destroy(class);
printk("chartest_exit\n");
}
module_init(chartest_init);
module_exit(chartest_exit);
MODULE_LICENSE("GPL");
参考:迅为linux驱动学习视频