中断域

如果中断号按照中断引脚编号来区分,当出现中断控制器级联的现状时,如何区分相同的中断号来自哪个中断控制器便成了一个问题。为了解决这一问题,内核将不同的中断控制器划分成了不同的中断域。即当内核注册中断控制器时,内核会为该中断控制器申请一块可用的中断域,与此同时利用该中断域对中断控制器的硬件中断号进行映射,从而得到与软件中断号一一映射的效果。

当上述动作完成后,内核也需要知道该中断域所属的中断控制器,为此,内核将该中断域与该中断控制器进行了关联。这样,当硬件设备产生中断,内核可以根据中断域映射到软件中断号,从而进行相应的中断处理。

关于中断控制器中的中断域的生成,内核提供了三种不同的方式,每种方式主要依赖于所采用的映射行为,分别是:

序号方式
1linear map(线性映射)
2tree map(树映射)
3no map(不映射)

因此,内核中也相应的提供了三种不同的接口,即irq_domain_add_*()函数来创建中断控制器所需的IRQ Domain;

关于三种映射方式的特点,这里简单说明:

序号方式
1linear map:维护固定大小的表,索引是硬件中断号,如果硬件中断最大数量固定,并且数值不大,可以选择线性映射
2tree map:硬件中断号可能很大,可以选择树映射
3no map:硬件中断号直接就是Linux的中断号

可见,通过中断域,虽然各个控制器的硬件中断号可以一样,但是各自所映射的软件中断号却是唯一的。

接下来,以MIPS架构为例,分析内核中通用的MIPS处理器的中断控制器。

int __init mips_cpu_irq_of_init(struct device_node *of_node, struct device_node *parent)
{
	__mips_cpu_irq_init(of_node);
	return 0;
}
IRQCHIP_DECLARE(cpu_intc, "mti,cpu-interrupt-controller", mips_cpu_irq_of_init);
//上述代码为MIPS处理器中断控制器初始化的入口函数

static void __init __mips_cpu_irq_init(struct device_node *of_node)
{
	clear_c0_status(ST0_IM);
	clear_c0_cause(CAUSEF_IP);

	irq_domain = irq_domain_add_leagcy(of_node, 8, MIPS_CPU_IRQ_BASE, 0, &mips_cpu_intc_irq_domain_ops, NULL);
	//该函数用来创建中断控制器所需的中断域

	...
}

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);
	//创建中断域

	if (domain)
		irq_domain_associate_many(domain, first_irq, first_hwirq, size);
		//如果中断与创建成功,则开始建立硬件中断号与软件中断号之间的映射
	return domain;
}

struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size, irq_hw_numbet_t hwirq_max, int direct_max, const struct irq_domain_ops *ops, void *host_data)
{
	struct irqchip_fwid *fwid;
	struct irq_domain *domain;

	static atomic_t unknown_domains;

	domain = kzalloc_node(sizeof(*domain) + (szieof(unsigned int) * size), GFP_KERNEL, if_node_to_nid(to_of_node(fwnode)));
	//申请struct irq_domain结构体对象
	if (!domain)
		return NULL;

	...
	//中间这部分是关于设备节点属性中fwnode属性的处理,同时也会根据fwnode的部分属性来填充domain的部分属性,比如:name等

	INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
	//初始化中断域所使用的基数树
	mutex_init(&domain->revmap_tree_mutex);
	//初始中断域所使用的锁
	domain->ops = ops;
	domain->host_data = host_data;
	domain->hwirq_max = hwirq_max;
	domain->revmap_size = size;
	domain->revmap_direct_max_irq = direct_max;
	irq_domain_check_hierarchy(domain);
	//对中断域domain的属性进行赋值
	...
	return domain;
}

