linux kernel的中断子系统之(三):IRQ number和中断描述符

作者:linuxer 发布于:2014-8-26 17:03 分类:中断子系统

一、前言

本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程。第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等。第三章描述中断描述符数据结构的各个成员。第四章描述了初始化中断描述符相关的接口API。第五章描述中断描述符相关的接口API。

二、基本概念

1、通用中断的代码处理示意图

一个关于通用中断处理的示意图如下:

zhongduan

在linux kernel中,对于每一个外设的IRQ都用struct irq_desc来描述,我们称之中断描述符(struct irq_desc)。linux kernel中会有一个数据结构保存了关于所有IRQ的中断描述符信息,我们称之中断描述符DB(上图中红色框图内)。当发生中断后,首先获取触发中断的HW interupt ID,然后通过irq domain翻译成IRQ nuber,然后通过IRQ number就可以获取对应的中断描述符。调用中断描述符中的highlevel irq-events handler来进行中断处理就OK了。而highlevel irq-events handler主要进行下面两个操作:

(1)调用中断描述符的底层irq chip driver进行mask,ack等callback函数,进行interrupt flow control。

(2)调用该中断描述符上的action list中的specific handler(我们用这个术语来区分具体中断handler和high level的handler)。这个步骤不一定会执行,这是和中断描述符的当前状态相关,实际上,interrupt flow control是软件(设定一些标志位,软件根据标志位进行处理)和硬件(mask或者unmask interrupt controller等)一起控制完成的。

 

2、中断的打开和关闭

我们再来看看整个通用中断处理过程中的开关中断情况,开关中断有两种:

(1)开关local CPU的中断。对于UP,关闭CPU中断就关闭了一切,永远不会被抢占。对于SMP,实际上,没有关全局中断这一说,只能关闭local CPU(代码运行的那个CPU)

(2)控制interrupt controller,关闭某个IRQ number对应的中断。更准确的术语是mask或者unmask一个 IRQ。

本节主要描述的是第一种,也就是控制CPU的中断。当进入high level handler的时候,CPU的中断是关闭的(硬件在进入IRQ processor mode的时候设定的)。

对于外设的specific handler,旧的内核(2.6.35版本之前)认为有两种:slow handler和fast handle。在request irq的时候,对于fast handler,需要传递IRQF_DISABLED的参数,确保其中断处理过程中是关闭CPU的中断,因为是fast handler,执行很快,即便是关闭CPU中断不会影响系统的性能。但是,并不是每一种外设中断的handler都是那么快(例如磁盘),因此就有slow handler的概念,说明其在中断处理过程中会耗时比较长。对于这种情况,如果在整个specific handler中关闭CPU中断,对系统的performance会有影响。因此,对于slow handler,在从high level handler转入specific handler中间会根据IRQF_DISABLED这个flag来决定是否打开中断,具体代码如下(来自2.6.23内核):

irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
    ……

    if (!(action->flags & IRQF_DISABLED))
        local_irq_enable_in_hardirq();

    ……
}

如果没有设定IRQF_DISABLED(slow handler),则打开本CPU的中断。然而,随着软硬件技术的发展:

(1)硬件方面,CPU越来越快,原来slow handler也可以很快执行完毕

(2)软件方面,linux kernel提供了更多更好的bottom half的机制

因此,在新的内核中,比如3.14,IRQF_DISABLED被废弃了。我们可以思考一下,为何要有slow handler?每一个handler不都是应该迅速执行完毕,返回中断现场吗?此外,任意中断可以打断slow handler执行,从而导致中断嵌套加深,对内核栈也是考验。因此,新的内核中在interrupt specific handler中是全程关闭CPU中断的。


3、IRQ number

从CPU的角度看,无论外部的Interrupt controller的结构是多么复杂,I do not care,我只关心发生了一个指定外设的中断,需要调用相应的外设中断的handler就OK了。更准确的说是通用中断处理模块不关心外部interrupt controller的组织细节(电源管理模块当然要关注具体的设备(interrupt controller也是设备)的拓扑结构)。一言以蔽之,通用中断处理模块可以用一个线性的table来管理一个个的外部中断,这个表的每个元素就是一个irq描述符,在kernel中定义如下:

struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
    [0 ... NR_IRQS-1] = {
        .handle_irq    = handle_bad_irq,
        .depth        = 1,
        .lock        = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
    }
};

