驱动第二天---中断
- 中断的基本概念
- 中断号的获取方式
- 中断申请
- 中断处理
- 上传数据给用户
- IO模型
- 异步信号
- 中断下半部
1. 中断的基本概念
参考裸机开发中的相关资料
2. 中断号的获取方式
设备树:dts 设备树源码 设备树里面一些通用的定义,提取出,形成类似c 头文件,dtsi 称之为:设备树的头文件,#include "xxx.dtsi" dtc 把dts编译成二进制文件(dtb) 给内核使用
1,中断号--就是一个号码,需要通过一定的方式去获取到
获取中断号到方法:
1, 宏定义
IRQ_EINT(号码) //eyxnos4412-mach.h//旧内核2.6.xxx
2,设备树文件中 //在3.14.0内核中,从设备树中获取
arch/arm/boot/dts/exynos4412-fs4412.dts
--> arch/arm/boot/dts/exynos4412.dtsi
-->arch/arm/boot/dts/exynos4x12.dtsi
-->arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
现在以key3为例: 1,看原理图 key3--->SIM_DET---->gpx1_2
2, 查看设备树文件:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
3,确定了一个中断号的源:以key3为例,对应datasheet(参考4412手册,752页关于中断号的描述)的中断号:26
4,在设备树中,关于gpx1_2的中断号也是26
在设备树头文件中:arch/arm/boot/dts/exynos4x12-pinctrl.dtsi
gpx1: gpx1 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
interrupt-parent = <&gic>;
interrupts = <0 24 0>, <0 25 0>, <0 26 0>, <0 27 0>,
<0 28 0>, <0 29 0>, <0 30 0>, <0 31 0>;
#interrupt-cells = <2>;
};
做一个设备树节目:描述Key3,并加载到开发板上面
vim arch/arm/boot/dts/exynos4412-fs4412.dts
在编程过程中,需要定义自己的节点--描述当前设备用的中断号
arch/arm/boot/dts/exynos4412-fs4412.dts
key3_int_nod{
compatible = "key3_int";
interrupt-parent = <&gpx1>;
interrupts = <2 4>
};
这里的interrupts = <2 4>
第一项2 表示。GPX1-2
第二项4 表示 中断触发方式 具体的值参考:
\Documentation\devicetree\bindings\pinctrl
编译设备树:make dtbs
烧录到开发板。。。。。
设备树调试信息: cat /proc/device-tree/
2.2 在驱动中通过设备树获取中断号
在驱动中,获取设备树的信息的APIs:
// 获取到设备树中到节点
//需要:linux/of.h
struct device_node *of_find_node_by_path(const char *path);
参数:path 就是设备树节点所在的路径
返回值:struct device_node *
// 通过节点去获取到中断号码
//需要:#include <linux/of_irq.h>
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
参数1:节点
参数2:索引号
返回值:中断号
3. 中断申请
3.1申请中断
获取到了中断号之后,我们需要做一个中断号,然后使用中断申请函数,去申请中断:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
参数1:中断号
参数2:这个类型的函数指针:typedef irqreturn_t (*irq_handler_t)(int, void *);
函数指针所指向的函数的返回值是一个枚举类型:
typedef enum irqreturn irqreturn_t;
具体有以下定义:
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
参数3:flag表示 中断触发的方式:
#define IRQF_TRIGGER_NONE 0x00000000//内部控制器触发中断的时候的标志
#define IRQF_TRIGGER_RISING 0x00000001//上升沿1<<0
#define IRQF_TRIGGER_FALLING 0x00000002//下降沿1<<1
#define IRQF_TRIGGER_HIGH 0x00000004//高电平
#define IRQF_TRIGGER_LOW 0x00000008//低电平
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
参数4:名字
可以通过cat /proc/intterrupts
参数5:给中断处理函数的参数
3.2 中断释放
释放设备中断资源:
free_irq(unsigned int irq, void * dev_id);
4.中断处理
irqreturn_t key_drv_irq_handler(int v,void *arg)
{
printk("-------------%s-----------\n",__FUNCTION__);
if(readl(reg_addr+4) &(1<<2) )
{
printk("key3 up!\n");
}
else
{
printk("key3 press down!\n");
}
return IRQ_HANDLED;
}
5. 上传数据给用户
static ssize_t key_drv_read(struct file *file, char __user *buf,
size_t nbytes, loff_t *ppos)
{
int usr_ret;
//printk("--------------%s--------------\n",__FUNCTION__);
usr_ret =copy_to_user(buf,&key_dev->key_t,sizeof(struct key_desc));
if(usr_ret >0)
{
printk("read error\n");
return usr_ret;
}
memset(&key_dev->key_t,0,sizeof(struct key_desc));
return 0;
}
6.IO模型
文件io模型:
1,非阻塞: 立即返回数据给用户,读写之间的时候段很短,非常浪费资源。
2,阻塞 :当进程在读取数据,如果资源/数据没准备好,进程就进行休眠,让出系统调度,可以减少资源开支
3,多路复用--select/poll:poll可用于多路设备,资源的监控,用于多种设备都是阻塞的情况下,可以实现轮循的动作。
4, 异步信号通知faync :不需要同步读写数据,负责监控一个可见的信号,当信号发生时才做处理,不影响其他任务的正常执行。
6.1 阻塞
当进程在读取外部设备的资源(数据),资源没有准备好,进程就会休眠
linux应用中,大部分的函数接口都是阻塞
scanf();
read();
write();
accept();
休眠在驱动的实现:
1,将当前进程加入到等待队列头中
add_wait_queue(wait_queue_head_t * q, wait_queue_t * wait)
2,将当前进程状态设置成TASK_INTERRUPTIBLE (可中断的状态)
set_current_state(TASK_INTERRUPTIBLE)
3,让出调度--休眠
schedule(void)
更加智能的接口,等同于上面的三个接口:
wait_event_interruptible(wait_queue_head_t *q, condition)
参数1:typedef struct __wait_queue_head wait_queue_head_t;
等待头 指针
参数2:为0的时候 就等待数据
为1的时候 ,就不等
驱动如何去写代码
1,等待队列头
wait_queue_head_t wq_head;
init_waitqueue_head(wait_queue_head_t *q);
2,在需要等待(没有数据)的时候,进行休眠
wait_event_interruptible(wait_queue_head_t *q, condition)
3,在一个合适的时候(有数据),会将进程唤醒
wake_up_interruptible
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(wq_head);
//同时设置标志位
key_dev->key_state = 1;
6.2 非阻塞
在读写的时候,如果没有数据,立刻返回,并且返回一个出错码 用的会比较少,因为比较耗资源
在打开(open)一个设备节点文件的时候,加入O_NONBLOCK这个标志,那么可以实现非阻塞
在fcntl.h 中的定义:
#ifndef O_NONBLOCK
#define O_NONBLOCK 00004000
对于应用程序代码来说:
open("/dev/key0", O_RDWR|O_NONBLOCK);
对于驱动程序来说:
//驱动中需要去区分,当前模式是阻塞还是非阻塞
//如果当前是非阻塞模式,并且没有数据,立马返回一个出错码
if( (file->f_flags & O_NONBLOCK) && (!key_dev->key_status))//非阻塞的情况
{
return -EAGAIN;
}
6.3 多路复用 (poll)
在ubuntu 主机里面,输入man poll
可以看到相应的使用方法
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数1:struct pollfd *fds
struct pollfd {
int fd; /* 打开的设备对应的fd */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
//对于events 和 revents 常用的无非就以下几种
//POLLIN 读
//POLLOUT 写
//POLLIN 出错
};
参数2:监控的 fds 的个数 参数3:超时的时间,单位为ms(毫秒)
如果是正数,表示等待n ms
如果是0的时候就马上返回,非阻塞
如果是负数,-1 表示无限等待
返回值: 为正数的时候表示成功,表示fd中有数据 返回为-1的时候 表示出错 且会设定errno 可以使用perror( ".....") 返回为0的时候 表示超时出错且fd没准备好
驱动里面代码怎么样写? 1、在file_operation 里面有这个成员:
unsigned int (*poll) (struct file *, struct poll_table_struct *);
其中,关于struct poll_table_struct的定义如下:
typedef struct poll_table_struct {
poll_queue_proc _qproc;
unsigned long _key;
} poll_table;
2、在file_operations myfops中添加注册成员:
const struct file_operations myfops={
.open = key_drv_open,
.write= key_drv_write,
.read = key_drv_read,
.release = key_drv_close,
.poll = key_poll;
};
3、实现这个成员函数:
unsigned int key_poll(struct file *filep, struct poll_table_struct * poll_t)
{
unsigned int mask;
printk("--------------%s--------------\n",__FUNCTION__);
poll_wait(filp, key_dev->wq_head, poll_t );
if(key_dev->key_status)
mask |= POLLIN;
else
mask = 0;
return mask;
}
7 异步信号
异步信号通知: 当有数据到时候,驱动会发送信号(SIGIO)给应用,就可以异步去读写数据,不用主动去读写
7.1 应用--处理信号,主要是读写数据
第一步:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
void catch_signale(int signo)
{
if(signo == SIGIO)
{
printf("we got sigal SIGIO");
// 读取数据
read(fd, &event, sizeof(struct key_event));
if(event.code == KEY_ENTER)
{
if(event.value)
{
printf("APP__ key enter pressed\n");
}else
{
printf("APP__ key enter up\n");
}
}
}
}
第二步:
int fcntl(int fd, int cmd, ... /* arg */ );
把SIGIO的信号关联到本进程(属主进程)
把fd改成异步的模式:F_flag |= FASYNC
#define FASYNC 00020000 /* fcntl, for BSD compatibility */
//first signed SIGIO to handler func
signal(SIGIO, signal_handler);
//second set process id to caught SIGIO
fcntl(fd, F_SETOWN, getid());
//last,set fd to async
flags = fcntl(fd, F_GETFL);
flags |= FASYNC;
fcntl(fd, F_SETFL, flags);
第三步:
驱动--发送信号
1,需要和进程进行关联--记录信号该发送给谁
实现一个fasync的接口:
//int (*fasync) (int, struct file *, int);
int key_fasync(int fd, struct file *filp, int on)
{
return fasync_helper(fd, filp, on, &key_dev->fasync);
}
const struct file_operations myfops={
。。。。。
.fasync = key_fsync,
};
//只需要调用这个接口:
int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp);
参数1-3从:int key_fasync(int fd, struct file *filp, int on)
传入即可
7.2 在某个特定的时候去发送信号,在有数据的时候
//发送信号
kill_fasync(&event->fasync, SIGIO, POLLIN);//read
8 中断下半部
中断上下文是什么概念?
中断下半部的实现的方法有哪些?
1,softirq :处理速度快,使用复杂,需要修改内核源码,对于我们用户来说呢,用得比较少也不推荐大家使用
2, tasklet :使用了sotfirq做为二次封装,处理速度较快,但是它工作于中断上下文,里面不能使用阻塞型接口,延时函数
3, workqueue :并没有使用了sotfirq做为二次封装,处理速度稍慢,对于用户来说无影响,灵活之处:工作在进程上下文,可以使用阻塞型函数,也可以延时。。。
一、tasklet的使用:
首先是,tasklet 的数据结构:
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
void tasklet_init(struct tasklet_struct t, void (func)(unsigned long), unsigned long data);
tasklet_schedule(struct tasklet_struct *t);
tasklet_kill(struct tasklet_struct *t);//释放资源
二、工作 队列 workqueue的实现
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
typedef void (*work_func_t)(struct work_struct *work);
a, 初始化
void work_irq_half(struct work_struct *work)
{
printk("-------%s-------------\n", __FUNCTION__);
// 表示有数据,需要去唤醒整个进程/等待队列
wake_up_interruptible(&key_dev->wq_head);
//同时设置标志位
key_dev->key_state = 1;
//发送信号
kill_fasync(&key_dev->faysnc, SIGIO, POLLIN);
}
struct work_struct mywork;
INIT_WORK(struct work_struct *work, work_func_t func);
b, 在上半部中放入到内核线程中--启动
schedule_work(&key_dev->mywork);