void irq_domain_associate_many(struct irq_domain *domain, unsigned int irq_base, irq_hw_number_t hwirq_base, int count)
{
	struct device_node *of_node;
	int i;

	of_node = irq_domain_get_of_node(domain);
	//根据中断域获取设备节点
	...
	for (i = 0; i < count; i++) {
		irq_domain_associate(domain, irq_base + i, hwirq_base + i);
		//开始一一为硬件中断号与软件中断号建立映射关系
	}
}

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);
	//根据软件中断号获取对应的struct irq_data结构体对象
	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);
		//执行中断域操作中的映射操作,即map()钩子函数
		...
	}
	...
	irq_domain_set_mapping(domain, hwirq, irq_data);
	//建立映射
	...
}

//关于domain->ops->map函数,MIPS处理器中断控制器已经进行了定义,其原型如下:
static int mips_cpu_intc_map(struct irq_domain *d, unsigned int irq, irq_whw_number_t hw)
{
	struct irq_chip *chip;

	if (hw < 2 && cpu_has_mipsmt) {
		chip = &mips_mt_cpu_irq_controller;
	} else {
		chip = &mips_cpu_irq_controller;
		//设置中断控制器类型
	}

	if (cpu_has_vint)
		set_vi_handler(hw, plat_irq_dispatch);

	irq_set_chip_and_handler(irq, chip, handle_percpu_irq);
	//设置软件中断号所关联的中断控制器以及中断处理函数
	return 0;
}

static void irq_domain_set_mapping(struct irq_domain *domain, irq_hw_number_t hwirq, struct irq_data *irq_data)
{
	if (hwirq < domain->revmap_size) {
		domain->linear_revmap[hwirq] = irq_data->irq;
		//当硬件中断号小于最大的映射范围时,使用线性映射
	} else {
		...
		radix_tree_insert(domain->revmap_tree, hwirq, irq_data);
		//当硬件中断号过大时,使用基数树创建映射
		...
	}
}

当完成上述操作之后,MIPS处理器的中断控制器便已完成了初始化。

由上边的代码可以知道,内核为MIPS处理器的中断控制器提供了8个中断处理,且中断处理函数为:

void handle_percpu_irq(struct irq_desc *desc)
{
	struct irq_chip *chip = irq_desc_get_chip(desc);
	//此时获取的中断控制器为MIPS中断控制器

	__kstat_incr_irqs_this_cpu(desc);
	if (chip->irq_ack)
		chip->irq_ack(&desc->irq_data);
		//中断响应
	handle_irq_event_percpu(desc);
	//中断处理
	if (chip->irq_eoi)
		chip->irq_eoi(&desc->irq_data);
}

irqreturn_t handle_irq_event_percpu(struct irq_desc *desc)
{
	irqreturn_t retval;
	unsigned int flags = 0;

	retval = __handle_irq_event_percpu(desc, &flags);
	//中断处理
	add_interrupt_randomness(desc->irq_data.irq, flags);
	if (!noirqdebug)
		note_interrupt(desc, retval);
	return retval;
}

irqreturn_t __handle_irq_event_percpu(struct 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) {
	//遍历desc中的每一个action对象,在MIPS中断控制器中,将该action对象设置为全局变量chained_action,其原型如下:
	//struct irqaction chained_action = {
	//	.handler = bad_chained_irq,
	//};
	//该中断处理表示当前中断不应该调用irq_action
		
		irqreturn_t res;

		if (irq_setting_can_thread(desc) && !(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
			lockdep_hardirq_threaded();

		trace_irq_handler_entry(irq, action);
		res = action->handler(irq, action->dev_id);
		//执行action对象中的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);
				//唤醒action对象中的线程函数
			case IRQ_HANDLED:
				*flags |= action->flags;
				break;
			default:
				break;
		}
		retval |= res;
	}
	return retval;
}

通过上边的代码,可以看出初始化MIPS处理器中的中断控制器时,并没有为每个中断提供正确的中断处理函数。因此,需要根据CPU实际的应用情况来设置中断处理函数。

假设当前存在中断级联的情况,接下来,分析第二级中断控制器的初始化。

关于下级中断控制器所要使用的上级中断控制器的中断引脚信息(即硬件中断号)一般会在设备树中的设备节点说明,因此当需要设置这些中断引脚的中断处理函数时,可以在下级中断控制器的初始化过程中,通过当前设备节点找到上级控制器的设备节点(即父设备节点),并根据硬件中断号获取映射到的软件中断号,从而设置中断处理函数。比如在二级中断控制器的初始化过程中:

//of_irq_get_byname函数由二级中断控制器liointc_of_init函数调用

int of_irq_get_byname(struct device_node *dev, const char *name)
{
//根据dts中的interrupt-names属性获取所对应的软件中断号
	int index;
	if (unlikely(!name))
		return -EINVAL;
	index = of_property_match_string(dev, "interrupt-names", name);
	//该函数通过从设备节点中的interrupts-names字符数组中获取与name字符串相同的数组对象的索引,即数组下标
	if (index < 0)
		return index;

	return of_irq_get(dev, index);
}

int of_irq_get(struct device_node *dev, int index)
{
//获取中断号
	int rc;
	struct of_phandle_args oirq;
	struct irq_domain *domain;

	rc = of_irq_parse_one(dev, index, &oirq);
	//该函数主要用来解析设备节点中的属性,并赋值到oirq中,其中,需要注意的是oirq中的设备节点属性np被赋值为当前设备节点的父设备节点
	...
	domain = irq_find_host(oirq.np);
	//该函数主要用来查找传入的设备节点的中断域
	//查找的方式主要通过遍历irq_domain_list全局链表来遍历每个中断域,并利用中断域的匹配方法,即match()钩子函数进行匹配,从而找到所需的中断域
	//根据上述分析,此时的中断域为当前中断控制器的父设备节点的中断域
	...
	return irq_create_of_mapping(&oirq);
	//返回软件中断号
}

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;
	of_phandle_args_to_fwspec(irq_data->np, irq_data->args, irq_data->args_count, &fwspec);
	//该函数主要将irq_data中的属性赋值给fwspec,这里所传入的irq_data参数实际为上边代码中的oirq变量
	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 heirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;

	if (fwspec->fwnode) {
		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
		//获取中断域domain对象,该函数执行的方法与上边代码中的irq_find_host函数一样,只是irq_find_host对该函数进行了封装
		if (!domain)
			domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
	} else {
		domain = irq_default_domain;
	}

	if (!domain) {
		pr_warn("no irq domain found for %s !\n", of_node_full_name(to_of_node(fwspec->fwnode)));
		return 0;
	}

	if (irq_domain_translate(domain, fwspec, &hwirq, &type))
	//该函数主要执行中断域中的translate和xlate两个函数,并将fwspec-param[0]赋值给hwirq
		return 0;
	...
	virq = irq_find_mapping(domain, hwirq);
	//根据硬件中断号,返回其所映射的软件中断号,该函数执行成功的前提为已经硬件中断与软件中断的映射关系已经成立
	//否则需要执行下面的步骤,会在后边进行说明
	//根据上边的分析,可知此时硬件中断与软件中断的映射关系已经成立
	...
	return virq;
}

static int irq_domain_translate(struct irq_domian *d, struct irq_fwspec *fwspec, irq_hw_number_t *hwirq, unsigned int type)
{
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
	if (d->ops->translate)
		return d->ops->translate(d, fwspec, hwirq, type);
#endif
	if (d->ops->xlate)
		return d->ops->xlate(d, to_of_node(fwspec->fwnode), fwspec->param, fwspec->param_count, hwirq, type);
		//该函数主要对type参数中的属性进行赋值

	*hwirq = fwspec->param[0];
	//将中断号赋值给hwirq
	return 0;
}

unsigned int irq_find_mapping()
{
	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;
	}

	if (hwirq < domain->revmap_size)
		return domain->linear_revmap[hwirq];
	//当中段映射采用线性映射时,将根据linear_revmap数组返回软件中断号
	
	rcu_read_lock();
	data = radix_tree_lookup(&domain->revmap_tree, hwirq);
	//基数树映射
	rcu_read_unlock();
	return data ? data->irq : 0;
}

//当根据硬件中断号获取到软件中断号之后,便可以设置该硬件中断引脚的中断处理程序,比如:
irq_set_chained_handler_and_data(irq_number, irq_handler, (void *)p_data);

按照上述的方法,便可以对MIPS处理器的中断处理程序进行修改,可以设置为级联情况下的中断处理程序。比如:

static void liointc_chained_handle_irq(struct irq_desc *desc)
{
	struct liointc_handler_data *handler = irq_desc_get_handler_data(desc);
	//return desc->common_data.handler_data;返回的属性在上述的irq_set_chained_handler_and_data函数中已被设置
	struct irq_chip *chip = irq_desc_get_chip(desc);
	//该中断控制器当前中断控制器的上级中断控制器
	struct irq_chip_generic *gc = handler->priv->gc;
	//当前中断控制器
	...

	while (pending) {
		int bit = __ffs(pending);
	
		generic_handle_irq(irq_find_mapping(gc->domain, bit));
		//此时传入的domain参数为级联中断控制器的中断域,进行中断处理
		pending &= ~BIT(bit);	
	}

	chained_irq_exit(chip, desc);
	//中断处理结束
} 

通过上边的分析,可以看出当出现中断控制器级联的情况时,当MIPS处理器中的中断控制器接收到中断信号时,该中断会交由二级中断控制器所提供的中断处理函数来处理。

与此同时,二级中断控制器也需要创建所需的中断域。比如:

domain = irq_domain_add_linear(node, 32, &irq_generic_chip_ops, priv);

注意,该函数只是进行了中断域的分配,以及添加到全局中断域链表中,而并没有为中断域建立硬件中断号与软件中断号之间的映射关系。另外一点,使用该函数创建中断域的过程当中,如果内核检查到中断域存在alloc操作,则将该中断域的标志位置位hirearchy。

当二级中断控制器存在三级中断控制器时,三级与二级的中断引脚相连接,因此可采用上述的方式,在三级中断控制器的初始化中设置这些中所使用的中断引脚的中断函数。比如:

//irq_of_parse_and_map()函数由三级中断控制器的初始化htvec_of_init()函数调用
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
//传入的dev参数为当前中断控制器的设备节点
	struct of_phandle_args oirq;
	
	if (of_irq_parse_one(dev, index, &oirq))
	//该函数获取所需信息填充到oirq中,比如当前设备节点的父设备节点会存放在oirq->np属性中等
		return 0;

	return irq_create_of_mapping(&oirq);
	//注意此时,二级中断控制器中的中断域并没有设置,因此该函数中将会执行其他步骤来获取软件中断号,另外,二级中断域不具有hirearchy标志
}

//irq_create_of_mapping()函数接下来的执行流程:

unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec)
{
	...
	//下面两种方式为中断映射未建立的前提下会执行
	if (irq_domain_is_hierarchy(domain)) {
	//检查中段域是否具有hirearchy标志;
		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, fwspec);
		//当中断域标志具有hierarchy时,会执行该函数
		//该函数执行__irq_domain_alloc_irqs(domain, -1, nr_irqs, node, arg, false, NULL)函数;
		...
	} else {
		virq = irq_create_mapping(domain, hwirq);
		//否则,利用该函数创建中断映射
		...
	}
	...
	return virq;
	//返回软件中断号
}

//由上边的分析可知,二级中断控制器的中断域不存在hierarchy标志

unsigned int irq_create_mapping(struct irq_domain *domain, irq_hw_number_t hwirq)
{
	struct device_node *of_node;
	int virq;
	...
	of_node = irq_domain_get_of_node(domain);
	//根据上边的分析,可知此时的节点为当前中断控制器的父设备节点
	...
	virq = irq_domain_alloc_desc(-1, 1, hwirq, of_node_to_nid(of_node), NULL);
	//该函数中最后通过执行alloc_descs函数来获得软件中断号
	...
	if (irq_domain_associate(domain, virq, hwirq)) {
	//关联软件中断号与硬件中断号
		irq_free_desc(virq);
		return 0;
	}
	...
	return virq;
}

int irq_domain_alloc_descs(int virq, unsigned int cnt, irq_hw_number_t hwirq, int node, const struct irq_affinity_desc *affinity)
{
	unsigned int hint;

	if (virq >= 0) {
		virq = __irq_alloc_descs(virq, virq, cnt, node, THIS_MODULE, affinity);
	} else {
		hint = hwirq %nr_irqs;
		if (hint == 0)
			hint++;
		virq = __irq_alloc_descs(-1, hint, cnt, node, THIS_MODULE, afffinity);
		if (virq <= 0 && hint > 1) {
			virq = __irq_alloc_descs(-1, 1, cnt, node, THIS_MODULE, affinity);
		}
	}
	
	return virq;
}

int __ref __irq_alloc_descs(int irq, unsigned int from , unsigned int cnt, int node, struct module *owner, const struct irq_affinity_desc *affinity)
{
//假设此时的传参分别为:irq = -1, from = hwirq, cnt = 1, affinity = NULL
	int start, ret;

	if (!cnt)
		return -EINVAL;

	if (irq >= 0) {
		if (from > irq)
			return -EINVAL;
		from = irq;
	} else {
		from = arch_dynirq_loweq_bound(from);
		//return from;
	}

	mutex_lock(&sparse_irq_lock);
	start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS, from, cnt, 0);
	//寻找allocated_irqs位图中可用的中断号的起始位置
	//关于allocated_irqs,其原型定义如下:
	//#define	DECLARE_BITMAP(name, bits)	unsigned long name[BITS_TO_LONGS(bits)]
	//static DECLARE_BITMAP(allocated_irqs, IRQ_BITMAP_BITS);
	//对上述的宏定义分后,得知allocated_irqs变量为长度为2的无常整型数组
	
	ret = -EEXIST;
	if (irq >= 0 && start != irq)
		goto unlock;

	if (start + cnt > nr_irqs) {
		ret = irq_expand_nr_irqs(start + cnt);
		if (net)
			goto unlock;
	}
	ret = alloc_descs(start, cnt, node, affinity, owner);
unlock:
	mutex_unlock(&sparse_irq_lock);
	return ret;
}

static inline unsigned long bitmap_find_next_zero_area(unsigned long *map, unsigned long size, unsigned long start, unsigned int nr, unsigned long align_mask)
{
	return bitmap_find_next_zero_area_off(map, size, start, nr, align_mask, 0);
}

unsigned long bitmap_find_next_zero_area_off(unsigned long *map, unsigned long size, unsigned long start, unsigned int nr, unsigned long align_mask, unsigned long align_offset)
{
	unsigned long index, end, i;
again:
	index = find_next_zero_bit(map, size, start);
	index = __ALIGN_MASK(index + align_offset, align_mask) - align_offset;

	end = index + nr;
	if (end > size)
		return end;
	i = find_next_bit(map, end, index);
	if (i < end) {
		start = i + 1;
		goto again;
	}
	return index;
}

//find_next_zero_bit();
static unsigned long _find_next_bit(const unsigned long *addr1, const unsigned long *addr2, unsigned long nbits, unsigned long start, unsigned long invert, unsigned long le)
{
//假设当前addr1为全局变量allocated_irqs,addr2为空,nbits为128,start为24,invert为全1,le为0
	unsigned long tmp, mask;
	
	if (unlikely(start >= nbits))
		return nbits;
	tmp = addr1[start / BITS_PER_LONG];
	//这里,BITS_PER_LONG表示long型的bit数,即64
	//因此,tmp表示start所在的数组元素的值
	if (addr)
		tmp &= addr2[start / BITS_PER_LONG];
	tmp ^= invert;
	//tmp异或操作之后,其中为0的bit位表示当前中断没有使用

	mask = BITMAP_FIRST_WORD_MASK(start);
	//该函数是将整型start用位图的形式来表示,即mask掩码
	if (le)
		mask = swab(mask);
	tmp &= mask;
	//此时,如果tmp不为0,则说明start中断号正在使用

	start = round_down(start, BITS_PER_LONG);
	//已经跳过的bit位个数
	while (!tmp) {
	//当tmp为0时,从数组对象的第二个元素开始查找
		start += BITS_PER_LONG;
		if (start >= nbits)
			return nbits;

		tmp = addr1[start / BITS_PER_LONG];
		if (addr2)
			tmp &= addr2[start / BITS_PER_LONG];
		tmp ^= invert;
	}
	if (le)
		tmp = swab(tmp);

	return min(start + __ffs(tmp), nbits);
}

static __always_inline unsigned long __ffs(unsigned long word)
{
	return __fls(word & -word);
}

static __always_inline unsigned long __fls(unsigned long word)
{
//假设word从第n位到第0位全为0,该函数主要用来获取数字(n+1)
	int num;
#if BITS_PER_LONG == 64
	if((word & 0xffffffff) == 0) {
		num += 32;
		word >>= 32;
	}
#endif
	if ((word & 0xffff) == 0) {
		num += 16;
		word >>= 16;
	}
	if ((word & 0xff) == 0) {
		num += 8;
		word >>= 8;
	}
	if ((word & 0xf) == 0) {
		num += 4;
		word >>= 4;
	}
	if ((word & 0x3) == 0) {
		num += 2;
		word >>= 2;
	}
	if ((word & 0x1) == 0)
		num += 1;
	return num;
}

static inline int alloc_descs(unsigned int start, unsigned int cnt, int node, const struct irq_affinity_desc *affinity, struct module *owner)
{
	u32 i;

	for (i = 0; i < cnt; i++) {
		struct irq_desc *desc = irq_to_desc(start + i);
		//该函数会在irq_desc全局数组中开始查找start+i所对应的struct irq_desc结构体对象
		desc->owner = owner;
	}
	bitmap_set(allocated_irqs, start, cnt);
	//将allocated_irqs数组中从start开始到start_cnt位全部置位1
	return start;
}
//通过上述代码,可以看出软件中断号的申请是通过对全局变量allocated_irqs的操作来完成,该变量为unsigned long *类型。
//其中,每一个bit位代表一个中断,假设allocated_irqs数组长度为n,则allocated_irqs数组可表示的中断个数为128 * n。
//因此,当allocated_irqs数组中的一位为空时,则代表该软件中断号可用



int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base, unsigned int nr_irqs, int node, void *arg, bool realloc, const struct irq_affinity_desc *affinity)
//当中断域为级联形式时,调用该函数来申请软件中断号
{
	int i, ret, virq;

	if (domain ==  NULL) {
		domain == irq_default_domain;
		if (WARN(!domain, "domain is NULL; cannot allocate IRQ\n"))
			return -EINVAL;
	}

	if (realloc && irq_base >= 0) {
		virq = irq_base;
	} else {
		virq = irq_domain_alloc_descs(irq_base, nr_irqs, 0, node, affinity);
		//申请软件中断号
		if (virq < 0) {
			pr_debug("cannot allocate IRQ(base %d, count %d)\n", irq_base, nr_irqs);
			return virq;
		}
	}

	if (irq_domain_alloc_irq_data(domain, virq, nr_irqs)) 
	...

	ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
	//关联软件中断号与硬件中断号
	...
	return virq;
}

int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain, unsigned int irq_base, unsigned int nr_irqs, void *arg)
{
	if (!domain->ops->alloc)
	...
	return domain->ops->alloc(domain, irq_base, nr_irqs, arg); 
	//执行中断域操作结构体中的alloc钩子函数
}

此外,内核还提供了一种用于中断级联的中断域级联方式。比如:

//priv->pic_domain = irq_domain_create_hierarchy(parent_domain, 0, PCI_COUNT, of_node_to_fwnode(node), &pch_pic_domain_ops, priv)

struct irq_domain *irq_domain_create_hierarchy(struct irq_domain *parent, unsigned int flags, unsigned int size, struct fwnode_handle *fwnode, const struct irq_domian_ops *ops, void *host_data)
{
	struct irq_domain *domain;

	if (size)
		domain = irq_domain_create_linear(fwnode, size, ops, host_data);
	else
		domain = irq_domain_create_tree(fwnode, ops, host_data);
	if (domain) {
		domain->parent = parent;
		//设置当前中断域的父节点
		domain->flags |= flags;
	}

	return domain;
} 

关于中断域的应用,可以像MIPS处理器中断控制器一样,直接设置中断域的映射,以及中断域中每个中断号的中断处理等。

而中断大多数是与设备相关的,如果中断域与中断号远多于设备,则预先设置所有中断域中的中断号可能是不必要的。因此,设备中断可在注册设备驱动使用时进行注册。以PCI设备为例,为了在注册设备驱动的过程中需要利用桥片来获取设备中断号,而这一操作由厂商自行实现。目前,发现可以通过两种方式来实现,分别是:

  1. 在挂接设备时,从PCI控制器中获得设备中断号,并存储在struct pci_dev->irq属性中,因此可直接通过该属性直接获取;
  2. 通过dts,获取当前设备所属的中断域,以及中断域的起始中断号,从而根据硬件中断号的偏移获得软件中断号,从而进行设备中断的注册;

关于上述的第二种方法,是在注册驱动时,如果驱动关联的总线也具有探测设备的功能,则此时会通过与总线关联的桥片来获取中断号,实际上这里的桥片可看做是PCI控制器。具体实现如下:

static int pci_device_probe(struct device *dev)
{
	...
	pci_assign_irq(pci_dev);
	...
}

void pci_assign_irq(struct pci_dev *dev)
{
	u8 pin;
	u8 slot = -1;
	int irq = 0;
	struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);
	...
	pci_read_config_byte(dev, PIC_INTERRUPT_PIN, &pin);
	//读取设备的中断引脚
	if (pin > 4)
		pin = 1;

	if (pin) {
		if (hbrg->swizzle_irq)
			slot = (*(hbrg->swizzle_irq))(dev, &pin);

		irq = (*(hbrg->map_irq))(dev, slot, pin);
		//该钩子函数用来映射当前设备中断
		if (irq == -1)
			irq = 0;
	}
	dev->irq = irq;
	...
}

//假设map_irq()钩子函数的实现如下:
static int loongson_map_irq(const struct pci_dev *dev, u8 slot, u8 pin)
{
	int irq;
	u8 val;

	irq = of_irq_parse_and_map_pci(dev, slot, pin);
	if (irq > 0)
		return irq;
	...
}

int of_irq_parse_and_map_pci(const struct pci_dev *dev, u8 slot, u8 pin)
{
	struct of_phandle_args oirq;
	int ret;

	ret = of_irq_parse_pci(dev, &oirq);
	//对dev所指向的设备节点进行解析,并将解析的数据存放到struct of_phandle_args oirq结构体对象中,其中设备中断号存放在oirq->args数组中
	if (ret)
		return 0;

	return irq_create_of_mapping(&oirq);
	//根据中断映射,返回软件中断号
	//关于该函数在上述的过程中进行了分析,其中主要执行irq_create_fwspec_mapping函数,上述中只对函数中已建立了中断映射的部分进行了说明
	//接下来说明其他的情况
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值