系统中每一个连接外设的中断线(irq request line)用一个中断描述符来描述,每一个外设的interrupt request line分配一个中断号(irq number),系统中有多少个中断线(或者叫做中断源)就有多少个中断描述符(struct irq_desc)。NR_IRQS定义了该硬件平台IRQ的最大数目。

总之,一个静态定义的表格,irq number作为index,每个描述符都是紧密的排在一起,一切看起来很美好,但是现实很残酷的。有些系统可能会定义一个很大的NR_IRQS,但是只是想用其中的若干个,换句话说,这个静态定义的表格不是每个entry都是有效的,有空洞,如果使用静态定义的表格就会导致了内存很大的浪费。为什么会有这种需求?我猜是和各个interrupt controller硬件的interrupt ID映射到irq number的算法有关。在这种情况下,静态表格不适合了,我们改用一个radix tree来保存中断描述符(HW interrupt作为索引)。这时候,每一个中断描述符都是动态分配,然后插入到radix tree中。如果你的系统采用这种策略,那么需要打开CONFIG_SPARSE_IRQ选项。上面的示意图描述的是静态表格的中断描述符DB,如果打开CONFIG_SPARSE_IRQ选项,系统使用Radix tree来保存中断描述符DB,不过概念和静态表格是类似的。

此外,需要注意的是,在旧内核中,IRQ number和硬件的连接有一定的关系,但是,在引入irq domain后,IRQ number已经变成一个单纯的number,和硬件没有任何关系。

 

三、中断描述符数据结构

1、底层irq chip相关的数据结构

中断描述符中应该会包括底层irq chip相关的数据结构,linux kernel中把这些数据组织在一起,形成struct irq_data,具体代码如下:

struct irq_data {
    u32            mask;----------TODO
    unsigned int        irq;--------IRQ number
    unsigned long        hwirq;-------HW interrupt ID
    unsigned int        node;-------NUMA node index
    unsigned int        state_use_accessors;--------底层状态,参考IRQD_xxxx
    struct irq_chip        *chip;----------该中断描述符对应的irq chip数据结构
    struct irq_domain    *domain;--------该中断描述符对应的irq domain数据结构
    void            *handler_data;--------和外设specific handler相关的私有数据
    void            *chip_data;---------和中断控制器相关的私有数据
    struct msi_desc        *msi_desc;
    cpumask_var_t        affinity;-------和irq affinity相关
};

中断有两种形态,一种就是直接通过signal相连,用电平或者边缘触发。另外一种是基于消息的,被称为MSI (Message Signaled Interrupts)。msi_desc是MSI类型的中断相关,这里不再描述。

node成员用来保存中断描述符的内存位于哪一个memory node上。 对于支持NUMA(Non Uniform Memory Access Architecture)的系统,其内存空间并不是均一的,而是被划分成不同的node,对于不同的memory node,CPU其访问速度是不一样的。如果一个IRQ大部分(或者固定)由某一个CPU处理,那么在动态分配中断描述符的时候,应该考虑将内存分配在该CPU访问速度比较快的memory node上。

 

2、irq chip数据结构

Interrupt controller描述符(struct irq_chip)包括了若干和具体Interrupt controller相关的callback函数,我们总结如下:

成员名字 描述
name 该中断控制器的名字,用于/proc/interrupts中的显示
irq_startup start up 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为enable函数
irq_shutdown shutdown 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为disable函数
irq_enable enable指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为unmask函数
irq_disable disable指定的irq domain上的HW interrupt ID。
irq_ack 和具体的硬件相关,有些中断控制器必须在Ack之后(清除pending的状态)才能接受到新的中断。
irq_mask mask指定的irq domain上的HW interrupt ID
irq_mask_ack mask并ack指定的irq domain上的HW interrupt ID。
irq_unmask mask指定的irq domain上的HW interrupt ID
irq_eoi 有些interrupt controler(例如GIC)提供了这样的寄存器接口,让CPU可以通知interrupt controller,它已经处理完一个中断
irq_set_affinity 在SMP的情况下,可以通过该callback函数设定CPU affinity
irq_retrigger 重新触发一次中断,一般用在中断丢失的场景下。如果硬件不支持retrigger,可以使用软件的方法。
irq_set_type 设定指定的irq domain上的HW interrupt ID的触发方式,电平触发还是边缘触发
irq_set_wake 和电源管理相关,用来enable/disable指定的interrupt source作为唤醒的条件。
irq_bus_lock 有些interrupt controller是连接到慢速总线上(例如一个i2c接口的IO expander芯片),在访问这些芯片的时候需要lock住那个慢速bus(只能有一个client在使用I2C bus)
irq_bus_sync_unlock unlock慢速总线
irq_suspend
irq_resume
irq_pm_shutdown
电源管理相关的callback函数
irq_calc_mask TODO
irq_print_chip /proc/interrupts中的信息显示

 

