20150226 IMX257 混杂设备miscdevice驱动程序
2015-02-26 16:00 李海沿
在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同。 所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。 在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。miscdevice的API实现在drivers/char/misc.c中。
一、混杂设备介绍
1. miscdevice结构体
struct miscdevice {
int minor; //次设备号
const char *name; //设备的名称
const struct file_operations *fops; //文件操作
struct list_head list; //misc_list的链表头
struct device *parent; //父设备(Linux设备模型中的东东了,哈哈)
struct device *this_device; //当前设备,是device_create的返回值,下边会看到
};
|
2. misc子系统初始化函数
-
static int __init misc_init(void)
-
{
-
int err;
-
-
#ifdef CONFIG_PROC_FS
-
/*创建一个proc入口项*/
-
proc_create("misc", 0, NULL, &misc_proc_fops);
-
#endif
-
/*在/sys/class/目录下创建一个名为misc的类*/
-
misc_class = class_create(THIS_MODULE, "misc");
-
err = PTR_ERR(misc_class);
-
if (IS_ERR(misc_class))
-
goto fail_remove;
-
-
err = -EIO;
-
/*注册设备,其中设备的主设备号为MISC_MAJOR,为10。设备名为misc,misc_fops是操作函数的集合*/
-
if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
-
goto fail_printk;
-
return 0;
-
-
fail_printk:
-
printk("unable to get major %d for misc devices/n", MISC_MAJOR);
-
class_destroy(misc_class);
-
fail_remove:
-
remove_proc_entry("misc", NULL);
-
return err;
-
}
-
/*misc作为一个子系统被注册到linux内核中*/
-
subsys_initcall(misc_init);
|
下边是register_chrdev函数的实现:
-
int register_chrdev(unsigned int major, const char *name,
-
const struct file_operations *fops)
-
{
-
struct char_device_struct *cd;
-
struct cdev *cdev;
-
char *s;
-
int err = -ENOMEM;
-
/*主设备号是10,次设备号为从0开始,分配256个设备*/
-
cd = __register_chrdev_region(major, 0, 256, name);
-
if (IS_ERR(cd))
-
return PTR_ERR(cd);
-
/*分配字符设备*/
-
cdev = cdev_alloc();
-
if (!cdev)
-
goto out2;
-
-
cdev->owner = fops->owner;
-
cdev->ops = fops;
-
/*Linux设备模型中的,设置kobject的名字*/
-
kobject_set_name(&cdev->kobj, "%s", name);
-
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
-
*s = '!';
-
/*把这个字符设备注册到系统中*/
-
err = cdev_add(cdev, MKDEV(cd->major, 0), 256);
-
if (err)
-
goto out;
-
-
cd->cdev = cdev;
-
-
return major ? 0 : cd->major;
-
out:
-
kobject_put(&cdev->kobj);
-
out2:
-
kfree(__unregister_chrdev_region(cd->major, 0, 256));
-
return err;
-
}
|
来看看这个设备的操作函数的集合:
-
static const struct file_operations misc_fops = {
-
.owner = THIS_MODULE,
-
.open = misc_open,
-
};
|
可以看到这里只有一个打开函数,用户打开miscdevice设备是通过主设备号对应的打开函数,在这个函数中找到次设备号对应的相应的具体设备的open函数。它的实现如下:
-
static int misc_open(struct inode * inode, struct file * file)
-
{
-
int minor = iminor(inode);
-
struct miscdevice *c;
-
int err = -ENODEV;
-
const struct file_operations *old_fops, *new_fops = NULL;
-
-
lock_kernel();
-
mutex_lock(&misc_mtx);
-
/*找到次设备号对应的操作函数集合,让new_fops指向这个具体设备的操作函数集合*/
-
list_for_each_entry(c, &misc_list, list) {
-
if (c->minor == minor) {
-
new_fops = fops_get(c->fops);
-
break;
-
}
-
}
-
-
if (!new_fops) {
-
mutex_unlock(&misc_mtx);
-
/*如果没有找到,则请求加载这个次设备号对应的模块*/
-
request_module("char-major-%d-%d", MISC_MAJOR, minor);
-
mutex_lock(&misc_mtx);
-
/*重新遍历misc_list链表,如果没有找到就退出,否则让new_fops指向这个具体设备的操作函数集合*/
-
list_for_each_entry(c, &misc_list, list) {
-
if (c->minor == minor) {
-
new_fops = fops_get(c->fops);
-
break;
-
}
-
}
-
if (!new_fops)
-
goto fail;
-
}
-
-
err = 0;
-
/*保存旧打开函数的地址*/
-
old_fops = file->f_op;
-
/*让主设备号的操作函数集合指针指向具体设备的操作函数集合*/
-
file->f_op = new_fops;
-
if (file->f_op->open) {
-
/*使用具体设备的打开函数打开设备*/
-
err=file->f_op->open(inode,file);
-
if (err) {
-
fops_put(file->f_op);
-
file->f_op = fops_get(old_fops);
-
}
-
}
-
fops_put(old_fops);
-
fail:
-
mutex_unlock(&misc_mtx);
-
unlock_kernel();
-
return err;
-
}
|
3. misc子注册函数
并且会自动生成设备节点
-
int misc_register(struct miscdevice * misc)
-
{
-
struct miscdevice *c;
-
dev_t dev;
-
int err = 0;
-
/*初始化misc_list链表*/
-
INIT_LIST_HEAD(&misc->list);
-
mutex_lock(&misc_mtx);
-
/*遍历misc_list链表,看这个次设备号以前有没有被用过,如果次设备号已被占有则退出*/
-
list_for_each_entry(c, &misc_list, list) {
-
if (c->minor == misc->minor) {
-
mutex_unlock(&misc_mtx);
-
return -EBUSY;
-
}
-
}
-
/*看是否是需要动态分配次设备号*/
-
if (misc->minor == MISC_DYNAMIC_MINOR) {
-
/*
-
*#define DYNAMIC_MINORS 64 /* like dynamic majors */
-
*static unsigned char misc_minors[DYNAMIC_MINORS / 8];
-
*这里存在一个次设备号的位图,一共64位。下边是遍历每一位,
-
*如果这位为0,表示没有被占有,可以使用,为1表示被占用。
-
*/
-
int i = DYNAMIC_MINORS;
-
while (--i >= 0)
-
if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)
-
break;
-
if (i<0) {
-
mutex_unlock(&misc_mtx);
-
return -EBUSY;
-
}
-
/*得到这个次设备号*/
-
misc->minor = i;
-
}
-
/*设置位图中相应位为1*/
-
if (misc->minor < DYNAMIC_MINORS)
-
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
-
/*计算出设备号*/
-
dev = MKDEV(MISC_MAJOR, misc->minor);
-
/*在/dev下创建设备节点,这就是有些驱动程序没有显式调用device_create,却出现了设备节点的原因*/
-
misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
-
"%s", misc->name);
-
if (IS_ERR(misc->this_device)) {
-
err = PTR_ERR(misc->this_device);
-
goto out;
-
}
-
-
/*
-
* Add it to the front, so that later devices can "override"
-
* earlier defaults
-
*/
-
/*将这个miscdevice添加到misc_list链表中*/
-
list_add(&misc->list, &misc_list);
-
out:
-
mutex_unlock(&misc_mtx);
-
return err;
-
}
|
misc_register:
匹配次设备号->找到一个没有占用的次设备号(如果需要动态分配的话)->计算设号->创建设备文-
miscdevice结构体添加到misc_list链表中。
4. misc子卸载函数
-
int misc_deregister(struct miscdevice *misc)
-
{
-
int i = misc->minor;
-
-
if (list_empty(&misc->list))
-
return -EINVAL;
-
-
mutex_lock(&misc_mtx);
-
/*在misc_list链表中删除miscdevice设备*/
-
list_del(&misc->list);
-
/*删除设备节点*/
-
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
-
if (i < DYNAMIC_MINORS && i>0) {
-
/*释放位图相应位*/
-
misc_minors[i>>3] &= ~(1 << (misc->minor & 7));
-
}
-
mutex_unlock(&misc_mtx);
-
return 0;
-
}
|
misc_deregister:
从mist_list中删除miscdevice->删除设备文件->位图位清零。
二、代码分析
1. 包含头文件:
#include <linux/miscdevice.h>
2. 定义混杂设备结构体以及实现相关的file_operation函数
3. 最后分别在init函数和exit函数中卸载
4. 编译测试
我们使用 ll /dev/key_misc 查看 其 设备的详细信息 可以发现其主设备号为10 次设备号110
然后 cat /proc/misc 查看当前混杂设备列表,发现我们的key_misc 110 当然还设有gpio dma等都使用混杂设备
本文部分知识点摘自 http://tomhibolu.iteye.com/blog/1214940
附驱动程序代码:
1 /******************************
2 misc device
3 *****************************/
4 #include <linux/module.h>
5 #include <linux/init.h>
6 #include <linux/kernel.h>
7 #include <linux/delay.h>
8 #include <linux/types.h>
9 #include <linux/ioctl.h>
10 #include <linux/gpio.h>
11 #include <linux/fs.h>
12 #include <linux/device.h>
13 #include <linux/miscdevice.h>
14
15 #define Driver_NAME "key_misc"
16 #define Driver_minor 110
17
18 /* 应用程序对设备文件/dev/key_query执行open(...)时,
19 * 就会调用key_open函数*/
20 static int key_open(struct inode *inode, struct file *file){
21 printk("<0>function open!\n");
22 return 0;
23 }
24 static int key_read(struct file *filp, char __user *buff, size_t count, loff_t *offp){
25 printk("<0>function read!\n");
26 return 0;
27 }
28 static ssize_t key_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos){
29 printk("<0>function write!\n");
30 return 1;
31 }
32
33 /* 这个结构是字符设备驱动程序的核心
34 * 当应用程序操作设备文件时所调用的open、read、write等函数,
35 * 最终会调用这个结构中指定的对应函数
36 */
37 static struct file_operations key_fops = {
38 .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
39 .open = key_open,
40 .read = key_read,
41 .write = key_write,
42 };
43
44 /*混杂设备结构体*/
45 static struct miscdevice key_misc = {
46 .minor = Driver_minor, //次设备号
47 .name = Driver_NAME, //混杂设备名字
48 .fops = &key_fops, //操作指针
49 };
50
51 /*
52 * 执行insmod命令时就会调用这个函数
53 */
54 static int __init key_init(void)
55 {
56 printk("<0>\nHello,this is %s module!\n\n",Driver_NAME);
57
58 misc_register(&key_misc);
59 return 0;
60 }
61
62 /*
63 * 执行rmmod命令时就会调用这个函数
64 */
65 static void __exit key_exit(void)
66 {
67 printk("<0>\nGoodbye,%s!\n\n",Driver_NAME);
68
69 misc_register(&key_misc);
70 }
71
72 /* 这两行指定驱动程序的初始化函数和卸载函数 */
73 module_init(key_init);
74 module_exit(key_exit);
75
76 /* 描述驱动程序的一些信息,不是必须的 */
77 MODULE_AUTHOR("Lover雪");
78 MODULE_VERSION("0.1.0");
79 MODULE_DESCRIPTION("IMX257 key Driver");
80 MODULE_LICENSE("GPL");
View Code
注: 程序中有一个错误: 在exit函数中应该为 misc_unregister ,此处写错了,大家记得改过来