linux 驱动中断与IO


  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 = <&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);
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值