备注:
在学习字符设备驱动后,简单做一个总结·,以3568上GPIO为例。
1.驱动加载模块步骤
1.申请字符号
2.添加字符设备
3.生成设备节点
2.申请设备号
每一个设备都具有唯一的一个设备号,先申请设备号,后面对设备的所有描述都是通过设备号唯一确认的。
设备号类型:dev_t 。 dev_t 是一个无符号的 32 位整形类型(unsigned int),其中高 12 位表示主设备号,低 20 位表示次设备号。在“内核源码/include/linux/kdev_t.h”中提供了设备号相关的宏定义。申请设备号有两种方式,一种是静态申请,一种是动态申请。静态申请就是自己设置主次设备号,动态申请是由系统分配,动态申请可以避免设备号重复。
2.1静态申请设备号
函数原型
register_chrdev_region(dev_t from, unsigned count, const char *name);
from: 自定义的 dev_t 类型设备号 表示设备号的起始值
count: 申请设备的数量(次设备)
name: 申请的设备名称
函数返回值:申请成功返回 0,申请失败返回负数
2.2动态申请设备号
函数原型
alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
dev *: 会将申请完成的设备号保存在 dev 变量中
baseminor: 次设备号的起始地址,一般次设备号从0开始
count: 申请设备的数量(次设备)
name: 申请的设备名称
函数返回值:申请成功返回 0,申请失败返回负数
2.3使用动态设备号完成申请设备号举例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO_DR 0xFDD60000
struct device_info{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_info; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *gpio0_B7;
};
struct device_info dev1;
static int __init chr_gpio0_B7_init(void)
{
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "number_gpio0_B7"); //动态分配设备号
if (ret < 0)
{
return -1;
}
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
}
结构体device_info中包含一个字符设备的信息,后面会逐步将这个字符设备的信息补全。这里是对设备号的申请。
这里主要先看初始化函数里面的内容,通过alloc_chrdev_region(&dev1.dev_num, 0, 1, "gp");动态方式申请一个设备号,设备号放在dev1.dev_num中。
3.添加字符设备
在Linux中使用cdev结构体描述一个字符设备,cdev结构体定义在linux/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; //隶属于同一主设备号的次设备号的个数.
};
在结构体中,包含了一个file_operations结构体,它是系统调用和驱动的一个桥梁,通过这个桥梁我们能用系统调用的函数与驱动中的函数对应上。
file_operations定义在linux/fs.h中
file_operations 结构体如下
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*dir_notify)(struct file *filp, unsigned long arg);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
};
// 常用定义
static struct file_operations cdev_gpio0_B7 = {
.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,//将 open 字段指向 chrdev_open(...)函数
.read = chrdev_read,//将 read字段指向 chrdev_read(...)函数
.write = chrdev_write,//将 write 字段指向 chrdev_write(...)函数
.release = chrdev_release,//将release 字段指向 chrdev_release(...)函数,对应应用程序中close()函数
};
//在应用程序中使用open函数
fd=open("/dev/gpio0_B7",O_RDWR);
//驱动中open函数
static int chrdev_open(struct inode *inode, struct file *file)
{
return 0;
}
//调用open函数就会调用驱动中的chrdev_open函数
3.1初始化设备结构体
cdev_init函数用于初始化cdev结构体成员,建立cdev和file_operations之间的关系。
void cdev_init(struct cdev *, const struct file_operations *){
menset(cdev,0,sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj,&ktype_cdev_default);
cdev->ops = fops;
}
3.2添加一个设备结构体
int cdev_add(struct cdev *, dev_t, unsigned);
(1)第一个参数为要添加的 struct cdev 类型的结构体
(2)第二个参数为申请的字符设备号
(3)第三个参数为该设备的数量。 这两个参数直接赋值给 struct cdev 的 dev 成员和 count 成员。
函数返回值:添加成功返回 0,添加失败返回负数。
3.3添加字符设备代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO_DR 0xFDD60000
struct device_info{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_info; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *gpio0_B7;
};
//定义一个结构体变量
struct device_info dev1;
static struct file_operations cdev_gpio0_B7 = {
.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,//将 open 字段指向 chrdev_open(...)函数
.read = chrdev_read,//将 read字段指向 chrdev_read(...)函数
.write = chrdev_write,//将 write 字段指向 chrdev_write(...)函数
.release = chrdev_release,//将release 字段指向 chrdev_release(...)函数,对应应用程序中close()函数
};
static int chrdev_open(struct inode *inode, struct file *file)
{
return 0;
}
/*向设备写入数据函数*/
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
return 0;
}
/**从设备读取数据*/
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static int __init chr_gpio0_B7_init(void)
{
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "number_gpio0_B7"); //动态分配设备号
if (ret < 0)
{
return -1;
}
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
/*2 初始化 cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_info, &cdev_gpio0_B7);
/*3 添加一个 cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_info, dev1.dev_num, 1);
if(ret<0)
{
}
}
在上面代码中新增加4个函数 chrdev_open(), chrdev_read, chrdev_write, chrdev_release。通过结构体struct file_operations cdev_gpio0_B7将这四个函数分别与操作系统中open(),read(),write(),close()函数对应。
通过初始化函数cdev_init(&dev1.cdev_info, &cdev_gpio0_B7);将它的信息初始化给我们设备中的cdev即dev1.cdev_info。
再使用cdev_add(&dev1.cdev_info, dev1.dev_num, 1);添加设备到内核中,并且与设备号进行绑定
4.生成设备节点
在linux中,每个设备在Linux系统中都有一个对应的“设备文件”代表他们,应用程序通过操作这个“设备文件”,便可以操作对应的硬件。
如fd=open("/dev/hello",O_RDWR);
打开的设备文件/dev/hello就是设备节点。所以Linux设备节点是应用程序沟通的一个桥梁。设备节点被创建在dev目录下。
如在图中 tty,tty0,tty1代表设备文件既设备节点,第一个字符C代表它是字符设备,tty0中的4代表主设备号,0代表次设备号。通过主设备号找到他对应的file_operations结构体。通过次设备号找到是同类设备的第几个,就确定了是哪个驱动。
创建设备节点也有两种方式,手动创建和自动创建
1、手动创建设备节点
通过命令mknod创建
mknod NAME TYPE MAJOR MINOR
NAME: 要创建的节点名称
TYPE: b 表示块设备,c 表示字符设备,p 表示管道
MAJOR:要链接设备的主设备号
MINOR: 要链接设备的从设备号
/*例如使用以下命令创建一个名为 my_device 的字符设备节点,链接设备的主设备号和从设备号分别为 123 和 0:*/
mknod /dev/my_device c 123 0
2、自动创建
udev机制:Linux中可以通过udev来实现设备节点的创建与删除,udev是一个用户程序,可以根据系统中设备的状态来创建或删除设备节点,比如当驱动程序成功加载到Linux时会自动在/dev目录下创建对应的设备节点,当驱动程序卸载的时候会自动删除/dev目录下设备节点。在嵌入式Linux中我们使用的是mdev,mdev是udev的简化版本。在使用busybox构建根文件系统的时候,busybox会自动创建mdev.
在加载设备驱动的时候,udev会去 /sys/class/xxx 寻找相关信息来创建 /dev/xxx 设备节点,所以要创建设备节点需要先创建类
4.1创建类
使用 class_create(owner, name)来创建类放在/sys/class/
class_create定义在/linux/device.h中
owner:struct module 结构体类型的指针,指向函数即将创建的这个 struct class 的模块。一般赋值为 THIS_MODULE。
name:char 类型的指针,代表即将创建的 struct class 变量的名字。也就是/sys/class/xxx 中的xxx
返回值:struct class * 类型的结构体。
删除设备逻辑类(删除类) extern void class_destroy(struct class *cls);
4.2创建设备
使用class_create创建好类以后,还需要使用device_create函数在类下面创建一个设备。定义在include/linux/device.h
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
函数作用:
用来在 class 类中下创建一个设备属性文件,udev 会自动识别从而进行设备节点的创建。
参数含义:
cls:指定所要创建的设备所从属的类。
parent:指定该设备的父设备,如果没有就指定为 NULL。
devt:指定创建设备的设备号。
drvdata:被添加到该设备回调的数据,没有则指定为 NULL。
fmt:添加到系统的设备节点名称。就是/dev/xxx 中的xxx
extern void device_destroy(struct class *cls, dev_t devt);
用来删除 class 类中的设备属性文件,udev 会自动识别从而进行设备节点的删除。
参数含义:
cls:指定所要创建的设备所从属的类。
devt:指定创建设备的设备号。
4.3代码补充
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO_DR 0xFDD60000
struct device_info{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_info; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *gpio0_B7;
};
//定义一个结构体变量
struct device_info dev1;
static struct file_operations cdev_gpio0_B7 = {
.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,//将 open 字段指向 chrdev_open(...)函数
.read = chrdev_read,//将 read字段指向 chrdev_read(...)函数
.write = chrdev_write,//将 write 字段指向 chrdev_write(...)函数
.release = chrdev_release,//将release 字段指向 chrdev_release(...)函数,对应应用程序中close()函数
};
static int chrdev_open(struct inode *inode, struct file *file)
{
return 0;
}
/*向设备写入数据函数*/
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
return 0;
}
/**从设备读取数据*/
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static int __init chr_gpio0_B7_init(void)
{
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "number_gpio0_B7"); //动态分配设备号
if (ret < 0)
{
return -1;
}
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
/*2 初始化 cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_info, &cdev_gpio0_B7);
/*3 添加一个 cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_info, dev1.dev_num, 1);
if(ret<0)
{
}
dev1. class = class_create(THIS_MODULE, "gpio0_B7_class");
if(IS_ERR(dev1.class))
{
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "gpio0_B7_device");
if(IS_ERR(dev1.device))
{
}
}
通过class_create(THIS_MODULE, "gpio0_B7_class");创建一个类,再通过dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "gpio0_B7_device");将类与设备号绑定,设置好设备的名称到此一个字符设备就完成了。
5.其他补充
5.1用户数据和内核数据交换
不能在用户态自接修改内核中的数据,即要想获取用户传递过来的信息需要先通过copy_from_user()函数获取,不能将用户数据直接赋值给驱动中的变量,也就不能将用户传递的变量作为判断条件。
定义在了 kernel/include/linux/uaccess.h 文件下
unsigned long copy_to_user_inatomic(void __user *to, const void *from, unsigned long n);
copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf))
函数作用:
把内核空间的数据复制到用户空间。
参数含义:
*to 是用户空间的指针
*from 是内核空间的指针
n 是从内核空间向用户空间拷贝的字节数
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
copy_from_user(test_dev->kbuf, buf, size)
函数作用:
把用户空间的数据复制到内核空间。
参数含义:
*to 是内核空间的指针
5.2私有数据
当同一类设备有多个驱动时,在申请时申请了多个,如GPIO(0-7),这些设备主设备号相同次设备号不同。使用私有数据可以更方便。
在前面定义一个设备时,至少需要定义设备号,主设备号,次设备号,设备类,设备节点等全局变量,当需要多个同类设备时,使用全局变量就不方便管理。
//全局变量方式
dev_t dev_num; //设备号
int major=0 ; //主设备号
int minor=0 ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *vir_gpio_dr;
//私有数据方式
struct device_test{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *vir_gpio_dr;
};
struct device_test dev1;
struct device_test dev2;
//如果该类设备只有一个,则可以在open函数中定义
file->private_data=&dev1;
//在read和write中
struct device_test *test_dev=(struct device_test *)file->private_data;
//通过操作test_dev结构体的成员变量来操作设备
//在驱动入口函数中对结构体变量赋值
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
/*2 初始化 cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);
/*3 添加一个 cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
/*4 创建类*/
dev1. class = class_create(THIS_MODULE, "test");
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
//如果有多个设备可以使用container_of函数来判断操作的时哪个设备
//函数原型:
container_of(ptr,type,member)
//函数作用:
//通过结构体变量中某个成员的首地址获取到整个结构体变量的首地址。
//参数含义:
//ptr 是结构体变量中某个成员的地址。
//type 是结构体的类型
//member 是该结构体变量的具体名字
static int cdev_test_open(struct inode *inode,struct file *file);
//在应用层使用fopen函数打开设备时,需要传入设备节点名 如/dev/gpio1 或 /dev/gpio0,根据不同的节点名找到对应的设备
//上层应用程序调用open()系统调用打开/dev/xxx设备节点时,最终会调用cdev->ops->open()。
//即调用函数int (*open) (struct inode *, struct file *);
struct inode *inode中保存传入设备的信息为gpio1还是gpio0,就可以获取到设备号,根据cdev确认打开的是哪个设备
//在open函数中
dev1.minor = 0; //设置 dev1 的次设备号为 0
dev2.minor = 1; //设置 dev2 的次设备号为 1
//inode->i_cdev 为该 inode 的cdev结构体,使用 container_of 函数找到结构体变量 dev1 dev2 的地址
//然后设置私有数据
file->private_data = container_of(inode->i_cdev, struct device_test, cdev_test);
在read和write根据次设备号完成对不同设备操作
struct device_test *test_dev = (struct device_test *)file->private_data;
//如果次设备号是 0,则为 dev1
if (test_dev->minor == 0)
{
}
//如果次设备号是 1,则为 dev2
else if(test_dev->minor == 1)
{
}
5.3物理地址映射
控制GPIO灯
在驱动中不能直接修改物理地址中寄存器的值,需要将物理地址映射后,修改虚拟地址中的值
在头文件<linux/io.h>
GPIO0_B7的数据寄存器为0xFDD60000
使用函数ioremap(0xFDD60000,4);转换地址为0xFDD60000,大小为4个字节
iounmap(FDD60000)转换失败使用取消转换函数
5.4错误处理
在编写驱动程序时,驱动程序应该提供函数执行失败后处理的能力。如果驱动程序中函数
执行失败了,必须取消掉所有失败前的注册,否则内核会处于一个不稳定的状态,因为它包含
了不存在代码的内部指针。在处理 Linux 错误时,最好使用 goto 语句。当错误发生时通过错误处理将设备去除掉。
6.代码汇总
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#define GPIO_DR 0xFDD60000
struct device_info{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_info; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *gpio0_B7_register;
};
//定义一个结构体变量
struct device_info dev1;
static int chrdev_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
return 0;
}
/*向设备写入数据函数*/
static ssize_t chrdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_info *dev=(struct device_info *)file->private_data;
if (copy_from_user(dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
return -1;
}
if(dev->kbuf[0]==1){ //如果应用层传入的数据是 1,则打开灯
*(dev->gpio0_B7_register) = 0x8000c040; //设置数据寄存器的值0x8000c040开灯
}
else if(dev->kbuf[0]==0){ //如果应用层传入的数据是 0,则关闭灯
*(dev->gpio0_B7_register) = 0x80004040; //设置数据寄存器的值0x80004040关灯
}
return 0;
}
/**从设备读取数据*/
static ssize_t chrdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct device_info *dev=(struct device_info *)file->private_data;
if (copy_to_user(buf, dev->kbuf, strlen( dev->kbuf)) != 0) // copy_to_user:内核空间向用户空
//间传数据
{
return -1;
}
return 0;
}
static int chrdev_release(struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations cdev_gpio0_B7 = {
.owner = THIS_MODULE,//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = chrdev_open,//将 open 字段指向 chrdev_open(...)函数
.read = chrdev_read,//将 read字段指向 chrdev_read(...)函数
.write = chrdev_write,//将 write 字段指向 chrdev_write(...)函数
.release = chrdev_release,//将release 字段指向 chrdev_release(...)函数,对应应用程序中close()函数
};
static int __init chr_gpio0_B7_init(void)
{
int ret;
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "number_gpio0_B7"); //动态分配设备号
if (ret < 0)
{
goto err_chrdev;
return -1;
}
dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号
/*2 初始化 cdev*/
dev1.cdev_info.owner = THIS_MODULE;
cdev_init(&dev1.cdev_info, &cdev_gpio0_B7);
/*3 添加一个 cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_info, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
dev1. class = class_create(THIS_MODULE, "gpio0_B7_class");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "gpio0_B7_device");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
dev1.gpio0_B7_register=ioremap(GPIO_DR,4); //将物理地址转化为虚拟地址,长度为4
if(IS_ERR(dev1.gpio0_B7_register))
{
ret=PTR_ERR(dev1.gpio0_B7_register); //PTR_ERR()来返回错误代码
goto err_ioremap;
}
return 0;
err_ioremap:
iounmap(dev1.gpio0_B7_register);
err_device_create:
class_destroy(dev1.class); //删除类
err_class_create:
cdev_del(&dev1.cdev_info); //删除 cdev
err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
err_chrdev:
return ret;
}
static void __exit chr_fops_exit(void) //驱动出口函数
{
/*注销字符设备*/
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
cdev_del(&dev1.cdev_info); //删除 cdev
device_destroy(dev1.class, dev1.dev_num); //删除设备
class_destroy(dev1.class); //删除类
iounmap(dev1.gpio0_B7_register);
}
module_init(chr_gpio0_B7_init);
module_exit(chr_fops_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("guo");
一个完整操作GPIO字符设备代码