一回触摸屏中断调节和测量检验引发的深入研究

作者:电脑系统

drivers/irqchip/irq-gic.c

如果该中断事件已经被其他的CPU处理了,那么我们仅仅是设定pending状态(为了委托正在处理的该中断的那个CPU进行处理),mask_ack_irq该中断并退出就OK了,并不做具体的处理。另外正在处理该中断的CPU会检查pending状态,并进行处理的。同样的,如果该中断被其他的CPU disable了,本就不应该继续执行该中断的specific handler,我们也是设定pending状态,mask and ack中断就退出了。当其他CPU的代码离开临界区,enable 该中断的时候,软件会检测pending状态并resend该中断。

void __init s3c24xx_init_irq(void)
{
    unsigned long pend;
    unsigned long last;
    int irqno;
    int i;

找到返回中断号后,再去执行handle_level_irq

当使用多种类型的interrupt controller的时候(例如HW 系统使用了S3C2451这样的SOC,这时候,系统有两种interrupt controller,一种是GPIO type,另外一种是SOC上的interrupt controller),则不适合在interrupt controller中进行设定,这时候,可以考虑在machine driver中设定。在这种情况下,handle_arch_irq 这个函数是在setup_arch函数中根据machine driver设定,具体如下:

        if (pend == 0 || pend == last)
            break;

这里handled = threads_handled

Notes:不同类型的触发方式体现在handle_irq,通过irq_set_chip_and_handler注册。有handle_edge_irq、handle_level_irq、handle_simple_irq、handle_percpu_devid_irq、handle_fasteoi_irq。   

    for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno ) {
        irqdbf("registering irq %d (s3c adc irq)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irq_adc, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

===> gpiochip_irqchip_add

具体代码如下:

    last = 0;
    for (i = 0; i < 4; i ) {
        pend = __raw_readl(S3C2410_SUBSRCPND);

如要要满足如上条件的话,那么只有让irqs_unhandled

3、支持EOI的handler

    for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno ) {
        irqdbf("registering irq %d (s3c uart1 irq)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irq_uart1,
                     handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

图片 1

2、machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number

看一下通用的几个handle,举个例子

根据log,我们可以在下图看到note_interrupt,即说明noirqdebug=0

在各种high level irq event handler中,总会有如下的代码:

        default:
            //irqdbf("registering irq %d (s3c irq)n", irqno);
            irq_set_chip_and_handler(irqno, &s3c_irq_chip,
                         handle_edge_irq);
            set_irq_flags(irqno, IRQF_VALID);
        }
    }

图片 2

(1)CPU A上正在处理x外设的中断

继续往下

int generic_handle_irq(unsigned int irq)
{
    struct irq_desc *desc = irq_to_desc(irq); ----------通过IRQ number获取该irq的描述符

    if (!desc)
        return -EINVAL;
    generic_handle_irq_desc(irq, desc);----------------调用high level的irq handler来处理该IRQ
    return 0;
}

static inline void style="color: #ff0000">generic_handle_irq_desc(unsigned int irq, struct irq_desc *desc)
{
    desc->handle_irq(irq, desc);--------------------------- style="color: #ff0000">这里面的handle_irq指向哪里呢?
}

static inline void irq_set_chip_and_handler(unsigned int irq, struct irq_chip *chip,
                        irq_flow_handler_t handle)
{
    irq_set_chip_and_handler_name(irq, chip, handle, NULL);
}

 

一个硬件驱动可以通过下面的方法进行自动探测它使用的IRQ:

在start_kernel中还有一个

 

(2)Ack中断的signal。这个signal可能是物理上的一个连接CPU和interrupt controller的铜线,也可能不是。对于X86+8259这样的结构,Ack中断的signal就是nINTA信号线,对于ARM+GIC而言,这个信号就是总线上的一次访问(读Interrupt Acknowledge Register寄存器)。CPU ack中断标识cpu开启启动中断服务程序(specific handler)去处理该中断。对于X86而言,ack中断可以让8259将interrupt vector数据送到数据总线上,从而让CPU获取了足够的处理该中断的信息。对于ARM而言,ack中断的同时也就是获取了发生中断的HW interrupt ID,总而言之,ack中断后,CPU获取了足够开启执行中断处理的信息。

    /* external interrupts */

一个中断开始的时候

3、调用该IRQ number对应的high level irq event handler,在这个high level的handler中,会通过和interupt controller交互,进行中断处理的flow control(处理中断的嵌套、抢占等),当然最终会遍历该中断描述符的IRQ action list,调用外设的specific handler来处理该中断

##########

irq_find_mapping这个函数来找之前irq_create_mapping的irq映射

IRQS_POLL_INPROGRESS标识了该IRQ正在被polling(上一章有描述),如果没有被轮询,那么返回false,进行正常的设定pending标记、mask and ack中断。如果正在被轮询,那么需要等待poll结束。

    /* setup the cascade irq handlers */

 

b、该中断描述符允许自动探测(不能设定IRQ_NOPROBE)

目录arch/arm/ 目录,拿最常见的2440来说,那么对应于arch/arm/mach-s3c2440/mach-mini2440.c

我们分析如下

    .macro    irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
    ldr    r1, = style="color: #ff0000">handle_arch_irq
    mov    r0, sp
    adr    lr, BSYM(9997f)
    ldr    pc, [r1]
#else
    arch_irq_handler_default
#endif
9997:
    .endm

#先看irq_get_desc_lock函数

图片 3

图片 4

回到start_kernel函数,

那么接下来我们就要找到mdesc->handle_irq又是在哪里被赋值了呢?

int probe_irq_off(unsigned long val)
{
    int i, irq_found = 0, nr_of_irqs = 0;
    struct irq_desc *desc;

    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {----只有处于IRQ自动探测中的描述符才会被处理
            if (!(desc->istate & IRQS_WAITING)) {----找到一个潜在的中断描述符
                if (!nr_of_irqs)
                    irq_found = i;
                nr_of_irqs ;
            }
            desc->istate &= ~IRQS_AUTODETECT; ----IRQS_WAITING没有被清除,说明该描述符
            irq_shutdown(desc);                                     不是自动探测的那个,shutdown之
        }
        raw_spin_unlock_irq(&desc->lock);
    }
    mutex_unlock(&probing_active);

    if (nr_of_irqs > 1) ------如果找到多于1个的IRQ,说明探测失败,返回负的IRQ个数信息
        irq_found = -irq_found;

    return irq_found;
}

====================

handle_arch_irq = gic_handle_irq

b、该中断被其他的CPU disable了。如果该中断被其他的CPU disable了,本就不应该继续执行该中断的specific handler,我们也是设定pending状态,mask and ack中断就退出了。当其他CPU的代码离开临界区,enable 该中断的时候,软件会检测pending状态并resend该中断。

    irq_set_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
    irq_set_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
    irq_set_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
    irq_set_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

首先我们顺着错误跟踪linux内核来看下

unsigned long irqs;
int irq;

irqs = probe_irq_on();--------启动IRQ自动探测
驱动那个打算自动探测IRQ的硬件产生中断
irq = probe_irq_off(irqs);-------结束IRQ自动探测

Linux中断导读之一--处理流程<4> http://www.linuxidc.com/Linux/2012-01/52839.htm

    ===>handle_domain_irq

我们再来看看probe_irq_off的代码:

void
__irq_set_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
          const char *name)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, 0);
 
    if (!desc)                                                //已填充
        return;
 
    if (!handle) {
        handle = handle_bad_irq;    //如果没有handle,那么赋予一个默认的handle,应当是个处理该错误的handle
    } else {
        if (WARN_ON(desc->irq_data.chip == &no_irq_chip))
            goto out;
    }
 
    /* Uninstall? */
    if (handle == handle_bad_irq) {
        if (desc->irq_data.chip != &no_irq_chip)                  //刚才填充的chip结构
            mask_ack_irq(desc);
        irq_state_set_disabled(desc);
        desc->depth = 1;
    }
    desc->handle_irq = handle;        //赋值为传入的handle
    desc->name = name;                    //********
 
    if (handle != handle_bad_irq && is_chained) {
        irq_settings_set_noprobe(desc);
        irq_settings_set_norequest(desc);
        irq_settings_set_nothread(desc);
        irq_startup(desc);
    }