3、中断描述符

在linux kernel中,使用struct irq_desc来描述一个外设的中断,我们称之中断描述符,具体代码如下:

struct irq_desc {
    struct irq_data        irq_data;
    unsigned int __percpu    *kstat_irqs;------IRQ的统计信息
    irq_flow_handler_t    handle_irq;--------(1)
    struct irqaction    *action; -----------(2)
    unsigned int        status_use_accessors;-----中断描述符的状态,参考IRQ_xxxx
    unsigned int        core_internal_state__do_not_mess_with_it;----(3)
    unsigned int        depth;----------(4)
    unsigned int        wake_depth;--------(5)
    unsigned int        irq_count;  ---------(6)
    unsigned long        last_unhandled;  
    unsigned int        irqs_unhandled;
    raw_spinlock_t        lock;-----------(7)
    struct cpumask        *percpu_enabled;-------(8)
#ifdef CONFIG_SMP
    const struct cpumask    *affinity_hint;----和irq affinity相关,后续单独文档描述
    struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
    cpumask_var_t        pending_mask;
#endif
#endif
    unsigned long        threads_oneshot; -----(9)
    atomic_t        threads_active;
    wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PROC_FS
    struct proc_dir_entry    *dir;--------该IRQ对应的proc接口
#endif
    int            parent_irq;
    struct module        *owner;
    const char        *name;
} ____cacheline_internodealigned_in_smp

(1)handle_irq就是highlevel irq-events handler,何谓high level?站在高处自然看不到细节。我认为high level是和specific相对,specific handler处理具体的事务,例如处理一个按键中断、处理一个磁盘中断。而high level则是对处理各种中断交互过程的一个抽象,根据下列硬件的不同:

(a)中断控制器

(b)IRQ trigger type

highlevel irq-events handler可以分成:

(a)处理电平触发类型的中断handler(handle_level_irq)

(b)处理边缘触发类型的中断handler(handle_edge_irq)

(c)处理简单类型的中断handler(handle_simple_irq)

(d)处理EOI类型的中断handler(handle_fasteoi_irq)

会另外有一份文档对high level handler进行更详细的描述。

(2)action指向一个struct irqaction的链表。如果一个interrupt request line允许共享,那么该链表中的成员可以是多个,否则,该链表只有一个节点。

(3)这个有着很长名字的符号core_internal_state__do_not_mess_with_it在具体使用的时候被被简化成istate,表示internal state。就像这个名字定义的那样,我们最好不要直接修改它。

#define istate core_internal_state__do_not_mess_with_it

(4)我们可以通过enable和disable一个指定的IRQ来控制内核的并发,从而保护临界区的数据。对一个IRQ进行enable和disable的操作可以嵌套(当然一定要成对使用),depth是描述嵌套深度的信息。

(5)wake_depth是和电源管理中的wake up source相关。通过irq_set_irq_wake接口可以enable或者disable一个IRQ中断是否可以把系统从suspend状态唤醒。同样的,对一个IRQ进行wakeup source的enable和disable的操作可以嵌套(当然一定要成对使用),wake_depth是描述嵌套深度的信息。

(6)irq_count、last_unhandled和irqs_unhandled用于处理broken IRQ 的处理。所谓broken IRQ就是由于种种原因(例如错误firmware),IRQ handler没有定向到指定的IRQ上,当一个IRQ没有被处理的时候,kernel可以为这个没有被处理的handler启动scan过程,让系统中所有的handler来认领该IRQ。

(7)保护该中断描述符的spin lock。

(8)一个中断描述符可能会有两种情况,一种是该IRQ是global,一旦disable了该irq,那么对于所有的CPU而言都是disable的。还有一种情况,就是该IRQ是per CPU的,也就是说,在某个CPU上disable了该irq只是disable了本CPU的IRQ而已,其他的CPU仍然是enable的。percpu_enabled是一个描述该IRQ在各个CPU上是否enable成员。

