Android下linux中断

Linux 内核提供了完善的中断框架,我们只需要申请中断,然后注册中断处理函数即可,使用非常方便,不需要一系列复杂的寄存器配置。本次我们就来学习一下如何在
Linux 下使用中断。

在 Linux 内核中也提供了大量的中断相关的 API 函数,我们来看一下这些跟中断有关的API 函数:
1、request_irq 函数

在 Linux 内核中要想使用某个中断是需要申请的,request_irq函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下(kernel\include\linux):
在这里插入图片描述函数参数和返回值含义如下:
第一个参数irq:
表示要分配的中断号,每个中断都有一个中断号,通过中断号即可区分不同的中断
第二个参数handler:
是一个指针,指向处理这个中断的实际中断处理程序,只要接收到中断,该函数就被调用
第三个参数flags:
中断处理程序的标志,这个标志可以是一个也可以是多个组合,下图是几个常用的中断标志在这里插入图片描述
第四个参数name:
中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
第五个参数dev:
如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值
0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

2、free_irq函数
使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。free_irq函数原型如下所示:

在这里插入图片描述

函数参数和返回值含义如下:
irq: 要释放的中断。
dev_id: 如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值: 无。
3、中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:irqreturn_t (*irq_handler_t) (int, void *)
第一个参数: 是要中断处理函数要相应的中断号。
第二个参数: 是一个指向 void的指针,也就是个通用指针,需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备,dev也可以指向设备数据结构。中断处理函数的返回值为 irqreturn_t 类型,irqreturn_t 类型定义如下所示:在这里插入图片描述可以看出 irqreturn_t 是个枚举类型,一共有三种返回值。
一般中断服务函数返回值使用该形式: return IRQ_HANDLED;

4、中断使能与禁止函数
常用的中断使用和禁止函数如下所示:

void enable_irq(unsigned int irq)
void disable_irq(unsigned int irq)

enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。

disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。

在这种情况下,可以使用另外一个中断禁止函数:

void disable_irq_nosync(unsigned int irq)

disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。

上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统这个时候可以使用如下两个函数:

local_irq_enable()
local_irq_disable()

local_irq_enable 用于使能当前处理器中断系统,local_irq_disable 用于禁止当前处理器中断系统。

假如 A 任务调用 local_irq_disable 关闭全局中断 10S,当关闭了 2S 的时候 B 任务开始运 行,B 任务也调用local_irq_disable 关闭全局中断 3S,3 秒以后 B 任务调用 local_irq_enable 函数将全局中断打开了。此时才过去 2+3=5 秒的时间,然后全局中断就被打开了,此时 A 任务要 关闭 10S 全局中断的愿望就破灭了,然后 A 任务就“生气了”,结果很严重,可能系统都要被 A 任务整崩溃。为了解决这个问题,B 任务不能直接简单粗暴的通过 local_irq_enable 函数来打 开全局中断,而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下 面两个函数:

>  local_irq_save(flags)
>  local_irq_restore(flags) 

这两个函数是一对,local_irq_save 函数用于禁止中断,并且将中断状态保存在 flags 中。 local_irq_restore用于恢复中断,将中断到 flags 状态。

5、上半部与下半部

在有些资料中也将上半部和下半部称为顶半部和底半部,都是一个意思。我们在使用 request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理 函数的执行时间。比如电容触摸屏通过中断通知 SOC 有触摸事件发生,SOC 响应中断,然后 通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。我们可以将通过 IIC 读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理 过程就分为了两部分:

上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部 去执行,这样中断处理函数就会快进快出。

这里有一些可以借鉴的参考点:

①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。

上半部处理很简单,直接编写中断处理函数就行了,关键是下半部该怎么做呢?Linux 内核提供了多种下半部机制,接下来我们来学习一下这些下半部机制。

5.1.1、tasklet
Linux 内核使用tasklet结构体:
在这里插入图片描述
func 函数就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数。如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:

 void tasklet_init(struct tasklet_struct *t,
                    void (*func)(unsigned long), 
                    unsigned long data);

函数参数和返回值含义如下:
t:要初始化的 tasklet func:
tasklet 的处理函数。
data:要传递给 func 函数的参数
返回值:没有返回值。

也 可 以 使 用 宏 DECLARE_TASKLET 来 一 次 性 完 成 tasklet 的 定 义 和 初 始 化 ,
DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下在这里插入图片描述
:其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量,func就是 tasklet 的处理函数,data 是传递给 func 函数的参数。

在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运 行,tasklet_schedule 函数原型如下:

 void tasklet_schedule(struct tasklet_struct *t) 

函数参数和返回值含义如下:
t:要调度的 tasklet,也就是 DECLARE_TASKLET 宏里面的 name。
返回值:没有返回值。

关于 tasklet 的参考使用示例如下所示:

//示例代码 tasklet 使用示例
/* 定义 taselet */
struct tasklet_struct testtasklet;
/* tasklet 处理函数 */
void testtasklet_func(unsigned long data) {
 /* tasklet 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 调度 tasklet */
 tasklet_schedule(&testtasklet);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 tasklet */
  tasklet_init(&testtasklet, testtasklet_func, data);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}

5.1.2、工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。Linux 内核使用 work_struct 结构体表示一个工作,内容如下:

