异常体系比较复杂,但是linux已经准备了很多的函数和框架,但是因为中断是和具体的开发板相关,所以中断需要我们自己来处理一些方面,但是这也是很少的一部分,很多公用的处理函数内核已经实现,linux内核搭建了一个非常容易扩充的中断处理体系。
中断系统结构涉及的方面很多,而且分布在很多的函数中,这里我主要理清一些结构和流程顺序已经在哪些函数中实现,我不知道其他人怎么样?但是我自己一开始怎是找不到linux内核是怎么把GPIO设置成中断的,我找了很久都找不到,还有我们很多的设置,初始化等等东西好像都没有实现,清除中断寄存器也不知道是怎么实现的,只是知道使用中断,差不多用request_irq函数就差不多了。下面我就把这些涉及的方方面面的流程整理出来。
一、重要结构
/include/linux/irq.h
irq_desc 内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断使用同一个中断号,一句话irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括俩个重要的结构irq_chip 和irqaction 。
irq_chip 里面基本上是一些回调函数,其中大多用于操作底层硬件,设置寄存器,其中包括设置GPIO为中断输入就是其中的一个回调函数,分析一些源代码
/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); 开启中断
- void (*eoi)(unsigned int irq);
- void (*end)(unsigned int irq);
- int (*set_affinity)(unsigned int irq,
- const struct cpumask *dest);
- int (*retrigger)(unsigned int irq);
- int (*set_type)(unsigned int irq, unsigned int flow_type); 设置中断类型,其中包括设置GPIO口为中断输入
- int (*set_wake)(unsigned int irq, unsigned int on);
- void (*bus_lock)(unsigned int irq); 上锁函数
- void (*bus_sync_unlock)(unsigned int irq); 解锁
- /* Currently used only by UML, might disappear one day.*/
- #ifdef CONFIG_IRQ_RELEASE_METHOD
- void (*release)(unsigned int irq, void *dev_id);
- #endif
- /*
- * For compatibility, ->typename is copied into ->name.
- * Will disappear.
- */
- const char *typename;
- };
include/linux/interrupt.h
- struct irqaction {
- irq_handler_t handler; 用户注册的中断处理函数
- unsigned long flags; 中断标识
- const char *name; 用户注册的中断名字,cat/proc/interrupts时可以看到
- void *dev_id; 可以是用户传递的参数或者用来区分共享中断
- struct irqaction *next; irqaction结构链,一个共享中断可以有多个中断处理函数
- int irq; 中断号
- struct proc_dir_entry *dir;
- irq_handler_t thread_fn;
- struct task_struct *thread;
- unsigned long thread_flags;
- };
二、中断流程
整个中断可以分为几个大的流程
1.中断初始化流程 注意这个阶段就是我非常迷惑的一个阶段,很多初始化,设置寄存器等等问题都是内核启动的时候就已经初始化了,这个阶段做很多工作,其中最重要的就是初始化了irq_chip结构。使得其中的众多函数已经设置好了,可以被调用了。注意这里只是实现了irq_chip结构的函数,要响应中断还有很多事情要做。
2.中断注册流程 这个流程是我们比较熟悉的,因为我们每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,没一项对应一个中断号),然后将irqaction结构添加到irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。
3.中断的处理流程 这个流程主要是产生中断后调用irq_chip中的函数来屏蔽,清除中断等等,然后调用irqaction结构中用户注册的中断函数处理中断,当然还有很多其他的事情要做,不过主要流程是这样。
中断流程应该还包括中断卸载,不过内容比较简单这里就不过啰唆了,下面我们俩详细分析一下这些具体的流程。
<一>中断初始化流程
下面内容转载他人博客,原文地址 http://blog.chinaunix.net/space.php?uid=15193587&do=blog&cuid=2194431
下面我们分析内核中断初始化的过程以及如何调用到一个新平台的irq初始化函数。
这里我们以s3c2410平台为例,他的中断初始化函数定义在:
void __init s3c24xx_init_irq(void)
{
……
}
|
注:MACHINE_START宏的作用是对mach_desc结构体进行初始化。mach_desc里定义了一些关键的体系架构相关的函数。Porting kernel到新平台时,这个结构体是非常关键的。
|
注:可以看到这里不仅初始化了init_arch_irq 全局变量,同时初始化了system_timer,init_machine等全局变量。这是kernel支持多平台的一种机制。当然这里system_timer和init_machine我不多描述,有兴趣的可以大家自己去看。机制和init_arch_irq大同小异。
/* arch/arm/kernel/irq.c */
void (*init_arch_irq)(void) __initdata = NULL;
|
asmlinkage void __init start_kernel(void)
{
……
trap_init();
rcu_init();
init_IRQ();
pidhash_init();
clockevents_init();
init_timers();
……
}
上面转载的文章分析的很好,我也是看了他的问题才找到一些答案的,比如init_arch_irq 函数指针,说明的是这是个全局的函数指针,跟体系结构相关。我接着上面的继续分析一下s3c24xx_init_irq()这个函数
这个函数比较长,主要是为每一个中断号都要进行设置,主要完成几个方面的工作
1.清楚中断标志
- /* first, clear all interrupts pending... */
- last = 0;
- for (i = 0; i < 4; i++) {
- pend = __raw_readl(S3C24XX_EINTPEND);
- if (pend == 0 || pend == last)
- break;
- __raw_writel(pend, S3C24XX_EINTPEND);
- printk("irq: clearing pending ext status %08x\n", (int)pend);
- last = pend;
- }
这里有个问题没有弄明白,要是有人知道很感谢告诉我一声,就是上面的for循环,循环清除中断标识,而且设置了一个last变量,不知道是不是为了防止一次擦除中断失败。
2. 设置irq_chip结构,中断的处理函数入口和中断标识
- for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
- irqdbf("registering irq %d (extended s3c irq)\n", irqno);
- set_irq_chip(irqno, &s3c_irqext_chip);
- set_irq_handler(irqno, handle_edge_irq);
- set_irq_flags(irqno, IRQF_VALID);
- }
<二>中断注册处理流程
requesrt_irq函数
- request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
- const char *name, void *dev)
- {
- return request_threaded_irq(irq, handler, NULL, flags, name, dev);
- }
继续看request_threaded_irq函数
/kernel/irq/manage.c
- action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
- if (!action)
- return -ENOMEM;
- action->handler = handler;
- action->thread_fn = thread_fn;
- action->flags = irqflags;
- action->name = devname;
- action->dev_id = dev_id;
- chip_bus_lock(irq, desc);
- retval = __setup_irq(irq, desc, action);
- chip_bus_sync_unlock(irq, desc);
__setup_irq内容比较多,主要完成几个方面的工作
1.添加irqaction结构到irq_desc的action链表中,需要判断是否为共享中断,只有共享中断可以添加多个中断处理函数,如果是共享中断,则要检查中断处理函数是否和链表中其他函数的触发方式等是否相同,只有一致才可以添加到链表中。
2.设置一些irq_chip结构中的函数指针指向默认函数
3.设置中断的触发方式和启动中断
完成request_irq后中断就可以被接收和处理了。下面我在整理一下设置GPIO口为中断输入的流程
request_irq() --> request_threaded_irq -->__setup_irq() --> __irq_set_trigger() (定义在kernel/irq/manage.c里) -->set_type
还有印象没,set_type是irq_chip结构中的一个回调函数,在arch/arm/plat-s3c24xx/irq.c具体的中断的irq_chip结构中set_type即对应s3c_irqext_type() (定义在arch/arm/plat-s3c24xx/irq.c里),看一个外部中断的代码
arch/arm/plat-s3c24xx/irq.c
- static struct irq_chip s3c_irqext_chip = {
- .name = "s3c-ext",
- .mask = s3c_irqext_mask,
- .unmask = s3c_irqext_unmask,
- .ack = s3c_irqext_ack,
- .set_type = s3c_irqext_type,
- .set_wake = s3c_irqext_wake
- };
- if ((irq >= IRQ_EINT0) && (irq <= IRQ_EINT3))
- {
- gpcon_reg = S3C2410_GPFCON;
- extint_reg = S3C24XX_EXTINT0;
- gpcon_offset = (irq - IRQ_EINT0) * 2;
- extint_offset = (irq - IRQ_EINT0) * 4;
- }
<三>中断的处理流程
中断处理流程又是一个比较复杂的过程,要牵涉到ARM的工作模式,异常,异常向量,还有一堆汇编代码。这些我不怎么懂,我只是看了其他一些人的分析,大概整个流程出来吧。
首先异常向量表,保存一条跳转指令,一般存放在0x00000000或者0xffff000地址,linux使用后者,中断发生后CPU进入异常模式,将跳转到相应的异常向量表处执行,异常向量表保存跳转指令,经过一段汇编,最后跳转到中断的入口asm_do_IRQ
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);
- irq_enter();
- /*
- * Some hardware gives randomly wrong interrupts. Rather
- * than crashing, do something sensible.
- */
- if (unlikely(irq >= NR_IRQS)) {
- if (printk_ratelimit())
- printk(KERN_WARNING "Bad IRQ%u\n", irq);
- ack_bad_irq(irq);
- } else {
- generic_handle_irq(irq);
- }
- /* AT91 specific workaround */
- irq_finish(irq);
- irq_exit();
- set_irq_regs(old_regs);
- }
include/linux/irq.h
- static inline void generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
- {
- #ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ
- desc->handle_irq(irq, desc);
- #else
- if (likely(desc->handle_irq))
- desc->handle_irq(irq, desc);
- else
- __do_IRQ(irq);
- #endif
- }
- static inline void generic_handle_irq(unsigned int irq)
- {
- generic_handle_irq_desc(irq, irq_to_desc(irq));
- }
这里调用desc->handle_irq分为俩种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断的中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。
我们在回到s3c24xx_init_irq()中,分析一个具体的,以外部中断为例
- 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);
- }
kernel/irq/chip.c
- void
- handle_edge_irq(unsigned int irq, struct irq_desc *desc)
- {
- spin_lock(&desc->lock); 上锁
- desc->status &= ~(IRQ_REPLAY | IRQ_WAITING);
- /*
- * If we're currently running this IRQ, or its disabled,
- * we shouldn't process the IRQ. Mark it pending, handle
- * the necessary masking and go out
- */
- if (unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) || 判断
- !desc->action)) {
- desc->status |= (IRQ_PENDING | IRQ_MASKED);
- mask_ack_irq(desc, irq); 屏蔽并清除中断
- goto out_unlock;
- }
- kstat_incr_irqs_this_cpu(irq, desc); 中断统计计数
- /* Start handling the irq */
- if (desc->chip->ack) 应答中断
- desc->chip->ack(irq);
- /* Mark the IRQ currently in progress.*/
- desc->status |= IRQ_INPROGRESS; 标记中断状态
- do {
- struct irqaction *action = desc->action;
- irqreturn_t action_ret;
- if (unlikely(!action)) {
- desc->chip->mask(irq);
- goto out_unlock;
- }
- /*
- * When another irq arrived while we were handling
- * one, we could have masked the irq.
- * Renable it, if it was not disabled in meantime.
- */
- if (unlikely((desc->status &
- (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
- (IRQ_PENDING | IRQ_MASKED))) {
- desc->chip->unmask(irq);
- desc->status &= ~IRQ_MASKED;
- }
- desc->status &= ~IRQ_PENDING;
- spin_unlock(&desc->lock);
- action_ret = handle_IRQ_event(irq, action); 处理中断,最重要的函数,注意参数,action这个参数将联系到我们的用户中断处理函数
- if (!noirqdebug)
- note_interrupt(irq, desc, action_ret);
- spin_lock(&desc->lock);
- } while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
- desc->status &= ~IRQ_INPROGRESS;
- out_unlock:
- spin_unlock(&desc->lock);
- }
kernel/irq/handle.c
- trace_irq_handler_entry(irq, action);
- ret = action->handler(irq, action->dev_id);
- trace_irq_handler_exit(irq, action, ret);
至此中断处理流程就结束了,如有不足之处,或者不对的地方,欢迎大家指出,转载请标明出处。