(9)threads_oneshot、threads_active和wait_for_threads是和IRQ thread相关,后续文档会专门描述。

 

四、初始化相关的中断描述符的接口

1、静态定义的中断描述符初始化

int __init early_irq_init(void)
{
    int count, i, node = first_online_node;
    struct irq_desc *desc;

    init_irq_default_affinity();

    desc = irq_desc;
    count = ARRAY_SIZE(irq_desc);

    for (i = 0; i < count; i++) {---遍历整个lookup table,对每一个entry进行初始化
        desc[i].kstat_irqs = alloc_percpu(unsigned int);---分配per cpu的irq统计信息需要的内存
        alloc_masks(&desc[i], GFP_KERNEL, node);---分配中断描述符中需要的cpu mask内存
        raw_spin_lock_init(&desc[i].lock);---初始化spin lock
        lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
        desc_set_defaults(i, &desc[i], node, NULL);---设定default值
    }
    return arch_early_irq_init();---调用arch相关的初始化函数
}

2、使用Radix tree的中断描述符初始化

int __init early_irq_init(void)
{……

    initcnt = arch_probe_nr_irqs();---体系结构相关的代码来决定预先分配的中断描述符的个数

    if (initcnt > nr_irqs)---initcnt是需要在初始化的时候预分配的IRQ的个数
        nr_irqs = initcnt; ---nr_irqs是当前系统中IRQ number的最大值

    for (i = 0; i < initcnt; i++) {--------预先分配initcnt个中断描述符
        desc = alloc_desc(i, node, NULL);---分配中断描述符
        set_bit(i, allocated_irqs);---设定已经alloc的flag
        irq_insert_desc(i, desc);-----插入radix tree
    }
  ……
}

即便是配置了CONFIG_SPARSE_IRQ选项,在中断描述符初始化的时候,也有机会预先分配一定数量的IRQ。这个数量由arch_probe_nr_irqs决定,对于ARM而言,其arch_probe_nr_irqs定义如下:

int __init arch_probe_nr_irqs(void)
{
    nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
    return nr_irqs;
}

3、分配和释放中断描述符

对于使用Radix tree来保存中断描述符DB的linux kernel,其中断描述符是动态分配的,可以使用irq_alloc_descs和irq_free_descs来分配和释放中断描述符。alloc_desc函数也会对中断描述符进行初始化,初始化的内容和静态定义的中断描述符初始化过程是一样的。最大可以分配的ID是IRQ_BITMAP_BITS,定义如下:

#ifdef CONFIG_SPARSE_IRQ
# define IRQ_BITMAP_BITS    (NR_IRQS + 8196)---对于Radix tree,除了预分配的,还可以动态
#else                                                                             分配8196个中断描述符
# define IRQ_BITMAP_BITS    NR_IRQS---对于静态定义的,IRQ最大值就是NR_IRQS
#endif

 

五、和中断控制器相关的中断描述符的接口

这部分的接口主要有两类,irq_desc_get_xxx和irq_set_xxx,由于get接口API非常简单,这里不再描述,主要描述set类别的接口API。此外,还有一些locked版本的set接口API,定义为__irq_set_xxx,这些API的调用者应该已经持有保护irq desc的spinlock,因此,这些locked版本的接口没有中断描述符的spin lock进行操作。这些接口有自己特定的使用场合,这里也不详细描述了。

1、接口调用时机

kernel提供了若干的接口API可以让内核其他模块可以操作指定IRQ number的描述符结构。中断描述符中有很多的成员是和底层的中断控制器相关,例如:

(1)该中断描述符对应的irq chip

(2)该中断描述符对应的irq trigger type

(3)high level handler

在过去,系统中各个IRQ number是固定分配的,各个IRQ对应的中断控制器、触发类型等也都是清楚的,因此,一般都是在machine driver初始化的时候一次性的进行设定。machine driver的初始化过程会包括中断系统的初始化,在machine driver的中断初始化函数中,会调用本文定义的这些接口对各个IRQ number对应的中断描述符进行irq chip、触发类型的设定。

在引入了device tree、动态分配IRQ number以及irq domain这些概念之后,这些接口的调用时机移到各个中断控制器的初始化以及各个具体硬件驱动初始化过程中,具体如下:

(1)各个中断控制器定义好自己的struct irq_domain_ops callback函数,主要是map和translate函数。

