内核的misc驱动框架详解:内核源码分析蜂鸣器驱动分析

Posted 正在起飞的蜗牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内核的misc驱动框架详解:内核源码分析蜂鸣器驱动分析相关的知识,希望对你有一定的参考价值。

1、什么是misc类设备

(1)misc类设备就是杂项类设备,那些功能不太复杂,不太好归类到已经创建好的类中,这些设备都可以归类到misc类设备。misc类设备就好像一个收纳盒子,不知道怎么归类的设备就归类到misc类设备里;
(2)misc类设备本质上是字符类设备,而且归类是建议不是强制,你想把设备归到哪一类设备都是可以的,只有按照相应驱动框架的注册方式去注册驱动,但是良好的归类习惯,能让人更容易理解你写的代码;

2、misc驱动框架的加载

static const struct file_operations misc_fops = 
	.owner		= THIS_MODULE,
	.open		= misc_open,
;

static int __init misc_init(void)

	int err;

#ifdef CONFIG_PROC_FS
	proc_create("misc", 0, NULL, &misc_proc_fops);
#endif

	//创建misc类
	misc_class = class_create(THIS_MODULE, "misc");
	err = PTR_ERR(misc_class);
	if (IS_ERR(misc_class))
		goto fail_remove;

	err = -EIO;

	//注册misc字符设备,主设备号是10,次设备号范围[0,255]
	if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
		goto fail_printk;
	misc_class->devnode = misc_devnode;
	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;


subsys_initcall(misc_init);

(1)CONFIG_PROC_FS宏:内核是否开启了proc文件系统,如果定义了该宏,会看到"/proc/misc"文件,里面记录了注册的misc类设备,可以用cat命令查看;
(2)创建了misc类,在sysfs目录下可以看到"/sys/class/misc"目录,将来misc类设备都放到该目录下;
(3)注册了名字为misc、主设备号是10的字符设备,设备节点的操作方法是misc_fops变量定义的,所以misc类设备的主设备号都是10;
(4)subsys_initcall(misc_init):将misc_init函数放到".initcall4.init"段,在内核启动过程中会自动调用,宏的分析参考博客:《内核加载驱动机制详解(module_init & module_exit)》

3、misc类设备驱动的注册

3.1、struct miscdevice结构体

struct miscdevice  
	int minor;	//次设备号
	const char *name;	//misc设备的名字
	const struct file_operations *fops;	//设备节点的操作方法
	struct list_head list;	//用来挂接的链表节点
	struct device *parent;	//设备的父节点
	struct device *this_device;
	const char *nodename;
	mode_t mode;
;
构建表格
minor次设备号
namemisc设备的名字,在./sys/class/misc目录中可以看到
fops设备节点的操作方法
list用来挂接的链表节点

3.2、misc驱动框架注册函数

int misc_register(struct miscdevice * misc)

	struct miscdevice *c;
	dev_t dev;
	int err = 0;

	INIT_LIST_HEAD(&misc->list);

	mutex_lock(&misc_mtx);

	//遍历已经注册的misc类设备,查看次设备号是否已经被注册
	list_for_each_entry(c, &misc_list, list) 
		if (c->minor == misc->minor) 
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		
	

	//如果此设备是MISC_DYNAMIC_MINOR,表示自动分配次设备号
	if (misc->minor == MISC_DYNAMIC_MINOR) 
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
		if (i >= DYNAMIC_MINORS) 
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		
		misc->minor = DYNAMIC_MINORS - i - 1;
		set_bit(i, misc_minors);
	

	//构建主次设备号
	dev = MKDEV(MISC_MAJOR, misc->minor);

	//在misc类下面创建设备
	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;
	

	//添加到内核管理misc类设备的链表中
	list_add(&misc->list, &misc_list);
 out:
	mutex_unlock(&misc_mtx);
	return err;

(1)将misc驱动中构建好的struct miscdevice结构体通过misc_register()函数注册到内核的misc驱动框架中;
(2)遍历已经注册的misc类设备,查看次设备号是否已经被注册;
(3)如果此设备是255,misc驱动框架将自动分配次设备号;
(4)构建主次设备号;
(5)在sysfs中创建misc类的设备;

3.3、misc设备的次设备号分配问题

#define DYNAMIC_MINORS 64 /* like dynamic majors */
static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);

int misc_register(struct miscdevice * misc)

	······
	//如果此设备是MISC_DYNAMIC_MINOR,表示自动分配次设备号
	if (misc->minor == MISC_DYNAMIC_MINOR) 
		int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS); //返回位图中bit值第一个为0的下标
		if (i >= DYNAMIC_MINORS) 
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		
		
		//换算得到次设备号
		misc->minor = DYNAMIC_MINORS - i - 1;
		
		//根据申请到的次设备号,位图中将对应的bit值置1
		set_bit(i, misc_minors);
	
	
	······

(1)在注册misc驱动时,将次设备号填255,就代表由misc驱动框架自动分配次设备号;
(2)自动分配的次设备号范围是[0,63],并且是按从大到小开始分配的,具体分析上面的代码;
(3)对位图不熟悉的参考博客:https://blog.csdn.net/weixin_42031299/article/details/124853167;

