混杂设备驱动
混杂设备
混杂设备是Linux内核提供的一个简化的字符设备驱动框架
从软件实现角度去看,混杂设备本质还是字符设备,只是它的主设备号由内核已经定义好为10,各个混杂设备驱动通过次设备号来区分
- 本质上是对标准字符设备驱动的封装
- 是一个编程框架,通过牺牲一定的灵活性来换取代码简洁性
//功能:描述混杂设备属性
struct miscdevice {const char *name;int minor;const struct file_operations *fops;...
};
name:混杂设备文件名,并且由
linux自动帮你创建,无需调用四个函数不需要手动创建设备文件,但是可以自己决定创建的个数
minor:给混杂设备指定的次设备号
一般指定宏:MISC_DYNAMIC_MINOR,表示让
linux内核帮你分配一个次设备号从属关系:给misc 类设备 动态分配一个 minor 号
一次
misc_register()只占用一个次设备号fops:混杂设备的硬件操作接口
每个 miscdevice 只能注册一次,绑定一个 minor。
一个 miscdevice 结构体 = 一个次设备号 = 一个设备文件
重要api
//misc_register() --- 注册混杂设备函数原型
cint misc_register(struct miscdevice *misc);
//misc_deregister() - 注销混杂设备
cvoid misc_deregister(struct miscdevice *misc);
为什么能简化代码
- 混杂设备统一使用主设备号10,自动分配次设备号
固定了使用主设备号10,不需要申请设备号代码
- 简化注册流程
不需要手动创建设备类(class)和设备节点(device)
不需要使用
cdev结构体
混杂设备框架在内核层面已经内置了一个统一的
cdev结构体,所有混杂设备共享这个cdev不需要
cdev_init(),不需要cdev_add(),不需要cdev_del()int misc_register(struct miscdevice *misc) {// 1. 内核已经注册了主设备号10的cdev// 2. 分配次设备号// 3. 创建设备节点 /dev/xxx// 4. 所有混杂设备的fops请求都通过这个统一的cdev路由 }
利用混杂设备控制蜂鸣器
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>struct beep_resource{char* name;int gpio;
};
static struct beep_resource beep_info={"beep",PAD_GPIO_C+14 };//写接口,直接判断<on|off>
static ssize_t beep_write(struct file* file,const char __user *buf,size_t count,loff_t *ppos){char kbuf[16]={ 0 };int kcmd=-1;if(count >= sizeof(kbuf))count=sizeof(kbuf)-1;if(copy_from_user(kbuf,buf,count)){return -EFAULT;}//加\0kbuf[count]='\0';//兼容printf和echoif(count >1 && kbuf[count-1]=='\n')kbuf[count-1]='\0';if(!strcmp(kbuf,"on")){kcmd=1;}else if(!strcmp(kbuf,"off")){kcmd=0;}gpio_set_value(beep_info.gpio,kcmd);pr_info("%s:the beep is %s\n",__func__,kcmd?"on":"off");return count;
}
static struct file_operations beep_fops={.owner = THIS_MODULE,.write = beep_write
};
/*混杂设备*/
static struct miscdevice beepMisc={.name="mybeep",.minor=MISC_DYNAMIC_MINOR,.fops=&beep_fops//这里取的是指针,传地址
};
static int __init beep_init(void){gpio_request(beep_info.gpio,beep_info.name);gpio_direction_output(beep_info.gpio,0);//0关1开//注册混杂设备misc_register(&beepMisc);pr_info("The beep driver is loaded!\n");return 0;
}
static void __exit beep_exit(void){//注销混杂设备misc_deregister(&beepMisc);gpio_set_value(beep_info.gpio,0);gpio_free(beep_info.gpio);pr_info("The beep driver is removed!\n");
}module_init(beep_init);
module_exit(beep_exit);
MODULE_LICENSE("GPL");
混杂设备简化了设备号申请,无需字符设备的三大函数
cdev_init,cdev_add,cev_del但是file_operations结构体需要写,注意传的是指针
蜂鸣器是0关1开(和灯相反)
利用混杂设备读取按键值状态
按键输入模块电路图
按键状态 输入电平 MCU 读取值 未按下 高(3.3V) 1 按下 低(0V) 0

应用程序:
轮询四个按键,用数组记录每一个按键的最新状态,当两次按键状态不一样时会打印按键状态
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h>struct btn_cmd{int index;int state; }; #define BTN_COUNT 4 //打印更加友好 const char* keyname[BTN_COUNT]={"key_up","vol_up","vol_down","key_down" }; //轮询四个按键状态,按键变化才打印 int main(void){int fd;int i;struct btn_cmd btn;int last_state[BTN_COUNT];memset(last_state,-1, sizeof(last_state));fd=open("/dev/mybtn",O_RDWR);if(fd < 0){perror("open fails\n");return -1;}while(1){for(i=0;i<BTN_COUNT;i++){btn.index=i;read(fd,&btn,sizeof(btn));if(btn.state!=last_state[i]){printf("The key %s is %s\n",keyname[i],btn.state?"released":"pressed");last_state[i]=btn.state;}}usleep(10000);}close(fd);return 0; }驱动程序:
首先读取应用程序的编号,再把按键的状态返回给应用程序#include <linux/init.h> #include <linux/module.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/miscdevice.h>struct btn_resource{char* name;int gpio; }; static struct btn_resource btn_info[]={{ .name="KEY_UP" , .gpio= PAD_GPIO_A+ 28},{ .name="VOL_UP" , .gpio= PAD_GPIO_B +30},{ .name="VOL_DOWN" , .gpio= PAD_GPIO_B +31},{ .name="KEY_DOWN" , .gpio= PAD_GPIO_B +9} }; #define BTN_COUNT ARRAY_SIZE(btn_info)struct btn_cmd{int index;int state; }; static ssize_t btn_read(struct file* file,char __user *buf,size_t count,loff_t *ppos){struct btn_cmd kbtn;//从应用程序读取按键编号if(copy_from_user(&kbtn,buf,sizeof(kbtn))){return -EFAULT;}kbtn.state=gpio_get_value(btn_info[kbtn.index].gpio);//拷贝给应用程序if(copy_to_user(buf,&kbtn,sizeof(kbtn))){return -EFAULT;}return sizeof(kbtn); } static struct file_operations btn_fops={.owner=THIS_MODULE,.read=btn_read }; static struct miscdevice btn_misc={.name="mybtn",.minor=MISC_DYNAMIC_MINOR,.fops=&btn_fops }; static int __init btn_init(void){int i;for(i=0;i<BTN_COUNT;i++){struct btn_resource* p=&btn_info[i];gpio_request(p->gpio,p->name);gpio_direction_input(p->gpio);}//注册混杂设备misc_register(&btn_misc);return 0; } static void __exit btn_exit(void){int i;//卸载混杂设备misc_deregister(&btn_misc);for(i=0;i<BTN_COUNT;i++){struct btn_resource* p=&btn_info[i];gpio_free(p->gpio);}} module_init(btn_init); module_exit(btn_exit); MODULE_LICENSE("GPL");
注意:按键是输入设备,所以用的是
input
运行效果:
利用混杂设备实现四个灯控制
方案1:应用程序传灯的编号,只注册一个混杂设备
应用程序
ioctl的第二个参数传灯的开关,引入结构体传灯的编号
传结构体是考虑可扩展性
ioctl系统调用的第三个参数通常是一个指向结构体的指针。这是Linux内核编程中的一个常见模式。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>#define LED_ON 0x100001
#define LED_OFF 0x100002
struct led_cmd{int index;
};
int main(int argc,char* argv[]){int fd;struct led_cmd ledCmd;unsigned long cmd;fd=open("/dev/myled",O_RDWR);if(argc < 3){printf("./xxx <1|2|3|4> <0n|off>\n");return -1;}ledCmd.index=atoi(argv[1]);if(!strcmp(argv[2],"on")){cmd=LED_ON;}else if(!strcmp(argv[2],"off")){cmd=LED_OFF;}ioctl(fd,cmd,&ledCmd);close(fd);return 0;
}
驱动程序
方案2:每个灯都注册自己的混杂设备,应用程序打开不同的驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>struct led_resource{char* name;int gpio;
};
static struct led_resource led_info[]={{ .name="LED1" , .gpio=PAD_GPIO_C + 7 }, { .name="LED2" , .gpio=PAD_GPIO_C + 11 }, { .name="LED3" , .gpio=PAD_GPIO_C + 12}, { .name="LED4" , .gpio=PAD_GPIO_B + 26 }
};
#define LED_COUNT ARRAY_SIZE(led_info)#define LED_ON 0x100001
#define LED_OFF 0x100002
struct led_cmd{int index;
};
static long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){struct led_cmd kledCmd;int kcmd;//(void __user*)argif(copy_from_user(&kledCmd,(struct led_cmd*)arg,sizeof(kledCmd))){return -EFAULT;}//编号合法性if(kledCmd.index <1 || kledCmd.index>=LED_COUNT ){return -EINVAL;}switch(cmd){case LED_ON:kcmd=1;break;case LED_OFF:kcmd=0;break;default:return -EINVAL;}gpio_set_value(led_info[kledCmd.index].gpio,!kcmd);pr_info("The led %d is %s\n",kledCmd.index,kcmd?"on":"off");return 0;
}
static struct file_operations led_fops={.owner = THIS_MODULE,.unlocked_ioctl = led_ioctl
};
/*混杂设备*/
static struct miscdevice led_misc={.name="myled",.minor=MISC_DYNAMIC_MINOR,.fops=&led_fops
};
static int __init led_init(void){int i;for(i=0;i<LED_COUNT;i++){struct led_resource* p=&led_info[i];gpio_request(p->gpio,p->name);gpio_direction_output(p->gpio,1);}//混杂设备注册misc_register(&led_misc);pr_info("the driver is loaded!\n");return 0;
}static void __exit led_exit(void){int i;//混杂设备卸载misc_deregister(&led_misc);for(i=0;i<LED_COUNT;i++){struct led_resource* p=&led_info[i];gpio_set_value(p->gpio,1);gpio_free(p->gpio);}pr_info("the driver is removed!\n");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
应用程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>#define LED_ON 0x100001
#define LED_OFF 0x100002
int main(int argc,char* argv[]){int fd;int index;unsigned long cmd;char name[32]={0};if(argc < 3){printf("./xxx <1|2|3|4> <on|off>\n");return -1;}index=atoi(argv[1]);sprintf(name,"/dev/myled%d",index);fd=open(name,O_RDWR);if(fd < 0){perror("open fails");return -1;}if(!strcmp(argv[2],"on"))cmd=LED_ON;else if(!strcmp(argv[2],"off"))cmd=LED_OFF;ioctl(fd,cmd);close(fd);return 0;
}
驱动程序
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>struct led_resource{char* name;int gpio;//每个灯有自己的混杂设备来生成设备文件struct miscdevice miscDevice;
};
static struct led_resource led_info[]={{ .name="LED1", .gpio=PAD_GPIO_C+7 },{ .name="LED2", .gpio=PAD_GPIO_C+11 },{ .name="LED3", .gpio=PAD_GPIO_C+12 },{ .name="LED4", .gpio=PAD_GPIO_B+26 }
};
#define LED_COUNT ARRAY_SIZE(led_info)#define LED_ON 0x100001
#define LED_OFF 0x100002
static int led_open(struct inode* inode,struct file* file){int minor=MINOR(inode->i_rdev);// 遍历查找匹配的minor号,因为是自动分配的int i;for(i = 0; i < LED_COUNT; i++){if(led_info[i].miscDevice.minor == minor){file->private_data = &led_info[i];return 0;}}return 0;
}
static long led_ioctl(struct file* file,unsigned int cmd,unsigned long arg){struct led_resource* p=file->private_data;int kcmd=-1;switch(cmd){case LED_ON:kcmd=1;break;case LED_OFF:kcmd=0;break;default:return -EINVAL;}gpio_set_value(p->gpio,!kcmd);pr_info("The %s is %s\n",p->name,kcmd?"on":"off");return 0;
}
/*file_operations需要写*/
static struct file_operations led_fops={.owner = THIS_MODULE,.unlocked_ioctl = led_ioctl,.open = led_open
};
static const char* miscName[]={"myled1","myled2","myled3","myled4"};
static int __init led_init(void){int i;for(i=0;i<LED_COUNT;i++){struct led_resource* p=&led_info[i];gpio_request(p->gpio,p->name);gpio_direction_output(p->gpio,1);//混杂设备p->miscDevice.name=miscName[i];p->miscDevice.minor=MISC_DYNAMIC_MINOR;p->miscDevice.fops=&led_fops;misc_register(&p->miscDevice);}pr_info("misc device driver is loaded!\n");return 0;
}
static void __exit led_exit(void){int i;for(i=0;i<LED_COUNT;i++){struct led_resource* p=&led_info[i];misc_deregister(&p->miscDevice);gpio_set_value(p->gpio,1);gpio_free(p->gpio);}pr_info("misc device driver is removed!\n");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
这里是自动分配次设备号,需要一次匹配,当然可以指定次设备号,有失败风险
这就是手动分配次设备号,77是随机值,因为直接从0开始容易冲突,所以还是自动分配后遍历更健壮