(2)在各个具体的硬件驱动初始化过程中,通过device tree系统可以知道自己的中断信息(连接到哪一个interrupt controler、使用该interrupt controller的那个HW interrupt ID,trigger type为何),调用对应的irq domain的translate进行翻译、解析。之后可以动态申请一个IRQ number并和该硬件外设的HW interrupt ID进行映射,调用irq domain对应的map函数。在map函数中,可以调用本节定义的接口进行中断描述符底层interrupt controller相关信息的设定。

 

2、irq_set_chip

这个接口函数用来设定中断描述符中desc->irq_data.chip成员,具体代码如下:

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

    desc->irq_data.chip = chip;
    irq_put_desc_unlock(desc, flags);---------------(2)

    irq_reserve_irq(irq);----------------------(3)
    return 0;
}

(1)获取irq number对应的中断描述符。这里用关闭中断+spin lock来保护中断描述符,flag中就是保存的关闭中断之前的状态flag,后面在(2)中会恢复中断flag。

(3)前面我们说过,irq number有静态表格定义的,也有radix tree类型的。对于静态定义的中断描述符,没有alloc的概念。但是,对于radix tree类型,需要首先irq_alloc_desc或者irq_alloc_descs来分配一个或者一组IRQ number,在这些alloc函数值,就会set那些那些已经分配的IRQ。对于静态表格而言,其IRQ没有分配,因此,这里通过irq_reserve_irq函数标识该IRQ已经分配,虽然对于CONFIG_SPARSE_IRQ(使用radix tree)的配置而言,这个操作重复了(在alloc的时候已经设定了)。

 

3、irq_set_irq_type

这个函数是用来设定该irq number的trigger type的。

int irq_set_irq_type(unsigned int irq, unsigned int type)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_buslock(irq, &flags, IRQ_GET_DESC_CHECK_GLOBAL);
    int ret = 0;

    type &= IRQ_TYPE_SENSE_MASK;
    ret = __irq_set_trigger(desc, irq, type);
    irq_put_desc_busunlock(desc, flags);
    return ret;
}

来到这个接口函数,第一个问题就是:为何irq_set_chip接口函数使用irq_get_desc_lock来获取中断描述符,而irq_set_irq_type这个函数却需要irq_get_desc_buslock呢?其实也很简单,irq_set_chip不需要访问底层的irq chip(也就是interrupt controller),但是irq_set_irq_type需要。设定一个IRQ的trigger type最终要调用desc->irq_data.chip->irq_set_type函数对底层的interrupt controller进行设定。这时候,问题来了,对于嵌入SOC内部的interrupt controller,当然没有问题,因为访问这些中断控制器的寄存器memory map到了CPU的地址空间,访问非常的快,因此,关闭中断+spin lock来保护中断描述符当然没有问题,但是,如果该interrupt controller是一个I2C接口的IO expander芯片(这类芯片是扩展的IO,也可以提供中断功能),这时,让其他CPU进行spin操作太浪费CPU时间了(bus操作太慢了,会spin很久的)。肿么办?当然只能是用其他方法lock住bus了(例如mutex,具体实现是和irq chip中的irq_bus_lock实现相关)。一旦lock住了slow bus,然后就可以关闭中断了(中断状态保存在flag中)。

解决了bus lock的疑问后,还有一个看起来奇奇怪怪的宏:IRQ_GET_DESC_CHECK_GLOBAL。为何在irq_set_chip函数中不设定检查(check的参数是0),而在irq_set_irq_type接口函数中要设定global的check,到底是什么意思呢?既然要检查,那么检查什么呢?和“global”对应的不是local而是“per CPU”,内核中的宏定义是:IRQ_GET_DESC_CHECK_PERCPU。SMP情况下,从系统角度看,中断有两种形态(或者叫mode):

(1)1-N mode。只有1个processor处理中断

(2)N-N mode。所有的processor都是独立的收到中断,如果有N个processor收到中断,那么就有N个处理器来处理该中断。

