你好!这里是风筝的博客,

欢迎和我一起交流。


中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。

从硬件角度来看,中断由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_PPI 9 0xf04>; /* 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 controllerlist_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_initif (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);//里面会注册domainif (!gic_cnt)gic_init_physaddr(node);if (parent) {//root GIC不会执行到这里,因为其parent是nullirq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupts属性,并进行mapping,返回IRQ numbergic_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_SSmemset(last_ipi_cache, 0x0, sizeof(last_ipi_cache));
#endifgic_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 IDgic_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个SGIif (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_SMPset_smp_cross_call(gic_raise_softirq);register_cpu_notifier(&gic_cpu_notifier);
#endifset_handle_irq(gic_handle_irq);//设定arch相关的irq handlerif (static_key_true(&supports_deactivate))pr_info("GIC: Using split EOI/Deactivate mode\n");}gic_dist_init(gic);//具体的硬件初始代码gic_cpu_init(gic);//初始化BSP的CPU interfacegic_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 domainif (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插入一个nodemutex_unlock(&revmap_trees_mutex);}mutex_unlock(&irq_domain_mutex);irq_clear_status_flags(virq, IRQ_NOREQUEST);//该IRQ已经可以申请了,因此clear相关flagreturn 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的flagirq_domain_set_info(d, irq, hw, chip, d->host_data,handle_percpu_devid_irq, NULL, NULL);//设定该中断描述符的irq chip和high level的handlerirq_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_irqirq_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类型的中断都是只送达该CPUgic_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 domainif (!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 IDreturn 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 typereturn 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)) {//如果已经配置过interruptvirq = 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 就是所说的virqif (!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);elseirq_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,返回未占用的virqif (virq <= 0) {pr_debug("-> virq allocation failed\n");return 0;}if (irq_domain_associate(domain, virq, hwirq)) {//建立mappingirq_free_desc(virq);return 0;}return virq;
}

具体的映射关系就在irq_domain_associate函数实现,这个函数在文章之前也分析过,在irq_domain_add_legacy函数里注册domain时也会调用到。

到此,说完了domain里如何实现hw irq到irq nuimber的映射,那到底该怎么用呢?

系统启动阶段,取决于内核的配置,内核会通过数组或基数树分配好足够多的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都可以requestWARN_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 irqactionif (!action)return -ENOMEM;action->handler = handler;//填充actionaction->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 {//循环处理actionthread_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;//传入的actionstruct 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;elsehandler_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_SSu32 irqactive, cpu;
#endifstruct gic_chip_data *gic = &gic_data[0];//获取root GIC的gic_chip_data,硬件相关的寄存器mapvoid __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_SSif (irqnr == 29)save_last_timer_stamp(cpu);
#endifhandle_domain_irq(gic->domain, irqnr, regs);//获取IRQ numbe以及irq的callbackcontinue;}//省略......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_DOMAINif (lookup)irq = irq_find_mapping(domain, hwirq);//将HW interrupt ID转成IRQ number
#endifif (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 numberrcu_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 listirqreturn_t res;trace_irq_handler_entry(irq, action);res = action->handler(irq, action->dev_id);//调用的就是request_irq、request_threaded_irq注册的handlertrace_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,设备树)

嵌入式Linux驱动笔记(二十七)------中断子系统框架分析相关推荐

  1. 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

    你好!这里是风筝的博客, 欢迎和我一起交流. 最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来. 我们知道: Linux抽象出FrameBuffer这个设备 ...

  2. 嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】

    转自:https://blog.csdn.net/Guet_Kite/article/details/78574781 权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 h ...

  3. 嵌入式Linux驱动笔记(二十五)------Input子系统框架

    你好!这里是风筝的博客, 欢迎和我一起交流. 一.Input子系统概述 二.Input子系统架构 三.Input子系统工作机制 3.1 核心层(input.c) 3.1.1 input_init函数 ...

  4. 嵌入式Linux驱动笔记(二十九)------内存管理之伙伴算法(Buddy)分析

    你好!这里是风筝的博客, 欢迎和我一起交流. 我们知道,在一个通用操作系统里,频繁申请内存释放内存都会出现一个非常著名的内存管理问题:内存碎片. 学过操作系统的都知道,有很多行之有效的方法(比如:记录 ...

  5. 嵌入式Linux驱动笔记--转自风筝丶

    为了阅读学习方便,将系列博客的网址进行粘贴,感谢原博客的分享. 嵌入式Linux驱动笔记(一)------第一个LED驱动程序 嵌入式Linux驱动笔记(二)------定时器 嵌入式Linux驱动笔 ...

  6. 嵌入式Linux驱动笔记(五)------学习platform设备驱动

    你好!这里是风筝的博客, 欢迎和我一起交流. 设备是设备,驱动是驱动. 如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法.如果把他们分开来,当设备发生变化时,只要改写设备 ...

  7. 嵌入式Linux驱动笔记(十一)------i2c设备之mpu6050驱动

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 上一节讲了i2c框架: 嵌入式Linux驱动笔记(十)------通俗易懂式了解i2c框架 这次就来写一写真正的i2c设备驱动: mpu605 ...

  8. 嵌入式Linux驱动笔记(十六)------设备驱动模型(kobject、kset、ktype)

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 前几天去面试,被问到Linux设备驱动模型这个问题,没答好,回来后恶补知识,找了些资料,希望下次能答出个满意答案. Linux早期时候,一个驱 ...

  9. 嵌入式Linux驱动笔记(三十一)------SYSRQ组合键使用

    你好!这里是风筝的博客, 欢迎和我一起交流. ALT+SYSRQ组合键是Linux调试的一种手段,即使在系统死机.panic.卡住等情况,只要系统还能响应中断,那么SYSRQ就派上用场了(比如触发cr ...

最新文章

  1. 软件测试工程师面试问题
  2. 1.22 实例:商品信息查询
  3. CSS display: table-cell 用于水平垂直居中
  4. 《SAS编程与数据挖掘商业案例》学习笔记之四
  5. python 图片转文字错误_python3把base64字符串写成图片文件出错
  6. Shiro学习总结(4)——Shrio登陆验证实例详细解读
  7. 用 Python 识别图片中的文字
  8. 图像去重,4 行代码就能实现,你值得拥有imagededup
  9. kuberneters dashboard认证及分级授权
  10. 特征提取方法: one-hot 和 TF-IDF
  11. editplus怎么在前后插入字符
  12. 基于高程的地面点云信息提取
  13. Python学习笔记——python基础 4. 函数进阶
  14. 学习Linux你必须知道的那些事儿
  15. 25道Python工程师面试必备知识点!
  16. 目标检测-SSD算法详细总结
  17. android相册和拍照并裁剪图片大小,Android 拍照并对照片进行裁剪和压缩实例详解...
  18. 蓝宝石rx580怎么超频_超频测试:提升8%
  19. Mechine learning for OpenCV 学习笔记 day3
  20. Matlab屏幕截图终极总结

热门文章

  1. 1982年以来NBA发展趋势分析
  2. (转)彻底解决工行U盾windows 7驱动程序无法使用的问题 vista下U盾驱动问题也可以参考此方法...
  3. 百度搜索“一起自学吧”做大数据专家,不做第一,只做唯一
  4. 实验四 配置默认路由
  5. EndNote执行Update Citations and Bibliography导致Word崩溃
  6. 今日头条的排名算法_今日头条搜索seo排名怎么做? 今日头条搜索排名规则...
  7. FindBugs-IDEA插件找到代码中潜在的问题
  8. 【Unity】Unity添加真实地图的方法探索
  9. 零基础如何学习ps?
  10. 2016大唐移动 在线笔试题