Linux IRQ Sub System II

IRQ Domain

概述

在 linux kernel中,我们使用下面两个ID来标识一个来自外设的中断:
1)IRQ number,即virq.CPU需要为每一个外设中断编号,我们称之IRQ Number。这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断。
2)HW interrupt ID,hwirq.对于 interrupt controller 而言,它收集了多个外设的中断信号并向上传递,因此,interrupt controller 需要对外设中断进行编码。Interrupt controller用 HW interrupt ID来标识外设的中断。在interrupt controller级联的情况下,仅仅用HW interrupt ID已经不能唯一标识一个外设中断,还需要知道该HW interrupt ID所属的interrupt controller。
(hwirq – > root interrupt controller,hwirq’ – > sub interrupt controller……)
这样,CPU和interrupt controller在标识中断上就有了一些不同的概念,但是,对于驱动工程师而言,我们和CPU视角是一样的,我们只希望得到一个IRQ number(virq),而不关系具体是那个interrupt controller上的那个HW interrupt ID(hwirq,hwirq’)。这样一个好处是在中断相关的硬件发生变化的时候,驱动软件不需要修改。因此,linux kernel中的中断子系统需要提供一个将HW interrupt ID映射到IRQ number上来的机制,就是“域”,即 irq domain。
随着linux kernel的发展,将interrupt controller抽象成irqchip这个概念越来越流行,甚至GPIO controller也已经被看成一个interrupt controller chip,这样,系统中至少有两个中断控制器了(如mt6853中,pio and gic),一个传统意义的中断控制器,一个是GPIO controller type的中断控制器。随着系统复杂度加大,外设中断数据增加,实际上系统可以需要多个中断控制器进行级联,面对这样的趋势,linux kernel工程师如何应对?答案就是irq domain这个概念。
irq domain,顾名思义,就是领域,范围的意思,也就是说,任何的定义出了这个范围就没有意义了。系统中所有的interrupt controller会形成树状结构,对于每个 interrupt controller 都可以连接若干个外设的中断请求(我们称之interrupt source),interrupt controller会对连接其上的interrupt source(根据其在Interrupt controller中物理特性)进行编号(也就是HW interrupt ID了)。但这个编号仅仅限制在本interrupt controller范围内。
在这里插入图片描述

接口

3.2.1向系统注册irq domain
Radix Tree map。建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。实际上,我们MT6853kernel-4.14就是使用基数树的方式。注册 irq_domain,其接口API如下:

IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);		err=gic_init_bases(dist_base,rdist_regs,nr_redist_regions,redist_stride,&node->fwnode);
		gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,&gic_data);
					return __irq_domain_add(fwnode, 0, ~0, 0, ops, host_data);

3.2.2为irq domain创建映射
上节的内容主要是向系统注册一个irq domain,具体HW interrupt ID和IRQ number的映射关系都是空的,因此,具体各个irq domain如何管理映射所需要的database还是需要建立的。对于Radix Tree map,我们要把那个反应IRQ number和HW interrupt ID的Radix tree建立起来。创建映射接口函数:
通常,一个普通设备的device tree node已经描述了足够的中断信息,在这种情况下,该设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,具体代码如下:

/**
 * irq_of_parse_and_map - Parse and map an interrupt into linux virq space
 * @dev: Device node of the device whose interrupt is to be mapped
 * @index: Index of the interrupt to map
 *
 * This function is a wrapper that chains of_irq_parse_one() and
 * irq_create_of_mapping() to make things easier to callers
 */
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
	struct of_phandle_args oirq;
	/*分析device node中的interrupt相关属性*/
	if (of_irq_parse_one(dev, index, &oirq))
		return 0;
	/* 创建映射,并返回对应的 IRQ number */
	return irq_create_of_mapping(&oirq);
}

对于一个使用Device tree的普通驱动程序,基本上初始化需要调用irq_of_parse_and_map获取IRQ number,然后调用request_threaded_irq申请中断handler。
比如在我们FP code中;

node = of_find_compatible_node(NULL, NULL, FP_IRQ_OF);//关联dts节点
fp_dev->int_port = irq_of_parse_and_map(node, 0);//通过dts中irq的描述获取virq;

数据结构描述

3.1 irq domain 的callback接口
struct irq_domain_ops 抽象了一个irq domain 的 callback 函数,定义如下:

struct irq_domain_ops {
	int (*match)(struct irq_domain *d, struct device_node *node,
		     enum irq_domain_bus_token bus_token);
	int (*select)(struct irq_domain *d, struct irq_fwspec *fwspec,
		      enum irq_domain_bus_token bus_token);
	int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
	void (*unmap)(struct irq_domain *d, unsigned int virq);
	int (*xlate)(struct irq_domain *d, struct device_node *node,
		     const u32 *intspec, unsigned int intsize,
		     unsigned long *out_hwirq, unsigned int *out_type);

#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
	/* extended V2 interfaces to support hierarchy irq_domains */
	int (*alloc)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs, void *arg);
	void (*free)(struct irq_domain *d, unsigned int virq,
		     unsigned int nr_irqs);
	void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
	void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
	int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
			 unsigned long *out_hwirq, unsigned int *out_type);