听起来有些抽象,我们还是用GIC作为例子来具体描述。在GIC中,SPI使用1-N模型,而PPI和SGI使用N-N模型。对于SPI,由于采用了1-N模型,系统(硬件加上软件)必须保证一个中断被一个CPU处理。对于GIC,一个SPI的中断可以trigger多个CPU的interrupt line(如果Distributor中的Interrupt Processor Targets Registers有多个bit被设定),但是,该interrupt source和CPU的接口寄存器(例如ack register)只有一套,也就是说,这些寄存器接口是全局的,是global的,一旦一个CPU ack(读Interrupt Acknowledge Register,获取interrupt ID)了该中断,那么其他的CPU看到的该interupt source的状态也是已经ack的状态。在这种情况下,如果第二个CPU ack该中断的时候,将获取一个spurious interrupt ID。

对于PPI或者SGI,使用N-N mode,其interrupt source的寄存器是per CPU的,也就是每个CPU都有自己的、针对该interrupt source的寄存器接口(这些寄存器叫做banked register)。一个CPU 清除了该interrupt source的pending状态,其他的CPU感知不到这个变化,它们仍然认为该中断是pending的。

对于irq_set_irq_type这个接口函数,它是for 1-N mode的interrupt source使用的。如果底层设定该interrupt是per CPU的,那么irq_set_irq_type要返回错误。

4、irq_set_chip_data

每个irq chip总有自己私有的数据,我们称之chip data。具体设定chip data的代码如下:

int irq_set_chip_data(unsigned int irq, void *data)
{
    unsigned long flags;
    struct irq_desc *desc = irq_get_desc_lock(irq, &flags, 0); 
    desc->irq_data.chip_data = data;
    irq_put_desc_unlock(desc, flags);
    return 0;
}

多么清晰、多么明了,需要文字继续描述吗?

5、设定high level handler

这是中断处理的核心内容,__irq_set_handler就是设定high level handler的接口函数,不过一般不会直接调用,而是通过irq_set_chip_and_handler_name或者irq_set_chip_and_handler来进行设定。具体代码如下:

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

……
    desc->handle_irq = 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, true);
    }
out:
    irq_put_desc_busunlock(desc, flags);
}

理解这个函数的关键是在is_chained这个参数。这个参数是用在interrupt级联的情况下。例如中断控制器B级联到中断控制器A的第x个interrupt source上。那么对于A上的x这个interrupt而言,在设定其IRQ handler参数的时候要设定is_chained参数等于1,由于这个interrupt source用于级联,因此不能probe、不能被request(已经被中断控制器B使用了),不能被threaded(具体中断线程化的概念在其他文档中描述)


原创文章,转发请注明出处。蜗窝科技。http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html

标签: irq 中断子系统 中断描述符

评论:

zengzhl
2014-12-10 20:01
仰望博主。很多芯片资料里面介绍ARM中断号,有的代码+32,有的又不加,请问有什么区别,以及什么时候+32?
linuxer
2014-12-11 10:08
@zengzhl:我不是很清楚你的问题,你能否给出更多的细节? 比如哪一个芯片资料?具体的代码片段...
chunhai
2014-11-04 17:26
感谢博主的分享,
有个小错误。
对于GIC而言,SPI是N-N mode,而SGI和PPI是1-N mode。  
这句话 有误。 SPI, PPI 是 1-N, SGI 是N-N 的。
linuxer
2014-11-04 19:18
@chunhai:多谢您的提醒,是笔误,我的本意是“SPI是1-N mode,而SGI和PPI是N-N mode”,我已经修改了相关的描述。

PS:你建议的修改也是有问题的(SPI, PPI 是 1-N, SGI 是N-N 的),anyway,感谢你的提醒
chunhai
2014-11-06 20:20
@chunhai:我也是仔细的查了一下。
SPI PPI属于 peripheral interrupts

也是是SPI PPI,N-1的意思我是这样理解的。只有一个CPU 去响应中断,其他的CPU 是不响应中断的,即使是其他的CPU同时响应了,但是过不到锁,也是只有一个CPU 处理中断。
不知我的理解对吗?

