• misc类设备驱动2——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()函数内容如下:

    1. static int __init misc_init(void)
    2. {
    3. int err;
    4. #ifdef CONFIG_PROC_FS
    5. proc_create("misc", 0, NULL, &misc_proc_fops);
    6. #endif
    7. misc_class = class_create(THIS_MODULE, "misc");//注册/sys/class/misc类
    8. err = PTR_ERR(misc_class);
    9. if (IS_ERR(misc_class))
    10. goto fail_remove;
    11. err = -EIO; //10 使用老接口注册字符设备驱动(主设备号10)
    12. if (register_chrdev(MISC_MAJOR,"misc",&misc_fops))
    13. goto fail_printk;
    14. misc_class->devnode = misc_devnode;
    15. return 0;
    16. fail_printk:
    17. printk("unable to get major %d for misc devices\n", MISC_MAJOR);
    18. class_destroy(misc_class);
    19. fail_remove:
    20. remove_proc_entry("misc", NULL);
    21. return err;
    22. }
    23. 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()函数的分析下文。

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


    2、misc_register()函数

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

    misc_register()函数内容如下:

    1. int misc_register(struct miscdevice * misc)
    2. {
    3. struct miscdevice *c;
    4. dev_t dev;
    5. int err = 0;
    6. INIT_LIST_HEAD(&misc->list);//(1)misc_list链表的作用
    7. mutex_lock(&misc_mtx);
    8. //遍历内核链表,查看该次设备号是否已经被占用
    9. list_for_each_entry(c, &misc_list, list) {
    10. if (c->minor == misc->minor) {//次设备号已经被占用
    11. mutex_unlock(&misc_mtx);
    12. return -EBUSY;
    13. }
    14. }
    15. if (misc->minor == MISC_DYNAMIC_MINOR) {//这个表示自动分配次设备号
    16. int i = find_first_zero_bit(misc_minors, DYNAMIC_MINORS);
    17. if (i >= DYNAMIC_MINORS) {
    18. mutex_unlock(&misc_mtx);
    19. return -EBUSY;
    20. }
    21. misc->minor = DYNAMIC_MINORS - i - 1;
    22. set_bit(i, misc_minors);//这个表示我要占用此次设备号
    23. }
    24. dev = MKDEV(MISC_MAJOR, misc->minor);//合成设备号
    25. misc->this_device = device_create(misc_class, misc->parent, dev,
    26. misc, "%s", misc->name);//创建设备文件
    27. if (IS_ERR(misc->this_device)) {
    28. int i = DYNAMIC_MINORS - misc->minor - 1;
    29. if (i < DYNAMIC_MINORS && i >= 0)
    30. clear_bit(i, misc_minors);
    31. err = PTR_ERR(misc->this_device);
    32. goto out;
    33. }
    34. /*
    35. * Add it to the front, so that later devices can "override"
    36. * earlier defaults
    37. */
    38. list_add(&misc->list, &misc_list);
    39. out:
    40. mutex_unlock(&misc_mtx);
    41. return err;
    42. }

    (1)struct miscdevice 结构体

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

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

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

    (2)misc_list链表的作用

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

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

    1. static LIST_HEAD(misc_list);
    2. /*
    3. #define LIST_HEAD_INIT(name) { &(name), &(name) }
    4. #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name)
    5. 因此上面式子展开后等价于
    6. static struct list_head misc_list = { &(misc_list), &(misc_list) }
    7. */

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

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

    3、misc_open()函数

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

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

    misc_open()函数代码如下:

    1. static int misc_open(struct inode * inode, struct file * file)
    2. { //文件在硬盘的路径 //设备文件的路径
    3. int minor = iminor(inode);
    4. struct miscdevice *c;
    5. int err = -ENODEV;
    6. const struct file_operations *old_fops, *new_fops = NULL;
    7. mutex_lock(&misc_mtx);
    8. list_for_each_entry(c, &misc_list, list) {
    9. if (c->minor == minor) {
    10. new_fops = fops_get(c->fops);//用次设备号寻找设备
    11. break;
    12. }
    13. }
    14. if (!new_fops) {
    15. mutex_unlock(&misc_mtx);
    16. request_module("char-major-%d-%d", MISC_MAJOR, minor);
    17. mutex_lock(&misc_mtx);
    18. list_for_each_entry(c, &misc_list, list) {
    19. if (c->minor == minor) {
    20. new_fops = fops_get(c->fops);
    21. break;
    22. }
    23. }
    24. if (!new_fops)
    25. goto fail;
    26. }
    27. err = 0;
    28. old_fops = file->f_op;
    29. file->f_op = new_fops;
    30. if (file->f_op->open) {
    31. file->private_data = c;
    32. err=file->f_op->open(inode,file);//主要的
    33. if (err) {
    34. fops_put(file->f_op);
    35. file->f_op = fops_get(old_fops);
    36. }
    37. }
    38. fops_put(old_fops);
    39. fail:
    40. mutex_unlock(&misc_mtx);
    41. return err;
    42. }

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

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

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

    1. [root@xjh proc]# cat devices
    2. Character devices:
    3. 1 mem
    4. //省略部分代码
    5. 7 vcs
    6. 10 misc //这里出现主设备号为10,名字为misc的字符设备
    7. 13 input
    8. //省略部分代码
    9. Block devices:
    10. //省略部分代码
    11. 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文件中,如下:

    1. #define DEFINE_MUTEX(mutexname) \
    2. struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
    3. /*
    4. #define __MUTEX_INITIALIZER(lockname) \
    5. { .count = ATOMIC_INIT(1) \
    6. , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
    7. , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
    8. __DEBUG_MUTEX_INITIALIZER(lockname) \
    9. __DEP_MAP_MUTEX_INITIALIZER(lockname) }
    10. */

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

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

    (3)上锁mutex_lock和解锁mutex_unlock

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


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

    1、dev_init()函数

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

    module_init(dev_init);

    该函数的内容如下:

    1. static int __init dev_init(void)
    2. {
    3. int ret;
    4. init_MUTEX(&lock);
    5. ret = misc_register(&misc);
    6. //接下来对硬件,即蜂鸣器进行初始化
    7. //向gpiolib申请gpio,并设置成上拉、输出模式,输出值为0
    8. /* GPD0_2 (PWMTOUT2) *///由原理图得知蜂鸣器的接口是GPD0_2
    9. ret = gpio_request(S5PV210_GPD0(2), "GPD0");//向gpiolib申请gpio
    10. if(ret)
    11. printk("buzzer-x210: request gpio GPD0(2) fail");
    12. s3c_gpio_setpull(S5PV210_GPD0(2), S3C_GPIO_PULL_UP);//设置成上拉
    13. s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(1));//设置成输出模式
    14. gpio_set_value(S5PV210_GPD0(2), 0);//设置输出值为0
    15. printk ("x210 "DEVICE_NAME" initialized\n");
    16. return ret;
    17. }

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

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

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

    1. init_MUTEX(&lock);
    2. /*
    3. #define init_MUTEX(sem)   sema_init(sem, 1) //这里参数为1
    4. static inline void sema_init(struct semaphore *sem, int val)
    5. {
    6.     static struct lock_class_key __key;
    7.     *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    8.     lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
    9. }
    10. */

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

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

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

    (3)gpio_request

    (4)printk

    2、x210_pwm_ioctl()函数

    该函数的内容如下:

    1. // PWM:GPF14->PWM0
    2. static int x210_pwm_ioctl(struct inode *inode, struct file *file, \
    3. unsigned int cmd, unsigned long arg)
    4. {
    5. switch (cmd)
    6. {
    7. case PWM_IOCTL_SET_FREQ:
    8. printk("PWM_IOCTL_SET_FREQ:\r\n");
    9. if (arg == 0)
    10. return -EINVAL;
    11. PWM_Set_Freq(arg);
    12. break;
    13. case PWM_IOCTL_STOP:
    14. default:
    15. printk("PWM_IOCTL_STOP:\r\n");
    16. PWM_Stop();
    17. break;
    18. }
    19. return 0;
    20. }
    21. //上面这两个宏按理定义在头文件中,然后让应用程序包含
    22. //但是这个文件却直接定义在开头了!不规范!

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

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

    (2)ioctl的使用方法

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

    3、硬件操作有关的代码

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

    PWM_Set_Freq()函数

    1. // TCFG0在Uboot中设置,这里不再重复设置
    2. // Timer0输入频率Finput=pclk/(prescaler1+1)/MUX1
    3. // =66M/16/16
    4. // TCFG0 = tcnt = (pclk/16/16)/freq;
    5. // PWM0输出频率Foutput =Finput/TCFG0= freq
    6. static void PWM_Set_Freq( unsigned long freq )
    7. {
    8. unsigned long tcon;
    9. unsigned long tcnt;
    10. unsigned long tcfg1;
    11. struct clk *clk_p;
    12. unsigned long pclk;
    13. //unsigned tmp;
    14. //设置GPD0_2为PWM输出
    15. s3c_gpio_cfgpin(S5PV210_GPD0(2), S3C_GPIO_SFN(2));
    16. tcon = __raw_readl(S3C2410_TCON);
    17. tcfg1 = __raw_readl(S3C2410_TCFG1);
    18. //mux = 1/16
    19. tcfg1 &= ~(0xf<<8);
    20. tcfg1 |= (0x4<<8);
    21. __raw_writel(tcfg1, S3C2410_TCFG1);
    22. clk_p = clk_get(NULL, "pclk");
    23. pclk = clk_get_rate(clk_p);
    24. tcnt = (pclk/16/16)/freq;
    25. __raw_writel(tcnt, S3C2410_TCNTB(2));
    26. __raw_writel(tcnt/2, S3C2410_TCMPB(2));//占空比为50%
    27. tcon &= ~(0xf<<12);
    28. tcon |= (0xb<<12); //disable deadzone, auto-reload, inv-off,
    29. //update TCNTB0&TCMPB0, start timer 0
    30. __raw_writel(tcon, S3C2410_TCON);
    31. tcon &= ~(2<<12); //clear manual update bit
    32. __raw_writel(tcon, S3C2410_TCON);
    33. }

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

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

    PWM_Stop()函数

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

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

  • 相关阅读:
    回文判断的三种写法
    2022蓝队HW初级面试题总结
    动态格子算法
    Nginx优化与防盗链
    java毕业设计超市网站mybatis+源码+调试部署+系统+数据库+lw
    JAVA学习-----TreeMap
    day32 泛型 数据结构 List
    [云原生k8s] k8s管理工具kubectl详解(一)
    【Python】Marshmallow:Python中的“棉花糖”
    Python3+Selenium框架搭建
  • 原文地址:https://blog.csdn.net/oqqHuTu12345678/article/details/126473895