12.5 misc设备驱动
Linux 包含许多的设备驱动类型,不管分类有多细,总会有些漏网的,这就是经常说到的“其他的”、“等等”。在 Linux 里面,把无法归类的五花八门的设备定义为混杂设备(用miscdevice 结构体描述)。Linux 内核所提供的 miscdevice 有很强的包容性,如 NVRAM(非易失随机存取存储器)、看门狗、DS1286 等实时时钟、字符 LCD、AMD 768 随机数发生器等,体现了大杂烩的本意。
miscdevice 共享一个主设备号 MISC_MAJOR(10),但次设备号不同。所有的 miscdevice设备形成一个链表,对设备访问时内核根据次设备号查找对应的 miscdevice 设备,然后调用其file_operations 结构体中注册的文件操作接口进行操作。在内核中,用 struct miscdevice 结构体表征 miscdevice 设备,这个结构体的定义如代码清单12.20 所示。
代码清单 12.20 miscdevice 结构体
include/linux/miscdevice.h
struct miscdevice {
int minor; /* 次设备号 */
const char *name; /* 名称 */
const struct file_operations *fops; /* 文件操作函数集的指针*/
struct list_head list;
struct device *parent;
struct device *this_device;
const struct attribute_group **groups;
const char *nodename;
umode_t mode;
};
miscdevice 在本质上仍然属于字符设备,只是被增加了一层封装,因此其驱动的主体工作还是 file_operations 的成员函数。代码清单 12.21 则给出了源代码 drivers/char/nvram.c 所实现NVRAM 驱动的 miscdevice 和 file_operations 实例。
代码清单 12.21 NVRAM(非易失随机存取存储器) 设备结构体
static const struct file_operations nvram_fops = {
.owner = THIS_MODULE,
.llseek = nvram_llseek,
.read = nvram_read,
.write = nvram_write,
.unlocked_ioctl = nvram_ioctl,
.open = nvram_open,
.release = nvram_release,
};
static struct miscdevice nvram_dev = {
NVRAM_MINOR,
"nvram",
&nvram_fops
};
对misddevice 的注册和注销分别通过如下两个API 完成:
include/linux/miscdevice.h
int misc_register(struct miscdevice * misc);
drivers/char/misc.c
/**
* misc_register - register a miscellaneous device
* @misc: device structure
*
* Register a miscellaneous device with the kernel. If the minor
* number is set to %MISC_DYNAMIC_MINOR a minor number is assigned
* and placed in the minor field of the structure. For other cases
* the minor number requested is used.
*
* The structure passed is linked into the kernel and may not be
* destroyed until it has been unregistered.
*
* A zero is returned on success and a negative errno code for
* failure.
*/
int misc_register(struct miscdevice * misc)
{
dev_t dev;
int err = 0;
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
if (i >= DYNAMIC_MINORS) {
err = -EBUSY;
goto out;
}
misc->minor = DYNAMIC_MINORS - i - 1;
set_bit(i, misc_minors);
} else {
struct miscdevice *c;
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
err = -EBUSY;
goto out;
}
}
}
dev = MKDEV(MISC_MAJOR, misc->minor);
misc->this_device = device_create(misc_class, misc->parent, dev,
misc, "%s", misc->name);
if (IS_ERR(misc->this_device)) {
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
void misc_deregister(struct miscdevice *misc);
/**
* misc_deregister - unregister a miscellaneous device
* @misc: device to unregister
*
* Unregister a miscellaneous device that was previously
* successfully registered with misc_register(). Success
* is indicated by a zero return, a negative errno code
* indicates an error.
*/
int misc_deregister(struct miscdevice *misc)
{
int i = DYNAMIC_MINORS - misc->minor - 1;
if (WARN_ON(list_empty(&misc->list)))
return -EINVAL;
mutex_lock(&misc_mtx);
list_del(&misc->list);
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
mutex_unlock(&misc_mtx);
return 0;
}
查看 drivers/char/nvram.c 的模块加载和卸载函数可知,其在加载的时候调用了“misc_register(&nvram_dev);”,而在模块卸载时调用了“misc_deregister(&nvram_dev);”。
混杂设备驱动实例
1、驱动源代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
static int test_miscdev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "----test_miscdev_open!----\n");
return 0;
}
static int test_miscdev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "----test_miscdev_release!----\n");
return 0;
}
static const struct file_operations test_miscdev_fops = {
.owner = THIS_MODULE,
.open = test_miscdev_open,
.release = test_miscdev_release,
};
static struct miscdevice test_miscdev = {
MISC_DYNAMIC_MINOR,
"misc_test",
&test_miscdev_fops
};
static int __init test_misdev_init(void)
{
int ret;
ret = misc_register(&test_miscdev);
if (ret) {
printk(KERN_ERR "test_misdev: can't misc_register on minor=%d\n",
MISC_DYNAMIC_MINOR);
goto out;
}
printk(KERN_INFO "----test_misdev_init!----\n");
return 0;
out:
return ret;
}
static void __exit test_misdev_exit(void)
{
misc_deregister(&test_miscdev);
printk(KERN_INFO "----test_misdev_exit!----\n");
}
module_init(test_misdev_init);
module_exit(test_misdev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("[email protected]");
MODULE_DESCRIPTION("a miscdev demo");
2、Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
obj-m := misc_dev_test.o
endif
3、测试
1)ls -l /dev/misc_test
crw------- 1 root root 10, 55 Jun 13 09:40 /dev/misc_test
2)lsmod | grep misc_dev_test
misc_dev_test 16384 0
3)ls -l /sys/class/misc/ | grep misc_test
lrwxrwxrwx 1 root root 0 Jun 13 09:41 misc_test -> ../../devices/virtual/misc/misc_test
4)cat /proc/misc | grep misc_test
55 misc_test