#endif
};

我们先看 translate函数,语义是翻译(translate)的意思,那么到底翻译什么呢?在DTS文件中,各个使用中断的device node会通过一些属性(例如interrupts和interrupt-parent属性)来提供中断信息给kernel以便kernel可以正确的进行driver的初始化动作。这里,interrupts属性所表示的interrupt specifier只能由具体的interrupt controller(也就是irq domain)来解析。而translate函数就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。
比如,

	if (is_fwnode_irqchip(fwspec->fwnode)) {
		if(fwspec->param_count != 2)
			return -EINVAL;

		*hwirq = fwspec->param[0];
		*type = fwspec->param[1];
		return 0;
	}
&silead_fp {
    interrupt-parent = <&pio>;
    interrupts = <5 IRQ_TYPE_EDGE_RISING>;
    status = "okay";
};

可见,通过translate函数获取dts里面中断属性中的hwirq和触发类型;
3.2 irq domain

在内核中,irq domain 的概念由 struct irq_domain 表示

/* Hardware interrupt number translation object */
struct irq_domain {
    struct list_head link;
    const char *name;
    const struct irq_domain_ops *ops;
    void *host_data;
    unsigned int flags;
    unsigned int mapcount;

    /* Optional data */
    struct fwnode_handle *fwnode;
    enum irq_domain_bus_token bus_token;
    struct irq_domain_chip_generic *gc;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
    struct irq_domain *parent;
#endif
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
    struct dentry       *debugfs_file;
#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内核中,所有的irq domain被挂入一个全局链表,链表头定义如下:
static LIST_HEAD(irq_domain_list);
list_add(&domain->link, &irq_domain_list);
struct irq_domain 中的link成员就是挂入这个队列的节点。通过 irq_domain_list 这个指针,可以获取整个系统中 HW interrupt ID 和 IRQ number的mapping DB 。host_data 定义了底层 interrupt controller 使用的私有数据,和具体的interrupt controller相关(对于GIC,该指针指向一个struct gic_chip_data 数据结构)。
对于Radix Tree map:
1)linear_revmap 没有用,revmap_size等于0。
2)hwirq_max 没有用,设定为一个最大值。
3)revmap_direct_max_irq 没有用,设定为0。
4)revmap_tree 指向Radix tree的root node。

中断在Device Tree 的描述

想要进行映射,首先要了解interrupt controller的拓扑结构。系统中的interrupt controller的拓扑结构以及其interrupt request line的分配情况(分配给哪一个具体的外设)都在Device Tree Source 文件中通过下面的属性给出了描述,这里简单总结一下:
用我们FP中的例子,

&silead_fp {
    interrupt-parent = <&pio>;
    interrupts = <5 IRQ_TYPE_EDGE_RISING>;/* hwirq:5;irq type:IRQ_TYPE_EDGE_RISING */
    status = "okay";
};

对于那些产生中断的外设,我们需要定义interrupt-parent和interrupts属性:
1)interrupt-parent。表明该外设的interrupt request line物理的连接到了哪一个中断控制器上
2)interrupts。这个属性描述了具体该外设产生的interrupt的细节信息(也就是传说中的interrupt specifier)。例如:HW interrupt ID(由该外设的device node中的interrupt-parent指向的interrupt controller解析)、interrupt触发类型等。

对于Interrupt controller,我们需要定义interrupt-controller和#interrupt-cells的属性:

  pio: pinctrl {
        compatible = "mediatek,mt6853-pinctrl";
        reg_bases = <&gpio>,
                <&iocfg_rb>,
                <&iocfg_rm>,
                <&iocfg_bm>,
                <&iocfg_bl>,
                <&iocfg_br>,
                <&iocfg_lm>,
                <&iocfg_rt>,
                <&iocfg_tl>;
        reg_base_eint = <&eint>;
        pins-are-numbered;
        gpio-controller;
        gpio-ranges = <&pio 0 0 203>;
        #gpio-cells = <2>;
        interrupt-controller;
        #interrupt-cells = <2>;/* 用2个32位的数据描述中断 ,比如, interrupts = <5 IRQ_TYPE_EDGE_RISING>;*/
        interrupts = <GIC_SPI 212 IRQ_TYPE_LEVEL_HIGH>;/* 从拓扑结构看,这个是有gic中#interrupt-cells 的属性值决定 */
        interrupt-parent = <&gic>;/* root intc*/
    };

