Linux里面的misc杂项设备是主设备号为10的驱动设备,它的注册跟使用比较的简单,所以比较适用于功能简单的设备。正因为简单,所以它通常嵌套在platform 总线驱动中,配合总线驱动达到更复杂,多功能的效果
一.杂项设备数据结构分析
杂项设备驱动结构还是很简单的,他可以夹杂到其它结构体当中,以丰富驱动的血肉。一般情况下,我们是将它嵌套在其它结构当中的。
- struct miscdevice {
- int minor;
- const charchar *name;
- const struct file_operations *fops;
- struct list_head list;
- struct device *parent;
- struct device *this_device;
- const charchar *nodename;
- umode_t mode;
- };
上面:
parent:这个指针决定了在/sys文件系统里面,它是创建在哪个目录下。如果为空就在/sys/class根目录下创建,如果不为空都是在/sys/class/misc 文件下面创建的一些属性文件。
this_device:这个就代表当前设备的设备结构体,这个在查找扩充数据结构时,非常有用。
- typedef struct led{
- struct miscdevice *led_dev;
- struct mutex led_lock;
- spinlock_t io_lock;
- int flset;
- int flen;
- uint8_t regs[4+1];
- }rt8547_dev_t;
上面就是我定义的数据结构,我就是把struct miscdevice 嵌套在我自己定义的头文件中。
二、杂项设备的注册过程
在介绍注册过程时,我们先来了解一些非常重要的全局变量。
- static LIST_HEAD(misc_list);
-
-
-
-
-
-
- static DEFINE_MUTEX(misc_mtx);
-
-
-
-
-
-
-
- #define DYNAMIC_MINORS 64 /* like dynamic majors */
- static DECLARE_BITMAP(misc_minors, DYNAMIC_MINORS);
-
-
-
-
-
misc_list:这是所有misc设备的头指针,打个比方说,所有misc设备结构体都挂在它上面。

注册的过程是用下面这个函数来实现的
- int misc_register(struct miscdevice * misc)
- {
- dev_t dev;
- int err = 0;
-
- INIT_LIST_HEAD(&misc->list);
-
- mutex_lock(&misc_mtx);
-
- 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);
- } else {
- struct miscdevice *c;
-
- list_for_each_entry(c, &misc_list, list) {
- if (c->minor == misc->minor) {
- mutex_unlock(&misc_mtx);
- return -EBUSY;
- }
- }
- }
-
- 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;
- }
-
-
-
-
-
- list_add(&misc->list, &misc_list);
- out:
- mutex_unlock(&misc_mtx);
- return err;
- }
-
-
- static inline void set_bit(int nr, volatile unsigned longlong *addr)
- {
-
- unsigned long mask = BIT_MASK(nr);
- unsigned longlong *p = ((unsigned longlong *)addr) + BIT_WORD(nr);
- unsigned long flags;
-
-
-
- _atomic_spin_lock_irqsave(p, flags);
- *p |= mask;
- _atomic_spin_unlock_irqrestore(p, flags);
- }
假如现在我们创建的次设备号是
20
,那么标志位偏移量就是
1<<20
,那么就会有上面的
misc_minors[0]
=
misc_minors[0] | 1<<20
;同样假如现在我们创建的次设备号是
40
,那么就会有
mask = 1<<(40%32)
,即
mask = 1<<8
,就有
misc_minors[1] = misc_minors[1] | 1<<8
。下面这幅图是根据我所使用的内核版本,我使用的内核里面只定义了64个标志位,这里也就画了8个字节。但是不管有多少个字节吧,原理还是一样的。

