Linux中断处理流程

  1. 中断处理流程

当中断发生时,Linux系统会跳转到asm_do_IRQ()函数(所有中断程序的总入口函数),并且把中断号irq传进来。根据中断号,找到中断号对应的irq_desc结构(irq_desc结构为内核中中断的描述结构,内核中有一个irq_desc结构的数组irq_desc_ptrs[NR_IRQS]),然后调用irq_desc中的handle_irq函数,即中断入口函数。我们编写中断的驱动,即填充并注册irq_desc结构。

  1. 中断处理数据结构:irq_desc

Linux内核将所有的中断统一编号,使用一个irq_desc[NR_IRQS]的结构体数组来描述这些中断:每个数组项对应着一个中断源(也可能是一组中断源),记录中断入口函数、中断标记,并提供了中断的底层硬件访问函数(中断清除、屏蔽、使能)。另外通过这个结构体数组项中的action,能够找到用户注册的中断处理函数。

复制代码

struct irq_desc {
unsigned int irq;
irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction action; / IRQ action list /
unsigned int status; /
IRQ status */

unsigned int        depth;        /* nested irq disables */
unsigned int        wake_depth;    /* nested wake enables */
unsigned int        irq_count;    /* For detecting broken IRQs */
unsigned long        last_unhandled;    /* Aging timer for unhandled count */
unsigned int        irqs_unhandled;
spinlock_t        lock;
const char        *name;

} ____cacheline_internodealigned_in_smp;

复制代码

(1)handle_irq:中断的入口函数

(2)chip:包含这个中断的清除、屏蔽、使能等底层函数

复制代码

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); void (*set_affinity)(unsigned int irq, const struct cpumask *dest); int (*retrigger)(unsigned int irq); int (set_type)(unsigned int irq, unsigned int flow_type); int (set_wake)(unsigned int irq, unsigned int on); / 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;
};

复制代码

(3)action:记录用户注册的中断处理函数、中断标志等内容

复制代码

struct irqaction {
irq_handler_t handler;
unsigned long flags;
cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir;
};

复制代码

  1. 中断处理流程总结

(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. Linux操作系统中断初始化

(1)init_IRQ()函数用来初始化中断体系结构,代码位于arch/arm/kernel/irq.c

复制代码

void __init init_IRQ(void)
{ int irq; for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();#endif
init_arch_irq();
}

复制代码

(2)init_arch_irq()函数,就是用来初始化irq_desc[NR_IRQS]的,与硬件平台紧密相关。init_arch_irq其实是一个函数指针,我们移植Linux内核时,以S3C2440平台为例,把init_arch_irq指向函数s3c24xx_init_irq()。
(3)s3c24xx_init_irq()函数在arch/arm/plat-s3c24xx/irq.c中定义,它为所有的中断设置了芯片相关的数据结构irq_desc[irq].chip,设置了处理函数入口irq_desc[irq].handle_irq。

(4)以外部中断EINT0为例:

复制代码

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为系统提供了一套操作EINT0~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,
};

复制代码

② set_irq_handler()函数的作用就是“irq_desc[irqno].handle_irq = handle_edge_irq”。发生中断后,asm_do_IRQ()函数会调用中断入口函数handle_edge_irq(),而handle_edge_irq()函数会调用用户注册的处理函数(即irq_desc[irqno].action)。

  1. 用户注册中断时带来的中断初始化

(1)用户(驱动程序)通过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)
{ struct irqaction *action; struct irq_desc desc; int retval; /
* handle_IRQ_event() always ignores IRQF_DISABLED except for
* the first irqaction (sigh). That can cause oopsing, but
* the behavior is classified as “will not fix” so we need to
* start nudging drivers away from using that idiom. */
if ((irqflags & (IRQF_SHARED|IRQF_DISABLED)) == (IRQF_SHARED|IRQF_DISABLED))
pr_warning("IRQ %d/%s: IRQF_DISABLED is not "
“guaranteed on shared IRQs\n”,
irq, devname);

#ifdef CONFIG_LOCKDEP /*
* Lockdep wants atomic interrupt handlers: /
irqflags |= IRQF_DISABLED;#endif
/

* Sanity-check: shared interrupts must pass in a real dev-ID,
* otherwise we’ll have trouble later trying to figure out
* which interrupt is which (messes up the interrupt freeing
* logic etc). */
if ((irqflags & IRQF_SHARED) && !dev_id) return -EINVAL;

desc = irq_to_desc(irq);    if (!desc)        return -EINVAL;    if (desc->status & IRQ_NOREQUEST)        return -EINVAL;    if (!handler)        return -EINVAL;

action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);    if (!action)        return -ENOMEM;

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, desc, action);
if (retval)
    kfree(action);

#ifdef CONFIG_DEBUG_SHIRQ if (irqflags & IRQF_SHARED) { /*
* It’s a shared IRQ – the driver ought to be prepared for it
* to happen immediately, so let’s make sure…
* We disable the irq to make sure that a ‘real’ IRQ doesn’t
* run in parallel with our fake. */
unsigned long flags;

    disable_irq(irq);
    local_irq_save(flags);

    handler(irq, dev_id);

    local_irq_restore(flags);
    enable_irq(irq);
}#endif
return retval;

}

复制代码

(2) request_irq()函数首先使用4个参数构造一个irqaction结构,然后调用__setup_irq函数将它链入链表中,简要代码如下:

复制代码