1)interrupt-controller。表明该 device node 就是一个中断控制器,这里 pio node 既是 GPIO控制器,也是中断控制器;
2)#interrupt-cells。该中断控制器用多少个cell(一个cell就是一个32-bit的单元)描述一个外设的interrupt request line。?具体每个cell表示什么样的含义由interrupt controller自己定义。
3)interrupts 和 interrupt-parent。对于那些不是root 的interrupt controller,其本身也是作为一个产生中断的外设连接到其他的interrupt controller上,因此也需要定义interrupts和interrupt-parent的属性。

Mapping DB的建立

3.5.1 Hwirq 和 virq 映射创建流程
系统中HW interrupt ID(hwirq)和IRQ number(virq)的 mapping DB是在整个系统初始化的过程中建立起来的,过程如下:
1)DTS文件描述了系统中的interrupt controller以及外设IRQ的拓扑结构,在linux kernel启动的时候,由bootloader传递给kernel(实际传递的是DTB)。
2)在Device Tree初始化的时候,形成了系统内所有的device node的树状结构,当然其中包括所有和中断拓扑相关的数据结构
(所有的interrupt controller的node和使用中断的外设node)
3)在machine driver初始化的时候会调用 of_irq_init 函数,在该函数中会扫描所有interrupt controller的节点,并调用适合的
interrupt controller driver进行初始化。毫无疑问,初始化需要注意顺序,首先初始化root,然后first level,second level,最后是leaf node。
在初始化的过程中,一般会调用上节中的接口函数向系统增加 irq domain。有些interrupt controller会在其driver初始化的过程中创建映射

flow:kernel 初始化过程 mt6853_r_tee/kernel-4.14$ vim init/main.c +638
	asmlinkage __visible void __init start_kernel(void)
		init_IRQ();
			irqchip_init();/* mt6853_r_tee/kernel-4.14$ vim arch/arm64/kernel/irq.c +96 */
				of_irq_init(__irqchip_of_table);/* mt6853_r_tee/kernel-4.14$ vim drivers/irqchip/irqchip.c +29,dtb数据置于__irqchip_of_table中*/
					void __init of_irq_init(const struct of_device_id *matches)/* mt6853_r_tee/kernel-4.14$ vim drivers/of/irq.c +485,Scan and init matching interrupt controllers in DT*/

4)在各个driver初始化的过程中,创建映射
3.5.2 interrupt controller 初始化的过程中,注册 irq domain
我们以 GIC-V3 的代码为例。具体代码在

mt6853_r_tee/kernel-4.14/drivers/irqchip$ vim irq-gic-v3.c +1317
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);
	static int __init gicv3_of_init(struct device_node *node, struct device_node *parent)
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)					gic_data.domain=irq_domain_create_tree(handle,&gic_irq_domain_ops,&gic_data);/* 注册 irq domain*/
			return__irq_domain_add(fwnode,0, ~0, 0, ops, host_data);list_add(&domain->link, &irq_domain_list);/* 实则是把 irq domain 添加到链表 irq_domain_list 中 */

3.5.3 在各个硬件外设的驱动初始化过程中,创建HW interrupt ID和IRQ number的映射关系
我们上面的描述过程中,已经提及:设备的驱动在初始化的时候可以调用 irq_of_parse_and_map 这个接口函数进行该device node中和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,具体代码如下:

flow:
/*
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gicv3_of_init);
	gic_of_setup_kvm_info(node);
		gic_v3_kvm_info.maint_irq = irq_of_parse_and_map(node, 0);
/**
 * irq_of_parse_and_map - Parse and map an interrupt into linux virq space
 * @dev: Device node of the device whose interrupt is to be mapped
 * @index: Index of the interrupt to map
 *
 * This function is a wrapper that chains of_irq_parse_one() and
 * irq_create_of_mapping() to make things easier to callers
 */
*/ ------ > 这一块 gic-v3 的暂时不用理会,在gic-v3初始化过程中这是属于 kvm部分的;
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
	struct of_phandle_args oirq;
	/* 分析device node中的interrupt相关属性 */
	if (of_irq_parse_one(dev, index, &oirq))
		return 0;

	return irq_create_of_mapping(&oirq);/* 创建映射 */
}

我们再来看看 irq_create_of_mapping 函数如何创建映射:

unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data)
{
	struct irq_fwspec fwspec;
	of_phandle_args_to_fwspec(irq_data, &fwspec);/* get dts 中 irq 数据,并置于 fwspec 中 */
	return irq_create_fwspec_mapping(&fwspec);
}

