混杂设备驱动
混杂设备也叫杂项设备,是对普通的字符设备(struct cdev)的一种封装。misc 设备会自动创建cdev,不需要像我 们以前那样手动创建,因此采用misc 设备驱动可以简化字符设备驱动的编写。具有以下特点:
1) 主设备号为10,次备号不同。
2) 大大简化cdev的设计流程。
3) 混杂设备与普通的字符设备在使用上并没有区别。
开发步骤
在模块加载函数中注册一个混杂设备
int misc_register(struct miscdevice * misc);
struct miscdevice结构体中我们主要关注3个成员:
1. minor:子设备号,由于主设备号都是10,子设备号必须指定而且不 能冲突, linux/miscdevice.h中定义了几个子设备号,如果需要动态获取,就用MISC_DYNAMIC_MINOR
2. name:将来在/dev下的设备名
3. fops:指向文件操作结构体变量的指针
int ret;
//创建混杂设备驱动
ret = misc_register(&fire_device);
if(ret)
{
printk("misc_register is error");
return ret;
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = fire_open,
.release = fire_release,
.read = fire_read,
};
//miscdevice 结构体
static struct miscdevice fire_device =
{
.minor = MISC_DYNAMIC_MINOR,
.fops = &fops,
.name = "fire"
};
对应的在模块卸载函数中注销这个混杂设备:misc_deregister(&fire_device);
Linux内核中的中断
如果按照之前的知识编写按键驱动程序,大概率会编写出一个查询方式的按键驱动。这样的做法对整个系统来说效率 比较低,使用中断方式是个不错的办法。
中断是什么?
中断就是单片机正在执行程序时,由于内部或外部事件的触发,打断当前程序,转而去处理这一事件,当处理完成后再回到原来被打断的地方继续执行原程序的过程。
在ARM体系结构中,中断通常由外设或外部输入产生,有时也可以由软件触发。中断是单片机系统处理紧急或突发事件的重要方式,如定时器溢出、按键输入、串口数据到达等。
什么是软中断?什么又是硬中断?
硬中断由硬件产生,每个设备或设备集都对应着一个中断向量号。
中断处理函数一定要快点执行完毕,越短越好。
Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是「上半部和下半部分」(或叫做顶半部和底半部)都是一个意思。
- 上半部直接处理硬件请求,也就是硬中断,主要是负责耗时短的工作,特点是快速执行;
- 下半部是由内核触发,也就是软中断,主要是负责上半部未完成的工作,通常都是耗时比较长的事情,特点是延迟执行
硬中断(上半部)是会打断 CPU 正在执行的任务,然后立即执行中断处理程序,而软中断(下半部)是以内核线程的方式执行。
举个例子,如果我们需要通过adc的值计算某个物理量的,那么几个 计算的过程可能是比较费时的。因此,从adcdat0寄存器读取数值应 该放在中断上半部。计算物理量的过程应该放在中断下半部,即read 函数。
主要意思就是不要在中断处理函数中写太多的东西,因为会占用cpu,中断处理函数仅仅响应中 断,然后清除中断标志位即可。
中断相关的函数
要使用linux内核中断必须要注册中断。注册中断使用的函数为
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
相关参数为:
irq:中断号,差不多就是裸机程序中的中断号。这里三星公司在 Linux源码中重新定义了2440等Soc的所有中断号。在文件 arch/arm/mach-s3c24a0/include/mach/irqs.h。结合mini2440开发板, 按键1所对应的就是IRQ_EINT8。
2. handler:中断处理函数指针,就是当某个中断产生时调用的中断服务函数的入口地址。
内核中的定义如下:typedef irqreturn_t (*irq_handler_t)(int, void *);
irq_handler_t是一个函数指针,这个函数指针必须指向返回值是irqreturn_t,函数参数必须是int和void *的那 种函数。
irqreturn_t fire_handler(int irq_num, void *dev)
{
if(irq_num == IRQ_EINT8)
{
fire = 1;
}
conditon = 1;
wake_up(&wq);
return IRQ_HANDLED;
}
3.flags:该参数用于表明中断产生的条件以及系统在处理时的行为,我们使用 IRQF_TRIGGER_FALLING和IRQF_DISABLED,表示电平的下降沿方式和在处理中断时不响应其他中断;
下降沿触发中断:
- 作用:在引脚电平从高变低(下降沿)时触发中断。
- 原理:当GPIO引脚检测到从高电平(逻辑1)变为低电平(逻辑0)时,生成一个中断请求。
- 应用场景:
- 按键检测:检测按键被松开(假设按键松开时引脚电平由高变低)。
- 传感器信号:当传感器输出信号由高变低时,触发中断处理数据。
4. name:为中断起个名字,设置以后可以在/proc/interrupts 文件 中看到对应的中断名字;
5.给中断服务函数传参,就是key_irq_handle的pData
void free_irq(unsigned int irq, void *dev_id);和request_irq相反,注销已经注册过的中断
void disable_irq(unsigned int irq);禁止某个中断发生,如果这个中断 正在执行,那么就等待中断执行结束后再禁止它
等待队列和poll轮询
read() 系统调用默认是阻塞的,即如果没有任何数据可读,read() 将会一直等待直到有数据可读或者发生错误。但是,有时设备驱动可能会以非阻塞模式工作,特别是在一些特殊情况下,例如键盘或其他输入设备,如果没有按键按下,设备可能不会产生任何数据供 read() 读取。
意思就是按键不按下 也不会一直读取0
为了实现一种机制使得 read() 能够在没有数据时阻塞,并在有数据时返回,通常会使用两种主要技术:等待队列和轮询机制(如 select() 和 poll())。
等待队列
等待队列是一个内核提供的同步工具,它允许内核在特定条件不满足时让进程挂起,然后在条件满足时唤醒它们。
static wait_queue_head_t wq; //等待队列头数据类型为 wait_queue_head_t,
static int conditon;//等待条件
init_waitqueue_head(&wq);//初始化等待队列头使用带参宏init_waitqueue_head(q)
wait_event_interruptible(wq, conditon);//让程序进入阻塞态
四种等待函数
1. wait_event(wq, condition) :等待以wq 为等待队列头的等待队列被 唤醒,前提是condition 条件必须满足(为真),否则一直阻塞。此函数会将进程设置为TASK_UNINTERRUPTIBLE 状态
2. wait_event_interruptible(wq, condition):与wait_event 函数类似,但 是此函数将进程设置为TASK_INTERRUPTIBLE,就是可以被信号打 断
3. wait_event_timeout(wq, condition, timeout):功能和wait_event 类似, 但是此函数可以添加超时时间,以jiffies 为单位。此函数有返回值, 如果返回0 的话表示超时时间到,而且condition为假。为1 的话表 示condition 为真,也就是条件满足了。
4. wait_event_interruptible_timeout(wq,condition, timeout) :与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。
轮询机制
如 select() 或 poll()。这些系统调用允许用户空间应用程序检查文件描述符是否准备好进行读取。当 select() 或 poll() 返回时,表示至少有一个监视的文件描述符准备好进行读取。
火焰传感器驱动
YL-38
驱动部分
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/irqreturn.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <mach/irqs.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#define GPGCON (0x56000060)
#define GPGDAT (0x56000064)
#define GPGUP (0x56000068)
static unsigned int *regGPGCON;
static unsigned int *regGPGDAT;
static unsigned int *regGPGUP;
static wait_queue_head_t wq; //用于管理等待队列
static int conditon;//等待条件
int fire = 0;//火焰传感器的信号
irqreturn_t fire_handler(int irq_num, void *dev)//
{
if(irq_num == IRQ_EINT8)
{
fire = 1;
}
conditon = 1;// 把等待条件变为真 进入阻塞
wake_up(&wq);//唤醒read()函数的阻塞状态
return IRQ_HANDLED;//表示中断已被正确处理
}
int fire_open(struct inode *p_node, struct file *fp)
{
printk("kernel open\n");
return 0;
}
int fire_release(struct inode *p_node, struct file *fp)
{
return 0;
}
ssize_t fire_write(struct file *fp, const char __user *user_buffer, size_t n, loff_t *offset)
{
return 0;
}
//注意!!!! 这是相当于要在应用程序中监听火焰传感器的数据(按键是否按下
//但我们不能在驱动层用一个死循环来实现
//在驱动层使用死循环会导致cpu无法做其他的操作而造成资源浪费
//如果当前没有中断发生,进程会阻塞直到中断发生并设置标志
//然后将中断标志复制到用户空间并返回。这样就实现了阻塞式的读取操作
ssize_t fire_read(struct file *fp, char __user *user_buffer, size_t n, loff_t *offset)
{
conditon = 0;//在进入阻塞前将等待条件设置为假 此时read()是非阻塞态
wait_event_interruptible(wq, conditon);//等待条件变为真
copy_to_user(user_buffer, &fire,4);//fire 是int型 4字节 往用户空间穿 fire的值 触发火焰为1 未触发是0
fire = 0;//重置标志位 因为是下降沿触发,所以在没有按下按钮时清除fire
return sizeof(fire);
}
static struct file_operations fops =
{
.owner = THIS_MODULE,
.open = fire_open,
.release = fire_release,
.read = fire_read,
};
//miscdevice 结构体
static struct miscdevice fire_device =
{
.minor = MISC_DYNAMIC_MINOR,
.fops = &fops,
.name = "fire"
};
static int __init fire_driver_init(void)
{
int ret;
//创建混杂设备驱动
ret = misc_register(&fire_device);
if(ret)
{
printk("misc_register is error");
return ret;
}
//注册中断
ret = request_irq(IRQ_EINT8, fire_handler, IRQF_TRIGGER_RISING | IRQF_DISABLED, "fire_irq", &fire_device);
if(ret)
{
printk("request_irq is error\n");
misc_deregister(&fire_device);
}
// 初始化等待队列
init_waitqueue_head(&wq);
//相应寄存器引脚映射
regGPGCON = ioremap(GPGCON,4);
regGPGDAT = ioremap(GPGDAT, 4);
regGPGUP = ioremap(GPGUP,4);
return 0;
}
static void __exit fire_exit(void)
{
iounmap(regGPGCON);
iounmap(regGPGDAT);
iounmap(regGPGUP);
//注销中断和混杂设备驱动
disable_irq(IRQ_EINT8);
free_irq(IRQ_EINT8, &fire_device);
misc_deregister(&fire_device);
}
module_init(fire_driver_init);
module_exit(fire_exit);
MODULE_LICENSE("GPL");
应用程序
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main(void)
{
int fd = open("/dev/fire",O_RDWR);
if(fd<0)
{
printf("file open error\n");
return 0;
}
int n;
while(1)
{
read(fd,&n,4);
printf("%d\n",n);
sleep(1);
}
return 0;
}