static 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 = 1;
} /
链入新表项 */
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 = 0;
        desc->status &= ~IRQ_DISABLED;            /* 启动中断 */
        if (desc->chip->startup)
            desc->chip->startup(irq);            else
            desc->chip->enable(irq);
    } else
        /* Undo nested disables: */
        desc->depth = 1;
}    /* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;    new->irq = irq;
register_irq_proc(irq);    new->dir = NULL;
register_handler_proc(irq, new);

}

复制代码

(3) __setup_irq()函数主要完成功能如下

① 将新建的irqaciton结构链入irq_desc[irq]结构体的action链表中

* 如果action链表为空,则直接链入

* 如果非空,则要判断新建的irqaciton结构和链表中的irqaciton结构所表示的中断类型是否一致:即是都声明为“可共享的”,是否都是用相同的触发方式,如果一致,则将新建的irqaciton结构链入

② 设置中断的触发方式;

③ 启动中断

  1. 卸载中断

卸载中断使用函数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)
{ struct irq_desc *desc = irq_to_desc(irq); struct irqaction **p;
unsigned long flags;

WARN_ON(in_interrupt());    if (!desc)        return;

spin_lock_irqsave(&desc->lock, flags);
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;            /* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHOD            if (desc->chip->release)
            desc->chip->release(irq, dev_id);#endif

        if (!desc->action) {
            desc->status |= IRQ_DISABLED;                if (desc->chip->shutdown)
                desc->chip->shutdown(irq);                else
                desc->chip->disable(irq);
        }
        spin_unlock_irqrestore(&desc->lock, flags);
        unregister_handler_proc(irq, action);            /* Make sure it's not being used on another CPU */
        synchronize_irq(irq);

#ifdef CONFIG_DEBUG_SHIRQ /*
* It’s a shared IRQ – the driver ought to be
* prepared for it to happen even now it’s
* being freed, so let’s make sure… We do
* this after actually deregistering it, to
* make sure that a ‘real’ IRQ doesn’t run in
* parallel with our fake */
if (action->flags & IRQF_SHARED) {
local_irq_save(flags);
action->handler(irq, dev_id);
local_irq_restore(flags);
}#endif
kfree(action); return;
}
printk(KERN_ERR “Trying to free already-free IRQ %d\n”, irq);
#ifdef CONFIG_DEBUG_SHIRQ
dump_stack();#endif
spin_unlock_irqrestore(&desc->lock, flags); return;
}
}

复制代码

  1. Linux中断处理流程分析

① 中断总入口函数: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 (irq >= NR_IRQS)
    handle_bad_irq(irq, &bad_irq_desc);    else
    generic_handle_irq(irq);    /* AT91 specific workaround */
irq_finish(irq);

irq_exit();
set_irq_regs(old_regs);

}

复制代码

② generic_handle_irq()会调用相应中断号描述结构的handle_irq,等价于irq_desc[irq].handle_irq(irq, desc)

③ 普通中断流程(以EINT0为例)

(1)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);

}

复制代码

(2)通过函数调用desc->chip->ack(irq)来响应中断,实际上就是清除中断以使得可以接受下一个中断,有了之前数据结构初始化的前提了解,可以知道实际上执行的就是s3c_irq_eint0t4.ack函数
(3)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);
}

复制代码

(4)用户通过函数request_irq()函数注册中断处理函数时候,传入参数irq和dev_id,在这里这两个参数被用户注册的中断处理函数action->handler()所使用。可见用户可以在注册中断处理函数的时候,指定参数dev_id,然后将来再由注册的中断处理函数使用这个参数。

④ 特殊处理流程(以外部中断EINT5为例)

(1)在S3C2440处理器架构中,EINT5中断属于EINT4t7中断集合,是一个子中断。当EINT5中断线发生中断事件,那么将先跳转到EINT4t7中断号对应的中断入口处理函数,也即是irq_desc[EINT4t7].handle_irq(irq,desc),进行具体子中断确定,然后再跳转到真正发生中断的中断入口处理函数执行。

(2)EINT5中断注册函数调用:

request_irq(IRQ_EINT5, eint5_irq, IRQT_BOTHEDGE, “S2”, NULL);

其实我们在没有注册EINT5中断源的时候,系统已经注册了EINT4t7的中断入口处理函数。中断集合EINT4t7的中断入口处理函数,是在arch/arm/plat-s3c24xx/irq.c中的函数s3c24xx_init_irq()来初始化的,内容如下:

set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);

(3)当发生EINT5中断事件,汇编阶段根据INTOFFSET确定中断号为IRQ_EINT4t7,asm_do_IRQ函数通过传入的这个参数,将跳转到irq_desc[EINT4t7].handle_irq(irq,desc)函数执行,也就是函数s3c_irq_demux_extint4t7(irq, desc),该函数的主要内容如下:

复制代码

static voids3c_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 */

/* we may as well handle all the pending IRQs here */

while (eintpnd) {
    irq = __ffs(eintpnd);
    eintpnd &= ~(1<<irq);

    irq += (IRQ_EINT4 - 4);

    generic_handle_irq(irq);
}

}

复制代码

(4)函数s3c_irq_demux_extint4t7()根据寄存器S3C24XX_EINTPEND、S3C24XX_EINTMASK重新计算中断号,这个时候将计算出真正的中断号IRQ_EINT5,然后通过generic_handle_irq(irq)来调用irq_desc[EINT5].handle_irq(irq,desc)。此后的过程与EINT0发生中断后的执行过程类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值