In a multiprocessor implementation, the GIC handles:
— SGIs using an N-N model, where the acknowledgement of an interrupt by one processor
has no effect on the state of the interrupt on other CPU interfaces
— peripheral interrupts using a 1-N model, where the acknowledgement of an interrupt by
one processor removes the pending status of the interrupt on any other targeted
processors, see Implications of the 1-N model on page 3-8.
In a multiprocessor implementation, the GIC uses a 1-N model to handle peripheral interrupts that target
more than one processor. This means that when the GIC recognizes an interrupt acknowledge from one of
the target processors it clears the pending state of the interrupt on all the other targeted processors. This
model means an interrupt can be handled by the first available processor. However, the interrupt might
generate an interrupt exception on more than one of the targeted processors, for example if two of the
targeted processors recognize the interrupt exception request from the GIC at similar times.
When multiple target processors attempt to acknowledge the interrupt, the following can occur:
• A processor reads the ICCIAR and obtains the interrupt ID of the interrupt to be serviced, see
Interrupt Acknowledge Register (ICCIAR) on page 4-56. More than one target processor might have
obtained this interrupt ID, if the processors read their ICCIARs at very similar times. The system
Interrupt Handling and Prioritization
ARM IHI 0048A Copyright © 2008 ARM Limited. All rights reserved. 3-9
Unrestricted Access Non-Confidential
might require software on the target processors to ensure that only one processor runs its interrupt
service routine. A typical mechanism to achieve this is implementing a lock on the interrupt service
routine (ISR), in shared memory. This might operate as follows:
— each target processor that obtains the interrupt ID from its read of the ICCIAR runs a
semaphore routine, attempting to obtain a lock on the ISR corresponding to the specified ID
value
— if a processor fails to obtain the lock it does no further processing of the interrupt, but writes
the interrupt ID to its ICCEOIR, see End of Interrupt Register (ICCEOIR) on page 4-59
— the processor that obtains the lock handles the interrupt and then writes the interrupt ID to its
ICCEOIR.
• A processor reads the ICCIAR and obtains the interrupt ID 1023, indicating a spurious interrupt. The
processor can return from its interrupt service routine without writing to its ICCEOIR.
The spurious interrupt ID indicates that the original interrupt is no longer pending, typically because
another target processor is handling it.
linuxer
2014-11-07 11:01
@chunhai:我们在PPI的mode上有些不同的意见,我又仔细的阅读了GIC的规范文档,这份文档是这样描述的:
1-N model的定义是 Only one processor handles this interrupt. The system must implement a mechanism to determine which processor handles an interrupt that is programmed to target more than one processor.
N-N model的定义是:All processors receive the interrupt independently. When a processor acknowledges the interrupt,the interrupt pending state is cleared only for that processor. The interrupt remains pending for the other processors.
我觉得PPI是符合N-N model定义的,各个processor是独立的收到中断的,PPI的register是banked,因此,ack其一不影响其他。因此我认为PPI是N-N mode。


在ARM官方发布的IHI0048B_b_gic_architecture_specification.pdf的3.1.1章节,有下面的内容:
In a multiprocessor implementation the GIC handles:
• software generated interrupts (SGIs) using the GIC N-N model
• peripheral (hardware) interrupts using the GIC 1-N model.

PPI属于peripheral interrupt,因此看起来你是对的,但是在该文档的3.2章节,有一处的描述如下:
In a multiprocessor implementation, the GIC handles:
— PPIs and SGIs using the GIC N-N model, where the acknowledgement of an interrupt by one processor has no effect on the state of the interrupt on other CPU interfaces
— SPIs using the GIC 1-N model, where the acknowledgement of an interrupt by one processor removes the pending status of the interrupt on any other targeted processors

从这里看,我的结论是对的。

不过,我认为我们不必纠结于属于哪一个mode,关键是理解PPI的硬件行为即可。
electrlife
2016-03-09 18:44
@linuxer:这里对于N-N的这种中断方式的应用场景感到迷惑,我的理解是如果这种中断发生了,那么中断程序无法识别该handler是否已经处理,也即这次中断可能会引发重复的handle,难道真的这种一个中断需要执行多次的需求?
郭健
2016-03-09 22:52
@electrlife:一个简单的例子就是应用在percpu的中断,比如说一个4核系统,每个cpu core都有自己的local timer产生中断,送达自己的CPU。

2014-09-30 15:52
拜读!
sea
2014-08-31 13:32
非常感谢。意思就是外设共享是需要外设间电路设计的支持的,不过你的图我还真没怎么看懂,我可以这样理解吗:
比如两个按键共享一个irq,那这两个按键分别至少需要两根线,其中一根线用来进行&逻辑并连接到中断控制器来触发中断,而另外一条线则直接连接到CPU的gpio,以便中断触发时,遍历对应irq_action来查询两条线的状态以确定是哪个按键触发的中断。