out:
    irq_put_desc_busunlock(desc, flags);
}

也就是必须要满足handled != desc->threads_handled_last 为假

(4)为何是有条件的unmask该IRQ?正常的话当然是umask就OK了,不过有些threaded interrupt(这个概念在下一份文档中描述)要求是one shot的(首次中断,specific handler中开了一枪,wakeup了irq handler thread,如果允许中断嵌套,那么在specific handler会多次开枪,这也就不是one shot了,有些IRQ的handler thread要求是one shot,也就是不能嵌套specific handler)。

在 kernel/irq/irqdesc.c
int __init early_irq_init(void)
{
    int count, i, node = first_online_node;
    struct irq_desc *desc;
 
    init_irq_default_affinity();
 
    printk(KERN_INFO "NR_IRQS:%dn", NR_IRQS);
 
    desc = irq_desc;
    count = ARRAY_SIZE(irq_desc);
 
    for (i = 0; i < count; i ) {
        desc[i].kstat_irqs = alloc_percpu(unsigned int);
        alloc_masks(&desc[i], GFP_KERNEL, node);
        raw_spin_lock_init(&desc[i].lock);
        lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
        desc_set_defaults(i, &desc[i], node, NULL);
    }
    return arch_early_irq_init();   //平台相关结构arm为NULL

 

有irq号找到对应的irq_desc,irq_desc->action->handler就是对应中断号的处理函数。

/**
 *    irq_set_chip - set the irq chip for an irq
 *    @irq:    irq number
 *    @chip:    pointer to irq chip description structure
 */
int irq_set_chip(unsigned int irq, struct irq_chip *chip)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0);
 
    if (!desc)
        return -EINVAL;
 
    if (!chip)
        chip = &no_irq_chip;
 
    desc->irq_data.chip = chip;         //*****
    irq_put_desc_unlock(desc, flags);
    /*
     * For !CONFIG_SPARSE_IRQ make the irq show up in
     * allocated_irqs. For the CONFIG_SPARSE_IRQ case, it is
     * already marked, and this call is harmless.
     */
    irq_reserve_irq(irq);
    return 0;
}

图片 5

 

        if (pend == 0 || pend == last)
            break;

如果想出现log那样的错误,必须满足条件

a、该中断描述符还没有通过request_threaded_irq或者其他方式申请该IRQ的specific handler(也就是irqaction数据结构)

Linux中断导读之一--注册部分<3> http://www.linuxidc.com/Linux/2012-01/52838.htm

drivers/irqchip/irq-gic.c

关于MULTI_IRQ_HANDLER这个配置项,我们可以再多说几句。当然,其实这个配置项的名字已经出卖它了。multi irq handler就是说系统中有多个irq handler,可以在run time的时候指定。为何要run time的时候,从多个handler中选择一个呢?HW interrupt block难道不是固定的吗?我的理解(猜想)是:一个kernel的image支持多个HW platform,对于不同的HW platform,在运行时检查HW platform的类型,设定不同的irq handler。

Thanks

通过此函数的dump_stack的信息,可以追溯到调用者

(4)控制总线和数据总线接口。通过这些接口,CPU可以访问(读写)interrupt controller的寄存器。

    for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno ) {
        irqdbf("registering irq %d (s3c uart0 irq)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irq_uart0,
                     handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

图片 6

一个ARM SOC总是有很多的GPIO,有些GPIO可以提供中断功能,这些GPIO的中断可以配置成level trigger或者edge trigger。一般而言,大家都更喜欢用level trigger的中断。有的SOC只能是有限个数的GPIO可以配置成电平中断,因此,在项目初期进行pin define的时候,大家都在争抢电平触发的GPIO。

相关阅读:

void __init setup_arch(char **cmdline_p)

电平触发的中断有什么好处呢?电平触发的中断很简单、直接,只要硬件检测到硬件事件(例如有数据到来),其assert指定的电平信号,CPU ack该中断后,电平信号消失。但是对于边缘触发的中断,它是用一个上升沿或者下降沿告知硬件的状态,这个状态不是一个持续的状态,如果软件处理不好,容易丢失中断。

#回到irq_set_chip_and_handler_name函数

而 action->handler**的回调函数是:**

一介绍了进入High level irq event handler的路径__irq_svc-->irq_handler-->handle_arch_irq,generic_handle_irq是入口函数,在generic_handle_irq_desc指向desc->handle_irq,这里面就根据中断类型不同采取不同的handler。

    irq_set_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    irq_set_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);

中断控制器流程:

(3)如果满足条件(IRQ触发了100,000次,但是99,900次没有处理),disable该IRQ。

继续跟进该函数:

mask_ack_irq(desc); 因为是电平中断,如果不做mask中断的动作的话,会因为中断电平一直是有效电平导致中断控制器会源源不断地给cpu发中断

a、没有注册specific handler。如果没有注册handler,那么保持mask并设定pending标记(这个pending标记有什么作用还没有想明白)。

继续往下找到init_irq函数,

所有的系统中,Interrupt controller和Peripheral device之间的接口都是一个Interrupt Request信号线。外设通过这个信号线上的电平或者边缘向CPU(实际上是通过interrupt controller)申请中断服务。

参数分别为irq number,irq chip结构,中断处理函数,

unsigned long probe_irq_on(void)
{

……
    for_each_irq_desc_reverse(i, desc) { ----scan 从nr_irqs-1 到0 的中断描述符
        raw_spin_lock_irq(&desc->lock);
        if (!desc->action && irq_settings_can_probe(desc)) {--------(1)
            desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;-----(2)
            if (irq_startup(desc, false))
                desc->istate |= IRQS_PENDING;
        }
        raw_spin_unlock_irq(&desc->lock);
    }
    msleep(100); --------------------------(3)

    for_each_irq_desc(i, desc) {
        raw_spin_lock_irq(&desc->lock);

        if (desc->istate & IRQS_AUTODETECT) {------------(4)
            if (!(desc->istate & IRQS_WAITING)) {
                desc->istate &= ~IRQS_AUTODETECT;
                irq_shutdown(desc);
            } else
                if (i < 32)------------------------(5)
                    mask |= 1 << i;
        }
        raw_spin_unlock_irq(&desc->lock);
    }

    return mask;
}

Linux中断导读之一--初始化<2> http://www.linuxidc.com/Linux/2012-01/52837.htm

图片 7

(2)如果满足上面的条件,那么该中断描述符属于备选描述符。设定其internal state为IRQS_AUTODETECT | IRQS_WAITING。IRQS_AUTODETECT表示本IRQ正处于自动探测中。

   init/main.c
  setup_arch(&command_line);
  arch/arm/kernel/setup.c
void __init setup_arch(char **cmdline_p)
{
    struct machine_desc *mdesc;
 
    setup_processor();
    mdesc = setup_machine_fdt(__atags_pointer);
    if (!mdesc)
        mdesc = setup_machine_tags(machine_arch_type);
    machine_desc = mdesc;
    machine_name = mdesc->name;
...
...
...

arch/arm/kernel/entry-armv.S

这里的irq_check_poll代码如下:

    /* register the main interrupts */

 如下为本人原创,在解决问题的过程中的一点心得,如果有描述不准确的地方还请各位指出,非常感谢

返回目录:《ARM-Linux中断系统》。

=======================

Include/linux/of.h

从逻辑层面上看,CPU和interrupt controller之间的接口包括:

同文件下

arch/arm/kernel/irq.c

(1)触发中断的signal。一般而言,这个(些)信号是电平触发的。对于ARM CPU,它是nIRQ和nFIQ信号线,对于X86,它是INT和NMI信号线,对于PowerPC,这些信号线包括MC(machine check)、CRIT(critical interrupt)和NON-CRIT(Non critical interrupt)。对于linux kernel的中断子系统,我们只使用其中一个信号线(例如对于ARM而言,我们只使用nIRQ这个信号线)。这样,从CPU层面看,其逻辑动作非常的简单,不区分优先级,触发中断的那个信号线一旦assert,并且CPU没有mask中断,那么软件就会转到一个异常向量执行,完毕后返回现场。

        printk("irq: clearing subpending status xn", (int)pend);
        __raw_writel(pend, S3C2410_SUBSRCPND);
        last = pend;
    }

 图片 8

void handle_percpu_devid_irq(unsigned int irq, struct irq_desc *desc)
{
    struct irq_chip *chip = irq_desc_get_chip(desc);
    struct irqaction *action = desc->action;
    void *dev_id = __this_cpu_ptr(action->percpu_dev_id);
    irqreturn_t res;

    kstat_incr_irqs_this_cpu(irq, desc);

    if (chip->irq_ack)
        chip->irq_ack(&desc->irq_data);

    trace_irq_handler_entry(irq, action);
    res = action->handler(irq, dev_id);
    trace_irq_handler_exit(irq, action, res);

    if (chip->irq_eoi)
        chip->irq_eoi(&desc->irq_data);
}


irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int flags = 0, irq = desc->irq_data.irq;

    do {
        irqreturn_t res;

        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);
        trace_irq_handler_exit(irq, action, res);
...
return retval;
}

每个irq都有一个描述结构struct irq_desc,详细描述了该irq的状态,

void handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);
    mask_ack_irq(desc); ---------------------(1)

    if (unlikely(irqd_irq_inprogress(&desc->irq_data)))---------(2)
        if (!irq_check_poll(desc))
            goto out_unlock;

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);--和retrigger中断以及自动探测IRQ相关
    kstat_incr_irqs_this_cpu(irq, desc);

    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {-----(3)
        desc->istate |= IRQS_PENDING;
        goto out_unlock;
    }

    handle_irq_event(desc);

    cond_unmask_irq(desc); --------------(4)

out_unlock:
    raw_spin_unlock(&desc->lock);
}

        __raw_writel(pend, S3C24XX_EINTPEND);
        printk("irq: clearing pending ext status xn", (int)pend);
        last = pend;
    }

struct irqaction *action

1.handle_edge_irq-->
2.handle_level_irq-->
3.handle_simple_irq-->
4.handle_fasteoi_irq-->
    handle_irq_event-->
        handle_irq_event_percpu
5.handle_percpu_devid_irq

 总结:简单浏览了系统启动时候板级相关部分的c代码,下一篇会介绍一下向量表部分。

图片 9

二、理解high level irq event handler需要的知识准备

static struct machine_desc * __init setup_machine_tags(unsigned int nr)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc = NULL, *p;
    char *from = default_command_line;
 
    init_tags.mem.start = PHYS_OFFSET;
 
    /*
     * locate machine in the list of supported machines.
     */
    for_each_machine_desc(p)
        if (nr == p->nr) {
            printk("Machine: %sn", p->name);
            mdesc = p;
            break;
        }

如要要满足如上条件的话,那么只有如下地方会让irqs_unhandled

4、具体CPU architecture相关的模块会进行现场恢复。

    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno ) {
        irqdbf("registering irq %d (ext int)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irq_eint0t4,
                     handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

desc->irqs_unhandled > 99900 为真

其实,直接从CPU的中断处理跳转到通用中断处理模块是不可能的,中断处理不可能越过interrupt controller这个层次。一般而言,通用中断处理模块会提供一些通用的中断代码处理库,然后由interrupt controller这个层次的代码调用这些通用中断处理的完成整个的中断处理过程。“interrupt controller这个层次的代码”是和硬件中断系统设计相关的,例如:系统中有多少个interrupt contrller,每个interrupt controller是如何控制的?它们是如何级联的?我们称这些相关的驱动模块为machine interrupt driver。

#ifdef CONFIG_FIQ
    init_FIQ();                                                    //fiq可选
#endif

那么这个set_handle_irq又是在哪里被调用的呢?

二介绍了IRQ自动探测、IRQ重发等机制。

看一下linux中断部分,分为三部分,初始化,处理流程以及注册流程。

Include/linux/of.h

(2)irq_count每次都会加一,记录IRQ被触发的次数。但只要大于100000才启动 step (3)中的检查。一旦启动检查,irq_count会清零,irqs_unhandled也会清零,进入下一个检查周期。

具体看一下irq_set_chip_and_handler函数,

图片 10

    ret=setup_irq(AP_TIMER4_INT, &at4_irq);


static struct irqaction at4_irq = 
{
    .name        = "at4_irq",
    .flags        = IRQF_DISABLED | IRQF_TRIGGER_RISING, 
    .handler    = at4_isr,-------------------------------------对应的中断处理函数。
};

    irqdbf("s3c2410: registering external interruptsn");

图片 11

(2)对于电平触发的high level handler,我们一开始就mask并ack了中断,因此后续specific handler因该是串行化执行的,为何要判断in progress标记呢?不要忘记spurious interrupt,那里会直接调用handler来处理spurious interrupt。

        __raw_writel(pend, S3C2410_SRCPND);
        __raw_writel(pend, S3C2410_INTPND);
        printk("irq: clearing pending status xn", (int)pend);
        last = pend;
    }

kernel/irq/chip.c

我们以高电平触发为例。当interrupt controller检测到了高电平信号,并通过中断的signal向CPU触发中断。这时候,对中断控制器进行ack并不能改变interrupt request signal上的电平状态,一直要等到执行具体的中断服务程序(specific handler),对外设进行ack的时候,电平信号才会恢复成低电平。在对外设ack之前,中断状态一直是pending的,如果没有mask中断,那么中断控制器就会assert CPU。

即使对应于上面的 .init_irq    = s3c24xx_init_irq,

 

总结:从架构相关的汇编处理跳转到Machine/控制器相关的handle_arch_irq,generic_handle_irq作为High level irq event handler入口。

#回到irq_set_chip函数,继续往下

图片 12

static void poll_spurious_irqs(unsigned long dummy)
{
    struct irq_desc *desc;
    int i;

    if (atomic_inc_return(&irq_poll_active) != 1)----确保系统中只有一个excuting thread进入临界区
        goto out;
    irq_poll_cpu = smp_processor_id(); ----记录当前正在polling的CPU

    for_each_irq_desc(i, desc) {------遍历所有的中断描述符
        unsigned int state;

        if (!i)-------------越过0号中断描述符。对于X86,这是timer的中断
             continue;

        /* Racy but it doesn't matter */
        state = desc->istate;
        barrier();
        if (!(state & IRQS_SPURIOUS_DISABLED))----名花有主的那些就不必考虑了
            continue;

        local_irq_disable();
        try_one_irq(i, desc, true);---------OK,尝试一下是不是可以处理
        local_irq_enable();
    }
out:
    atomic_dec(&irq_poll_active);
    mod_timer(&poll_spurious_irq_timer,--------一旦触发了该timer,就停不下来
          jiffies POLL_SPURIOUS_IRQ_INTERVAL);
}

是个静态数组,

===> __irq_do_set_handler

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;

    desc->istate &= ~IRQS_PENDING;----CPU已经准备处理该中断了,因此,清除pending状态
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);--设定INPROGRESS的flag
    raw_spin_unlock(&desc->lock);

    ret = handle_irq_event_percpu(desc, action); ---遍历action list,调用specific handler

    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);---处理完成,清除INPROGRESS标记
    return ret;
}

    for (irqno = IRQ_EINT4t7; irqno <= IRQ_ADCPARENT; irqno ) {       //注册所有中断,这里是从4---31,可以有选择性的处理
        /* set all the s3c2410 internal irqs */

 

什么时候会resend一个中断呢?我们考虑一个简单的例子:

/* s3c24xx_init_irq
 *
 * Initialise S3C2410 IRQ system
*/

针对内核版本Linux-4.9.18

b、该中断被其他的CPU disable了

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:        per irq and chip data passed down to chip functions
 * @timer_rand_state:    pointer to timer rand state struct
 * @kstat_irqs:        irq stats per cpu
 * @handle_irq:        highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:        the irq action chain
 * @status:        status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:        disable-depth, for nested irq_disable() calls
 * @wake_depth:        enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:        stats field to detect stalled irqs
 * @last_unhandled:    aging timer for unhandled count
 * @irqs_unhandled:    stats field for spurious unhandled interrupts
 * @lock:        locking for SMP
 * @affinity_hint:    hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:    pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @threads_active:    number of irqaction threads currently running
 * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers
 * @dir:        /proc/irq/ procfs entry
 * @name:        flow handler name for /proc/interrupts output
 */
struct irq_desc {
    struct irq_data        irq_data;
    struct timer_rand_state *timer_rand_state;
    unsigned int __percpu    *kstat_irqs;
    irq_flow_handler_t    handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
    irq_preflow_handler_t    preflow_handler;
#endif
    struct irqaction    *action;    /* IRQ action list */                               //handle链表,挂接的irq handle
    unsigned int        status_use_accessors;
    unsigned int        core_internal_state__do_not_mess_with_it;
    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;
    raw_spinlock_t        lock;
    struct cpumask        *percpu_enabled;
#ifdef CONFIG_SMP
    const struct cpumask    *affinity_hint;
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t        pending_mask;
#endif
#endif
    unsigned long        threads_oneshot;
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry    *dir;
#endif
    struct module        *owner;
    const char        *name;
} ____cacheline_internodealigned_in_smp;

2、interrupt controller相关的代码

那么我们接下来看看到底是怎么被调用到这里的,一个中断的产生又是怎样的?

一、如何进入high level irq event handler

    for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno ) {
        irqdbf("registering irq %d (s3c uart2 irq)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irq_uart2,
                     handle_level_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

图片 13

当发生了一个中断,但是没有被处理(有两种可能,一种是根本没有注册的specific handler,第二种是有handler,但是handler否认是自己对应的设备触发的中断),怎么办?毫无疑问这是一个异常状况,那么kernel是否要立刻采取措施将该IRQ disable呢?也不太合适,毕竟interrupt request信号线是允许共享的,直接disable该IRQ有可能会下手太狠,kernel采取了这样的策略:如果该IRQ触发了100,000次,但是99,900次没有处理,在这种条件下,我们就是disable这个interrupt request line。多么有情有义的策略啊!相关的控制数据在中断描述符中,如下:

对中断的一下描述以及操作该中断的功能函数,也就是该中断所在chip;

 

handle_edge_irq代码如下:

    irqdbf("s3c2410_init_irq: clearing interrupt status flagsn");

Kernel/irq/spurious.c

void check_irq_resend(struct irq_desc *desc, unsigned int irq)
{
    if (irq_settings_is_level(desc)) {-------电平中断不存在resend的问题
        desc->istate &= ~IRQS_PENDING;
        return;
    }
    if (desc->istate & IRQS_REPLAY)----如果已经设定resend的flag,退出就OK了,这个应该
        return;                                                和irq的enable disable能多层嵌套相关
    if (desc->istate & IRQS_PENDING) {-------如果有pending的flag则进行处理
        desc->istate &= ~IRQS_PENDING;
        desc->istate |= IRQS_REPLAY; ------设置retrigger标志

        if (!desc->irq_data.chip->irq_retrigger ||
            !desc->irq_data.chip->irq_retrigger(&desc->irq_data)) {----调用底层irq chip的callback
#ifdef CONFIG_HARDIRQS_SW_RESEND
也可以使用软件手段来完成resend一个中断,具体代码省略,有兴趣大家可以自己看看
#endif
        }
    }
}

=======

Kernel/irq/chip.c

一、前言

=======

===> irq_set_chained_handler_and_data

四接着一重点介绍了电平和边沿触发两种类型中断的High level irq event handler。

重点看一下,irq_to_desc 函数在include/linux/irqnr.c中。

因为这里:

static asmlinkage void __exception_irq_entry style="color: #ff0000">gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];-------------------------------获取root GIC的硬件描述符
    void __iomem *cpu_base = gic_data_cpu_base(gic); --------------获取root GIC mapping到CPU地址空间的信息

    do {
        irqstat = readl_relaxed(cpu_base GIC_CPU_INTACK);------获取HW interrupt ID
        irqnr = irqstat & ~0x1c00;

        if (likely(irqnr > 15 && irqnr < 1021)) {-------------------------------SPI和PPI的处理
            irqnr = irq_find_mapping(gic->domain, irqnr);------------------将HW interrupt ID转成IRQ number
            handle_IRQ(irqnr, regs);---------------------------------------------处理该IRQ number
            continue;
        }
        if (irqnr < 16) {------------------------------------------------------------IPI类型的中断处理
            writel_relaxed(irqstat, cpu_base GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno ) {
        irqdbf("registering irq %d (extended s3c irq)n", irqno);
        irq_set_chip_and_handler(irqno, &s3c_irqext_chip,
                     handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }

        ===>__handle_domain_irq

(3)结束中断(EOI,end of interrupt)的signal。这个signal用来标识CPU已经完成了对该中断的处理(specific handler或者ISR,interrupt serivce routine执行完毕)。实际的物理形态这里就不描述了,和ack中断signal是类似的。

irq chip结构:

gic_of_init

使用handle_level_irq这个handler的硬件中断系统行为如下:

struct irq_desc *
__irq_get_desc_lock(unsigned int irq, unsigned long *flags, bool bus,
            unsigned int check)
{
    struct irq_desc *desc = irq_to_desc(irq);
 
    if (desc) {
        if (check & _IRQ_DESC_CHECK) {
            if ((check & _IRQ_DESC_PERCPU) &&
                !irq_settings_is_per_cpu_devid(desc))
                return NULL;
 
            if (!(check & _IRQ_DESC_PERCPU) &&
                irq_settings_is_per_cpu_devid(desc))
                return NULL;
        }
 
        if (bus)
            chip_bus_lock(desc);
        raw_spin_lock_irqsave(&desc->lock, *flags);
    }
    return desc;
}

 

使用handle_edge_irq这个handler的硬件中断系统行为如下:

先看第一部分初始化:

如下图:

a、该中断事件已经被其他的CPU处理了

    /* first, clear all interrupts pending... */                                       //清空所有irq状态

===> __handle_domain_irq

(3)这里有两个场景

    last = 0;
    for (i = 0; i < 4; i ) {
        pend = __raw_readl(S3C2410_INTPND);

这里有一个全局的handle_arch_irq

irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{

……

    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)            
static const struct machine_desc __mach_desc_##_type    
 __used                            
 __attribute__((__section__(".arch.info.init"))) = {    
    .nr        = MACH_TYPE_##_type,        
    .name        = _name,
 
#define MACHINE_END                
};

action_ret = IRQ_WAKE_THREAD

const struct irq_domain_ops gic_irq_domain_ops = {
    .map = gic_irq_domain_map,
    .xlate = gic_irq_domain_xlate,
};

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
                irq_hw_number_t hw)
{
    if (hw < 32) {--------------------------------------------小于32的中断HW id
        irq_set_percpu_devid(irq);
        irq_set_chip_and_handler(irq, &gic_chip,--------------设置irq对应的irq_desc成员handle_irq为handle_percpu_devid_irq
                     handle_percpu_devid_irq);
        set_irq_flags(irq, IRQF_VALID | IRQF_NOAUTOEN);
    } else {--------------------------------------------------大于等于32的HW id
        irq_set_chip_and_handler(irq, &gic_chip,--------------设置irq对应的irq_desc成员handle_irq为handle_fasteoi_irq,
                     handle_fasteoi_irq);
        set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
    }
    irq_set_chip_data(irq, d->host_data);
    return 0;
}

void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
                  irq_flow_handler_t handle, const char *name)
{
    irq_set_chip(irq, chip);
    __irq_set_handler(irq, handle, 0, name);
}

首先上代码:

 

    irqdbf("s3c2410_init_irq: registering s3c2410 interrupt handlersn");

typedef    void (*irq_flow_handler_t)(struct irq_desc *desc);

gic_nr是GIC的编号,linux kernel初始化过程中,每发现一个GIC,都是会指向GIC driver的初始化函数的,不过对于第一个GIC,gic_nr等于0,对于第二个GIC,gic_nr等于1。当然handle_arch_irq这个函数指针不是per CPU的变量,是全部CPU共享的,因此,初始化一次就OK了。

kernel/irq/chip.c
/**
 *    handle_level_irq - Level type irq handler
 *    @irq:    the interrupt number
 *    @desc:    the interrupt description structure for this irq
 *
 *    Level type interrupts are active as long as the hardware line has
 *    the active level. This may require to mask the interrupt and unmask
 *    it after the associated handler has acknowledged the device, so the
 *    interrupt line is back to inactive.
 */
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock);   
    mask_ack_irq(desc);
 
    if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
        if (!irq_check_poll(desc))
            goto out_unlock;
 
    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);   //清空等待,replay标志,现在正在处理
    kstat_incr_irqs_this_cpu(irq, desc);
 
    /*
     * If its disabled or no action available
     * keep it masked and get out of here
     */
    if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
        goto out_unlock;
 
    handle_irq_event(desc);
 
    if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))
        unmask_irq(desc);
out_unlock:
    raw_spin_unlock(&desc->lock);
}

那么满足这个条件就必须action_ret == IRQ_NONE

(1)判断是否需要执行下面的action list的处理。这里分成几种情况:

#define irq_to_desc(irq) (&irq_desc[irq])

request_threaded_irq线程化注册中断的第2个参数

void note_interrupt(unsigned int irq, struct irq_desc *desc,  irqreturn_t action_ret)
{
    if (desc->istate & IRQS_POLL_INPROGRESS ||  irq_settings_is_polled(desc))
        return;

    if (action_ret == IRQ_WAKE_THREAD)----handler返回IRQ_WAKE_THREAD是正常情况
        return;

    if (bad_action_ret(action_ret)) {-----报告错误,这些是由于specific handler的返回错误导致的
        report_bad_irq(irq, desc, action_ret);
        return;
    }

    if (unlikely(action_ret == IRQ_NONE)) {-------是unhandled interrupt
        if (time_after(jiffies, desc->last_unhandled HZ/10))---(1)
            desc->irqs_unhandled = 1;---重新开始计数
        else
            desc->irqs_unhandled ;---判定为unhandled interrupt,计数加一
        desc->last_unhandled = jiffies;-------保存本次unhandled interrupt对应的jiffies时间
    }

if (unlikely(try_misrouted_irq(irq, desc, action_ret))) {---是否启动Misrouted IRQ fixup
    int ok = misrouted_irq(irq);
    if (action_ret == IRQ_NONE)
        desc->irqs_unhandled -= ok;
}

    desc->irq_count ;
    if (likely(desc->irq_count < 100000))-----------(2)
        return;

    desc->irq_count = 0;
    if (unlikely(desc->irqs_unhandled > 99900)) {--------(3)
        __report_bad_irq(irq, desc, action_ret);---报告错误
        desc->istate |= IRQS_SPURIOUS_DISABLED;
        desc->depth ;
        irq_disable(desc);

        mod_timer(&poll_spurious_irq_timer,----------(4)
              jiffies POLL_SPURIOUS_IRQ_INTERVAL);
    }
    desc->irqs_unhandled = 0;
}

可以从include/linux/irqdesc.h 中找到其定义:

通过上图,我们可以看到,必须满足条件:

1、具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler

        if (pend == 0 || pend == last)
            break;

arch/arm/kernel/setup.c

note_interrupt就是进行unhandled interrupt和spurious interrupt处理的。对于这类中断,linux kernel有一套复杂的机制来处理,你可以通过command line参数(noirqdebug)来控制开关该功能。

        case IRQ_EINT4t7:
        case IRQ_EINT8t23:
        case IRQ_UART0:
        case IRQ_UART1:
        case IRQ_UART2:
        case IRQ_ADCPARENT:
            irq_set_chip_and_handler(irqno, &s3c_irq_level_chip,
                         handle_level_irq);
            break;

接下来我们看下这个函数的实现就知道了

因为在调用probe_irq_off已经触发了自动探测IRQ的那个硬件中断,因此在该中断的high level handler的执行过程中,该硬件对应的中断描述符的IRQS_WAITING标致应该已经被清除,因此probe_irq_off函数scan中断描述符DB,找到处于auto probe中,而且IRQS_WAITING标致被清除的那个IRQ。如果找到一个,那么探测OK,返回该IRQ number,如果找到多个,说明探测失败,返回负的IRQ个数信息,没有找到的话,返回0。

这里.arch.info.init这个段是静态被填充的,一般都是和具体片子相关的代码,通常对应于文件

 

void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{

……
        generic_handle_irq(irq);

……
}

    irqdbf("s3c2410: registered interrupt handlersn");
}

图片 14

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

================

desc->irqs_unhandled > 99900 为真

如果能够自动探测到IRQ,上面程序中的irq(probe_irq_off的返回值)就是自动探测的结果。后续程序可以通过request_threaded_irq申请该IRQ。probe_irq_on函数主要的目的是返回一个32 bit的掩码,通过该掩码可以知道可能使用的IRQ number有哪些,具体代码如下:

        case IRQ_RESERVED6:
        case IRQ_RESERVED24:
            /* no IRQ here */
            break;

因为上面我们已经分析过bad_action_ret(action_ret)返回为0

更多关于GIC相关的信息,请参考linux kernel的中断子系统之(七):GIC代码分析。对于ARM处理器,handle_IRQ代码如下:

//初始设置成默认值
#irqdesc.h  struct irq_desc irq_desc[NR_IRQS];
static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
        struct module *owner)
{
    int cpu;
 
    desc->irq_data.irq = irq;
    desc->irq_data.chip = &no_irq_chip;
    desc->irq_data.chip_data = NULL;
    desc->irq_data.handler_data = NULL;
    desc->irq_data.msi_desc = NULL;
    irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
    irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
    desc->handle_irq = handle_bad_irq;
    desc->depth = 1;
    desc->irq_count = 0;
    desc->irqs_unhandled = 0;
    desc->name = NULL;
    desc->owner = owner;
    for_each_possible_cpu(cpu)
        *per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
    desc_smp_init(desc, node);
}

 图片 15

void handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
    raw_spin_lock(&desc->lock); -----------------(0)

    desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);----参考上一章的描述
    if (unlikely(irqd_irq_disabled(&desc->irq_data) ||-----------(1)
             irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
        if (!irq_check_poll(desc)) {
            desc->istate |= IRQS_PENDING;
            mask_ack_irq(desc);
            goto out_unlock;
        }
    }
    kstat_incr_irqs_this_cpu(irq, desc); ---更新该IRQ统计信息

    desc->irq_data.chip->irq_ack(&desc->irq_data); ---------(2)

    do {
        if (unlikely(!desc->action)) { -----------------(3)
            mask_irq(desc);
            goto out_unlock;
        }

        if (unlikely(desc->istate & IRQS_PENDING)) { ---------(4)
            if (!irqd_irq_disabled(&desc->irq_data) &&
                irqd_irq_masked(&desc->irq_data))
                unmask_irq(desc);
        }

        handle_irq_event(desc); -------------------(5)

    } while ((desc->istate & IRQS_PENDING) &&
         !irqd_irq_disabled(&desc->irq_data)); -------------(6)

out_unlock:
    raw_spin_unlock(&desc->lock); -----------------(7)
}

    last = 0;
    for (i = 0; i < 4; i ) {
        pend = __raw_readl(S3C24XX_EINTPEND);

这里我们就要对handle_simple_irq 与handle_level_irq做个分析了,具体的分析大家可以网上看蜗窝的资料以及csdn上很多对这块有详细的描述,我这里简单叙述下我个人的理解

TODO

        switch (irqno) {
            /* deal with the special IRQs (cascaded) */

因此在note_interrupt函数里面只会从如下分支进去

irqreturn_t handle_irq_event(struct irq_desc *desc)
{

    raw_spin_unlock(&desc->lock); -------和上面的(0)对应

    处理具体的action list

    raw_spin_lock(&desc->lock);--------和上面的(7)对应
}

    /* register the uart interrupts */

===>generic_handle_irq_desc

原文地址:linux kernel的中断子系统之(四):High level irq event handler

在arch/arm/include/asm/mach/arch.h 中
/*
 * Machine type table - also only accessible during boot
 */
extern struct machine_desc __arch_info_begin[], __arch_info_end[];
#define for_each_machine_desc(p)            
    for (p = __arch_info_begin; p < __arch_info_end; p )
__arch_info_begin和__arch_info_end在arch/arm/kernel/vmlinux.lds.s 中
.init.arch.info : {
        __arch_info_begin = .;
        *(.arch.info.init)
        __arch_info_end = .;
    }

图片 16

说到具体的CPU,我们还是用ARM为例好了。对于ARM,我们在ARM中断处理文档中已经有了较为细致的描述。这里我们看看如何从从具体CPU的中断处理到machine相关的处理模块 ,其具体代码如下:

arch/arm/kernel/irq.c
void __init init_IRQ(void)
{
    machine_desc->init_irq();
}

因此有提示的log信息可以看出,是走的else的分支,bad_action_ret(action_ret)返回为0

我们在注册函数的时候使用setup_irq,有两个参数一个是IRQ number,另一个是irqaction。

图片 17

static int iproc_gpio_probe(struct platform_device *pdev)

 

irqreturn_t handle_irq_event(struct irq_desc *desc)
{
    struct irqaction *action = desc->action;
    irqreturn_t ret;
 
    desc->istate &= ~IRQS_PENDING;
    irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);  //处理中
    raw_spin_unlock(&desc->lock);
 
    ret = handle_irq_event_percpu(desc, action);
 
    raw_spin_lock(&desc->lock);
    irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
    return ret;
}

图片 18

(4)当enable x外设的IRQ的时候,需要检测pending状态以便resend该中断,否则,该中断会丢失的

###########

首先handle_level_irq这个函数是在这里注册到kernel中的

(3)在调用specific handler处理具体的中断的时候,由于不持有中断描述符的spin lock,因此其他CPU上有可能会注销其specific handler,因此do while循环之后,desc->action有可能是NULL,如果是这样,那么mask irq,然后退出就OK了

MACHINE_START(MINI2440, "MINI2440")
    /* Maintainer: Michel Pollet <buserror@gmail.com> */
    .atag_offset    = 0x100,
    .map_io        = mini2440_map_io,
    .init_machine    = mini2440_init,
    .init_irq    = s3c24xx_init_irq,
    .timer        = &s3c24xx_timer,
MACHINE_END

 

struct irq_desc {
……
    unsigned int        irq_count;-------- style="color: #ff0000">记录发生的中断的次数,每100,000则回滚
    unsigned long        last_unhandled;----- style="color: #ff0000">上一次没有处理的IRQ的时间点
    unsigned int        irqs_unhandled;------ style="color: #ff0000">没有处理的次数
……
}

early_irq_init(); 函数

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

三介绍了CPU和中断控制器之间的接口,中断控制器和外设之间的接口。

其中:

图片 19

在上面的代码中,如果配置了MULTI_IRQ_HANDLER的话,ARM中断处理则直接跳转到一个叫做handle_arch_irq函数,如果系统中只有一个类型的interrupt controller(可能是多个interrupt controller,例如使用两个级联的GIC),那么handle_arch_irq可以在interrupt controller初始化的时候设定。代码如下:

static struct irq_chip s3c_irq_adc = {
    .name        = "s3c-adc",
    .irq_mask    = s3c_irq_adc_mask,
    .irq_unmask    = s3c_irq_adc_unmask,
    .irq_ack    = s3c_irq_adc_ack,
};

===>__handle_irq_event_percpu  (kernel/irq/handle.c)

 

handle_level_irq

图片 20

 

    desc->irq_data.chip = chip;
把刚才的irq芯片操作函数填充进来,

图片 21

四、几种典型的high level irq event handler

即/arch/arm/plat-s3c24xx/irq.c中:

===>set_handle_irq

1、边缘触发的handler。

在该文件下你可以找到:

===> handle_irq_event  (kernel/irq/handle.c)

 

/kernel/irq/handle.c
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
    irqreturn_t retval = IRQ_NONE;
    unsigned int random = 0, irq = desc->irq_data.irq;
 
    do {
        irqreturn_t res;
 
        trace_irq_handler_entry(irq, action);
        res = action->handler(irq, action->dev_id);             //回调action的hanler,即链表结构,这个结构是register的时候注册的
        trace_irq_handler_exit(irq, action, res);
 
        if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interruptsn",
                  irq, action->handler))
            local_irq_disable();
 
        switch (res) {
        case IRQ_WAKE_THREAD:
            /*
             * Catch drivers which return WAKE_THREAD but
             * did not set up a thread function
             */
            if (unlikely(!action->thread_fn)) {
                warn_no_thread(irq, action);
                break;
            }
 
            irq_wake_thread(desc, action);
 
            /* Fall through to add to randomness */
        case IRQ_HANDLED:
            random |= action->flags;
            break;
 
        default:
            break;
        }
 
        retval |= res;
        action = action->next;
    } while (action);
 
    if (random & IRQF_SAMPLE_RANDOM)
        add_interrupt_randomness(irq);
 
    if (!noirqdebug)
        note_interrupt(irq, desc, retval);
    return retval;
}

 图片 22

调用high level handler的代码逻辑非常简单,如下:

 图片 23

static bool irq_check_poll(struct irq_desc *desc)
{
    if (!(desc->istate & IRQS_POLL_INPROGRESS))
        return false;
    return irq_wait_for_poll(desc);
}

再看下图

Notes:从中断向量表中__irq_svc-->irq_handler-->handle_arch_irq,其中handle_arch_irq是由具体的中断控制器实现。

 

handle_arch_irq = mdesc->handle_irq;

===>generic_handle_irq

……

if (gic_nr == 0) {
        set_handle_irq( style="color: #ff0000">gic_handle_irq);
}

……

 

2、Interrupt controller和Peripheral device之间的接口

直到满足handled != desc->threads_handled_last 为假

在中断处理的最后,总会有一段代码如下:

图片 24

(2)ack该中断。对于中断控制器,一旦被ack,表示该外设的中断被enable,硬件上已经准备好触发下一次中断了。再次触发的中断会被调度到其他的CPU上。现在,我们可以再次回到步骤(1)中,为什么这里用mask and ack而不是单纯的ack呢?如果单纯的ack则意味着后续中断还是会触发,这时候怎么处理?在pending+in progress的情况下,我们要怎么处理?记录pending的次数,有意义吗?由于中断是完全异步的,也有可能pending的标记可能在另外的CPU上已经修改为replay的标记,这时候怎么办?当事情变得复杂的时候,那一定是本来方向就错了,因此,mask and ack就是最好的策略,我已经记录了pending状态,不再考虑pending嵌套的情况。

如下只是个小记录:

实际上,由于在handle_irq_event中处理action list的耗时还是比较长的,因此处理具体的action list的时候并没有持有中断描述符的spin lock。在如果那样的话,其他CPU在对中断描述符进行操作的时候需要spin的时间会很长的。

 

 

(1)是否是一次有效的unhandled interrupt还要根据时间来判断。一般而言,当硬件处于异常状态的时候往往是非常短的时间触发非常多次的中断,如果距离上次unhandled interrupt的时间超过了10个jiffies(如果HZ=100,那么时间就是100ms),那么我们要把irqs_unhandled重新计数。如果不这么处理的话,随着时间的累计,最终irqs_unhandled可能会达到99900次的,从而把这个IRQ错误的推上了审判台。

handle_domain_irq

1、自动探测IRQ

Kernel/irq/manage.c

2、电平触发的handler

Irq_thread

我们还是以2个级联的GIC为例来描述interrupt controller相关的代码。代码如下:

#define SPURIOUS_DEFERRED        0x80000000

上面的1、4这两个步骤在linux kernel的中断子系统之(六):ARM中断处理过程中已经有了较为细致的描述,步骤2在linux kernel的中断子系统之(二):irq domain介绍中介绍,本文主要描述步骤3,也就是linux中断子系统的high level irq event handler。

一个中断开始后,从entry-armv.S中进入

(1)那些能自动探测IRQ的中断描述符需要具体两个条件:

这里即gpiochip->irq_handler = handle_level_irq

c、该中断描述符没有注册specific handler。这个比较简单,如果没有irqaction,根本没有必要调用action list的处理

那么就会出现threads_handled会一直 的情况,从而产生本文最开头的错误

(5)处理该中断请求事件

(1)考虑CPU<------>interrupt controller<------>device这样的连接方式中,我们认为high level handler主要是和interrupt controller交互,而specific handler(request_irq注册的那个)是和device进行交互。Level类型的中断的特点就是只要外设interrupt request line的电平状态是有效状态,对于interrupt controller,该外设的interrupt总是active的。由于外设检测到了事件(比如数据到来了),因此assert了指定的电平信号,这个电平信号会一直保持,直到软件清除了外设的状态寄存器。但是,high level irq event handler这个层面只能操作Interrupt controller,不能操作具体外设的寄存器(那应该属于具体外设的specific interrupt handler处理内容,该handler会挂入中断描述符中的IRQ action list)。直到在具体的中断服务程序(specific handler中)操作具体外设的寄存器,才能让这个asserted电平信号消息。

 图片 25

图片 26

因为handler为NULL,所以handler = irq_default_primary_handler

正是因为level trigger的这个特点,因此,在high level handler中首先mask并ack该IRQ。这一点和边缘触发的high level handler有显著的不同,在handle_edge_irq中,我们仅仅是ack了中断,并没有mask,因为边缘触发的中断稍纵即逝,一旦mask了该中断,容易造成中断丢失。而对于电平中断,我们不得不mask住该中断,如果不mask住,只要CPU ack中断,中断控制器将持续的assert CPU中断(因为有效电平状态一直保持)。如果我们mask住该中断,中断控制器将不再转发该interrupt source来的中断,因此,所有的CPU都不会感知到该中断,直到软件unmask。这里的ack是针对interrupt controller的ack,本身ack就是为了clear interrupt controller对该IRQ的状态寄存器,不过由于外部的电平仍然是有效信号,其实未必能清除interrupt controller的中断状态,不过这是和中断控制器硬件实现相关的。

 

2、resend一个中断

 图片 27

三、和high level irq event handler相关的硬件描述

===> handle_irq_event_percpu   (kernel/irq/handle.c)

这里会清除IRQS_REPLAY状态,表示该中断已经被retrigger,一次resend interrupt的过程结束。

这样子就将从HW id到IRQ number,然后通过中断控制器驱动,再到通过中断模块,最后调用到对应中断的处理函数。

gic_handle_irq

本章主要介绍几种典型的high level irq event handler,在进行high level irq event handler的设定的时候需要注意,不是外设使用电平触发就选用handle_level_irq,选用什么样的high level irq event handler是和Interrupt controller的行为以及外设电平触发方式决定的。介绍每个典型的handler之前,我会简单的描述该handler要求的硬件行为,如果该外设的中断系统符合这个硬件行为,那么可以选择该handler为该中断的high level irq event handler。

kernel/irq/manage.c

我们以上升沿为例描述边缘中断的处理过程(下降沿的触发是类似的)。当interrupt controller检测到了上升沿信号,会将该上升沿状态(pending)锁存在寄存器中,并通过中断的signal向CPU触发中断。需要注意:这时候,外设和interrupt controller之间的interrupt request信号线会保持高电平,这也就意味着interrupt controller不可能检测到新的中断信号(本身是高电平,无法形成上升沿)。这个高电平信号会一直保持到软件ack该中断(调用irq chip的irq_ack callback函数)。ack之后,中断控制器才有可能继续探测上升沿,触发下一次中断。

 

(0) 这时候,中断仍然是关闭的,因此不会有来自本CPU的并发,使用raw spin lock就防止其他CPU上对该IRQ的中断描述符的访问。针对该spin lock,我们直观的感觉是raw_spin_lock和(7)中的raw_spin_unlock是成对的,实际上并不是,handle_irq_event中的代码是这样的:

回归到最初的问题,之前我们分析出如下的结论:

(4)启动timer,轮询整个系统中的handler来处理这个中断(轮询啊,绝对是真爱啊)。这个timer的callback函数定义如下:

曾有一次调试触摸屏的时候遇到如下的问题

1、从具体CPU architecture的中断处理到machine相关的处理模块

图片 28

Notes:根据不同的中断号,handle_irq是不同的。在irq_domain_ops的map中处理。

irq_count和irqs_unhandled都是比较直观的,为何要记录unhandled interrupt发生的时间呢?我们来看具体的代码。具体的相关代码位于note_interrupt中,如下:

当外设触发一次中断后,一个大概的处理过程是:

 图片 29

handle_level_irq的代码如下:

gpiochip_set_chained_irqchip

3、unhandled interrupt和spurious interrupt

大家可以看出来,handle_simple_irq做的事情很简单,而handle_level_irq却做了这个动作:

3、调用high level handler

再继续看回如下图,action_ret就是retval

(2)x外设的中断再次到来(CPU A已经ack该IRQ,因此x外设的中断可以再次触发),这时候其他CPU会处理它(mask and ack),并设置该中断描述符是pending状态,并委托CPU A处理该pending状态的中断。需要注意的是CPU已经ack了该中断,因此该中断的硬件状态已经不是pending状态,无法触发中断了,这里的pending状态是指中断描述符的软件状态。

 首先非常感谢陈莉君老师的指点,题目名字也是陈老师起的,也很荣幸此文章能在蜗窝上发表一次,感谢郭大侠给的机会

其中handle_fasteoi_irq-->handle_irq_event-->handle_irq_event_percpu,下面看看handle_percpu_devid_irq和handle_irq_event_percpu。

而handle_simple_irq就是非常简单的处理中断,没有mask中断,原本代码是写的handle_simple_irq,而触摸屏的中断是设置为线程化的,并且为电平触发方式,那么如果没有mask该中断,那么当一次线程化中断处理函数还未执行完成的时候,又会有源源不断地中断一直进来,那么就会出现threads_handled会一直 的情况,从而产生本文最开头的错误

(4)这时候,我们还没有控制那个想要自动探测IRQ的硬件产生中断,因此处于自动探测中,并且IRQS_WAITING并清除的一定不是我们期待的IRQ(可能是spurious interrupts导致的),这时候,clear IRQS_AUTODETECT,shutdown该IRQ。

原创文章,转发请注明出处。蜗窝科技。

 

这里会清除IRQS_WAITING状态。

参考:

1、CPU layer和Interrupt controller之间的接口

图片 30

desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

到此这个问题就已经分析完了

ARM+GIC组成的系统不符合这个类型。虽然GIC提供了IAR(Interrupt Acknowledge Register)寄存器来让ARM来ack中断,但是,在调用high level handler之前,中断处理程序需要通过读取IAR寄存器获得HW interrpt ID并转换成IRQ number,因此实际上,对于GIC的irq chip,它是无法提供本场景中的irq_ack函数的。很多GPIO type的interrupt controller符合上面的条件,它们会提供pending状态寄存器,读可以获取pending状态,而向pending状态寄存器写1可以ack该中断,让interrupt controller可以继续触发下一次中断。

 图片 31

(6)只要有pending标记,就说明该中断还在pending状态,需要继续处理。当然,如果有其他的CPU disable了该interrupt source,那么本次中断结束处理。

 图片 32

(5)最大探测的IRQ是31(mask是一个32 bit的value),mask返回的是可能的irq掩码。

图片 33

注册接口有如下,最接近中断处理函数的是handle_irq_event_percpu和handle_percpu_devid_irq。

 图片 34

(3)CPU B上由于同步的需求,disable了x外设的IRQ,这时候,CPU A没有处理pending状态的x外设中断就离开了中断处理过程。

图片 35

(4)如果中断描述符处于pending状态,那么一定是其他CPU上又触发了该interrupt source的中断,并设定了pending状态,“委托”本CPU进行处理,这时候,需要把之前mask住的中断进行unmask的操作。一旦unmask了该interrupt source,后续的中断可以继续触发,由其他的CPU处理(仍然是设定中断描述符的pending状态,委托当前正在处理该中断请求的那个CPU进行处理)。

/startup/modules #
 [  233.370296] irq 44: nobody cared (try booting with the "irqpoll" option)
[  233.376983] CPU: 0 PID: 0 Comm: swapper Tainted: G           O    4.9.18 #8
[  233.383912] Hardware name: Broadcom Cygnus SoC
[  233.388378] [<c010cbfc>] (unwind_backtrace) from [<c010a5fc>] (show_stack 0x10/0x14)
[  233.396103] [<c010a5fc>] (show_stack) from [<c0145d38>] (__report_bad_irq 0x24/0xa4)
[  233.403821] 
[<c0145d38>] (__report_bad_irq) from [<c0145fdc>] (note_interrupt 0x1c8/0x274)
[  233.412052] 
[<c0145fdc>] (note_interrupt) from [<c014400c>] (handle_irq_event_percpu 0x44/0x50)
[  233.420715] 
[<c014400c>] (handle_irq_event_percpu) from [<c0144040>] (handle_irq_event 0x28/0x3c)
[  233.429550] 
[<c0144040>] (handle_irq_event) from [<c0146574>] (handle_simple_irq 0x70/0x78)
[  233.437868] 
[<c0146574>] (handle_simple_irq) from [<c01438d8>] (generic_handle_irq 0x18/0x28)
[  233.446366] 
[<c01438d8>] (generic_handle_irq) from [<c02adb3c>] (iproc_gpio_irq_handler 0xd0/0x11c)
[  233.455376] 
[<c02adb3c>] (iproc_gpio_irq_handler) from [<c01438d8>] (generic_handle_irq 0x18/0x28)
[  233.464297] 
[<c01438d8>] (generic_handle_irq) from [<c0143980>] (__handle_domain_irq 0x80/0xa4)
[  233.472959] 
[<c0143980>] (__handle_domain_irq) from [<c01013d0>] (gic_handle_irq 0x50/0x84)
[  233.481275] [<c01013d0>] (gic_handle_irq) from [<c010b02c>] (__irq_svc 0x6c/0x90)
[  233.488723] Exception stack(0xc0901f60 to 0xc0901fa8)
[  233.493754] 1f60: c0112900 c0717028 c0901fb8 00000000 c093af4c 00000000 00000335 c0826220
[  233.501896] 1f80: 00000001 414fc091 df9eab80 00000000 c0900038 c0901fb0 c010843c c0108440
[  233.510034] 1fa0: 60000013 ffffffff
[  233.513514] [<c010b02c>] (__irq_svc) from [<c0108440>] (arch_cpu_idle 0x2c/0x38)
[  233.520887] [<c0108440>] (arch_cpu_idle) from [<c013a6ec>] (cpu_startup_entry 0x50/0xc0)
[  233.528956] [<c013a6ec>] (cpu_startup_entry) from [<c0800d70>] (start_kernel 0x414/0x4b0)
[  233.537097] handlers:
[  233.539363] 
[<c014408c>] irq_default_primary_handler threaded [<bf03ff68>] synaptics_rmi4_irq [synaptics_dsx]
[  233.549300] Disabling IRQ #44

注:这份文档充满了猜测和空想,很多地方描述可能是有问题的,不过我还是把它发出来,抛砖引玉,希望可以引发大家讨论。

这里是如何转换过去的呢?

(3)在等待过程中,系统仍然允许,各种中断依然会触发。在各种high level irq event handler中,总会有如下的代码:

图片 36

 图片 37

经过如上图,我们可以发现action_ret = IRQ_NONE

Linux内核版本:linux-4.9.18

            ===>generic_handle_irq

 https://blog.csdn.net/DroidPhone/article/details/7489756

            ===>irq_set_type

 图片 38

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

===>__gic_init_bases

所以可以这样改,驱动注册的时候可以通过这个函数来设置自己的中断控制器需要走哪种流控处理


内核也有其他驱动有类似的改法
drivers/gpio/gpio-aspeed.c
static int aspeed_gpio_set_type(struct irq_data *d, unsigned int type)
{
    u32 type0 = 0;
    u32 type1 = 0;
    u32 type2 = 0;
    u32 bit, reg;
    const struct aspeed_gpio_bank *bank;
    irq_flow_handler_t handler;
    struct aspeed_gpio *gpio;
    unsigned long flags;
    void __iomem *addr;
    int rc;

    rc = irqd_to_aspeed_gpio_data(d, &gpio, &bank, &bit);
    if (rc)
        return -EINVAL;

    switch (type & IRQ_TYPE_SENSE_MASK) {
    case IRQ_TYPE_EDGE_BOTH:
        type2 |= bit;
    case IRQ_TYPE_EDGE_RISING:
        type0 |= bit;
    case IRQ_TYPE_EDGE_FALLING:
        handler = handle_edge_irq;
        break;
    case IRQ_TYPE_LEVEL_HIGH:
        type0 |= bit;
    case IRQ_TYPE_LEVEL_LOW:
        type1 |= bit;
        handler = handle_level_irq;
        break;
    default:
        return -EINVAL;
    }

    spin_lock_irqsave(&gpio->lock, flags);

    addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE0);
    reg = ioread32(addr);
    reg = (reg & ~bit) | type0;
    iowrite32(reg, addr);

    addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE1);
    reg = ioread32(addr);
    reg = (reg & ~bit) | type1;
    iowrite32(reg, addr);

    addr = bank_irq_reg(gpio, bank, GPIO_IRQ_TYPE2);
    reg = ioread32(addr);
    reg = (reg & ~bit) | type2;
    iowrite32(reg, addr);

    spin_unlock_irqrestore(&gpio->lock, flags);

    irq_set_handler_locked(d, handler);

    return 0;
}

kernel/irq/spurious.c

Kernel/irq/handle.c

这里的desc->handle_irq 其实就是handle_level_irq

 

 图片 39

[  233.549300] Disabling IRQ #44

        ==>__irq_set_trigger

Kernel/irq/spurious.c

图片 40

从上图可以看出,如果想出现那样的错误,必须满足条件

这里有这样的函数set_handle_irq

handle_level_irq

 图片 41

 图片 42

 

Include/linux/irqchip.h

中断控制器怎么判断哪个是哪个handle_level_irq?

这个全局的handle_arch_irq会在如下地方被赋值

上图是正确的修改,如果gpiochip_irqchip_add的第四个参数是handle_simple_irq的话,

[  233.370296] irq 44: nobody cared (try booting with the "irqpoll" option)

 

===> handle_arch_irq被赋值

感谢网友smcdef的建议

这里会一直将threads_handled ,这里handled = threads_handled

而desc->threads_handled_last会在如下位置设置为SPURIOUS_DEFERRED

因此不同的驱动在注册的时候可以注册自己所需要的中断控制器驱动方式

因此我们得出一个结论:

那么为什么这个threads_handled会一直 呢?

action_ret == IRQ_NONE为真

request_threaded_irq

这个函数的作用是检查是否有中断嵌套

图片 43

Kernel/irq/spurious.c

图片 44

res即为action_ret

图片 45

            ===>irq_find_mapping 

drivers/pinctrl/bcm/pinctrl-iproc-gpio.c

 

    ===>__setup_irq

Include/linux/gpio/driver.h

本文由美高梅4858官方网站发布,转载请注明来源

关键词: Linux中断子系统