3.4、内核如何记录注册的misc类驱动

int misc_register(struct miscdevice * misc)

	······
	//遍历已经注册的misc类设备,查看次设备号是否已经被注册
	list_for_each_entry(c, &misc_list, list) 
		if (c->minor == misc->minor) 
			mutex_unlock(&misc_mtx);
			return -EBUSY;
		
	
	······
	//将misc类设备的struct miscdevice结构体添加到misc_list链表中
	list_add(&misc->list, &misc_list);
	······



static int misc_open(struct inode * inode, struct file * file)

	······
	//根据次设备号在注册的misc驱动程序中进行匹配
	list_for_each_entry(c, &misc_list, list) 
		if (c->minor == minor) 
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		
	
	······

(1)在内核中用链表来管理misc类设备,在注册时添加到链表,在open操作misc设备时进行遍历链表;
(2)在misc设备的管理链表中,通过次设备号来唯一的标记设备;

4、misc设备的操作分析

4.1、共用主设备号的影响

(1)在加载misc驱动框架的时候,调用register_chrdev()函数注册了主设备号为10、名字是misc的字符驱动,我们后续通过misc_register()函数
注册的misc驱动都是主设备号相同,次设备号不同;
(2)主设备号相同的设备,都是共用struct file_operations结构体的,也就是misc_fops变量,这里面只实现了open方法,所以misc类的设备节点的open方法都是同一个函数;
(3)不同的misc设备会有不同的read、write等方法,明显不能共用struct file_operations结构体,并且misc_fops变量还只实现了open方法;
(4)所以misc_fops变量的open方法,肯定会想办法将设备节点的fops操作方法,替换成misc驱动的fops;

4.2、如何通过次设备号区分各个misc设备

static int misc_open(struct inode * inode, struct file * file)

	//解析处次设备号
	int minor = iminor(inode);
	······
	//根据次设备号在注册的misc驱动程序中进行匹配
	list_for_each_entry(c, &misc_list, list) 
		if (c->minor == minor) 
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		
	
	······

(1)在调用misc_register()注册misc驱动时,函数内部调用了device_create()函数创建了"/dev/xxx"设备节点,设备节点的inode结构体里就保存了主次设备号,在open打开设备节点时,内核是调用的misc_open()函数,会传入设备节点的struct inode结构体;
(2)根据解析得到的此设备号,在记录misc设备的链表中找到之前注册的struct miscdevice结构体;
(3)主次设备号更详细的内容参见博客:https://blog.csdn.net/weixin_42031299/article/details/124557522;

4.3、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;

	mutex_lock(&misc_mtx);

	//根据次设备号在注册的misc驱动程序中进行匹配,得到注册的驱动的fops
	list_for_each_entry(c, &misc_list, list) 
		if (c->minor == minor) 
			new_fops = fops_get(c->fops);		//获取匹配到的misc驱动的fops
			break;
		
	

	//上一步中没有查找到,尝试手动加载对应主次设备号的驱动,然后再次去匹配
	if (!new_fops) 
		mutex_unlock(&misc_mtx);
		request_module("char-major-%d-%d", MISC_MAJOR, minor);	//尝试手动加载对应主次设备号的驱动
		mutex_lock(&misc_mtx);

		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;
	
	//将设备节点文件的fops替换成对应misc设备的fops
	file->f_op = new_fops;	
	if (file->f_op->open) 
	
		//将misc驱动框架的struct miscdevice结构体保存在struct file结构体中,后续会用
		file->private_data = c;	
		
		//调用注册的misc驱动的fops的open方法
		err=file->f_op->open(inode,file);	//调用新的fops的open方法
		if (err) 
			fops_put(file->f_op);
			file->f_op = fops_get(old_fops);
		
	
	
	fops_put(old_fops);
fail:
	mutex_unlock(&misc_mtx);
	return err;

(1)解析出次设备号,然后根据次设备号去找到注册驱动时的struct miscdevice结构体;
(2)将设备节点文件的struct file结构体的f_op替换成misc驱动struct miscdevice结构体的fops;
(3)调用misc驱动真正的open函数;

4.4、read、write等操作

经过open函数的替换,后续的read、write等操作方法,都是调用我们在注册misc驱动时,填充的struct miscdevice结构体里的fops,
这个是和具体硬件相关的,后面以蜂鸣器的驱动具体分析;

5、X210开发板的蜂鸣器驱动分析

后续补充

创作打卡挑战赛 赢取流量/现金/CSDN周边激励大奖

以上是关于内核的misc驱动框架详解:内核源码分析蜂鸣器驱动分析的主要内容,如果未能解决你的问题,请参考以下文章

linux驱动开发之misc设备与蜂鸣器驱动

hisi3516dv300芯片基于hwmon驱动框架的温度获取驱动源码分析

使用itop4412开发板单独编译驱动模块

linux设备驱动之misc驱动框架源码分析

Linux 帧缓冲子系统详解:LCD介绍framebuffer驱动框架LCD驱动源码分析

LCD驱动源码分析(s3cfb.c)