接下来重点分析一下 irq_create_fwspec_mapping

	if (fwspec->fwnode) {
		domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_WIRED);
		if (!domain)
			domain = irq_find_matching_fwspec(fwspec, DOMAIN_BUS_ANY);
	} else {
		domain = irq_default_domain;
	}	
	irq_find_matching_fwspec
		list_for_each_entry(h, &irq_domain_list, link) {
			if (h->ops->select && fwspec->param_count)
				rc = h->ops->select(h, fwspec, bus_token);
					gic_irq_domain_select
						return d == gic_data.domain;/,调用 gic-v3 初始化过程中创建的域,gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data);
			else if (h->ops->match)
				rc = h->ops->match(h, to_of_node(fwnode), bus_token);
			else
				rc = ((fwnode != NULL) && (h->fwnode == fwnode) &&
					((bus_token == DOMAIN_BUS_ANY) ||
					(h->bus_token == bus_token)));
	
			if (rc) {
				found = h;
				break;
			}
		}

A:这里的代码主要是找到 irq domain。这是根据传递进来的参数 fwspec寻找的,本质上是通过遍寻链表的方式查找:

if (irq_domain_translate(domain, fwspec, &hwirq, &type))
	#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
		if (d->ops->translate)/* 在 mt6853中是 通过 irq_domain->ops->translate的函数获取&hwirq, &type */
			return d->ops->translate(d, fwspec, hwirq, type);
				gic_irq_domain_select
					return d == gic_data.domain;/*即,调用 gic-v3 初始化过程中创建的域,gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops, &gic_data);*/
	#endif
		if (d->ops->xlate)
			return d->ops->xlate(d, to_of_node(fwspec->fwnode),
						fwspec->param, fwspec->param_count,
						hwirq, type);
	
		/* If domain has no translation, then we assume interrupt line */
		*hwirq = fwspec->param[0];
		return 0;

B:通过 dts 中中断描述获取 hwirq 和 irq type;流程如下:

Flow:
if (irq_domain_translate(domain, fwspec, &hwirq, &type))
	return d->ops->translate(d, fwspec, hwirq, type);
		gic_irq_domain_translate
			*hwirq = fwspec->param[0];
			*type = fwspec->param[1];

virq = irq_create_mapping(domain, hwirq);
	virq = irq_find_mapping(domain, hwirq);/*先判断是否存在 virq ,在这里应该是能获取到的*/
	virq = irq_domain_alloc_descs(-1, 1, hwirq, of_node_to_nid(of_node), NULL);/* 分配一个virq */
		__irq_alloc_descs
				start = bitmap_find_next_zero_area(allocated_irqs, IRQ_BITMAP_BITS,from, cnt, 0);/*从 allocated_irqs 数组中查找到一个为0的数对应的值作为virq */
				ret = alloc_descs(start, cnt, node, affinity, owner);
					desc = alloc_desc(start + i, node, flags, mask, owner);
					irq_insert_desc(start + i, desc);
						radix_tree_insert(&irq_desc_tree, irq, desc);/* 即用 radix tree 方式以hwirq 为索引,存储 irq_desc */

C:解析完了,最终还是要调用 irq_create_mapping 函数来创建HW interrupt ID和IRQ number的映射关系。

irq_data = irq_get_irq_data(virq);
irqd_set_trigger_type(irq_data, type);

`D:调用 irq_set_irq_type 函数设定 trigger type

irq_create_mapping 函数建立HW interrupt ID和IRQ number的映射关系。该接口函数以 irq domain 和 HW interrupt ID 为参数,返回IRQ number。具体的代码如下:
``

```c
unsigned int irq_create_mapping(struct irq_domain *domain,
                irq_hw_number_t hwirq)
{
    unsigned int hint;
    int virq;

	/* 判断virq,如果映射已经存在,那么不需要映射,直接返回 */
	/* 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);
	if (virq <= 0) {
		pr_debug("-> virq allocation failed\n");
		return 0;
	}
	
    if (irq_domain_associate(domain, virq, hwirq)) {---建立mapping
			ret = domain->ops->map(domain, virq, hwirq);/* 通过 irq_domain->ops->map建立映射关系 */
			irq_domain_set_mapping(domain, hwirq, irq_data);
				radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);/* --向radix tree插入一个node */
					return __radix_tree_insert(root, index, 0, entry);
        irq_free_desc(virq);
        return 0;
    }

    return virq;
}

通过以上流程,我们就可以获取一个virq以及其对应的中断描述符了(struct irq_desc *desc = irq_to_desc(irq);即以virq为irq_desc[virq]数组下标)。(这种是简单的获取方式)
E.获取对应的 irq_desc ;

Flow:
	virq = irq_find_mapping(domain, hwirq);
		data = radix_tree_lookup(&domain->revmap_tree, hwirq);
			return data ? data->irq : 0;

可见,是通过 hwirq 为索引,radix tree的方式获取;

创建和获取流程如下所示:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

人在路上……

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值