misc类设备驱动3——misc驱动框架源码分析(核心层+具体操作层)

以下内容源于朱有鹏嵌入式课程的学习与整理,如有其侵权请告知删除。

前言

misc类设备驱动1——misc类设备的简介可知,misc类设备驱动框架包括以下两部分:

1、内核开发者实现的部分

drivers/char/misc.c文件主要包括2个关键点:类的创建、开放给驱动开发者的接口。


2、驱动工程师实现的部分

比如蜂鸣器驱动x210_kernel\drivers\char\buzzer\x210-buzzer.c文件。

下面将对misc.c文件、x210-buzzer.c文件进行分析。

一、驱动核心层misc.c文件的分析

1、misc_init()函数

misc类设备驱动框架本身也是一个模块(这意味着可裁剪),内核启动时自动加载。

subsys_initcall(misc_init);

misc_init()函数内容如下:

static int __init misc_init(void)
{
	int err;

#ifdef CONFIG_PROC_FS
	proc_create("misc", 0, NULL, &misc_proc_fops);
#endif
	misc_class = class_create(THIS_MODULE, "misc");//注册/sys/class/misc类
	err = PTR_ERR(misc_class);
	if (IS_ERR(misc_class))
		goto fail_remove;

	err = -EIO;         //10   使用老接口注册字符设备驱动(主设备号10)
	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)该函数的主要工作内容

使用class_create()函数注册了misc类,因此可以在/sys/class/目录下找到misc目录。

使用register_chrdev()这个老接口字符设备驱动注册函数,注册了主设备号为10的字符设备驱动。因此"cat /proc/devices"时,字符设备列表处显示有“10 misc”。

存疑:/dev/buzzer这个设备文件是在哪里在什么时候创建的?这个设备文件在x210-buzzer.c文件中的misc_register()函数中创建。

(2)misc_fops变量的定义

使用register_chrdev()这个老接口字符设备驱动注册函数时,参数中有misc_fops这个变量,其定义如下。其中misc_open()函数的分析下文。

static const struct file_operations misc_fops = {
	.owner		= THIS_MODULE,
	.open		= misc_open,//分析见下文
};


2、misc_register()函数

驱动框架设计了杂散设备的注册接口:misc_register()。驱动工程师借助misc类设备驱动框架编写驱动时,只需要调用misc_register()函数注册自己的设备即可,其余均不用管。

misc_register()函数内容如下:

int misc_register(struct miscdevice * misc)
{
	struct miscdevice *c;
	dev_t dev;
	int err = 0;

	INIT_LIST_HEAD(&misc->list);//(1)misc_list链表的作用

	mutex_lock(&misc_mtx);
    //遍历内核链表,查看该次设备号是否已经被占用
	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) {//这个表示自动分配次设备号
		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->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;
}

(1)struct miscdevice 结构体

该结构体是对一个杂散设备的抽象,或者说该结构体的变量表示一个杂散设备。

此结构体定义在x210_kernel\include\linux\miscdevice.h文件中,内容如下:

struct miscdevice  {
	int minor; //杂散设备的主设备号规定为10,可变的仅有次设备号
	const char *name; //杂散设备的名字
	const struct file_operations *fops;//文件操作函数指针
	struct list_head list;
	struct device *parent;
	struct device *this_device;
	const char *nodename;
	mode_t mode;
};

(2)misc_list链表的作用

在misc.c文件开头处,定义了一个misc_list链表,即“ static LIST_HEAD(misc_list); ”,用来记录所有内核中注册了的杂散类设备(P.s. 之前的字符设备,是固定大小(255)的数组)。

当我们向内核注册一个misc类设备时,内核就会向misc_list链表中插入一个节点。当使用cat /proc/misc打印出信息时,其实就是遍历此链表。

static LIST_HEAD(misc_list);

/*
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)

因此上面式子展开后等价于
static struct list_head misc_list = { &(misc_list), &(misc_list) }

*/

3)主设备号和次设备号的作用和区分

主设备号表示类,次设备号表示某具体设备。

3、misc_open()函数

之前说到,misc_fops这个变量中的成员open函数,指向misc_open()函数。

static const struct file_operations misc_fops = {
	.owner		= THIS_MODULE,
	.open		= misc_open,//分析见下文
};

misc_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);
	
	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);

		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) {
		file->private_data = c;
		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);
	return err;
}

misc_open()函数最终映射到x210_buzzer.c中的open函数

4、misc设备在proc文件系统下的展现

通过"cat /proc/devices"时,字符设备列表处显示有“10 misc”。如下所示。

[root@xjh proc]# cat devices 
Character devices:
  1 mem
//省略部分代码
  7 vcs
 10 misc  //这里出现主设备号为10,名字为misc的字符设备
 13 input
//省略部分代码

Block devices:
//省略部分代码
254 device-mapper

5、内核互斥锁

(1)内核用于防止竞争状态的手段包括:原子访问、自旋锁、互斥锁、信号量。

关于这些手段的介绍,可见博客:内核中防止竞争状态的手段 - 涛少& - 博客园

(2)static DEFINE_MUTEX(misc_mtx);

在misc.c文件开头处有如下代码:

static DEFINE_MUTEX(misc_mtx);

DEFINE_MUTEX这个宏包含在x210_kernel\include\linux\mutex.h文件中,如下:

#define DEFINE_MUTEX(mutexname) \
	struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

/*
#define __MUTEX_INITIALIZER(lockname) \
		{ .count = ATOMIC_INIT(1) \
		, .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
		, .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
		__DEBUG_MUTEX_INITIALIZER(lockname) \
		__DEP_MAP_MUTEX_INITIALIZER(lockname) }
*/

所以static DEFINE_MUTEX(misc_mtx);展开后成为:

