以下内容源于朱有鹏嵌入式课程的学习与整理,如有其侵权请告知删除。
由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类设备驱动框架本身也是一个模块(这意味着可裁剪),内核启动时自动加载。
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,//分析见下文 };
驱动框架设计了杂散设备的注册接口: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)主设备号和次设备号的作用和区分
主设备号表示类,次设备号表示某具体设备。
之前说到,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函数。
通过"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
(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,它也属于模块化设计。
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
该函数的内容如下:
// 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——板载蜂鸣器驱动测试
硬件操作有关的代码,是指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)此函数用于关闭蜂鸣器。