中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。
从硬件角度来看,中断由CPU、中断控制器(Interrupt Controller),其他外设 组成。各个外设在硬件上是通过中断线(irq request line)与CPU相连的,在复杂的系统中,外设比较多的情况下,就需要一个中断控制器来协助CPU进行中断的处理,比如ARM架构下的GIC,或者X86架构中的APIC。根据外设的多少,Interrupt Controller可以级联。
关于GIC:
GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1~V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64服务器系统结构)。
GIC通过AMBA(Advanced Microcontroller Bus。Architecture)这样的片上总线连接到一个或者多个ARM processor上。
具体GIC硬件的实现形态有两种,一种是在ARM vensor研发自己的SOC的时候,会向ARM公司购买GIC的IP,这些IP包括的型号有:PL390,GIC-400,GIC-500。其中GIC-500最多支持128个 cpu core,它要求ARM core必须是ARMV8指令集的(例如Cortex-A57),符合GIC architecture specification version 3。另外一种形态是ARM vensor直接购买ARM公司的Cortex A9或者A15的IP,Cortex A9或者A15中会包括了GIC的实现,当然,这些实现也是符合GIC V2的规格。
从软件角度来看,Linux的中断子系统框架,就是本文的重点!
本文以Linux4.x版本代码为例,使用全志R系列平台。
在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:
1、IRQ number。CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
2、HW interrupt ID。对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对外设中断进行编码。Interrupt controller用HW interrupt ID来标识外设的中断。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller(HW interrupt ID在不同的Interrupt controller上是会重复编码的)。
这样,CPU和interrupt controller在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和CPU视角是一样的,我们只希望得到一个IRQ number,而不关系具体是那个interrupt controller上的那个HW interrupt ID。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不需要修改。因此,linux kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制,这就是本文主要的内容。
Linux kernel中使用IRQ domain来描述一个中断控制器所管理的中断源。也就是说,每个中断控制器都有自己的domain,可以将IRQ Domain看作是Interrupt controller的软件抽象。
这里的Interrupt controller并不仅仅是指传统意义上的中断控制器,如GIC,也可以代表一种“虚拟”的中断控制器,如GPIO 控制器。GPIO控制器也可以注册一个IRQ domain来管理GPIO中断,所以它也可以实现成为一个虚拟的中断控制器。
(以上,蜗窝科技真是极好的网站!)
从开始的start_kernel函数看起
start_kernel->init_IRQ->irqchip_init(有设备树的情况)
void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table);
acpi_probe_device_table(irqchip);
}
其中,__irqchip_of_table是什么呢?
以通用的irq-gic.c为例:通过IRQCHIP_DECLARE这个宏定义若干个静态的struct of_device_id常量,编译系统会把所有的IRQCHIP_DECLARE宏定义的数据放入到一个特殊的section中(__irqchip_of_table),我们称这个特殊的section叫做irq chip table,这个table也就保存了kernel支持的所有的中断控制器的ID信息:
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#ifdef CONFIG_OF
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
这里我是:
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
对应的compatible为"arm,cortex-a15-gic",data字段这是 gic_of_init函数。
设备树节点为:
gic: interrupt-controller@03020000 {
compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic";
#interrupt-cells = <3>;
#address-cells = <0>;
device_type = "gic";
interrupt-controller;
reg = <0x0 0x03021000 0 0x1000>, /* GIC Dist */
<0x0 0x03022000 0 0x2000>, /* GIC CPU */
<0x0 0x03024000 0 0x2000>, /* GIC VCPU Control */
<0x0 0x03026000 0 0x2000>; /* GIC VCPU */
interrupts = ; /* GIC Maintenence IRQ */
interrupt-parent = <&gic>;
};
继续查看of_irq_init函数:
void __init of_irq_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np, *parent = NULL;
struct of_intc_desc *desc, *temp_desc;
struct list_head intc_desc_list, intc_parent_list;
INIT_LIST_HEAD(&intc_desc_list);
INIT_LIST_HEAD(&intc_parent_list);
for_each_matching_node_and_match(np, matches, &match) {//在所有的device node中寻找定义了interrupt-controller属性的中断控制器节点并匹配of_device_id ,形成树状结构
if (!of_find_property(np, "interrupt-controller", NULL) ||
!of_device_is_available(np))
continue;
//省略......
desc = kzalloc(sizeof(*desc), GFP_KERNEL);//分配内存
if (WARN_ON(!desc)) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data;//data既是IRQCHIP_DECLARE里的最后一个参数,宏展开既是of_device_id的data字段,of_device_id的第二个参数既是compatible,用来和dts匹配的
desc->dev = of_node_get(np);
desc->interrupt_parent = of_irq_find_parent(np);
if (desc->interrupt_parent == np)
desc->interrupt_parent = NULL;
list_add_tail(&desc->list, &intc_desc_list);//挂入链表
}
while (!list_empty(&intc_desc_list)) {//从根节点开始,依次递进到下一个level的interrupt controller
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
int ret;
if (desc->interrupt_parent != parent)//最开始的时候parent变量是NULL,确保第一个被处理的是root interrupt controller。在处理完root node之后,parent变量被设定为root interrupt controller,因此,第二个循环中处理的是所有parent是root interrupt controller的child interrupt controller。也就是level 1(如果root是level 0的话)的节点。
continue;
list_del(&desc->list);
of_node_set_flag(desc->dev, OF_POPULATED);
//省略......
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);//执行初始化函数,既gic_of_init
if (ret) {
of_node_clear_flag(desc->dev, OF_POPULATED);
kfree(desc);
continue;
}
list_add_tail(&desc->list, &intc_parent_list);//处理完的节点放入intc_parent_list链表,后面会用到
}
/* Get the next pending parent that might have children */
desc = list_first_entry_or_null(&intc_parent_list,
typeof(*desc), list);
if (!desc) {
pr_err("of_irq_init: children remain, but no parents\n");
break;
}
list_del(&desc->list);//每处理完一个节点就会将该节点删除,当所有的节点被删除,整个处理过程也就是结束了
parent = desc->dev;
kfree(desc);
}
list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
list_del(&desc->list);
kfree(desc);
}
err:
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
list_del(&desc->list);
of_node_put(desc->dev);
kfree(desc);
}
}
在machine driver初始化的时候会调用of_irq_init函数,在该函数中会扫描所有interrupt controller的节点,并调用适合的interrupt controller driver进行初始化,根据里面配置的级联中断信息按顺序做好映射的工作。毫无疑问,初始化需要注意顺序,首先初始化root,然后first level,second level,最后是leaf node。
继续跟踪desc->irq_init_cb的回调函数:
int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
void __iomem *dist_base;
u32 percpu_offset;
int irq;
dist_base = of_iomap(node, 0);//获取reg地址空间,GIC Distributor的寄存器地址,负责连接系统中所有的中断源
WARN(!dist_base, "unable to map gic dist registers\n");
cpu_base = of_iomap(node, 1);//获取reg地址空间,GIC CPU interface的寄存器地址,
WARN(!cpu_base, "unable to map gic cpu registers\n");
if (gic_cnt == 0 && !gic_check_eoimode(node, &cpu_base))
static_key_slow_dec(&supports_deactivate);
if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
percpu_offset = 0;
__gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset,
&node->fwnode);//里面会注册domain
if (!gic_cnt)
gic_init_physaddr(node);
if (parent) {//root GIC不会执行到这里,因为其parent是null
irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupts属性,并进行mapping,返回IRQ number
gic_cascade_irq(gic_cnt, irq);//设置handler
}
gic_cnt++;
return 0;
}
值得说明的是,root GIC不会执行执行irq_of_parse_and_map函数,关于irq_of_parse_and_map函数,我们放到后面叙述。
进入__gic_init_bases函数:
static void __init __gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct fwnode_handle *handle)
{
irq_hw_number_t hwirq_base;
struct gic_chip_data *gic;
int gic_irqs, irq_base, i;
BUG_ON(gic_nr >= MAX_GIC_NR);
#ifdef CONFIG_SUNXI_GIC_ACCESS_SS
memset(last_ipi_cache, 0x0, sizeof(last_ipi_cache));
#endif
gic_check_cpu_features();
gic = &gic_data[gic_nr];//gic_nr标识GIC number,等于0就是root GIC
#ifdef CONFIG_GIC_NON_BANKED
//省略......
gic_irqs = cpu_gic_readl(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;//变量gic_irqs保存了该GIC支持的最大的中断数目
gic_irqs = (gic_irqs + 1) * 32;
if (gic_irqs > 1020)//GIC规范规定最大的中断数目不能超过1020,1020-1023是有特别用户的interrupt ID
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;
if (handle) { /* DT/ACPI */
gic->domain = irq_domain_create_linear(handle, gic_irqs,
&gic_irq_domain_hierarchy_ops,
gic);//创建线性映射
} else { /* Legacy support */
if (gic_nr == 0 && (irq_start & 31) > 0) {
hwirq_base = 16;//hwirq_base = 16也就意味着忽略掉16个SGI
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;
}
gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
numa_node_id());//分配描述符,第二个参数16就是起始搜索的IRQ number。gic_irqs指明要分配的irq number的数目
if (IS_ERR_VALUE(irq_base)) {
WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n",
irq_start);
irq_base = irq_start;
}
gic->domain = irq_domain_add_legacy(NULL, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);//注册irq domain,创建映射
}
if (WARN_ON(!gic->domain))
return;
if (gic_nr == 0) {
for (i = 0; i < NR_GIC_CPU_IF; i++)
gic_cpu_map[i] = 0xff;
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);
register_cpu_notifier(&gic_cpu_notifier);
#endif
set_handle_irq(gic_handle_irq);//设定arch相关的irq handler
if (static_key_true(&supports_deactivate))
pr_info("GIC: Using split EOI/Deactivate mode\n");
}
gic_dist_init(gic);//具体的硬件初始代码
gic_cpu_init(gic);//初始化BSP的CPU interface
gic_pm_init(gic);//初始化GIC的Power management
}
这里面,通过irq_alloc_descs最终会调用alloc_descs函数一次性分配若干desc,当然,对于静态数据类型的,不会真正分配,仅仅是站上那个位置而已,对于CONFIG_SPARSE_IRQ的那种,的确会分配desc的内存并插入到radix tree中。
在linux kernel中,对于每一个外设的IRQ都用struct irq_desc来描述,我们称之中断描述符(struct irq_desc),这是一个很重要的结构体,整个通用中断子系统几乎都是围绕着irq_desc结构进行,irq_desc里有一项叫做 irqaction。它里面就包含着 handler() 中断处理函数。
其中irq_domain_add_legacy函数,会注册domain,也是一次性的创建所有的irq number到HW interrupt ID的映射。irq domain管理具体HW interrupt ID(interrupt request line的标识)和IRQ number(外设中断编号)的映射关系,为何映射呢?
从linux kernel的角度来看,任何外部的设备的中断都是一个异步事件,kernel都需要识别这个事件。在内核中,用IRQ number来标识某一个设备的某个interrupt request。有了IRQ number就可以定位到该中断的描述符(struct irq_desc)。但是,对于中断控制器而言,它不并知道IRQ number,它只是知道HW interrupt number(中断控制器会为其支持的interrupt source进行编码,这个编码被称为Hardware interrupt number )。不同的软件模块用不同的ID来识别interrupt source,这样就需要映射了。
domain将这种映射关系分成了三类:
1).线性映射。其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的IRQ number。
2).Radix Tree map。建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。
3).no map。有些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。
irq domain用来抽象一个相对独立的中断域,毫无疑问,GPIO中断就是一个domain。gpio_to_irq,本身就是把GPIO domain的中断号(例如gpio number),转换为CPU视角的中断number。
在注册GIC的irq domain的时候还有一个重要的数据结构gic_irq_domain_ops,其类型是struct irq_domain_ops ,对于GIC,其irq domain的操作函数是gic_irq_domain_ops,定义如下:
static const struct irq_domain_ops gic_irq_domain_ops = {
.map = gic_irq_domain_map,//创建IRQ number和GIC hw interrupt ID之间映射关系
.unmap = gic_irq_domain_unmap,
};
这里我们重点看三个函数:
【1】irq_domain_add_legacy函数
【2】gic_dist_init函数
【3】set_handle_irq
先看【1】irq_domain_add_legacy函数:
struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
unsigned int size,
unsigned int first_irq,
irq_hw_number_t first_hwirq,
const struct irq_domain_ops *ops,
void *host_data)
{
struct irq_domain *domain;
domain = __irq_domain_add(of_node_to_fwnode(of_node), first_hwirq + size,
first_hwirq + size, 0, ops, host_data);//注册irq domain
if (domain)
irq_domain_associate_many(domain, first_irq, first_hwirq, size);//创建映射
return domain;
}
注册domain的同时,还会把gic_irq_domain_ops 结构体赋给domain->ops,
每个interrupt controller都会形成一个irq domain,负责解析其下游的interrut source。如果interrupt controller有级联的情况,那么一个非root interrupt controller的中断控制器也是其parent irq domain的一个普通的interrupt source。
最后调用关系为:
irq_domain_associate_many-->irq_domain_associate
int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
irq_hw_number_t hwirq)
{
struct irq_data *irq_data = irq_get_irq_data(virq);
int ret;
//省略......
mutex_lock(&irq_domain_mutex);
irq_data->hwirq = hwirq;
irq_data->domain = domain;
if (domain->ops->map) {
ret = domain->ops->map(domain, virq, hwirq);//调用domain的map回调函数
if (ret != 0) {
if (ret != -EPERM) {
pr_info("%s didn't like hwirq-0x%lx to VIRQ%i mapping (rc=%d)\n",
domain->name, hwirq, virq, ret);
}
irq_data->domain = NULL;
irq_data->hwirq = 0;
mutex_unlock(&irq_domain_mutex);
return ret;
}
/* If not already assigned, give the domain the chip's name */
if (!domain->name && irq_data->chip)
domain->name = irq_data->chip->name;
}
if (hwirq < domain->revmap_size) {
domain->linear_revmap[hwirq] = virq;//填写线性映射lookup table的数据
} else {
mutex_lock(&revmap_trees_mutex);
radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);//向radix tree插入一个node
mutex_unlock(&revmap_trees_mutex);
}
mutex_unlock(&irq_domain_mutex);
irq_clear_status_flags(virq, IRQ_NOREQUEST);//该IRQ已经可以申请了,因此clear相关flag
return 0;
}
函数主要 调用gic_irq_domain_ops 的map这个回调函数,即gic_irq_domain_map函数,同时填写线形映射的关系,hwirq为数组的下标,对应的内容为virq,即irq number。
static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq,
irq_hw_number_t hw)
{
struct irq_chip *chip = &gic_chip;
if (static_key_true(&supports_deactivate)) {
if (d->host_data == (void *)&gic_data[0])
chip = &gic_eoimode1_chip;
}
if (hw < 32) {//SGI或者PPI
irq_set_percpu_devid(irq);//SGI或者PPI和SPI最大的不同是per cpu的,SPI是所有CPU共享的,因此需要分配per cpu的内存,设定一些per cpu的flag
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_percpu_devid_irq, NULL, NULL);//设定该中断描述符的irq chip和high level的handler
irq_set_status_flags(irq, IRQ_NOAUTOEN);
} else {//SPI类型
irq_domain_set_info(d, irq, hw, chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);//设定该中断描述符的irq chip和high level irq event handler ,最终赋值给中断描述符的handle_irq
irq_set_probe(irq);
}
return 0;
}
调用map函数的时机是在创建(或者更新)HW interrupt ID(hw参数)和IRQ number(virq参数)关系的时候。其实,从发生一个中断到调用该中断的handler仅仅调用一个request_threaded_irq是不够的,还需要针对该irq number设定:
(1)设定该IRQ number对应的中断描述符(struct irq_desc)的irq chip
(2)设定该IRQ number对应的中断描述符的highlevel irq-events handler
(3)设定该IRQ number对应的中断描述符的 irq chip data 这些设定不适合由具体的硬件驱动来设定,因此在Interrupt controller,也就是irq domain的callback函数中设定。
值得一提的是,map函数里的irq_domain_set_info函数,将handle_fasteoi_irq函数作为实参传进,
最后被赋值到desc->handle_irq(irq_domain_set_info–>__irq_set_handler–>__irq_do_set_handler --> desc->handle_irq = handle 这里handle即是handle_fasteoi_irq),
handle_fasteoi_irq函数在中断响应时执行gic_handle_irq调用到,各个irq对应的struct irq_desc里的每个handle_irq成员都初始化为handle_fasteoi_irq,是一个重要函数.
而且irq_domain_set_info函数里还会调用irq_set_chip函数,里面会执行:desc->irq_data.chip = chip;这个接口函数用来设定中断描述符中desc->irq_data.chip成员,这样既可通过IRQnumer找到irq_dese,通过irq_desc找到其对应的chip。
接着看【2】gic_dist_init函数:
static void __init gic_dist_init(struct gic_chip_data *gic)
{
unsigned int i;
u32 cpumask;
unsigned int gic_irqs = gic->gic_irqs;//获取该GIC支持的IRQ的数目
void __iomem *base = gic_data_dist_base(gic);//获取该GIC对应的Distributor基地址
cpu_gic_writel(GICD_DISABLE, base + GIC_DIST_CTRL);//用来控制全局的中断forward情况。写入0表示Distributor不向CPU interface发送中断请求信号,也就disable了全部的中断请求
/*
* Set all global interrupts to this CPU only.
*/
cpumask = gic_get_cpumask(gic);
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
for (i = 32; i < gic_irqs; i += 4)
cpu_gic_writel(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);//设定每个SPI类型的中断都是只送达该CPU
gic_dist_config(base, gic_irqs, NULL);//配置GIC distributor的其他寄存器
cpu_gic_writel(GICD_ENABLE, base + GIC_DIST_CTRL);//开启Distributor
}
最后看【3】set_handle_irq函数:
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}
这里,函数传入的参数是gic_handle_irq函数,相当于handle_arch_irq 这个函数指针指向了handle_irq函数,注意这里,到最后响应中断时会调用到这个。
还记得之前说的,root GIC不会执行到irq_of_parse_and_map的函数吗,在非root GIC里:
interrupt controller可以级联。对于root GIC,其传入的parent是NULL,因此不会执行级联部分的代码。对于second GIC,它是作为其parent(root GIC)的一个普通的irq source,因此,也需要注册该IRQ的handler。由此可见,非root的GIC的初始化分成了两个部分:一部分是作为一个interrupt controller,执行和root GIC一样的初始化代码。另外一方面,GIC又作为一个普通的interrupt generating device,需要象一个普通的设备驱动一样,注册其中断handler。
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
struct of_phandle_args oirq;
if (of_irq_parse_one(dev, index, &oirq))//解析给device node的interrupts等中断属性,并封装到oirq中
return 0;
return irq_create_of_mapping(&oirq);//建立映射
}
使用irq_of_parse_and_map函数进行device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系。
unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
struct irq_fwspec fwspec;
of_phandle_args_to_fwspec(irq_data, &fwspec);
return irq_create_fwspec_mapping(&fwspec);
}
unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
struct irq_domain *domain;
struct irq_data *irq_data;
irq_hw_number_t hwirq;
unsigned int type = IRQ_TYPE_NONE;
int virq;
if (fwspec->fwnode) {
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);//开始match domain
if (!domain)
domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
} else {
domain = irq_default_domain;
}
//省略......
if (irq_domain_translate(domain, fwspec, &hwirq, &type))//里面是调用irq domain的xlate函数,如果没有定义xlate函数,那么取interrupts属性的第一个cell作为HW interrupt ID
return 0;
//省略......
virq = irq_find_mapping(domain, hwirq);//如果已经map过
if (virq) {
if (type == IRQ_TYPE_NONE || type == irq_get_trigger_type(virq))
return virq;
if (irq_get_trigger_type(virq) == IRQ_TYPE_NONE) {
irq_data = irq_get_irq_data(virq);
if (!irq_data)
return 0;
irqd_set_trigger_type(irq_data, type);//设定trigger type
return virq;
}
pr_warn("type mismatch, failed to map hwirq-%lu for %s!\n",
hwirq, of_node_full_name(to_of_node(fwspec->fwnode)));
return 0;
}
if (irq_domain_is_hierarchy(domain)) {//如果已经配置过interrupt
virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
if (virq <= 0)
return 0;
} else {
/* Create mapping */
virq = irq_create_mapping(domain, hwirq);//创建HW interrupt ID和IRQ number的映射关系,IRQ numbe 就是所说的virq
if (!virq)
return virq;
}
irq_data = irq_get_irq_data(virq);
if (!irq_data) {
if (irq_domain_is_hierarchy(domain))
irq_domain_free_irqs(virq, 1);
else
irq_dispose_mapping(virq);
return 0;
}
/* Store trigger type */
irqd_set_trigger_type(irq_data, type);//调用irq_set_irq_type函数设定trigger type 这个type也是从interrupts属性解析出来的
return virq;
}
详细流程参考注释,主要看下irq_create_mapping函数是怎么map的:
unsigned int irq_create_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
struct device_node *of_node;
int virq;
/* Look for default domain if nececssary */
if (domain == NULL)
domain = irq_default_domain;
if (domain == NULL) {
WARN(1, "%s(, %lx) called with NULL domain\n", __func__, hwirq);
return 0;
}
of_node = irq_domain_get_of_node(domain);
/* Check if mapping already exists */
virq = irq_find_mapping(domain, hwirq);//如果映射已经存在,那么不需要映射
if (virq) {
pr_debug("-> existing mapping on virq %d\n", virq);
return virq;
}
/* Allocate a virtual interrupt number */
virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);//分配一个IRQ 描述符以及对应的irq number,返回未占用的virq
if (virq <= 0) {
pr_debug("-> virq allocation failed\n");
return 0;
}
if (irq_domain_associate(domain, virq, hwirq)) {//建立mapping
irq_free_desc(virq);
return 0;
}
return virq;
}
具体的映射关系就在irq_domain_associate函数实现,这个函数在文章之前也分析过,在irq_domain_add_legacy函数里注册domain时也会调用到。
系统启动阶段,取决于内核的配置,内核会通过数组或基数树分配好足够多的irq_desc结构,系统中每一个irq都对应着一个irq_desc结构。
我们知道,申请中断时,使用request_threaded_irq函数:
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{
struct irqaction *action;
struct irq_desc *desc;
int retval;
//省略......
desc = irq_to_desc(irq);//从irq number得到desc描述符
if (!desc)
return -EINVAL;
if (!irq_settings_can_request(desc) ||////并非系统中所有的IRQ number都可以request
WARN_ON(irq_settings_is_per_cpu_devid(desc)))
return -EINVAL;
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);//分配struct irqaction
if (!action)
return -ENOMEM;
action->handler = handler;//填充action
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
retval = irq_chip_pm_get(&desc->irq_data);
if (retval < 0) {
kfree(action);
return retval;
}
chip_bus_lock(desc);
retval = __setup_irq(irq, desc, action);//进行实际注册,挂载到struct desc的desc->action下
chip_bus_sync_unlock(desc);
if (retval) {
irq_chip_pm_put(&desc->irq_data);
kfree(action->secondary);
kfree(action);
}
//省略......
return retval;
}
函数的第一个参数是irq number,一般在使用request_threaded_irq函数之前,该设备节点就通过irq_of_parse_and_map()函数里的of_irq_parse_one解析过该节点,然后获得该中断所隶属的interrupt-controller的irq domain,从而得到表示第index个中断的参数中解析出hwirq和中断类型,最后从系统中为该hwriq分配一个全局唯一的virq,并将映射关系存放到中断控制器的irq domain中。
最后会调用__setup_irq函数进行注册:
static int
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{
struct irqaction *old, **old_ptr;
unsigned long flags, thread_mask = 0;
int ret, nested, shared = 0;
cpumask_var_t mask;
new->irq = irq;
//省略......
nested = irq_settings_is_nested_thread(desc);//判断是否是嵌套中断线程
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
new->handler = irq_nested_primary_handler;//抛出一个警告,nested irq的调用时父中断的handler中处理的,而不是在这里
} else {
if (irq_settings_can_thread(desc)) {//有的中断不允许线程化,设置了IRQ_NOTHREAD标志
ret = irq_setup_forced_threading(new);//强制线程化
if (ret)
goto out_mput;
}
}
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);//里面创建线程
if (ret)
goto out_mput;
if (new->secondary) {
ret = setup_irq_thread(new->secondary, irq, true);
if (ret)
goto out_thread;
}
raw_spin_lock_irqsave(&desc->lock, flags);
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
//省略......
/* add new interrupt at end of irq queue */
do {//循环处理action
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}
//省略......
}
}
__setup_irq来完成注册函数处理,还将action注册到desc[irq]中,do_while(old)循环使得old_ptr存放desc->action最后一个action的地址(即最后一个action存放nex的地址),且最后一个action->next的指针指向为空。最后,将要插入的新的action放到连尾。
可以看下setup_irq_thread函数创建线程,里面是:
kthread_create(irq_thread, new, "irq/%d-s-%s", irq, new->name);//创建一个名为irq/irq-name的线程,该线程调用irq_thread,参数为新的irqaction,只是创建,并没有唤醒
static int irq_thread(void *data)
{
struct callback_head on_exit_work;
struct irqaction *action = data;//传入的action
struct irq_desc *desc = irq_to_desc(action->irq);//描述符
irqreturn_t (*handler_fn)(struct irq_desc *desc,
struct irqaction *action);
if (force_irqthreads && test_bit(IRQTF_FORCED_THREAD,
&action->thread_flags))
handler_fn = irq_forced_thread_fn;
else
handler_fn = irq_thread_fn;
init_task_work(&on_exit_work, irq_thread_dtor);
task_work_add(current, &on_exit_work, false);
irq_thread_check_affinity(desc, action);
while (!irq_wait_for_interrupt(action)) {//等待中断唤醒,通过检测action的thread_flags标志来检测是否中断发生然后调度该线程的执行
irqreturn_t action_ret;
irq_thread_check_affinity(desc, action);
action_ret = handler_fn(desc, action);//调用action->thread_fn函数
if (action_ret == IRQ_HANDLED)
atomic_inc(&desc->threads_handled);
if (action_ret == IRQ_WAKE_THREAD)
irq_wake_secondary(desc, action);
wake_threads_waitq(desc);
}
task_work_cancel(current, irq_thread_dtor);
return 0;
}
那最后,硬件响应中断的流程是怎么的呢?
汇编部分不太熟,可以参考这篇文档:ARMv8-中断处理接口
总而言之,pc指针会跳转到:handle_arch_irq
还记得之前描述的吗,handle_arch_irq在set_handle_irq函数里指向了gic_handle_irq,所以,最后是执行到gic_handle_irq函数:
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
#ifdef CONFIG_SUNXI_GIC_ACCESS_SS
u32 irqactive, cpu;
#endif
struct gic_chip_data *gic = &gic_data[0];//获取root GIC的gic_chip_data,硬件相关的寄存器map
void __iomem *cpu_base = gic_data_cpu_base(gic);//获取root GIC mapping到CPU地址空间的信息
do {
irqstat = cpu_gic_readl(cpu_base + GIC_CPU_INTACK);//读取INTACK中断控制器寄存器,获知hwirq,即可知道哪个外设产生的中断
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
//省略......
if (likely(irqnr > 15 && irqnr < 1021)) {//SPI和PPI的处理
if (static_key_true(&supports_deactivate))
cpu_gic_writel(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SUNXI_GIC_ACCESS_SS
if (irqnr == 29)
save_last_timer_stamp(cpu);
#endif
handle_domain_irq(gic->domain, irqnr, regs);//获取IRQ numbe以及irq的callback
continue;
}
//省略......
break;
} while (1);
}
这里,对于SPI和PPI类型的处理,是调用的handle_domain_irq,通过此函数,即可从hwirq知道IRQ number,从而进一步知道desc描述符。
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter();//响应IRQ中断后,ARM会自动把CPSR中的I位置位,表明禁止新的IRQ请求
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq);//将HW interrupt ID转成IRQ number
#endif
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);//调用中断流控制函数
}
irq_exit();
set_irq_regs(old_regs);//设置中断的触发类型,根据相应的类型设置中断流控制函数
return ret;
}
这里有两个函数:irq_find_mapping和generic_handle_irq。
可以进irq_find_mapping函数稍微看看,如何通过domain, hwirq获取IRQ number:
unsigned int irq_find_mapping(struct irq_domain *domain,
irq_hw_number_t hwirq)
{
struct irq_data *data;
//省略......
if (hwirq < domain->revmap_direct_max_irq) {
data = irq_domain_get_irq_data(domain, hwirq);
if (data && data->hwirq == hwirq)
return hwirq;
}
/* Check if the hwirq is in the linear revmap. */
if (hwirq < domain->revmap_size)
return domain->linear_revmap[hwirq];//获取IRQ number
rcu_read_lock();
data = radix_tree_lookup(&domain->revmap_tree, hwirq);
rcu_read_unlock();
return data ? data->irq : 0;
}
可以看到,函数和我们预期想的一样,对于线性映射,这里同样是通过hwirq作为linear_revmap数组的下标,取出IRQ number。
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);//获取相应中断的描述符
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc);//里面是执行:desc->handle_irq(desc)
return 0;
}
generic_handle_irq函数比较简单,通过之前函数获取到的IRQ number之后即可得到desc中断描述符,进而执行desc->handle_irq这个回调函数。即是handle_fasteoi_irq函数,前面也有描述过,最后其调用关系为:
handle_fasteoi_irq-->handle_irq_event-->handle_irq_event_percpu-->__handle_irq_event_percpu
irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags)
{
irqreturn_t retval = IRQ_NONE;
unsigned int irq = desc->irq_data.irq;
struct irqaction *action;
for_each_action_of_desc(desc, action) {//遍历该中断描述符的整个action list
irqreturn_t res;
trace_irq_handler_entry(irq, action);
res = action->handler(irq, action->dev_id);//调用的就是request_irq、request_threaded_irq注册的handler
trace_irq_handler_exit(irq, action, res);
switch (res) {
case IRQ_WAKE_THREAD:
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
__irq_wake_thread(desc, action);//唤醒request_threaded_irq注册的内核线程,执行thread_fn
/* Fall through to add to randomness */
case IRQ_HANDLED:
*flags |= action->flags;
break;
default:
break;
}
retval |= res;
}
return retval;
}
终于!在这里看到了action->handler,这是在request_threaded_irq注册中断的时候传递的handler处理函数,
以及,通过__irq_wake_thread函数来唤醒在request_threaded_irq()申请注册强制线程中断时在申请创建好了的线程,这里唤醒下半部处理线程来处理,里面是调用wake_up_process(action->thread);
所以说,当一个irq被触发时,内核会遍历action链表,调用action结构中的回调handler或者激活其中的中断线程,之所以实现为一个链表,是为了实现中断的共享,多个设备共享同一个irq,这在外围设备中是普遍存在的。
在整个流程中,首先获取触发中断的HW irq,知道HW irq,就通过irq domain知道了IRQ number,进而也知道了irq_desc,最后也会知道中断描述符中的highlevel irq-events handler,即handle_irq!
顺带一提:驱动里最常见gpio_to_irq函数,里面也会调用到irq_create_mapping函数,因为有的驱动,在使用gpio中断的时候,没有使用interrupts属性,而是在设备里简单的自定了一个中断gpio号,然后在驱动里取出gpio号,再手动的申请。
参考窝蜗科技系列文章:
Linux kernel的中断子系统之(二):IRQ Domain介绍
linux kernel的中断子系统之(七):GIC代码分析
【Linux基础系列之】中断系统(1)-框架
Linux中断子系统框架流程详解(基于Kernel 3.16,arm,设备树)