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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Guet_Kite/article/details/97261350

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

欢迎和我一起交流。


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

从硬件角度来看,中断由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 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时也会调用到。

到此,说完了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都可以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,设备树)

展开阅读全文

没有更多推荐了,返回首页