linux io中断驱动编程,驱动第二天---中断与I/O

# 驱动第二天---中断 #

[TOC]

----------

1. 中断的基本概念

2. 中断号的获取方式

3. 中断申请

4. 中断处理

5. 上传数据给用户

6. IO模型

7. 异步信号

8. 中断下半部

----------

## 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 = ;

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 = ;

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

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_NONBLOCK00004000

对于应用程序代码来说:

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

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

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 FASYNC00020000/* 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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值