当前位置: 首页 > news >正文

混杂设备驱动

混杂设备

混杂设备是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);

为什么能简化代码

  1. 混杂设备统一使用主设备号10,自动分配次设备号

固定了使用主设备号10,不需要申请设备号代码

  1. 简化注册流程
  • 不需要手动创建设备类(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开始容易冲突,所以还是自动分配后遍历更健壮

http://www.dtcms.com/a/541708.html

相关文章:

  • 【SpringMVC】Spring MVC 核心全解析:从 MVC 思想到 Web 交互(请求、响应、参数传递全实践)
  • 最专业的手机网站建设磐安建设局网站
  • 做英文行程的网站上海迪士尼乐园官网
  • SAM2学习笔记
  • 基于dcmtk的dicom工具 第十一章 加载dicom文件多帧图数据
  • 建网站多少钱一平方电工证免考拿证
  • 桌面开发,在线%可视化,招聘系统demo,基于python,matplotlib,request,爬虫,数据库无
  • 护照识别接口-高效智能的OCR身份核验新体验-OCR文字识别API
  • 性能测试 | 性能测试工具JMeter直连数据库和逻辑控制器的使用
  • 建站 报价专业网站开发制作公司
  • 手机版网站如何做直接进网站的浏览器
  • 网站建设总结与心得体会颍州网站建设
  • 引力概念中的混乱点:传统物理学与张祥前统一场论对比
  • 通管局报审通关秘籍:网约车平台网络安全防护与保障能力报告
  • 郑州市建设网站wordpress 加斜杠
  • 正规网站建设公司一般要多少钱建设银行面试经验网站
  • [ssh]系统重装后,如何重新设置window免密登录远程linux服务器
  • 网站模板预览与编辑器旅游网站简介
  • 网站建设ppt百度文库注册公司地址可以用家庭地址
  • 学习一下kernel6.12中sugov_iowait_apply的函数逻辑
  • 北京网站开发报价做搜狗网站优化首页软
  • node.js网站开发十大购物网站排行榜
  • ProcDump 学习笔记(6.14):在调试器中查看转储(WinDbg / Visual Studio 快速上手)
  • 正规网站建设报价网站建设案例精粹 电子书
  • PE之不同区域的结构体定义
  • Ubuntu24.04 赋予 Qt 应用程序 修改系统时间 权限
  • 50+孙悦梓潼舞台状态惊艳全网!自律的女神,连下颌线都是顶配
  • Rust:复合类型内存布局
  • net网站是国际域名吗wordpress发布文章页面错误
  • 英语学习 第一周 重难点