linux kernel的中断子系统之(三):IRQ number和中断描述符
作者:linuxer 发布于:2014-8-26 17:03 分类:中断子系统
一、前言
本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程。第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等。第三章描述中断描述符数据结构的各个成员。第四章描述了初始化中断描述符相关的接口API。第五章描述中断描述符相关的接口API。
二、基本概念
1、通用中断的代码处理示意图
一个关于通用中断处理的示意图如下:
在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

评论:
2017-01-12 00:23
2016-12-02 15:30
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.
2016-05-31 14:49
看了你们的文件,收益匪浅,你写的文章,我其实已经看过好机会了,之前只是大概的看看,没有这么认真的研读。这次认真的研读了下,有去思考,所以,有些疑问。
1 highlevel irq-events handle(desc->handle_irq) interrupt domain抽象的handler,应该是执行这个interrupt controller相关的动作,那具体是哪些呢,能麻烦举个例子吗。
2 specifier handler这个是完成哪些任务呢?
3 用户注册的handler,这个就是用户想要完成的任务了,没什么好说的。
还有,这3个之间有的执行流程有什么关系吗?
2016-03-18 16:12
2016-03-18 17:08
我的理解是这样的,诚然,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)执行,这样,首先初始化的中断驱动如何利用驱动模型呢?
2016-03-18 18:38
除非regulator是较后的启动!
2016-09-05 15:39
Linux早期的版本,跟arch相关的硬件操作都会放在arch里面,随着soc越来越多,arch/arm里面已经非常冗余和混乱了,通过关注新内核动向,arch/arm里面的硬件操作会逐渐由系统退化成驱动,这样显得更加清晰标准。例如从linux-3.10之后arch/arm64已经没有arch/arm里面拥有很多mach相关的操作,同样irq已经移到了driver下,后面的内核版本cpuidle也是逐渐退化成驱动了,在后面硬件驱动逐渐跟arch撇清关系,例如irq-gic-v3.c,用硬件的版本号来区分。不知道我的理解是否正确,欢迎大家吐槽。
2016-03-03 17:37
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子系统?
2016-03-04 12:40
一、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函数中进行中断控制器的初始化。
哪种好呢?我喜欢第一种,但是内核中大部分的平台都是使用了第二种方法,具体为什么可能需要再仔细考虑一下。
2016-03-04 15:39
明明没有指定interrupt-controller属性,却莫名其妙的建立irq domain,有点奇怪的感觉。
2016-03-04 18:10
BTW,这位帅哥,如果你有兴趣,可以一起整啊
2016-03-05 18:06
那些高大上的文章,我们其实也很喜闻乐见的,我都浏览过,只是还不完全理解,暂时不能和大神交流。
本人还很期待大神的关于VFS、网络子系统的神作。
系统对用户的接口是fs,通过vfs,再深入到设备驱动,应该很high;另外网络子系统也是我有兴趣的方向~!
大神的文章像大百科全书一样,而且都是基于新内核,再次膜拜!!!
2019-03-09 11:58
2016-02-26 11:46
但是它是在哪里被赋值呢?
(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);
2016-02-26 21:51
你的问题是关于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,你可以看看。
当然,我上面说的方法我认为是最好的方式,不过,内核中也有一些遗留的代码是固定设定的(在中断控制器驱动中一次性设定),最终应该会被慢慢淘汰掉。
2016-02-27 06:55
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完成吧?
2016-02-28 23:15
2、gic_irq_domain_map--->irq_set_chip_and_handler中会设定irq_desc->handle_irq。
3、是的
2016-02-29 17:22
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函数里合适吗?
2016-02-29 23:32
......
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这个函数可以看一看,然后你就明白了。
2016-03-01 16:09
我又有个疑问:在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的映射岂不是发生两次?
2016-03-01 23:01
2015-03-09 09:22
2015-03-09 16:18
2015-03-09 16:34
2015-03-09 18:03
if (irq_settings_can_autoenable(desc))
irq_startup(desc, true);
在request-irq之前调用disable irq也没有问题,前提是正确初始化了硬件和中断描述符。不过针对你的需求,我倒是觉得你可以调用set_irq_flags来设定IRQF_NOAUTOEN这个flag
2015-03-09 18:41
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;
}
2015-05-01 22:22
事实上如果你搜索kernel代码的话,会发现很少有人调用IRQF_NOAUTOEN这个flag,所有调用它的都是1级或者2级interrupt controller,唯一的例外就是CSR的这份uart代码,所以我觉得它没有参考价值。
从这个函数来看,申请中断和开启中断中间只隔了一个uart controller的初始化函数。那么如果把startup_uart_controller(port)函数放到最前面,等这个函数把uart硬件初始化好了之后,再调用request_irq函数来注册中断,这样子就不需要IRQF_NOAUTOEN了。
request_irq的注释里面已经说明了,这个函数返回之后,handler就有可能马上被调用。如果硬件还没有准备好,说明你注册中断时间点很有可能不合理。
功能
最新评论
- wangjing
写得太好了 - wangjing
写得太好了! - DRAM
圖面都沒辦法顯示出來好像掛點了。 - Simbr
bus至少是不是还有个subsystem? - troy
@testtest:只要ldrex-modify-strex... - gh
Linux 内核在 sparse 内存模型基础上实现了vme...
文章分类
随机文章
文章存档
- 2025年4月(5)
- 2024年2月(1)
- 2023年5月(1)
- 2022年10月(1)
- 2022年8月(1)
- 2022年6月(1)
- 2022年5月(1)
- 2022年4月(2)
- 2022年2月(2)
- 2021年12月(1)
- 2021年11月(5)
- 2021年7月(1)
- 2021年6月(1)
- 2021年5月(3)
- 2020年3月(3)
- 2020年2月(2)
- 2020年1月(3)
- 2019年12月(3)
- 2019年5月(4)
- 2019年3月(1)
- 2019年1月(3)
- 2018年12月(2)
- 2018年11月(1)
- 2018年10月(2)
- 2018年8月(1)
- 2018年6月(1)
- 2018年5月(1)
- 2018年4月(7)
- 2018年2月(4)
- 2018年1月(5)
- 2017年12月(2)
- 2017年11月(2)
- 2017年10月(1)
- 2017年9月(5)
- 2017年8月(4)
- 2017年7月(4)
- 2017年6月(3)
- 2017年5月(3)
- 2017年4月(1)
- 2017年3月(8)
- 2017年2月(6)
- 2017年1月(5)
- 2016年12月(6)
- 2016年11月(11)
- 2016年10月(9)
- 2016年9月(6)
- 2016年8月(9)
- 2016年7月(5)
- 2016年6月(8)
- 2016年5月(8)
- 2016年4月(7)
- 2016年3月(5)
- 2016年2月(5)
- 2016年1月(6)
- 2015年12月(6)
- 2015年11月(9)
- 2015年10月(9)
- 2015年9月(4)
- 2015年8月(3)
- 2015年7月(7)
- 2015年6月(3)
- 2015年5月(6)
- 2015年4月(9)
- 2015年3月(9)
- 2015年2月(6)
- 2015年1月(6)
- 2014年12月(17)
- 2014年11月(8)
- 2014年10月(9)
- 2014年9月(7)
- 2014年8月(12)
- 2014年7月(6)
- 2014年6月(6)
- 2014年5月(9)
- 2014年4月(9)
- 2014年3月(7)
- 2014年2月(3)
- 2014年1月(4)
2017-02-15 10:45
在不重新编译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.
非常感谢!