struct mutex misc_mtx = {        
         .count = ATOMIC_INIT(1) ,
         .wait_lock = __SPIN_LOCK_UNLOCKED(misc_mtx.wait_lock) ,
         .wait_list = LIST_HEAD_INIT(misc_mtx.wait_list) ,   
     };

(3)上锁mutex_lock和解锁mutex_unlock

要访问某资源时,要给该资源上锁;使用完后,要解锁。当某进程想访问已经被上锁的资源时,会休眠,直到其他进程解锁后此进程才能访问该资源。


三、具体操作层x210-buzzer.c文件的分析

1、dev_init()函数

蜂鸣器归类为杂项字符设备,其驱动文件是x210-buzzer.c,它也属于模块化设计。

module_init(dev_init);

该函数的内容如下:

static int __init dev_init(void)
{
	int ret;

	init_MUTEX(&lock);
	ret = misc_register(&misc);
	
    //接下来对硬件,即蜂鸣器进行初始化
    //向gpiolib申请gpio,并设置成上拉、输出模式,输出值为0 
	/* GPD0_2 (PWMTOUT2) *///由原理图得知蜂鸣器的接口是GPD0_2
	ret = gpio_request(S5PV210_GPD0(2), "GPD0");//向gpiolib申请gpio
	if(ret)
		printk("buzzer-x210: request gpio GPD0(2) fail");
		
	s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);//设置成上拉
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));//设置成输出模式
	gpio_set_value(S5PV210_GPD0(2), 0);//设置输出值为0 

	printk ("x210 "DEVICE_NAME" initialized\n");
    	return ret;
}

(1)互斥锁初始化:init_MUTEX(&lock)

互斥锁其实就是计数值为1的信号量,所以sema_init(sem, 1)中参数设为1。

有待深入。open和release函数里面也有互斥锁。

init_MUTEX(&lock);

/*
#define init_MUTEX(sem)   sema_init(sem, 1) //这里参数为1

static inline void sema_init(struct semaphore *sem, int val)
{
    static struct lock_class_key __key;
    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
*/

(2) misc_register(&misc)中的变量misc

变量misc的数据类型是struct miscdevice,该变量定义如下。

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,//其值为255,表示自动分配次设备号
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

(3)gpio_request

(4)printk

2、x210_pwm_ioctl()函数

该函数的内容如下:

// PWM:GPF14->PWM0
static int x210_pwm_ioctl(struct inode *inode, struct file *file, \
                                    unsigned int cmd, unsigned long arg)
{
	switch (cmd) 
	{
		case PWM_IOCTL_SET_FREQ:
			printk("PWM_IOCTL_SET_FREQ:\r\n");
			if (arg == 0)
				return -EINVAL;
			PWM_Set_Freq(arg);
			break;

		case PWM_IOCTL_STOP:
		default:
			printk("PWM_IOCTL_STOP:\r\n");
			PWM_Stop();
			break;
	}

	return 0;
}
//上面这两个宏按理定义在头文件中,然后让应用程序包含
//但是这个文件却直接定义在开头了!不规范!

(1)为什么会有ioctl这个函数?

ioctl函数是对设备进行输入与输出,既然如此,为何不用read和write函数呢?其实只有read和write函数也是可以的,之前的驱动没有ioctl函数也可以工作。但read和write函数会导致应用层和驱动层的交互麻烦。比如写驱动的人在驱动中定义亮灭,应用层的人怎么知道呢?使用ioctr后,通过命令码的名字就可以明确知道命令码含义。

(2)ioctl的使用方法

在应用层编写代码,见misc类设备驱动0——板载蜂鸣器驱动测试

3、硬件操作有关的代码

硬件操作有关的代码,是指PWM_Set_Freq()函数、PWM_Stop()函数。

PWM_Set_Freq()函数

// TCFG0在Uboot中设置,这里不再重复设置
// Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1
//                     =66M/16/16
// TCFG0 = tcnt = (pclk/16/16)/freq;
// PWM0输出频率Foutput =Finput/TCFG0= freq
static void PWM_Set_Freq( unsigned long freq )
{
	unsigned long tcon;
	unsigned long tcnt;
	unsigned long tcfg1;

	struct clk *clk_p;
	unsigned long pclk;

	//unsigned tmp;
	
	//设置GPD0_2为PWM输出
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));

	tcon = __raw_readl(S3C2410_TCON);
	tcfg1 = __raw_readl(S3C2410_TCFG1);

	//mux = 1/16
	tcfg1 &= ~(0xf<<8);
	tcfg1 |= (0x4<<8);
	__raw_writel(tcfg1, S3C2410_TCFG1);
	
	clk_p = clk_get(NULL, "pclk");
	pclk  = clk_get_rate(clk_p);

	tcnt  = (pclk/16/16)/freq;
	
	__raw_writel(tcnt, S3C2410_TCNTB(2));
	__raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%

	tcon &= ~(0xf<<12);
	tcon |= (0xb<<12);	//disable deadzone, auto-reload, inv-off, 
                        //update TCNTB0&TCMPB0, start timer 0
	__raw_writel(tcon, S3C2410_TCON);
	
	tcon &= ~(2<<12);			//clear manual update bit
	__raw_writel(tcon, S3C2410_TCON);
}

(1)此函数用于打开蜂鸣器并设置想要的频率。

(2)蜂鸣器分为有源蜂鸣器,无源蜂鸣器。有源蜂鸣器可以用PWM信号驱动并改变频率,无源蜂鸣器好像不可以?这里是有源蜂鸣器。

PWM_Stop()函数

void PWM_Stop( void )
{
	//将GPD0_2设置为input
	s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0));	
}

(1)此函数用于关闭蜂鸣器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天糊土

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值