[1] 为什么要引入misc
- 在嵌入式系统中外设的种类五花八门,有很多外设无法进行系统性的分类;就把这一部分无法分类的驱动全部归类到misc驱动当中;
- 字符设备驱动不管静态还是动态分配都会消耗一个主设备号,misc驱动也是字符设备驱动,有一个固定的主设备号10,这样就不会消耗主设备号;
- misc设备驱动使用简单,注册misc驱动的时候会去维护一个misc设备链表,通过次设备号查找注册到misc链表中的fops结构体,进一步调用到我们外设对应的操作方法;
[2] misc源码分析(kernel_5_10)
- misc框架代码比较少,代码路径为:kernel/drivers/char/misc.c,下面是源码驱动的注释;
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
/*
* Head entry for the doubly linked miscdevice list
*/
static LIST_HEAD(misc_list);
static DEFINE_MUTEX(misc_mtx);
/* 这部分表面最大可以动态分配的misc设备数量为128个 */
#define DYNAMIC_MINORS 128 /* like dynamic majors */
/* 这个宏定义展开就是 */
/* static unsigned long misc_minors[4] */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
/* 这部分用于在proc文件系统中手动创建misc设备 */
#ifdef CONFIG_PROC_FS
static void *misc_seq_start(struct seq_file *seq, loff_t *pos)
{
mutex_lock(&misc_mtx);
return seq_list_start(&misc_list, *pos);
}
static void *misc_seq_next(struct seq_file *seq, void *v, loff_t *pos)
{
return seq_list_next(v, &misc_list, pos);
}
static void misc_seq_stop(struct seq_file *seq, void *v)
{
mutex_unlock(&misc_mtx);
}
static int misc_seq_show(struct seq_file *seq, void *v)
{
const struct miscdevice *p = list_entry(v, struct miscdevice, list);
seq_printf(seq, "%3i %s\n", p->minor, p->name ? p->name : "");
return 0;
}
static const struct seq_operations misc_seq_ops = {
.start = misc_seq_start,
.next = misc_seq_next,
.stop = misc_seq_stop,
.show = misc_seq_show,
};
#endif
/* 打开misc设备文件 */
static int misc_open(struct inode *inode, struct file *file)
{
/* 通过inode号计算出子设备号 */
int minor = iminor(inode);
struct miscdevice *c;
int err = -ENODEV;
const struct file_operations *new_fops = NULL;
mutex_lock(&misc_mtx);
/* 遍历misc_list链表,通过子设备号查找到对应的misc设备 */
list_for_each_entry(c, &misc_list, list)
{
if (c->minor == minor)
{
/* 获取对应的fops结构体 */
new_fops = fops_get(c->fops);
break;
}
}
/* 如果上面没有获取到子设备号对应的fops结构体 */
if (!new_fops)
{
mutex_unlock(&misc_mtx);
/* 这里创建一个进程去用户空间去加载这个module */
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);
/* 再次遍历链表,如果还没有找到就是misc设备没有注册fops,不支持open */
list_for_each_entry(c, &misc_list, list)
{
if (c->minor == minor)
{
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops)
goto fail;
}
/*
* Place the miscdevice in the file's
* private_data so it can be used by the
* file operations, including f_op->open below
*/
/* 保存查找到的misc设备结构体到file结构体中私有数据中,方便后面read等等操作使用 */
file->private_data = c;
err = 0;
/* 替换fops为查找到的misc设备驱动注册的fops,这里之后misc框架的fops就完成使命了 */
replace_fops(file, new_fops);
/* 如果定义了open回调,就调用misc设备驱动中注册的open函数 */
if (file->f_op->open)
err = file->f_op->open(inode, file);
fail:
mutex_unlock(&misc_mtx);
return err;
}
static struct class *misc_class;
static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
.llseek = noop_llseek,
};
/* 导出misc_register函数,用于其它misc设备调用 */
/* misc设备中会先创建并且填充 struct miscdevice结构体 */
int misc_register(struct miscdevice *misc)
{
dev_t dev;
int err = 0;
/* 先判断misc设备驱动中有没有指定子设备号 */
bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
/* 调用内核链表接口,初始化misc链表头 */
INIT_LIST_HEAD(&misc->list);
/* 分配子设备号的过程中不能被打断 */
mutex_lock(&misc_mtx);
/* 如果没有指定子设备号 */
if (is_dynamic)
{
/* 在128bit中找一个没有被使用的位 */
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;
/* 遍历misc list查看是否有设备使用这个子设备号 */
list_for_each_entry(c, &misc_list, list)
{
/* 如果有返回设备繁忙 */
if (c->minor == misc->minor)
{
err = -EBUSY;
goto out;
}
}
}
/* 不管动态还是手动指定,上述流程走完使用misc主设备号和设备的子设备号组成一个设备号 */
dev = MKDEV(MISC_MAJOR, misc->minor);
/* 创建字符设备device,注册设备节点文件 */
misc->this_device =
device_create_with_groups(misc_class, misc->parent, dev,
misc, misc->groups, "%s", misc->name);
if (IS_ERR(misc->this_device))
{
/* 如果注册device失败,释放掉被占用的位,后面可以被其它misc设备驱动使用 */
if (is_dynamic)
{
int i = DYNAMIC_MINORS - misc->minor - 1;
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
/* 清除当前misc设备的子设备号 */
misc->minor = MISC_DYNAMIC_MINOR;
}
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
/* 完成上述操作之后,插入到misc_list链表中 */
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
EXPORT_SYMBOL(misc_register);
void misc_deregister(struct miscdevice *misc)
{
/* 计算出是该misc设备使用的是哪一位 */
int i = DYNAMIC_MINORS - misc->minor - 1;
/* 判断是否已经register过,如果没有register不能执行deregister */
if (WARN_ON(list_empty(&misc->list)))
return;
mutex_lock(&misc_mtx);
/* 在链表中删除这个设备 */
list_del(&misc->list);
/* 删除device */
device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
/* 释放占用的动态申请的bit位 */
if (i < DYNAMIC_MINORS && i >= 0)
clear_bit(i, misc_minors);
mutex_unlock(&misc_mtx);
}
EXPORT_SYMBOL(misc_deregister);
static char *misc_devnode(struct device *dev, umode_t *mode)
{
struct miscdevice *c = dev_get_drvdata(dev);
if (mode && c->mode)
*mode = c->mode;
if (c->nodename)
return kstrdup(c->nodename, GFP_KERNEL);
return NULL;
}
static int __init misc_init(void)
{
int err;
struct proc_dir_entry *ret;
/* 这里创建proc文件系统调试接口,用于通过proc接口手动创建misc设备 */
ret = proc_create_seq("misc", 0, NULL, &misc_seq_ops);
/* 创建misc类,后面创建的misc设备都是这个类 */
misc_class = class_create(THIS_MODULE, "misc");
err = PTR_ERR(misc_class);
if (IS_ERR(misc_class))
goto fail_remove;
err = -EIO;
/* 向链表中注册misc的fops结构体 */
if (register_chrdev(MISC_MAJOR, "misc", &misc_fops))
goto fail_printk;
misc_class->devnode = misc_devnode;
return 0;
fail_printk:
pr_err("unable to get major %d for misc devices\n", MISC_MAJOR);
class_destroy(misc_class);
fail_remove:
if (ret)
remove_proc_entry("misc", NULL);
return err;
}
subsys_initcall(misc_init);
[3] misc驱动编程接口
- misc框架使用起来比较简单,编程结构较少,按照上面的分析只需要填充fops(操作方法结构体)、name(名称)、minor(子设备号);
/* 头文件 */
include/linux/miscdevice.h
/* 注册misc设备 */
int misc_register(struct miscdevice *misc);
/* 取消注册misc设备 */
void misc_deregister(struct miscdevice *misc);
[4] misc驱动实验
- 实验平台临滴科技出品的RK3568,本次实验较为简单,相关代码如下所示;
# 编译之前需要先设置交叉编译工具链
export ARCH=arm64
export CROSS_COMPILE=aarch64-none-linux-gnu-
export PATH=$PATH:SDKPATH/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin
# 之后执行检查当前环境是否设置成功
aarch64-none-linux-gnu-gcc -v
/* misc驱动 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
int test_open(struct inode *inode, struct file *file)
{
pr_info("open misc device file!!!\n");
return 0;
}
int test_release(struct inode *inode, struct file *file)
{
pr_info("misc device closed!!!\n");
return 0;
}
static struct file_operations testops = {
.open = test_open,
.release = test_release,
};
static struct miscdevice testdev = {
.name = "neardi",
.minor = MISC_DYNAMIC_MINOR, /* dymic minor */
.fops = &testops,
};
static int __init misc_test_init(void)
{
int ret;
ret = misc_register(&testdev);
if (ret < 0)
pr_err("misc dev register failed!!\n");
return 0;
}
static void __exit misc_test_exit(void)
{
misc_deregister(&testdev);
}
module_init(misc_test_init);
module_exit(misc_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wilson.yang@nearditech");
# 独立于kernel或者放在kernel中编译moudle
PWD := $(shell pwd)
KDIR := $(PWD)/../../kernel
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
obj-m += misc_test.o
/* 应用层代码 */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, const char **argv)
{
int fd;
fd = open("/dev/neardi", O_RDWR);
if (fd < 0) {
printf("open misc device node failed\n");
return -1;
}
close(fd);
return 0;
}
- 实验现象,加载成功之后出现/dev/neardi设备文件节点,编译执行应用程序,dmesg有打印信息;
root@LPA3568:/home/neardi# insmod misc_test.ko
root@LPA3568:/home/neardi# ls /dev/neardi
/dev/neardi
root@LPA3568:/home/neardi# dmesg
[ 196.419598] open misc device file!!!
[ 196.419636] misc device closed!!!