以下内容源于朱有鹏嵌入式课程的学习与整理,如有其侵权请告知删除。
前言
由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_FSproc_create("misc", 0, NULL, &misc_proc_fops); #endifmisc_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 vcs10 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_2ret = gpio_request(S5PV210_GPD0(2), "GPD0");//向gpiolib申请gpioif(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) //这里参数为1static 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/16tcfg1 &= ~(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设置为inputs3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(0)); }
(1)此函数用于关闭蜂鸣器。