可以看看/dev目录下的设备节点主设备是10,次设备号是42.这样的话,标志位就放在第二排了。
三、引人深思的misc_class
misc_class就是一个引子,创建了这样一个class之后,所有的misc设备的class设备文件都会在/sys/class/misc目录下创建。
- static struct classclass *misc_class;
-
- 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");
- err = PTR_ERR(misc_class);
- if (IS_ERR(misc_class))
- goto fail_remove;
-
- err = -EIO;
- 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);
只要有了misc_class对象,所有misc设备就有地方放了。
四.杂项设备的注销过程
杂项设备的注销过程,其实就是注册过程的逆向过程。只是说这里代码非常简单,其实主要工作都在device_destroy()中
- int misc_deregister(struct miscdevice *misc)
- {
- int i = DYNAMIC_MINORS - misc->minor - 1;
-
- if (WARN_ON(list_empty(&misc->list)))
- return -EINVAL;
-
- mutex_lock(&misc_mtx);
- list_del(&misc->list);
- device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));
- if (i < DYNAMIC_MINORS && i >= 0)
- clear_bit(i, misc_minors);
- mutex_unlock(&misc_mtx);
- return 0;
- }
-
- void device_destroy(struct classclass *class, dev_t devt)
- {
- struct device *dev;
-
- dev = class_find_device(class, NULL, &devt, __match_devt);
- if (dev) {
- put_device(dev);
- device_unregister(dev);
- }
- }
五.Rt8547闪光灯misc设备驱动的分析
下面的这个例子是我在添加camera 闪光灯rt8547设备时,编写的一个闪光灯驱动的初版文件。初步的功能我已经验证过了,是可以工作的。
1)open函数中的共享精神
下面限于篇幅问题,我不会贴出所有代码,只是加上自己的一些分析,和在编码过程中遇到的问题。
- rt8547_dev_t *led_dev = NULL;
-
- static int led_open(struct inode *node, struct file *file){
-
- struct my_platfrom_struct *my_data = kmalloc(sizeof(struct my_platfrom_struct), GFP_KERNEL);
- if (!my_data){
- RT8547_ERR("malloc my_data falied!");
- }
- my_data->grade = 1;
- my_data->age = 22;
- my_data->phone = 110;
- file->private_data = my_data;
- RT8547_INFO("open led devices success!!!\n");
- return 0;
- }
上面代码中,我最想强调的就是file->private_data = my_data.这行代码鬼使神功的将我们需要共享的变量,放到了其它函数都能看得到的地方。因为在其它的read,write,ioctl,release等函数都可以通过file->private_data拿到这个共享的变量。一定要学会共享,理解共享的精神。
2)ioctl函数中的多元化
- static long led_ioctl(struct file *file, unsigned int cmd,unsigned long arg){
- struct my_platfrom_struct *para = (struct my_platfrom_struct *)arg;
- struct my_platfrom_struct *my_dat = (struct my_platfrom_struct *)file->private_data;
-
- switch(cmd){
- case LED_IO_GET_MY_DATA:
- copy_to_user(para,my_dat,sizeof(struct my_platfrom_struct));
- break;
- case LED_IO_OPEN_TORCH:
- {
- int level;
- copy_from_user(&level,(void*)arg,sizeof(int));
- rt8547_send_data(led_dev,RT8547_CONTROL3,RT8547_MODE_MASK,1);
- rt8547_send_data(led_dev,RT8547_CONTROL3,0x0f,level&0x0f);
- RT8547_INFO("Open Torch, level:%d",level);
- rt8547_set_led_on(led_dev);
- }
- break;
- case LED_IO_OPEN_FLASH:
- {
- int level;
- copy_from_user(&level,(void*)arg,sizeof(int));
- rt8547_send_data(led_dev,RT8547_CONTROL3,RT8547_MODE_MASK,0);
- rt8547_send_data(led_dev,RT8547_CONTROL2,0x1f,level&0x1f);
- RT8547_INFO("Open Flash, level:%d",level);
- rt8547_set_led_on(led_dev);
- }
- break;
- case LED_IO_CLOSE_LED:
- rt8547_set_led_off(led_dev);
- RT8547_INFO("rt8547 close device!\n");
- break;
- }
- return 0;
- }
ioctl函数在驱动中用到最多,在实际开发中一般都是通过ioctl和kernel交互的。
3)Rt8547 gpio 初始化和释放
gpio 初始化相当重要,不初始化就不能用了。gpio的初始化和释放所有kernel都是一样的过程,大家要熟记这些方法。
- static void rt8547_gpio_deinit(const rt8547_dev_t *dev)
- {
- gpio_direction_input(dev->flen);
- gpio_free(dev->flen);
-
- gpio_direction_input(dev->flset);
- gpio_free(dev->flset);
- }
-
- static int rt8547_gpio_init(const rt8547_dev_t *dev)
- {
- int ret;
- gpio_request(dev->flen,NULL);
- if (ret) {
- RT8547_INFO("sensor: flen already request %d.\n",dev->flen);
- goto request_flen_fail;
- }
- gpio_direction_output(dev->flen, 1);
- gpio_set_value(dev->flen,0);
-
- gpio_request(dev->flset,NULL);
- if (ret) {
- printk("sensor: flset already request %d.\n",dev->flen);
- goto request_flset_fail;
- }
- gpio_direction_output(dev->flset, 1);
- gpio_set_value(dev->flset,0);
- return 0;
-
- request_flset_fail:
- gpio_free(dev->flset);
- request_flen_fail:
- gpio_free(dev->flen);
- request_fail:
- return ret;
- }
注意:大家有没有注意到上面函数gpio口的释放,有什么异样没有。也许你已经发现,谁先注册的,就放到最后在释放,这也是为了kernel的稳定性才这样写的。当有一个gpio口请求失败,那么之前注册成功的gpio资源都要释放。要不然也许有其它模块在请求这个gpio口资源呢。记住注册过程和释放过程是相对的。如下所示。

