register_chrdev
和 unregister_chrdev
这两个函数是老版本驱动使用的函数,现在新的字符设备驱动已经不再使用这两个函数,而是使用Linux内核推荐的新字符设备驱动API函数。
使用 register_chrdev
函数注册字符设备的时候,会出现两个问题。①、需要我们事先确定好哪些主设备号没有使用。②、会将一个主设备号下的所有次设备号都使用掉。解决这两个问题最好的方法就是要使用设备号的时候向 Linux 内核申请。
#define MY_NAME "new_dev_test"
int major; /*主设备号*/
int minor; /*次设备号*/
dev_t dev; /*设备号*/
if(major) /*定义了主设备号*/
{
dev = MKDEV(major,0); /*次设备默认从0开始*/
register_chrdev_region(dev,1,MY_NAME); /*静态申请设备号*/
}else
{
alloc_chrdev_region(&dev,0,1,MY_NAME); /*动态申请设备号*/
major = MAJOR(dev); /*主设备号*/
minor = MINOR(dev); /*次设备号*/
}
unregister_chrdev_region(dev, 1); /*注销设备号*/
在 linux/cdev.h
中定义了cdev
设备结构体。
struct cdev {
struct kobject kobj;
struct module *owner; /*默认就是THIS_MODULE*/
const struct file_operations *ops; /*文件结构体*/
struct list_head list;
dev_t dev; /*设备号*/
unsigned int count;
};
/* dev结构体初始化函数 */
void cdev_init(struct cdev *, const struct file_operations *);
/* 向 Linux 系统添加字符设备 */
int cdev_add(struct cdev * cdev, dev_t dev, unsigned count);
// count代表添加字符设备个数
// cdev_add 和 alloc_chrdev_region 或者 register_chrdev_region实现了 register_chrdev 函数
/* cdev_del 函数从 Linux 内核中删除相应的字符设备 */
void cdev_del(struct cdev *);
// cdev_del 和 unregister_chrdev_region 这两个函数合起来的功能相当于 unregister_chrdev 函数。
struct cdev dev_test;
static struct file_operations my_fops = {
.owner = THIS_MODULE,
/* 其他操作函数 */
};
dev_test.owner = THIS_MODULE;
cdev_init(&dev_test, &my_fops);
cdev_add(&dev_test, dev, 1); /*添加字符设备*/
cdev_del(&dev_test);
当我们使用 modprobe
加载驱动程序以后还需要使用命令“mknod”
手动创建设备节点。在驱动中实现自动创建设备节点的功能以后,使用 modprobe
加载驱动模块成功的话就会自动在/dev
目录下创建对应的设备文件。
udev
是一个用户程序,在 Linux
下通过 udev
来实现设备文件的创建与删除,udev
可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备文件。比如使用modprobe
命令成功加载驱动模块以后就自动在/dev
目录下创建对应的设备节点文件,使用rmmod
命令卸载驱动模块以后就删除掉/dev
目录下的设备节点文件。使用 busybox
构建根文件系统的时候,busybox
会创建一个 udev
的简化版本—mdev
。使用mdev
来实现设备节点文件的自动创建与删除,Linux
系统中的热插拔事件也由 mdev
管理。
自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add
函数后面添加自动创建设备节点相关代码。
在 linux/device.h
中定义如下:
extern struct class * __must_check __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
/* 销毁设备类 */
extern void class_destroy(struct class *cls);
/* This is a #define to keep the compiler from merging different
* instances of the __key variable */
/* 创建设备类 参数 owner 一般为 THIS_MODULE,参数 name 是类名字 */
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
class = class_create(THIS_MODULE,MY_NAME);
class_destroy(class);
/*
创建设备
参数 parent 是父设备,一般为 NULL,也就是没有父设备;
参数 devt 是设备号;
参数 drvdata 是设备可能会使用的一些数据,一般为 NULL;
参数 fmt 是设备名字,如果设置 fmt=xxx 的话,就会生成/dev/xxx这个设备文件。
返回值就是创建好的设备。
*/
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
/* 销毁创建的设备 */
extern void device_destroy(struct class *cls, dev_t devt);
device = device_create(class,NULL,dev,NULL,MY_NAME);
device_destroy(class,dev);
dev_t dev; /*设备号*/
struct class *class; /*类*/
struct device *device; /*设备*/
#define MY_NAME "my_led"
/*驱动入口函数 当使用“insmod my_dev.ko”命令或“modprobe my_dev.ko”加载驱动的时候,
mydev_init 这个函数就会被调用*/
static int __init mydev_init(void)
{
class = class_create(THIS_MODULE,MY_NAME);
device = device_create(class,NULL,dev,NULL,MY_NAME);
return 0;
}
/*驱动出口函数 当使用“rmmod my_dev.ko”命令或者“modprobe -r my_dev.ko”卸载具体驱动的时候
mydev_exit 函数就会被调用*/
static void __exit mydev_exit(void)
{
device_destroy(class,dev);
class_destroy(class);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息
将设备的所有属性给设置为一个结构体。编写驱动 open
函数的时候将设备结构体作为私有数据添加到设备文件中。
typedef struct my_dev{
int major; /*主设备号*/
int minor; /*次设备号*/
dev_t dev; /*设备号*/
struct cdev cdev; /* cdev */
struct class *class; /*类*/
struct device *device; /*设备*/
}my_dev_t;
my_dev_t my_dev;
/*打开设备*/
static int my_dev_open(struct inode *inode, struct file *file)
{
file->private_data = &my_dev; /*设置私有数据*/
return 0;
}
#include // module_init、module_exit函数的头文件所在地
#include //添加文件描述符,设备注册注销头文件
#include // 添加module_XXX宏定义头文件
#include
#include
#include
#include
#include
#include // 包含readl等IO操作函数
#include
#include
#include
#include
#include
#define NEW_CHAR_CNT 1 /*设备号数量*/
#define MY_NAME "new_dev_test" /*设备名字*/
typedef struct my_dev{
int major; /*主设备号*/
int minor; /*次设备号*/
dev_t dev; /*设备号*/
struct cdev cdev; /* cdev */
struct class *class; /*类*/
struct device *device; /*设备*/
}my_dev_t;
my_dev_t my_new_dev; /*新设备*/
#define LED_OFF 0 /* 关灯 */
#define LED_ON 1 /* 开灯 */
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的虚拟地址 */
static void __iomem *IMX6U_CCM_CCGR1; // 设置IO时钟虚拟地址
static void __iomem *SW_MUX_GPIO1_IO03; // 设置IO复用虚拟地址
static void __iomem *SW_PAD_GPIO1_IO03; // 设置IO属性虚拟地址
static void __iomem *GPIO1_DR; // 设置IO输出功能虚拟地址
static void __iomem *GPIO1_GDIR; // led虚拟地址
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LED_ON) /*开灯*/
{
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LED_OFF) /*关灯*/
{
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
file->private_data = &my_new_dev; /*设置私有数据*/
return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf,
size_t cnt, loff_t * offt)
{
return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
int ret = 0;
unsigned char data_buf[1];
unsigned char led_stat;
/*接收用户空间的数据*/
ret = copy_from_user(data_buf,buf,cnt);
if(ret < 0)
{
printk(KERN_INFO "write fail ...\n");
return -EFAULT;
}
led_stat = data_buf[0]; /* 获取状态值 */
if(led_stat == LED_ON)
{
led_switch(LED_ON);
}else if(led_stat == LED_OFF)
{
led_switch(LED_OFF);
}
return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 */
static int __init mydev_init(void)
{
u32 val = 0;
/* 初始化 LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能 GPIO1 时钟 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 清除以前的设置 */
val |= (3 << 26); /* 设置新值 */
writel(val, IMX6U_CCM_CCGR1);
/* 3、设置 GPIO1_IO03 的复用功能,将其复用为
* GPIO1_IO03,最后设置 IO 属性。
*/
writel(5, SW_MUX_GPIO1_IO03);
/* 寄存器 SW_PAD_GPIO1_IO03 设置 IO 属性 */
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置 GPIO1_IO03 为输出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); /* 清除以前的设置 */
val |= (1 << 3); /* 设置为输出 */
writel(val, GPIO1_GDIR);
/* 5、默认关闭 LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
/*分配设备号*/
if(my_new_dev.major) /*定义了主设备号*/
{
my_new_dev.dev = MKDEV(my_new_dev.major,0); /*次设备默认从0开始*/
register_chrdev_region(my_new_dev.dev,NEW_CHAR_CNT,MY_NAME); /*静态申请设备号*/
}else
{
alloc_chrdev_region(&my_new_dev.dev,0,NEW_CHAR_CNT,MY_NAME); /*动态申请设备号*/
my_new_dev.major = MAJOR(my_new_dev.dev); /*主设备号*/
my_new_dev.minor = MINOR(my_new_dev.dev); /*次设备号*/
}
/*注册设备号*/
cdev_init(&my_new_dev.cdev,&my_fops); /*初始化cdev*/
cdev_add(&my_new_dev.cdev,my_new_dev.dev,NEW_CHAR_CNT); /*添加一个dev*/
/*创建类*/
my_new_dev.class = class_create(THIS_MODULE,MY_NAME);
if(IS_ERR(my_new_dev.class))
{
return PTR_ERR(my_new_dev.class);
}
/*创建设备*/
my_new_dev.device = device_create(my_new_dev.class,NULL,my_new_dev.dev,NULL,MY_NAME);
if(IS_ERR(my_new_dev.device))
{
return PTR_ERR(my_new_dev.device);
}
return 0;
}
/*驱动出口函数 */
static void __exit mydev_exit(void)
{
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/*注销字符设备*/
cdev_del(&my_new_dev.cdev);
unregister_chrdev_region(my_new_dev.dev, NEW_CHAR_CNT);
/*注销掉类和设备*/
device_destroy(my_new_dev.class,my_new_dev.dev);
class_destroy(my_new_dev.class);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息
#include // module_init、module_exit函数的头文件所在地
#include //添加文件描述符,设备注册注销头文件
#include // 添加module_XXX宏定义头文件
#include
#include
#include
#include
#include
#include // 包含readl等IO操作函数
#include
#include
#include
#include
#include
#define NEW_CHAR_CNT 1 /*设备号数量*/
#define MY_NAME "new_dev_test" /*设备名字*/
typedef struct my_dev{
int major; /*主设备号*/
int minor; /*次设备号*/
dev_t dev; /*设备号*/
struct cdev cdev; /* cdev */
struct class *class; /*类*/
struct device *device; /*设备*/
}my_dev_t;
my_dev_t my_new_dev; /*新设备*/
/*打开设备*/
static int my_dev_open (struct inode *inode, struct file *file)
{
file->private_data = &my_new_dev; /*设置私有数据*/
return 0;
}
/*读设备*/
static ssize_t my_dev_read (struct file *file, char __user *buf,
size_t cnt, loff_t * offt)
{
return 0;
}
/*写设备*/
static ssize_t my_dev_write (struct file *file, const char __user *buf,
size_t cnt, loff_t *offt)
{
return 0;
}
/*释放设备*/
static int my_dev_close (struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations my_fops = {
.owner = THIS_MODULE, // 惯例,直接写即可
.open = my_dev_open, // 打开设备
.read = my_dev_read, // 读设备
.write = my_dev_write, // 写设备
.release = my_dev_close, // 关闭设备
};
/*驱动入口函数 */
static int __init mydev_init(void)
{
/*分配设备号*/
if(my_new_dev.major) /*定义了主设备号*/
{
my_new_dev.dev = MKDEV(my_new_dev.major,0); /*次设备默认从0开始*/
register_chrdev_region(my_new_dev.dev,NEW_CHAR_CNT,MY_NAME); /*静态申请设备号*/
}else
{
alloc_chrdev_region(&my_new_dev.dev,0,NEW_CHAR_CNT,MY_NAME); /*动态申请设备号*/
my_new_dev.major = MAJOR(my_new_dev.dev); /*主设备号*/
my_new_dev.minor = MINOR(my_new_dev.dev); /*次设备号*/
}
/*注册设备号*/
cdev_init(&my_new_dev.cdev,&my_fops); /*初始化cdev*/
cdev_add(&my_new_dev.cdev,my_new_dev.dev,NEW_CHAR_CNT); /*添加一个dev*/
/*创建类*/
my_new_dev.class = class_create(THIS_MODULE,MY_NAME);
if(IS_ERR(my_new_dev.class))
{
return PTR_ERR(my_new_dev.class);
}
/*创建设备*/
my_new_dev.device = device_create(my_new_dev.class,NULL,my_new_dev.dev,NULL,MY_NAME);
if(IS_ERR(my_new_dev.device))
{
return PTR_ERR(my_new_dev.device);
}
return 0;
}
/*驱动出口函数 */
static void __exit mydev_exit(void)
{
/*注销字符设备*/
cdev_del(&my_new_dev.cdev);
unregister_chrdev_region(my_new_dev.dev, NEW_CHAR_CNT);
/*注销掉类和设备*/
device_destroy(my_new_dev.class,my_new_dev.dev);
class_destroy(my_new_dev.class);
}
module_init(mydev_init); // 注册加载函数
module_exit(mydev_exit); // 注册卸载函数
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("dongfang"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("------"); // 描述模块的别名信息