蜗窝科技

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

linuxer · · 中断子系统 · 阅读 120413 · 2014年8月存档

一、前言

本文主要围绕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_startupstart up 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为enable函数
irq_shutdownshutdown 指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为disable函数
irq_enableenable指定的irq domain上的HW interrupt ID。如果不设定的话,default会被设定为unmask函数
irq_disabledisable指定的irq domain上的HW interrupt ID。
irq_ack和具体的硬件相关,有些中断控制器必须在Ack之后(清除pending的状态)才能接受到新的中断。
irq_maskmask指定的irq domain上的HW interrupt ID
irq_mask_ackmask并ack指定的irq domain上的HW interrupt ID。
irq_unmaskmask指定的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_unlockunlock慢速总线
irq_suspend irq_resume irq_pm_shutdown电源管理相关的callback函数
irq_calc_maskTODO
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(具体中断线程化的概念在其他文档中描述)

原创文章,转发请注明出处。蜗窝科技。/irq_subsystem/interrupt_descriptor.html

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

继续讨论

新讨论请用 GitHub 账号在下方发言——评论会保存在本仓库的 Discussions 里,与站点同寿; 再下方的「历史评论」是原站的只读存档。

评论(79 条,只读存档)

以下为原站的历史评论(只读存档);继续讨论请用上方的评论区。

  • sea

    --------------------------------------------------------------------------------------------------- action指向一个struct irqaction的链表。如果一个interrupt request line允许共享,那么该链表中的成员可以是多个,否则,该链表只有一个节点。 --------------------------------------------------------------------------------------------------- 虽然说知道interrupt request line允许共享,但是一直理解不了这个概念。对于连接到物理设备的irq怎么实现interrupt request line共享呢,什么情况下会存在一个irq_desc下有多个irq_action?或者说interrupt request line共享只存在于多中断控制器级联的情况下,还是其它的情况?希望博主解答一下,谢谢!
    • forion

      @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

      @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

        @linuxer:完了,上面用字符画的电路图很失败,不过,大家应该会明白的
        • sea

          @linuxer:不好意思,没点回复。
          • linuxer

            @sea:不要在意,都是形式而已。我的回复在上面。 BTW,我在有的浏览器上看上面的字符图片是对的,不过现在用的是火狐,看到的就是错位的,不知道怎么回事
  • sea

    非常感谢。意思就是外设共享是需要外设间电路设计的支持的,不过你的图我还真没怎么看懂,我可以这样理解吗: 比如两个按键共享一个irq,那这两个按键分别至少需要两根线,其中一根线用来进行&逻辑并连接到中断控制器来触发中断,而另外一条线则直接连接到CPU的gpio,以便中断触发时,遍历对应irq_action来查询两条线的状态以确定是哪个按键触发的中断。 或者就是具有中断共享功能的中断控制器,两个按键直接连接到特定的端口就实现了中断共享,当中断发生时,遍历irq_action通过读取中断控制器的某个寄存器的值来确定时哪个按键触发的中断。 总而言之,中断共享需要形成闭环,必须要有中断触发和中断触发源state查询两个部分。 以上只是我个人的愚见,不知道正确不?ps:发表评论不能贴图啊!
    • linuxer

      @sea:我的意思是这样的。中断控制器的每一个interrupt source都是一个实际的物理铜线,而每个device的interrupt request信号也是一个实际的物理铜线,一对一当然简单,但是如果要共享,那么需要一对二。与门是两个输入,一个输出,输出接到中断控制器,两个输入分别接两个设备的interrupt request信号(这是在两个设备都是低电平触发而言,如果不是这样,需要相应进行电路设计)。 对于与门,只要输入端有一个逻辑0(低电平),那么输出就是0(低电平),只有两个device的interrupt request信号都是逻辑1(高电平),输出才是逻辑1(高电平),正好符合了我们这里的设计场景
      • sea

        @linuxer:知道了,谢谢!
  • 海

    拜读!
  • chunhai

    感谢博主的分享, 有个小错误。 对于GIC而言,SPI是N-N mode,而SGI和PPI是1-N mode。 这句话 有误。 SPI, PPI 是 1-N, SGI 是N-N 的。
    • linuxer

      @chunhai:多谢您的提醒,是笔误,我的本意是“SPI是1-N mode,而SGI和PPI是N-N mode”,我已经修改了相关的描述。 PS:你建议的修改也是有问题的(SPI, PPI 是 1-N, SGI 是N-N 的),anyway,感谢你的提醒
    • chunhai

      @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

        @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

          @linuxer:这里对于N-N的这种中断方式的应用场景感到迷惑,我的理解是如果这种中断发生了,那么中断程序无法识别该handler是否已经处理,也即这次中断可能会引发重复的handle,难道真的这种一个中断需要执行多次的需求?
          • 郭健

            @electrlife:一个简单的例子就是应用在percpu的中断,比如说一个4核系统,每个cpu core都有自己的local timer产生中断,送达自己的CPU。
  • zengzhl

    仰望博主。很多芯片资料里面介绍ARM中断号,有的代码+32,有的又不加,请问有什么区别,以及什么时候+32?
    • linuxer

      @zengzhl:我不是很清楚你的问题,你能否给出更多的细节? 比如哪一个芯片资料?具体的代码片段...
  • tigger

    最近处理一个共享中断的case,共享中断确实如楼主所说,来了中断要调用一遍所有的挂在这个中断号下面的,irq handler。也就是下面的描述 (发生中断的时候要逐个询问是不是你的中断,软件上就是遍历action list啦),因此外设的irq handler中最好是一开始就启动判断,看看是否是自己的中断,如果不是,返回IRQ_NONE,表示这个中断不归我管。 容易出问题的地方就是,最好在irq handler中最好是一开始就启动判断,如果这个判断设计有误,比如不是自己的中断,还会跑自己的后面的handler,那么就有可能引入诡异的问题。
  • heziq

    有过这样的需求request_irq的时候并不想中断函数被立即调用。所以我一般情况是,在request-irq之前,先disable-irq。请问这样可以吗?我看过源代码,没有发现可能引起异常的部分。
    • wowo

      @heziq:request IRQ和hardware enable,应该是两个逻辑,只有hardware enable了,才有可能产生中断。因此一般的做法是:在probe中request IRQ,在需要使用设备时,enable它,中断产生。
      • heziq

        @wowo:@wowo:难道在probe中,request IRQ后,必须后面在加个enable IRQ, 我注册的中断函数才会被执行吗?我看见所有的源代码都是probe完以后,就等irq发生。我的意思是:我reqest IRq 后,不想立即调用我的中断处理函数。 等我需要的时候在打开中断,响应中断。有什么方法不?
        • heziq

          @heziq:“我看见所有的源代码都是probe完以后,就等irq发生。”有误,源代码中request IRQ 以后
        • wowo

          @heziq:我想表达的是: 中断handler是否被调用,有两个因素决定:一是irq是否enable,这是由irq的接口决定的;二是产生中断的设备是否可以产生中断,例如uart,是否enable了UART的收发功能。 大多数时候,设备驱动关注的是后一种,通过控制设备的行为,控制是否产生中断。
    • linuxer

      @heziq:是否在request_irq的时候enable该irq是一个option,由其中断描述符的NOAUTOEN这个flag决定的,具体可以参考代码: if (irq_settings_can_autoenable(desc)) irq_startup(desc, true); 在request-irq之前调用disable irq也没有问题,前提是正确初始化了硬件和中断描述符。不过针对你的需求,我倒是觉得你可以调用set_irq_flags来设定IRQF_NOAUTOEN这个flag
      • heziq

        @linuxer:高实在是高! static int sirfsoc_uart_startup(struct uart_port *port) { struct sirfsoc_uart_port *sirfport = to_sirfport(port); unsigned int index = port->line; int ret; set_irq_flags(port->irq, IRQF_VALID | IRQF_NOAUTOEN); ret = request_irq(port->irq, sirfsoc_uart_isr, 0, SIRFUART_PORT_NAME, sirfport); if (ret != 0) { dev_err(port->dev, "UART%d request IRQ line (%d) failed.\n", index, port->irq); goto irq_err; } startup_uart_controller(port); enable_irq(port->irq); irq_err: return ret; }
        • buyit

          @heziq:你找的这段代码来自于CSR,(reviewer是出书的某位大牛)。 事实上如果你搜索kernel代码的话,会发现很少有人调用IRQF_NOAUTOEN这个flag,所有调用它的都是1级或者2级interrupt controller,唯一的例外就是CSR的这份uart代码,所以我觉得它没有参考价值。 从这个函数来看,申请中断和开启中断中间只隔了一个uart controller的初始化函数。那么如果把startup_uart_controller(port)函数放到最前面,等这个函数把uart硬件初始化好了之后,再调用request_irq函数来注册中断,这样子就不需要IRQF_NOAUTOEN了。 request_irq的注释里面已经说明了,这个函数返回之后,handler就有可能马上被调用。如果硬件还没有准备好,说明你注册中断时间点很有可能不合理。
          • wowo

            @buyit:非常赞同,如果有奇奇怪怪的代码存在,很有可能是流程上有问题。
          • linuxer

            @buyit:IRQF_NOAUTOEN这个flag绝非为“普通”设备而打造,也不是给那些“文艺”类型的设备使用,它是为那些“2B”设备而产生的。这些设备会产生spurious interrupt,撰写driver的时候需要: 1、申请中断的时候不要打开中断 2、当期待中真正中断要来的时候调用enable_irq 3、处理完成后disable之 普通的request_irq不能满足这类奇葩设备的要求
            • buyit

              @linuxer:理解。 heziq 找的例子貌似不属于这种2b设备,看起来更像是代码逻辑的问题。
      • heziq

        @linuxer:完全如你所料!
  • callme_friend

    关于irq_desc->handle_irq,通过文章看是有:边沿触发、电平触发等类型。 但是它是在哪里被赋值呢? (1)通过查看内核和GIC初始化,只看到分配了irq_desc,并将irq_desc->handle_irq初始化为handle_bad_irq,而不是文章里介绍的边沿触发、电平触发处理函数; (2)在驱动程序申请中断时,调用request_threaded_irq();而这个函数也未看到设置irq_desc->handle_irq。 我搜源码,一般只看到gpio控制器有调用irq_set_handler(handle_edge_irq);而在uart设备上未看到这样的设置?当这个uart设备中断时,程序走到irq_desc->handle_irq()时,到底调用的是哪个函数? W(b) vector_irq vector_irq: __irq_usr irq_handler ldr r1, =handle_arch_irq //已初始化为gic_handle_irq ldr pc, [r1] || \/ gic_handle_irq() handle_IRQ(irqnr, regs) desc->handle_irq(irq, desc);
    • 郭健

      @callme_friend:基本上,边沿触发、电平触发这些属于硬件中断的属性,对于一个HW Interrupt而言,它附属于某一个interrupt controller(irq domain),有类型(SPI/PPI等),有ID,有属性(上升沿或者电平触发),而这些信息最好的方法就是通过device tree的方法传递给kernel。 你的问题是关于irq_desc->handle_irq的设定,而所谓irq_desc是和IRQ number息息相关的,IRQ number就是virtual interrupt ID。哪里设定handle_irq呢?固定设定当然不好了,最好的方法就是驱动初始化的时候根据device tree传递的信息来相应的进行初始化。 一般驱动初始化的过程如下(仅仅说中断相关的内容): 1、分配一个IRQ number 2、建立IRQ number到HW Interrupt ID之间的mapping(在某个irq domain上) 3、注册IRQ number对应的handler irq_desc->handle_irq在上面所说的第二步设定,怎么进行IRQ number到HW Interrupt ID之间的mapping呢?是通过struct irq_domain数据结构中的struct irq_domain_ops操作函数集中的map成员函数来完成的。从名字就可以退出其功能,就是建立IRQ和HW interrupt ID映射关系的时候调用的。在这个函数中,知道IRQ number,可以找到中断描述符,知道HW interrupt的ID以及属性,因此也知道如何设定irq_desc->handle_irq。 对于GIC而言,这个函数是gic_irq_domain_map,你可以看看。 当然,我上面说的方法我认为是最好的方式,不过,内核中也有一些遗留的代码是固定设定的(在中断控制器驱动中一次性设定),最终应该会被慢慢淘汰掉。
      • callme_friend

        @郭健:针对你上面说的“一般驱动初始化的过程”: 1、分配一个IRQ number -----------这个应该是early_irq_init()和gic_init_bases()时建立irq_desc[]或radix树 2、建立IRQ number到HW Interrupt ID之间的mapping(在某个irq domain上) -------------这一步貌似是gic初始化的时候建立的吧?而且这里没有看到设置irq_desc->handle_irq呀?irq_desc->handle_irq在何处赋值是我要问的核心问题。 3、注册IRQ number对应的handler -------这一步应该是request_irq完成吧?
        • 郭健

          @callme_friend:1、不是的,early_irq_init()是中断描述符初始化,gic_init_bases是具体的中断控制器驱动初始化,我们这里说的是一个特定的设备驱动的IRQ的初始化,在过去,都是静态定义的,现在推荐使用device tree的方法动态分配。你可以参考drivers/of/irq.c文件 2、gic_irq_domain_map--->irq_set_chip_and_handler中会设定irq_desc->handle_irq。 3、是的
          • callme_friend

            @郭健:1、嗯,大神的device tree也看了几遍了,还要继续看几遍。 2、还是关于irq_desc->handle_irq何时赋值的问题,我刚开始问的也是这个问题。 《gic_irq_domain_map--->irq_set_chip_and_handler中会设定irq_desc->handle_irq。》---------------gic_irq_domain_map这个函数是在哪里被调用呢? 您的上篇文章《Linux kernel的中断子系统之(二):IRQ Domain介绍》里提及是在irq_of_parse_and_map()里建立;但是我用si搜索3.17内核,调用这个函数的设备驱动不算多。irq_of_parse_and_map()的合适调用时机是哪里?放在驱动的probe函数里合适吗?
            • 郭健

              @callme_friend:其实基本上驱动也不会直接调用这些借口函数,这些接口函数多半进行了封装。例如:对于platform bus上的设备,其驱动的probe函数可以使用下面的代码: ...... adc->irq = platform_get_irq(pdev, 1); if (adc->irq <= 0) { dev_err(dev, "failed to get adc irq\n"); return -ENOENT; } ret = devm_request_irq(dev, adc->irq, s3c_adc_irq, 0, dev_name(dev), adc); if (ret < 0) { dev_err(dev, "failed to attach adc irq\n"); return ret; } ...... platform_get_irq这个函数可以看一看,然后你就明白了。
              • callme_friend

                @郭健:在platform_get_irq()里确实看到最终调用了map,初始化了irq_desc->handle_irq。 我又有个疑问:在of_platform_populate()里,把dts解析出的node转化为platform设备时,也会调用domain_ops的map,也会初始化irq_desc->handle_irq;这样是不是和驱动的probe函数里有重复执行map的可能? 我跟的代码是3.17版本: zynq_init_machine()--> of_platform_populate()--> //创建平台设备 of_platform_bus_create()--> of_platform_device_create_pdata()--> of_device_alloc()--> of_irq_to_resource_table()--> of_irq_to_resource()--> irq_of_parse_and_map()--> irq_create_of_mapping()--> irq_create_mapping()--> irq_domain_associate()--> //即gic_irq_domain_map:里面初始化irq_desc->handle_irq domain->ops->map() 我的dts如下: gpio0: gpio@e000a000 { compatible = "xlnx,zynq-gpio-1.0"; #gpio-cells = <2>; clocks = <&clkc 42>; gpio-controller; interrupt-parent = <&intc>; interrupts = <0 20 4>; reg = <0xe000a000 0x1000>; }; 对应的驱动代码: static struct platform_driver zynq_gpio_driver = { .probe = zynq_gpio_probe, zynq_gpio_probe() platform_get_irq()--> of_irq_get()--> irq_create_of_mapping()--> irq_create_mapping(domain,hwirq)--> irq_domain_associate()--> //即gic_irq_domain_map:里面初始化irq_desc->handle_irq domain->ops->map() //gic_irq_domain_map ------------------------------------------------------------- 上述gpio节点,会不会在of_platform_populate()和zynq_gpio_probe()时,被先后调用?那hw irq到virq的映射岂不是发生两次?
              • 郭健

                @郭健:你可以再仔细看看irq_create_mapping的代码,虽然其名字是创建映射,但是该函数首先是通过HW interrupt ID来找virtual interrupt ID,如果存在,则不需要创建,直接返回virtual interrupt ID,因此,映射不会创建两次的。
  • callme_friend

    貌似不能再回复了,重启一层。 irq_create_mapping()确实不会重复建立映射,看的不仔细~~ (1)我按照大神思路去看了3.17内核的gpio(基于zynq)部分初始代码,确实在probe()函数里新建了gpio irq的domain。但是这个过程很不优雅:因为在dts的gpio部分,没有看到如下所示、包含interrupt-controller的内容: gpf: gpf { gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; }; 上面这个node是明确表明这是一个interrupt-controller,所以在内核init_IRQ时,应该就会建立domain吧? 而我的这个zynq的dts文件关于gpio的部分如下: gpio0: gpio@e000a000 { compatible = "xlnx,zynq-gpio-1.0"; #gpio-cells = <2>; clocks = <&clkc 42>; gpio-controller; interrupt-parent = <&intc>; interrupts = <0 20 4>; reg = <0xe000a000 0x1000>; }; ------------------------------------------------------ 这里并没有看到interrupt-controller属性,但是probe函数里居然新建了irq domain,总感觉别扭? (2)通过上面的dts差异,结合大神的《GPIO子系统系列》文章,我在该dts中没看到关于pin bank、pin ctrl的描述,是不是可以认定它没有完整的支持GPIO子系统?
    • 郭健

      @callme_friend:支持GPIO type的中断控制器有两种方法: 一、irq chip driver的方法,需要: 1、device tree中对应的node有interrupt-controller的属性 2、在drivers/irqchip目录下增加对应的irq chip driver的代码(driver)。需要定义IRQCHIP_DECLARE,以便可以匹配device tree中的节点(device) 二、使用platform driver的方法,需要: 1、定义platform driver和of_match_table数据结构 2、device tree中的节点会变成platform中的device 3、利用驱动模型的机制,让device和driver匹配并在probe函数中进行中断控制器的初始化。 哪种好呢?我喜欢第一种,但是内核中大部分的平台都是使用了第二种方法,具体为什么可能需要再仔细考虑一下。
      • callme_friend

        @郭健:站在dts角度,第二种方法您觉得别扭吗? 明明没有指定interrupt-controller属性,却莫名其妙的建立irq domain,有点奇怪的感觉。
      • callme_friend

        @郭健:另外,大神的《GPIO子系统》还继续整不?
        • 郭健

          @callme_friend:本来不想搞了,不过最近思路有些变化。原来总想搞一些高大上,什么调度器啦,RCU啦,多么cool,最近打算搞一些人民群众喜闻乐见的,和具体工作相关的各种硬件驱动的frame work。因此,GPIO应该会继续整的。 BTW,这位帅哥,如果你有兴趣,可以一起整啊
          • callme_friend

            @郭健:兴趣是有,就是能力不足,我离大神水平太远太远! 那些高大上的文章,我们其实也很喜闻乐见的,我都浏览过,只是还不完全理解,暂时不能和大神交流。 本人还很期待大神的关于VFS、网络子系统的神作。 系统对用户的接口是fs,通过vfs,再深入到设备驱动,应该很high;另外网络子系统也是我有兴趣的方向~! 大神的文章像大百科全书一样,而且都是基于新内核,再次膜拜!!!
    • wangyunqian

      @callme_friend:三年过去了,说下我的理解:定不定义interrupt-controller属性,跟在哪里创建 irq domain没有什么关系吧。像gic的irq domain也是在init的时候创建的呀。dts里的interrupt-controller只是表示本node是一个中断控制器的node。如果gpio的node不添加该关键字,使用gpio中断的设备没有办法利用device tree提供的标准机制,比如说就不能在设备的node里面定义interrupts关键字,就必须在自己的驱动里面调用gpio_to_irq()来获取中断号。以上是我的个人理解,还请帮忙指正!
  • electrlife

    从驱动子系统一路看过来,对于中断子系统有点疑问,中断控制器一般也说也是一类设备,应该算是平台设备,所以我觉得中断子系统应该也是类似使用设备驱动模型的方式来实现,但这段时间学习下来,似乎没有看到设备驱动模型的身影,不知是否我理解有偏差。如其它的子系统,一般也都是基本设备的驱动模型来的,比如I2C,USB,SPI等,难道中断控制器不可以实现成设备驱动模型的结构吗?或者更进一步,设备驱动模块适应哪些方面的抽象,或是对于哪些场景有限制??
    • 郭健

      @electrlife:我很喜欢这个问题,多谢! 我的理解是这样的,诚然,interrupt controller从实际的物理拓扑结构上看,的确是和其他的HW block(例如USB device controller什么的)没有什么不同,如果驱动USB device controller的软件使用了platform bus、platform device以及platform driver的模型,那么interrupt controller的驱动也应该使用类似的方法。但是,interrupt controller是很特殊的一个HW block,它应该在所有的设备驱动初始化之前ready,毕竟每一种driver都需要中断子系统提供的服务。因此,中断子系统的初始化(init_IRQ)必须先于驱动模型初始化(driver_init)执行,这样,首先初始化的中断驱动如何利用驱动模型呢?
      • electrlife

        @郭健:多谢回复,这样理解有点道理,比如clocksource clock_dev_event等也是如此,但是对regulator好像不适用,我觉得regulator应该是系统比较早启动的一类设备。 除非regulator是较后的启动!
        • 吴晓鹏

          @electrlife:@郭健:郭健兄说的是一方面的原因,也存在另一方面的原因:历史遗留问题。 Linux早期的版本,跟arch相关的硬件操作都会放在arch里面,随着soc越来越多,arch/arm里面已经非常冗余和混乱了,通过关注新内核动向,arch/arm里面的硬件操作会逐渐由系统退化成驱动,这样显得更加清晰标准。例如从linux-3.10之后arch/arm64已经没有arch/arm里面拥有很多mach相关的操作,同样irq已经移到了driver下,后面的内核版本cpuidle也是逐渐退化成驱动了,在后面硬件驱动逐渐跟arch撇清关系,例如irq-gic-v3.c,用硬件的版本号来区分。不知道我的理解是否正确,欢迎大家吐槽。
          • linuxer

            @吴晓鹏:你说的非常的对,irq chip的驱动是一种普通的设备驱动,应该放到drivers目录下,不过不是electrlife要问的问题,他的疑问是irq chip为何不使用platform这样的设备模型的方式来实现。
  • 奔跑的蜗牛

    hi 各位大神, 看了你们的文件,收益匪浅,你写的文章,我其实已经看过好机会了,之前只是大概的看看,没有这么认真的研读。这次认真的研读了下,有去思考,所以,有些疑问。 1 highlevel irq-events handle(desc->handle_irq) interrupt domain抽象的handler,应该是执行这个interrupt controller相关的动作,那具体是哪些呢,能麻烦举个例子吗。 2 specifier handler这个是完成哪些任务呢? 3 用户注册的handler,这个就是用户想要完成的任务了,没什么好说的。 还有,这3个之间有的执行流程有什么关系吗?
    • 奔跑的蜗牛

      @奔跑的蜗牛:不好意思,通过后面几篇的阅读,已经理解了。谢谢。
  • joeychen

    请教: 设备驱动中断在哪里设置绑定的cpu?
    • hello-world

      @joeychen:通过irq_set_affinity接口可以设定啊
  • 理查德1030

    请教: err = request_irq(irqdesc->hwirq, irqdesc->handler, \ irqdesc->flags, irqdesc->name, irqdesc->dev_id); err return -22; 疑问:为什么75没有占用,我还是注册不上,我需要继续修改哪些东西呢? 说明: (1)hwirq是芯片手册看描述的gpio bank中断号75,flags=0,dev_id=(void *)0 (2)cat /proc/interrupts 看了一下,未发现中断号被占用。 如下是系统已有的中断被hisi sdk有些模块占用,没有源码,所以不知道SDK中做了什么。。 { CPU0 CPU1 29: 3080 464 GIC 29 arch_timer ...... 45: 52 0 GIC 45 himci ...... 70: 0 0 GIC 70 JPEGU_0 71: 0 0 GIC 71 IVE 80: 0 0 GIC 80 FISHEYE } (3)我想自己实现一个gpio中断,irq_chip已初始化,irq_domain也做了初始化(可能初始化不对),irq_domian用的是radix tree,但hisi3519的中断子系统是几级GIC也不清楚。 (4)内核版本linux3.18.y, 芯片ARM-hisi3519v101.
    • 理查德1030

      @理查德1030:/proc/irq/目录下已经有芯片手册所有终端号命名的文件,是不是表示irq_domain已经做了初始化?只要实现struct irq_chip和request_irq,。但为什么75就是返回-22,换个未被占用的号48返回成功,/proc/interrupts也会添加(虽然不起作用)。
  • Peter

    刚接触Interrupt子系统,就被该文章吸引了,超赞. 当前这边文章中提到了GIC中断描述符的创建及初始化( irq_set_irq_type函数中),那GPIO类型的中断描述符又是怎么创建和初始化的呢? 还望大神指点. 感谢.
    • hello-world

      @Peter:你可以看看_gpiochip_irqchip_add这个函数的实现就明白了
      • Peter

        @hello-world:@hello-world, 谢谢,在irq_create_mapping函数中看到了.
  • loren

    博主,请教一个问题: 在不重新编译kernel,也不修改IDT表(安全性、稳定性考虑)的情况下,我想在driver中动态分配一个vector number用来做IPI的中断vector,有什么办法可以做到吗? 我有参考pci_enable_msi()的实现,但是由于里面用到的assingn_irq_vector()函数没有EXPORT出来,所以我没办法使用。我看assign_irq_vector()函数主要是将以irq为参数来分配对应的vector,并将两者的关系记录到vector_irq[]数组中。 kernel version为3.16.0. 非常感谢!
    • linuxer

      @loren:在驱动中使用IPI还是比较少见的,呵呵~~我对ARM平台比较熟悉,在ARM平台上,IPI都是专用的,不能分配。
  • 善昆

    在已经知道hw irq的时候,我想绕过dts方式,有办法获取虚拟irq号吗?
    • linuxer

      @善昆:当然可以,自己分配一个就OK了,当然,你需要自己创建mapping。当然,不建议用这么底层的接口。
      • 善昆

        @linuxer:因为特殊需求不能重编译内核,所以没有办法通过修改dts入手,也是因为追查到了irq_create_mapping函数,才阅读了本篇文章。但是这个函数domain参数一直不知道该如何获取,本想自己创建一个irq_desc,但是发现alloc_desc函数还是需要node... 不知道有没有方法获得这个domain,或许可以通过已经存在与dts中断里设备找到CIG的domain吧
  • 于长河

    您好linuxer,我有个疑问:在local_irq_disable中最终是对cpsr寄存器的I位置位,那么这个local是怎样体现出来的呢?是不是在一个多核cpu中其中每一核都有一个独立的cpsr寄存器
    • linuxer

      @于长河:当然了,cpsr寄存器是per cpu的,每一个cpu core都有自己的cpsr寄存器。
      • 于长河

        @linuxer:明白了,谢谢
  • linuxer_fy 主页

    我有个疑问。不知道是放到(二)IRQ Domain一文中,还是这篇中。估且放这里吧。 ARM的不是很熟悉。就X86上来说,多级联interrupt controller中(例A和B),它们是和CPU通过系统数据总线来传递HW interrupt ID的。又由于每个irq domain可能有重复编号的ID,所以才有了domain来抽象和识别。但是 (1)A和B是独立各自向CPU传递HW ID的话,如果A和B同时发生中断,需要传递HW ID,如何解决冲突。硬件处理?此情况下是否会有中断丢失? (2)在CPU只收到HW interrupt ID的情况下,系统还要配合HW情报去翻译成合适的irq number, 也就是每次都要“遍历”整个irq domain list,且还要能处理不同类型的interrupt controller芯片 (3)我记得X86上两片8259a级联时(每片8259支持0~7个IRQ Line),可以直接产生0~15个中断号,即2片8259可以直接组合在数据总线上将0~15中断号(相当于HW interrupt ID)传递给CPU,此时CPU并不需要再去翻译成irq number了,因为HW interrupt ID是唯一的,不重复。 如果2片 8259设计成domain的方式,那就要系统额外去识别: (二)IRQ Domain一文中最后: “2、具体如何在中断处理过程中,将HW interrupt ID转成IRQ number (2)根据HW 寄存器信息和irq domain信息获取HW interrupt ID ” 这里“根据HW寄存器信息”就是看8259A芯片的中断请求寄存器中相应的bit位是否有中断存在?
  • magichouse

    @linuxer 本节主要描述的是第一种,也就是控制CPU的中断。当进入high level handler的时候,CPU的中断是关闭的(硬件在进入IRQ processor mode的时候设定的)。 ------------------------------------------------ hi,请教一下,进入high level handler时,cpu的中断是关闭的,这一点是硬件设定的?还是软件设定的? 再次打开的时机是什么?
    • wowo

      @magichouse:这个比较严谨的回答是,要视具体的arch而定,不过对arm来说(这里以arm64为例),是arm的core(硬件)做的,具体可参考下面的资料: DDI0487A_d_armv8_arm.pdf ====================================== ... D1.10 Exception entry On taking an exception to AArch64 state: ... • All of PSTATE.{D, A, I, F} are set to 1. See Process state, PSTATE on page D1-1434. ... type ProcState is ( ... bits (1) I, // IRQ mask bit ... )
  • ailon

    在多路服务器中,每个CPU都有一个uart控制器,在CPU角度来看每个uart的irq都是一样的,那我应该如何区分到底是哪个一个uart控制器来的中断呢,现在的内核版本是不是没有对这方面的考虑
    • Lambert 主页

      @ailon:说下个人理解: 每个UART的中断输出都是连接到同一个中断控制器吗? 如果是的话,应该是不同的中断引脚,其硬件中断编号必定不同。 如果不是,中断控制器是多个,那么中断Domain不同。 更甚,多个UART share一个中断Pin,那么就要看中断控制器本身有没有区分方法,知道是哪一路过来的中断
  • poolloop

    您好,在中断相关的文章中,偶尔会看到“mask”这个动作。 我的问题是,一般所说的“mask这个动作”是全局的(影响所有的cpu)呢,还是仅仅影响当前local cpu的呢? 在不同的架构下(arm/x86)是否都有一样的意义,谢谢!
  • Alexander

    你好,请问x86平台的irq_dmain怎么和io apci机制连接起来的?一直搞不懂
  • Toni

    有没有关于PCIE MSI中断的一些介绍?
  • 刘

    第一个问题就是:为何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中)。 irq_get_desc_buslock lock住了bus时也有调用raw_spin_lock_irqsave 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; }
  • 立志学linux

    有没有大佬知道,high level handler 和 通过request_threaded_irq注册的那个的handler(也就是irq_desc->irqaction->handler)是什么关系呀
    • 5song

      @立志学linux:一般handle_irq()会进行如下操作,可参考(kernel/irq/chip.c: void handle_edge_irq(struct irq_desc *desc)): 调用中断描述符中的底层chip driver进行mask以及中断ACK回调,进行IRQ flow control 调用handle_irq_event向上发送中断事件,遍历描述符里的action list,并执行action->handler回调(request_threaded_irq可以绑定该handler), 执行完action->handler后,要么中断已经处理完成,要么就执行__irq_wake_thread唤醒我们绑定的 irq thread处理函数(也就是我们常说的上半部处理函数)