或者就是具有中断共享功能的中断控制器,两个按键直接连接到特定的端口就实现了中断共享,当中断发生时,遍历irq_action通过读取中断控制器的某个寄存器的值来确定时哪个按键触发的中断。
总而言之,中断共享需要形成闭环,必须要有中断触发和中断触发源state查询两个部分。
以上只是我个人的愚见,不知道正确不?ps:发表评论不能贴图啊!
linuxer
2014-09-01 09:05
@sea:我的意思是这样的。中断控制器的每一个interrupt source都是一个实际的物理铜线,而每个device的interrupt request信号也是一个实际的物理铜线,一对一当然简单,但是如果要共享,那么需要一对二。与门是两个输入,一个输出,输出接到中断控制器,两个输入分别接两个设备的interrupt request信号(这是在两个设备都是低电平触发而言,如果不是这样,需要相应进行电路设计)。
对于与门,只要输入端有一个逻辑0(低电平),那么输出就是0(低电平),只有两个device的interrupt request信号都是逻辑1(高电平),输出才是逻辑1(高电平),正好符合了我们这里的设计场景
sea
2014-09-01 10:13
@linuxer:知道了,谢谢!
sea
2014-08-29 16:41
---------------------------------------------------------------------------------------------------
action指向一个struct irqaction的链表。如果一个interrupt request line允许共享,那么该链表中的成员可以是多个,否则,该链表只有一个节点。
---------------------------------------------------------------------------------------------------
虽然说知道interrupt request line允许共享,但是一直理解不了这个概念。对于连接到物理设备的irq怎么实现interrupt request line共享呢,什么情况下会存在一个irq_desc下有多个irq_action?或者说interrupt request line共享只存在于多中断控制器级联的情况下,还是其它的情况?希望博主解答一下,谢谢!
forion
2014-08-29 17:15
@sea:hi sea
我先回复一下,然后等群主来了,在给你更专业的回复。
我想你可能没有搞明白中断级联跟中断共享这两个概念。
级联的中断控制器,连接到上一级的中断控制器是一根线,但是从cpu的角度,子中断控制器的各个中断线,也就是HW IRQ Number 都有唯一明确的irq number的,这个irq number就是你request_irq的时候用到的irq number,理解中断级联,你要看群主的第二篇文章 irq domain
2:中断共享,说的是共享中断号,所以在request_irq的时候
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
用同一个irq number,通过flag IRQF_SHARED来表明是共享中断,然后通过最后的参数void *dev来互相区分,然后是在irq handler里面通过void *dev,来区分实际是那个外设发来的中断。如果我没有记错,好像之前的按键驱动习惯用共享中断。
linuxer
2014-08-29 19:15
@sea:forion同学讲的很好,我简单补充两句,呵呵~~~有点领导范儿哦。
如果中断控制器可以支持足够多的interrupt source,那么在两个外设间共享一个interrupt request line是不推荐的,毕竟有一些额外的开销(发生中断的时候要逐个询问是不是你的中断,软件上就是遍历action list啦),因此外设的irq handler中最好是一开始就启动判断,看看是否是自己的中断,如果不是,返回IRQ_NONE,表示这个中断不归我管。
早期PC时代,使用8259中断控制器,级联的8259最多支持15个外部中断,但是PC外设那么多,因此需要irq share。现在,ARM平台上的系统设计很少会采用外设共享IRQ方式,毕竟一般ARM SOC提供的有中断功能的GPIO非常的多,足够用的。
当然,如果确实需要两个外设共享IRQ,那也只能如此设计了。对于HW,中断控制器的一个interrupt source的引脚要接到两个外设的interrupt request line上,怎么接?直接连接可以吗?当然不行,对于低电平触发的情况,我们可以考虑用与门连接中断控制器和外设:
                 ---------
                 |        |----------Device 1 interrupt request line
interrupt----|    &   |
source           |        |----------Device 2 interrupt request line
                  --------

只要任意一个设备的interrupt request line有低电平信号,就可以在中断控制器的interrupt source信号线上产生低电平,从而触发中断。
linuxer
2014-08-29 19:18
@linuxer:完了,上面用字符画的电路图很失败,不过,大家应该会明白的
sea
2014-08-31 13:33
@linuxer:不好意思,没点回复。
linuxer
2014-09-01 08:59
@sea:不要在意,都是形式而已。我的回复在上面。
BTW,我在有的浏览器上看上面的字符图片是对的,不过现在用的是火狐,看到的就是错位的,不知道怎么回事

发表评论:

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog