【linux kernel】linux中断管理——中断管理框架(01)

linux中断管理——中断管理框架

文章目录

        • linux中断管理——中断管理框架
        • 一、中断管理框架简介
        • 二、中断管理框架源码分析
          • (2-1)中断管理框架下的驱动程序如何初始化
          • (2-2)中断管理框架如何解析设备树中的中断控制器信息
          • (2-3)linux如何把硬件中断号映射到linux内核的IRQ中断号

注:本文代码均出自linux内核版本:4.1.15

一、中断管理框架简介

​ 在之前的linux版本中,一颗SoC芯片的内部中断管理较为简单,但是随着技术发展,后来的SoC的内部中断变得复杂了,主要有以下几个特点:

(1)一颗SoC内部会有多个中断控制器

(2)每个中断控制器管理的中断源的数量很多。

(3)一颗SoC芯片内部的中断控制器呈现级联的情况。

​ 因此,面对日益复杂的中断管理,在linux 3.x以后便引入了irq domain管理框架。irq domain框架可以支持多个中断控制器,且支持设备树机制。

linux 内核使用struct irq_domain来描述一个中断控制器,定义如下(/include/linux/irqdomain.h):

struct irq_domain {
	struct list_head link;
	const char *name;
	const struct irq_domain_ops *ops;
	void *host_data;
	unsigned int flags;

	/* Optional data */
	struct device_node *of_node;
	struct irq_domain_chip_generic *gc;
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	struct irq_domain *parent;
#endif

	/* reverse map data. The linear map gets appended to the irq_domain */
	irq_hw_number_t hwirq_max;
	unsigned int revmap_direct_max_irq;
	unsigned int revmap_size;
	struct radix_tree_root revmap_tree;
	unsigned int linear_revmap[];
};

​ linux内核将中断控制器的驱动代码放于目录:(/drivers/irqchip)下。从该目录下的makefile文件可知,该目录下有两类文件:

(1)用于描述中断框架的文件:irqchip.c、irq-gic.c、irq-gic-common.c等。

(2)与具体SoC内部中断管理器相关的描述文件:文件很多,见linux源码目录。

irq-gic.c文件是符合GIC-V2规范的驱动;irq-gic-v3.c文件是符合GIC-V3规范的驱动代码。

二、中断管理框架源码分析

​ 本小节分析以下三个问题:

(1)中断管理框架下的驱动程序如何初始化?

(2)中断管理框架如何解析设备树中关于中断控制器信息?

(3)linux如何把硬件中断号映射到linux内核的IRQ中断号?


(2-1)中断管理框架下的驱动程序如何初始化

​ 首先,在linux 内核中使用IRQCHIP_DECLARE这个宏函数被不同的irqchip驱动程序使用来声明它们的设备树compatible字符串和初始化函数之间的关联。定义如下(/drivers/irqchip/irqchip.h):

#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
  • name :中断控制器名称
  • compat :设备树compatible字符串
  • fn :初始化函数

​ 在(/drivers/irqchip/irq-gic.c)文件中有以下代码片段:

IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
IRQCHIP_DECLARE(arm11mp_gic, "arm,arm11mp-gic", gic_of_init);
IRQCHIP_DECLARE(arm1176jzf_dc_gic, "arm,arm1176jzf-devchip-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);
IRQCHIP_DECLARE(msm_8660_qgic, "qcom,msm-8660-qgic", gic_of_init);
IRQCHIP_DECLARE(msm_qgic2, "qcom,msm-qgic2", gic_of_init);

​ 可见初始化函数为gic_of_init。定义如下(/drivers/irqchip/irq-gic.c):

static 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;

	if (WARN_ON(!node))
		return -ENODEV;

	dist_base = of_iomap(node, 0);
	WARN(!dist_base, "unable to map gic dist registers\n");

	cpu_base = of_iomap(node, 1);
	WARN(!cpu_base, "unable to map gic cpu registers\n");

	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);
	if (!gic_cnt)
		gic_init_physaddr(node);

	if (parent) {
		irq = irq_of_parse_and_map(node, 0);
		gic_cascade_irq(gic_cnt, irq);
	}

	if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
		gicv2m_of_init(node, gic_data[gic_cnt].domain);

	gic_cnt++;
	return 0;
}

在以上代码中,gic_init_bases()比较关键(该函数较长)(/drivers/irqchip/irq-gic.c):

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
			   void __iomem *dist_base, void __iomem *cpu_base,
			   u32 percpu_offset, struct device_node *node)
{
	irq_hw_number_t hwirq_base;
	struct gic_chip_data *gic;
	int gic_irqs, irq_base, i;

	BUG_ON(gic_nr >= MAX_GIC_NR);

	gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKED
	if (percpu_offset) { /* Frankein-GIC without banked registers... */
		unsigned int cpu;

		gic->dist_base.percpu_base = alloc_percpu(void __iomem *);
		gic->cpu_base.percpu_base = alloc_percpu(void __iomem *);
		if (WARN_ON(!gic->dist_base.percpu_base ||
			    !gic->cpu_base.percpu_base)) {
			free_percpu(gic->dist_base.percpu_base);
			free_percpu(gic->cpu_base.percpu_base);
			return;
		}

		for_each_possible_cpu(cpu) {
			u32 mpidr = cpu_logical_map(cpu);
			u32 core_id = MPIDR_AFFINITY_LEVEL(mpidr, 0);
			unsigned long offset = percpu_offset * core_id;
			*per_cpu_ptr(gic->dist_base.percpu_base, cpu) = dist_base + offset;
			*per_cpu_ptr(gic->cpu_base.percpu_base, cpu) = cpu_base + offset;
		}

		gic_set_base_accessor(gic, gic_get_percpu_base);
	} else
#endif
	{			/* Normal, sane GIC... */
		WARN(percpu_offset,
		     "GIC_NON_BANKED not enabled, ignoring %08x offset!",
		     percpu_offset);
		gic->dist_base.common_base = dist_base;
		gic->cpu_base.common_base = cpu_base;
		gic_set_base_accessor(gic, gic_get_common_base);
	}

	/*
	 * Initialize the CPU interface map to all CPUs.
	 * It will be refined as each CPU probes its ID.
	 */
	for (i = 0; i < NR_GIC_CPU_IF; i++)
		gic_cpu_map[i] = 0xff;

	/*
	 * Find out how many interrupts are supported.
	 * The GIC only supports up to 1020 interrupt sources.
	 */
	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs;

	if (node) {		/* DT case */
		gic->domain = irq_domain_add_linear(node, gic_irqs,
						    &gic_irq_domain_hierarchy_ops,
						    gic);
	} else {		/* Non-DT case */
		/*
		 * For primary GICs, skip over SGIs.
		 * For secondary GICs, skip over PPIs, too.
		 */
		if (gic_nr == 0 && (irq_start & 31) > 0) {
			hwirq_base = 16;
			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());
		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(node, gic_irqs, irq_base,
					hwirq_base, &gic_irq_domain_ops, gic);
	}

	if (WARN_ON(!gic->domain))
		return;

	if (gic_nr == 0) {
#ifdef CONFIG_SMP
		set_smp_cross_call(gic_raise_softirq);
		register_cpu_notifier(&gic_cpu_notifier);
#endif
		set_handle_irq(gic_handle_irq);
	}

	gic_dist_init(gic);
	gic_cpu_init(gic);
	gic_pm_init(gic);
}

上述代码中使用:

	gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
	gic_irqs = (gic_irqs + 1) * 32;
	if (gic_irqs > 1020)
		gic_irqs = 1020;
	gic->gic_irqs = gic_irqs;

计算GIC控制器最多支持的中断源的个数,GIC-V2规范中最多支持1024个中断源。

使用irq_domain_add_linear注册一个irq_domain。该函数运行完成后,irq_domain将被加入到全局链表irq_domain_list中。

总结一下:总而言之,从其目录下的源文件可见,具体驱动的初始化由IRQCHIP_DECLARE承担。

(2-2)中断管理框架如何解析设备树中的中断控制器信息

​ 这要以ARM架构的设备树解析机制为例。就由customize_machine()函数看起了,该函数定义如下(/arch/arm/kernel/setup.c):

static int __init customize_machine(void)
{
	/*
	 * customizes platform devices, or adds new ones
	 * On DT based machines, we fall back to populating the
	 * machine from the device tree, if no callback is provided,
	 * otherwise we would always need an init_machine callback.
	 */
	of_iommu_init();
	if (machine_desc->init_machine)
		machine_desc->init_machine();
#ifdef CONFIG_OF
	else
		of_platform_populate(NULL, of_default_bus_match_table,
					NULL, NULL);
#endif
	return 0;
}
arch_initcall(customize_machine);

接着,函数调用链如下:
【linux kernel】linux中断管理——中断管理框架(01)_第1张图片

irq_of_parser_and_map()解析并映射一个中断到linux virq空间,定义如下(/drivers/of/irq.c):

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))
		return 0;

	return irq_create_of_mapping(&oirq);
}
EXPORT_SYMBOL_GPL(irq_of_parse_and_map);

第5行调用of_irq_parse_one()用于解析DTS文件中定义的设备属性。

(2-3)linux如何把硬件中断号映射到linux内核的IRQ中断号

​ 在(2-2)小节中,irq_of_parse_and_map()函数中会调用irq_create_of_mapping()函数,定义如下:

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_domain *domain;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	int virq;

	domain = irq_data->np ? irq_find_host(irq_data->np) : irq_default_domain;
	if (!domain) {
		pr_warn("no irq domain found for %s !\n",
			of_node_full_name(irq_data->np));
		return 0;
	}

	/* If domain has no translation, then we assume interrupt line */
	if (domain->ops->xlate == NULL)
		hwirq = irq_data->args[0];
	else {
		if (domain->ops->xlate(domain, irq_data->np, irq_data->args,
					irq_data->args_count, &hwirq, &type))
			return 0;
	}

	if (irq_domain_is_hierarchy(domain)) {
		/*
		 * If we've already configured this interrupt,
		 * don't do it again, or hell will break loose.
		 */
		virq = irq_find_mapping(domain, hwirq);
		if (virq)
			return virq;

		virq = irq_domain_alloc_irqs(domain, 1, NUMA_NO_NODE, irq_data);
		if (virq <= 0)
			return 0;
	} else {
		/* Create mapping */
		virq = irq_create_mapping(domain, hwirq);
		if (!virq)
			return virq;
	}

	/* Set type if specified and different than the current one */
	if (type != IRQ_TYPE_NONE &&
	    type != irq_get_trigger_type(virq))
		irq_set_irq_type(virq, type);
	return virq;
}
EXPORT_SYMBOL_GPL(irq_create_of_mapping);

第8行代码:通过device node找到外设所属的中断控制器的irq_domain

第33行代码:irq_domain_alloc_irqs()是映射的核心函数。


irq_domain_alloc_irqs()这个映射函数较长,下回分析了。本回结束。。。

未完待续!

你可能感兴趣的:(小生聊【Linux,kernel】,linux,linux,kernel,C语言,中断管理,GIC中断控制器)