struct work_struct {
						 atomic_long_t data; 
						 struct list_head entry;
						 work_func_t func; /* 工作队列处理函数 */
};

这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下:

struct workqueue_struct {
						 struct list_head pwqs; 
						 struct list_head list; 
						 struct mutex mutex; 
						 int work_color;
						 int flush_color; 
						 atomic_t nr_pwqs_to_flush;
						 struct wq_flusher *first_flusher;
						 struct list_head flusher_queue; 
						 struct list_head flusher_overflow;
						 struct list_head maydays; 
						 struct worker *rescuer; 
						 int nr_drainers; 
						 int saved_max_active;
						 struct workqueue_attrs *unbound_attrs;
						 struct pool_workqueue *dfl_pwq; 
						 char name[WQ_NAME_LEN];
						 struct rcu_head rcu;
						 unsigned int flags ____cacheline_aligned;
						 struct pool_workqueue __percpu *cpu_pwqs;
						 struct pool_workqueue __rcu *numa_pwq_tbl[];
};

Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用
worker 结构体表示工作者线程,worker 结构体内容如下:

struct worker {
				 union {
				 struct list_head entry; 
				 struct hlist_node hentry;
				 };
				 struct work_struct *current_work; 
				 work_func_t current_func; 
				 struct pool_workqueue *current_pwq;
				 bool desc_valid;
				 struct list_head scheduled; 
				 struct task_struct *task; 
				 struct worker_pool *pool; 
				 struct list_head node; 
				 unsigned long last_active; 
				 unsigned int flags; 
				 int id; 
				 char desc[WORKER_DESC_LEN];
				 struct workqueue_struct *rescue_wq;
};

可以看出,每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:

#define INIT_WORK(_work, _func)

_work 表示要初始化的工作,_func 是工作对应的处理函数。也可以使用 DECLARE_WORK 宏一次性完成工作的创建和初始化,宏定义如下:

#define DECLARE_WORK(n, f)

n 表示定义的工作(work_struct),f 表示工作对应的处理函数。和 tasklet 一样,工作也是需要调度才能运行的,工作的调度函数为 schedule_work,函数原型如下所示:

bool schedule_work(struct work_struct *work)

函数参数和返回值含义如下:
work:要调度的工作。
返回值:0 成功,其他值 失败。

关于工作队列的参考使用示例如下所示:

/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
 /* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id) {
 ......
 /* 调度 work */
 schedule_work(&testwork);
 ......
}
/* 驱动入口函数 */
static int __init xxxx_init(void) {
 ......
 /* 初始化 work */
 INIT_WORK(&testwork, testwork_func_t);
 /* 注册中断处理函数 */
 request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
 ......
}

6、设备树中断信息节点
如果使用设备树的话就需要在设备树中设置好中断属性信息,Linux 内核通过读取设备树
中的中断属性信息来配置中断。

gic: interrupt-controller@ff131000 {
	compatible = "arm,gic-400";
	#interrupt-cells = <3>;
	#address-cells = <0>;
	interrupt-controller;
	reg = <0x0 0xff131000 0 0x1000>,
	      <0x0 0xff132000 0 0x2000>,
	      <0x0 0xff134000 0 0x2000>,
	      <0x0 0xff136000 0 0x2000>;
	interrupts = <GIC_PPI  (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;
};

compatible 属性值为““arm,gic-400””在 Linux 内核源码中搜索““arm,gic-400””即可找到 GIC 中断控制器驱动文件。#interrupt-cells 和#address-cells、#size-cells 一样。表示此中断控制器下设备的 cells大小,对于设备而言,会使用 interrupts 属性描述中断信息,#interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的GIC 来说,一共有 3个 cells,这三个 cells 的含义如下:

第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。

第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号
的范围为 0~15。

第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候
表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 
中断的 CPU 掩码。

interrupt-controller 节点为空,表示当前节点是中断控制器。

对于 gpio 来说,gpio 节点也可以作为中断控制器,比如

gpio0: gpio0@ff040000 {
	compatible = "rockchip,gpio-bank";
	reg = <0x0 0xff040000 0x0 0x100>;
	interrupts = <GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&pmucru PCLK_GPIO0_PMU>;
	gpio-controller;
	#gpio-cells = <2>;

	interrupt-controller;
	#interrupt-cells = <2>;
};

interrupts 描述中断源信息,对于 gpio0 来说一共有两条信息,中断类型都是 SPI,触发电平都是 IRQ_TYPE_LEVEL_HIGH。中断源是 3,interrupt-controller 表明了 gpio0 节点也是个中断控制器,用于控制 gpio0所有 IO的中断。#interrupt-cells 修改为 2
简单总结一下与中断有关的设备树属性信息:

①、#interrupt-cells,指定中断源的信息 cells 个数。
②、interrupt-controller,表示当前节点为中断控制器。
③、interrupts,指定中断号,触发方式等。
④、interrupt-parent,指定父中断,也就是中断控制器。

7、获取中断号
编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:

unsigned int irq_of_parse_and_map(struct device_node *dev,  int index)

函数参数和返回值含义如下:

dev:设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。

如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如
下:

int gpio_to_irq(unsigned int gpio)

函数参数和返回值含义如下:

gpio:要获取的 GPIO 编号。
返回值:GPIO 对应的中断号。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

那肯定是很多年以后!

你的鼓励就我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值