linux驱动之中断处理过程C程序部分
当发生中断之后,linux系统在汇编阶段经过一系列跳转,最终跳转到asm_do_IRQ()函数,开始C程序阶段的处理。在汇编阶段,程序已经计算出发生中断的中断号irq,这个关键参数最终传递给asm_do_IRQ()。linux驱动中断处理C程序部分,主要涉及linux中断系统数据结构的初始化和C程序的具体执行跳转。
一、中断处理数据结构
linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断源(可能是一个中断,也可能是一组中断),记录了中断的入口处理函数(不是用户注册的处理函数)、中断标记,并提供了中断的底层硬件访问函数(中断清除、屏蔽、使能)。另外,通过这个结构体数组项成员action,能够找到用户注册的中断处理函数。结构体irq_desc的数据类型在include/linux/irq.h中定义,内容如下:
struct irq_desc {
irq_flow_handler_t handle_irq; /* 当前中断的处理函数入口 */
struct irq_chip *chip; /* 低层的硬件访问 */
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
struct irqaction *action; /* 用户提供的中断处理函数链表 */
unsigned int status; /* IRQ状态 */ unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
spinlock_t lock;
const char *name; /* 中断名称 */
} ____cacheline_internodealigned_in_smp;
irq_desc成员变量handle_irq是这个或这组中断的入口处理函数,成员变量chip结构体包含了这个中断的清除、屏蔽或者使能等底层函数,结构体类型irq_chip的定义也在include/linux/irq.h中,内容如下:
struct irq_chip {
const char *name;
unsigned int (*startup)(unsigned int irq);
void (*shutdown)(unsigned int irq);
void (*enable)(unsigned int irq);
void (*disable)(unsigned int irq); void (*ack)(unsigned int irq);
void (*mask)(unsigned int irq);
void (*mask_ack)(unsigned int irq);
void (*unmask)(unsigned int irq);
const char *typename;
};
irq_desc成员变量action记录了用户注册的中断处理函数、中断标志等等内容,其类型irqaction类型定义在include/linux/interrupt.h中,内容如下:
struct irqaction {
irq_handler_t handler; /* 用户注册的中断处理函数 */
unsigned long flags; /* 中断标志,是否共享中断,电平触发还是边沿触发等 */
cpumask_t mask; /* 用于SMP */
const char *name; /* 用户注册的中断名字,/proc/interrupts */
void *dev_id; /* 用户传递给handler的参数,还可以用来区分共享中断 */
struct irqaction *next; /* 指向下一个irqaciton结构体指针 */
int irq; /* 中断号 */
struct proc_dir_entry *dir;
};
用户注册的每个中断处理函数对应一个irqaciton结构体,一个中断源可以有多个处理函数(共享终端),它们的irqaciton结构体可以构成一个单项链表,irq_desc[irqn].action则是表头。irq_desc[NR_IRQS]结构体数组的构成情况如下图所示:
中断的处理流程如下:
(1) 发生中断后,CPU执行异常向量vector_irq的代码;
(2)在vector_irq里面,最终会调用中断处理C程序总入口函数asm_do_IRQ();
(3)asm_do_IRQ()根据中断号调用irq_des[NR_IRQS]数组中的对应数组项中的handle_irq();
(4)handle_irq()会使用chip的成员函数来设置硬件,例如清除中断,禁止中断,重新开启中断等;
(5)handle_irq逐个调用用户在action链表中注册的处理函数。
可见,中断体系结构的初始化,就是构造irq_desc[NR_IRQS]这个数据结构;用户注册中断就是构造action链表;用户卸载中断就是从action链表中去除对应的项。
二、中断处理系统(数据结构)初始化
1、操作系统相关中断初始化
init_IRQ()函数用来初始化中断体系结构,代码在arch/arm/kernel/irq.c中,代码如下:
void __init init_IRQ(void)
{
int irq; for (irq = ; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
init_arch_irq();
}
init_arch_irq()函数,就是用来初始化irq_desc[NR_IRQS]的,而且这个函数是与硬件平台紧密相关的,是一个函数指针。在移植linux内核的时候,将它指向硬件平台相关的中断初始化函数。这里我们以S3C2440硬件平台为例,这个函数指针指向s3c24xx_init_irq()。
s3c24xx_init_irq()函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有的中断设置了芯片相关的数据结构irq_desc[irq].chip,设置了处理函数入口irq_desc[irq].handle_irq。以外部中断EINT0为例,用来设置它们的代码如下:
void __init s3c24xx_init_irq(void)
{
for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
irqdbf("registering irq %d (ext int)\n", irqno);
set_irq_chip(irqno, &s3c_irq_eint0t4);
set_irq_handler(irqno, handle_edge_irq);
set_irq_flags(irqno, IRQF_VALID);
}
}
set_irq_chip()函数的作用就是“irq_desc[irqno].chip = & s3c_irq_eint0t4”。s3c_irq_eint0t4为系统提供了一套操作EIN0~EINT4的中断底层的函数集合,内容如下:
static struct irq_chip s3c_irq_eint0t4 = {
.name = "s3c-ext0",
.ack = s3c_irq_ack,
.mask = s3c_irq_mask,
.unmask = s3c_irq_unmask,
.set_wake = s3c_irq_wake,
.set_type = s3c_irqext_type,
};
中断处理函数入口为handle_edge_irq(),也及时“irq_desc[irqno].handle_irq = handle_edge_irq”.发生中断后,do_asm_irq()函数会调用该中断入口处理函数handle_edge_irq(),而该函数会调用用户注册的具体处理函数。
2、用户注册中断时带来的中断初始化
用户(驱动程序)通过request_irq()函数向内核注册中断处理函数,request_irq()函数根据中断号找到数组irq_desc[irqno]对应的数组项,然后在它的action链表中添加一个action表项。,该函数在kernel/irq/manage.c中定义,函数内容如下:
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
{
action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);
..........
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
...........
retval = setup_irq(irq, action);
}
request_irq()函数首先使用4个参数构造一个irqaction结构,然后调用setup_irq()函数将它链入链表中,代码如下:
int setup_irq(unsigned int irq, struct irqaction *new)
{
/* 判断是否没有注册过,如果已经注册了就判断是否是可共享的中断 */
p = &desc->action;
old = *p;
if (old) {
if (!((old->flags & new->flags) & IRQF_SHARED) ||
((old->flags ^ new->flags) & IRQF_TRIGGER_MASK)) {
old_name = old->name;
goto mismatch;
} /* add new interrupt at end of irq queue */
do {
p = &old->next;
old = *p;
} while (old);
shared = ;
} /* 链入新表项 */
*p = new; /* 如果在链入之前不是空链,那么之前的共享中断已经设置了中断触发方式,没有必要重复设置 */
/* 如果链入之前是空链,那么就需要设置中断触发方式 */
if (!shared) {
irq_chip_set_defaults(desc->chip); /* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
if (desc->chip && desc->chip->set_type)
desc->chip->set_type(irq,
new->flags & IRQF_TRIGGER_MASK);
else
printk(KERN_WARNING "No IRQF_TRIGGER set_type "
"function for IRQ %d (%s)\n", irq,
desc->chip ? desc->chip->name :
"unknown");
} else
compat_irq_chip_set_default_handler(desc); desc->status &= ~(IRQ_AUTODETECT | IRQ_WAITING |
IRQ_INPROGRESS); if (!(desc->status & IRQ_NOAUTOEN)) {
desc->depth = ;
desc->status &= ~IRQ_DISABLED;
/* 启动中断 */
if (desc->chip->startup)
desc->chip->startup(irq);
else
desc->chip->enable(irq);
} else
/* Undo nested disables: */
desc->depth = ;
}
/* Reset broken irq detection when installing new handler */
desc->irq_count = ;
desc->irqs_unhandled = ; new->irq = irq;
register_irq_proc(irq);
new->dir = NULL;
register_handler_proc(irq, new);
}
setup_irq()函数主要完成的功能如下:
(1)将新建的irqaciton结构链入irq_desc[irq]结构体的action链表中;
① 如果action链表为空,则直接链入;
② 如果非空,则要判断新建的irqaciton结构和链表中的irqaciton结构所表示的中断类型是否一致:即是都声明为“可共享的”,是否都是用相同的触发方式,如果一致,则将新建的irqaciton结构链入;
(2)设置中断的触发方式;
(3)启动中断。
3、卸载中断
卸载中断使用函数free_irq(),该函数定义在kernel/irq/manage.c中,需要用到两个参数irq、dev_id。通过这参数irq可以定位到action链表,再使用dev_id在链表中找到要卸载的表项(共享中断的情况)。如果它是唯一表项,那么删除中断,还需要调用irq_desc[irq].chip->shutdown()或者irq_desc[irq].chip->disable()来关闭中断,代码的主要内容如下:
void free_irq(unsigned int irq, void *dev_id)
{
desc = irq_desc + irq;
p = &desc->action;
for (;;) {
struct irqaction *action = *p; if (action) {
struct irqaction **pp = p; p = &action->next;
if (action->dev_id != dev_id)
continue; /* Found it - now remove it from the list of entries */
*pp = action->next; if (!desc->action) {
desc->status |= IRQ_DISABLED;
if (desc->chip->shutdown)
desc->chip->shutdown(irq);
else
desc->chip->disable(irq);
} unregister_handler_proc(irq, action);
kfree(action);
return;
}
return;
}
}
三、中断的处理过程
从asm_do_IRQ()函数开始,分析linux系统中断的处理流程,它在arch/arm/kernel/irq.c中定义,内容如下:
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq; if (irq >= NR_IRQS)
desc = &bad_irq_desc; irq_enter(); desc_handle_irq(irq, desc); /* AT91 specific workaround */
irq_finish(irq); irq_exit();
set_irq_regs(old_regs);
}
其中desc_handle_irq(irq,desc)是一个内联函数调用,内联函数展开的结果,就是irq_desc[irq].handle_irq(irq,desc)。内容如下:
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
1、普通流程(以外部中断EINT0为例)
以外部中断EINT0为例分析程序的执行流程。通过对中断系统数据结构初始化的了解,我们知道irq_desc[IRQ_EINT0].handle_irq函数指针指向handle_edge_irq(),该函数在kernel/irq/chip.c中被定义,用来处理边沿触发的中断,内容如下:
void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
kstat_cpu(cpu).irqs[irq]++; /* Start handling the irq */
desc->chip->ack(irq); /* Mark the IRQ currently in progress.*/
desc->status |= IRQ_INPROGRESS; action_ret = handle_IRQ_event(irq, action);
}
通过函数调用desc->chip->ack(irq)来响应中断,实际上就是清除中断以使得可以接受下一个中断,有了之前数据结构初始化的前提了解,可以知道实际上执行的就是s3c_irq_eint0t4.ack函数。handle_IRQ_event函数逐个执行action链表中用户注册的中断处理函数,它在kernel/irq/handle.c中定义,关键代码如下:
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
do {
ret = action->handler(irq, action->dev_id);
if (ret == IRQ_HANDLED)
status |= action->flags;
retval |= ret;
action = action->next;
} while (action);
}
用户通过函数request_irq()函数注册中断处理函数时候,传入参数irq和dev_id,在这里这两个参数被用户注册的中断处理函数action->handler()所使用。可见用户可以在注册中断处理函数的时候,指定参数dev_id,然后将来再由注册的中断处理函数使用这个参数。
2、特殊流程(以外部中断EINT5为例)
在S3C2440处理器架构中,EINT5中断属于EINT4t7中断集合,是一个子中断。当EINT5中断线发生中断事件,那么将先跳转到EINT4t7中断号对应的中断入口处理函数,也即是irq_desc[EINT4t7].handle_irq(irq,desc),进行具体子中断确定,然后再跳转到真正发生中断的中断入口处理函数执行。回顾一下EINT5这个中断注册时的函数调用,如下:
request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, "S2", NULL);
我们并没有在注册EINT5中断处理函数的时候,一并注册了EINT4t7的处理函数,其实我们也不可能使用IRQ_EINT4t7来注册用户中断,因为我们只会使用一个中断源。中断集合EINT4t7的中断入口处理函数,是在arch/arm/plat-s3c24xx/irq.c中的函数s3c24xx_init_irq()来初始化的,内容如下:
set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
由此可见,函数s3c_irq_demux_extint4t7()就是EINT4t7的中断入口处理函数。所以,当发生EINT5中断事件,那么汇编阶段根据INTOFFSET确定中断号为IRQ_EINT4t7,asm_do_IRQ函数通过传入的这个参数,将跳转到irq_desc[EINT4t7].handle_irq(irq,desc)函数执行,也就是函数s3c_irq_demux_extint4t7(irq,desc),该函数的主要内容如下:
static void
s3c_irq_demux_extint4t7(unsigned int irq,
struct irq_desc *desc)
{
unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK); eintpnd &= ~eintmsk;
eintpnd &= 0xff; /* only lower irqs */ while (eintpnd) {
irq = __ffs(eintpnd);
eintpnd &= ~(<<irq);
irq += (IRQ_EINT4 - );
desc_handle_irq(irq, irq_desc + irq);
}
}
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
desc->handle_irq(irq, desc);
}
函数s3c_irq_demux_extint4t7()的作用,就是根据寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK重新计算中断号,这个时候将计算出真正的中断号IRQ_EINT5,然后通过desc_handle_irq(irq, irq_desc + irq)来调用irq_desc[EINT5].handle_irq(irq,desc)。此后的过程与EINT0发生中断后的执行过程类似。
可见,由于S3C2440在设计的时候最多允许32个中断源(剩余的中断源采用子中断的方式),所以汇编阶段根据INTOFFSET确定的中断号取值范围为IRQ_EINT0~IRQ_EINT0+31。也就是说asm_do_IRQ函数的参数irq的取值范围只有32个值,发生中断可能是一个实际的中断号,也可能是一组中断的中断号。如果是一个实际的中断号,那么直接跳转到该中断号对应irq_desc结构体下的handle_irq()执行即可;如果是一组中断的中断号,那么将通过这组中断对应的irq_desc结构体下的handle_irq()先来确定实际发生中断的中断号,然后再来执行其中断入口处理函数。
参考资料: 《嵌入式Linux应用开发完全手册》
linux驱动之中断处理过程C程序部分的更多相关文章
- linux驱动之中断处理过程汇编部分
linux系统下驱动中,中断异常的处理过程,与裸机开发中断处理过程非常类似.通过简单的回顾裸机开发中断处理部分,来参考学习linux系统下中断处理流程. 一.ARM裸机开发中断处理过程 以S3C244 ...
- Linux驱动之中断处理体系结构简析
S3C2440中的中断处理最终是通过IRQ实现的,在Linux驱动之异常处理体系结构简析已经介绍了IRQ异常的处理过程,最终分析到了一个C函数asm_do_IRQ,接下来继续分析asm_do_IRQ, ...
- Linux中断 - ARM中断处理过程
一.前言 本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作.具体整个处理过程分成三个步骤来描述: 1.第二章描述了中断处理的准备过程 2.第三章描述了当发生中的时候 ...
- Linux内核system_call中断处理过程
“平安的祝福 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ” men ...
- Linux kernel的中断子系统之(六):ARM中断处理过程
返回目录:<ARM-Linux中断系统>. 总结:二中断处理经过两种模式:IRQ模式和SVC模式,这两种模式都有自己的stack,同时涉及到异常向量表中的中断向量. 三ARM处理器在感知到 ...
- Linux驱动之触摸屏程序编写
本篇博客分以下几部分讲解 1.介绍电阻式触摸屏的原理 2.介绍触摸屏驱动的框架(输入子系统) 3.介绍程序用到的结构体 4.介绍程序用到的函数 5.编写程序 6.测试程序 1.介绍电阻式触摸屏的原理 ...
- ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程
ARM Linux驱动篇 学习温度传感器ds18b20的驱动编写过程 原文地址:http://www.cnblogs.com/NickQ/p/9026545.html 一.开发板与ds18b20的入门 ...
- Linux内核设计第五周学习总结 分析system_call中断处理过程
陈巧然原创作品 转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 使用gdb跟踪分析一 ...
- Linux内核分析第五周学习总结——分析system_call中断处理过程
Linux内核分析第五周学习总结--分析system_call中断处理过程 zl + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/U ...
随机推荐
- 通过 python ssh库连接并发送命令给设备
import paramiko import time hostname = '192.168.248.156' port = 22 user = 'zhou' passwd = ' paramiko ...
- 通过源码分析View的测量
要理解View的测量,首先要了解MeasureSpec,系统在测量view的宽高时,要先确定MeasureSpec. MeasureSpec(32为int值)由两部分组成: SpecMode(高2位) ...
- AndroidStudio安装、配置、测试
AndroidStudio安装.配置.测试(win7_64bit) 目录 1.概述 2.本文用到的工具 3.安装测试 4.模拟器安装.使用 5.常用配置 6.注事事项 7.相关博文 >>看 ...
- Floyd算法_MATLAB
%求图中任意两点之间的最短距离与最短路径 %floyd算法通用程序,输入a为赋权邻接矩阵 %输出为距离矩阵D,和最短路径矩阵path function D=floyd(a) n=size(a,);%行 ...
- web前端(9)—— CSS属性
属性 终于到css属性,前面就零零散散的用了什么color,font-size之类,本篇博文就专项的介绍它了 字体属性 font-family 此属性是设置字体样式的,比如微软雅黑,方正书体,华文宋体 ...
- 通过http上下文判断是否是Ajax请求
using System; namespace System.Web.Mvc { /// <summary>Represents a class that extends the < ...
- 解决“Eclipse中启动Tomcat后,http://localhost:8080/无法访问”的问题
这个问题是eclipse造成的,我们可以修改配置来实现通过eclipse启动tomcat可以访问http://localhost:8080 打开Server试图(打开前不要启动tomcat),双击其中 ...
- js获取当前页面url网址信息
js如何准确获取当前页面url网址信息 在WEB开发中,时常会用到javascript来获取当前页面的url网址信息,在这里是我的一些获取url信息的小总结. 下面我们举例一个URL,然后获得它的各个 ...
- Win 10 启用Linux子系统---Kali 和Ubuntu子系统
注:转载请注明出处,谢谢!!! 一.Linux on Windows简介 Win10一周年版推出了用于Windows的Linux子系统这一功能.Linux子系统和Windows的结合真是有一种神互补. ...
- 异常--finally关键字
finally定义: finally{}代码块中的代码是一定会执行的,一般用来关闭资源或者一些必须执行的代码,如数据库连接的关闭