4)led_fops和misdevice设备填充
- static const struct file_operations led_fops = {
- .owner = THIS_MODULE,
- .open = led_open,
- .read = led_read,
- .write = led_write,
- .unlocked_ioctl = led_ioctl,
- .release = led_release,
- };
-
- static struct miscdevice led_device = {
- .minor = LED_MINOR,
- .name = LED_DEVICE_NAME,
- .fops = &led_fops,
- };
上面两个结构是字符设备必不可少的两个结构体。大家有没有发现这两个结构一般都放在靠近驱动模块末尾的位置。不知道大家有没有考虑过为什么会有这样的情况。这是由于驱动中一般没有声明open,read,write,ioctl等接口函数,如果你不想将file_operations安排在驱动末尾。你也可以声明这些驱动函数,这样就不会受限制了。这个要看自己了,一般不这样来,这也是一种习惯。
5)rt8547驱动初始化函数和注销函数
- int led_init(){
- int ret;
- RT8547_INFO("led_init!\n");
- led_dev = kzalloc(sizeof(rt8547_dev_t), GFP_KERNEL);
- if(!led_dev){
- RT8547_ERR("alloc mem failed");
- }
-
- led_dev->led_dev = &led_device;
- led_dev->flset= GPIO_CAM_FLASH_SET;
- led_dev->flen = GPIO_CAM_FLASH_EN;
- led_dev->regs[RT8547_DUMMY] = 0;
- led_dev->regs[RT8547_CONTROL1] = 0x06;
- led_dev->regs[RT8547_CONTROL2] = 0x12;
- led_dev->regs[RT8547_CONTROL3] = 0x02;
- led_dev->regs[RT8547_CONTROL4] = 0x0f;
-
-
- rt8547_gpio_init(led_dev);
- mutex_init(&led_dev->led_lock);
- ret = misc_register(led_dev->led_dev);
- if (unlikely(ret)) {
- RT8547_ERR("failed to register misc device'%s'!\n",
- led_dev->led_dev->name);
- goto out_free_buffer;
- }
-
- ret = device_create_file(led_dev->led_dev->this_device, &dev_attr_flash);
- if (ret < 0) {
- printk(KERN_ALERT"Failed to create attribute flash.");
- goto err_register_flash;
- }
-
- RT8547_INFO("create device attribute file flash!\n");
- return 0;
- err_register_flash:
-
- out_free_buffer:
- kfree(led_dev);
- return ret;
- }
-
- void led_exit(void)
- {
- RT8547_INFO("rt8547 exit!\n");
- misc_deregister(led_dev->led_dev);
- kfree(led_dev);
- }
驱动初始化函数中一般进行全局设备结构题的初始化,资源的请求,设备树的解析。当然如果是platform设备,这些也可以在probe函数中做。大家有没有发现我在上面的初始化函数中使用device_create_file()创建了一个设备属性文件。后面我会专门写一篇博文专门来分析这些和创建设备文件相关的函数。
6)应用程序
- #include <fcntl.h>
- #include <errno.h>
- #include <sys/ioctl.h>
- #include <stdio.h>
- #include "led_rt8547.h"
-
- #define FILE_PATH "/dev/rt8547_led"
-
- int main(){
- int ret;
- int mode,level;
- my_data my_dat;
-
- int fd = open(FILE_PATH,O_RDWR);
- if ( -1 == fd){
- printf("open led_flash node failed!\n");
- return -1;
- }
- printf("unsigned size:%d\n",sizeof(unsigned long));
-
- printf("\n1:GET_MY_DATA\n \
- 2:LED_OPEN_TORCH\n \
- 3:LED_OPEN_FLASH\n \
- 4:LED_CLOSE\n");
- printf("mode:");
- scanf("%d",&mode);
- if(2 == mode || 3 == mode){
- printf("level:");
- scanf("%d",&level);
- }
- printf("Rt8547 mode:%d,level:%d\n",mode,level);
- switch(mode){
- case 1:
- ret = ioctl(fd,LED_IO_GET_MY_DATA, &my_dat);
- printf("Rt8547 Get my data success!\n");
- printf("Rt8547 grade:%d,age:%d,phone:%d",my_dat.grade,my_dat.age,my_dat.phone);
- if(0 != ret){
- printf("Rt8547 Get my data failed!\n");
- ret = -1;
- }
- break;
- case 2:
- ret = ioctl(fd,LED_IO_OPEN_TORCH, &level);
- printf("Rt8547 open torch success!\n");
- if(0 != ret){
- printf("Rt8547 open torch failed!\n");
- ret = -1;
- }
- break;
- case 3:
- ret = ioctl(fd,LED_IO_OPEN_FLASH, &level);
- printf("Rt8547 open flash success!\n");
- if(0 != ret){
- printf("Rt8547 open flash failed!\n");
- ret = -1;
- }
- break;
- case 4:
- ret = ioctl(fd,LED_IO_CLOSE_LED, &level);
- printf("Rt8547 close led success!\n");
- if(0 != ret){
- printf("Rt8547 close led failed!\n");
- ret = -1;
- }
- break;
- }
- return ret;
- }
上面调用系统调用,一切都在代码中,read the fucking code!!!!!!
五.调试过程中遇到的问题及解决办法和思路
1.Android.mk编写问题
在Anrdroid大环境下,最主要的问题是找不到头文件的问题。一开始的编译的时候,找不到头文件,后来参考其它工程的Android.mk发现他们一般都包含了$(TARGET_OUT_INTERMEDIATES)/KERNEL/source/include/video(如下所示),大家一看都明白,这里就是存放头文件的地方。其实在编译的过程中,kernel的头文件都会拷贝到out目录对应工程的/KERNEL/source/include目录下,这些头文件和kernel源码中是一样的。大家一定要牢记。我在编译led_flash测试程序时,用的就是下面这个Android.mk
其中LOCAL_MODULE_TAGS := optional意思就是所有版本都编译该模块。
知识扩展:
user: 指该模块只在user版本下才编译
eng: 指该模块只在eng版本下才编译
tests: 指该模块只在tests版本下才编译
optional:指该模块在 所有版本下都编译,默认是optional
- LOCAL_PATH:= $(call my-dir)
-
- include $(CLEAR_VARS)
- #LOCAL_ADDITIONAL_DEPENDENCIES := $(common_deps)
-
- #头文件路径
- LOCAL_C_INCLUDES := \
- $(TARGET_OUT_INTERMEDIATES)/KERNEL/source/include/misc \
- $(TARGET_OUT_INTERMEDIATES)/KERNEL/source/include/video
-
- LOCAL_SRC_FILES := led_flash.c #源文件
- LOCAL_MODULE := led_flash #应用程序,编译成可执行程序时的名字
- LOCAL_MODULE_TAGS := optional
-
- include $(BUILD_EXECUTABLE)
2)Kernel头文件编写问题
在上面Android.mk中包含了头文件路径时,有时候还是会提示找不到一些头文件。比如我在led_rt8547.h头文件中添加了一个#include <linux/mutex.h>。编译的时候找不到mutex.h这个头文件。这是因为我们在Android.mk中根本就没有包含mutex.h头文件所在的路径。为此我有以下几点意见:
1.应用程序一般都是使用IOCTL命令和一些宏信息,所以在驱动模块的头文件中,不要添加一些和kernel相关的结构体,尽量使用我们自己定义的数据类型。
2.头文件尽量少包含与kernel紧密联系的头文件(有联系的就要将头文件路径加到android.mk中),要不然也找不到头文件(除非已经包含了头文件路径),我们自己定义的其它头文件是可以添加进来。
3.模块中和内核紧密联系的结构体,尽量放在驱动源码中,避免编译报错。应用层其实也用不到这些结构体,那么就不要放到头文件中了。
![]()
![]()
![]()
![]()