irq_domain 分析

linux 中断简述中,对整个中断框架做了一个简单的概述。其中irq_domain在中断中的位置,也做了概述。记下来分析irq_domain的具体使用。

1.irq doamin创建申请

申请一个irq doamin,可使用的接口如下,这些接口调用的核心接口为 __irq_domain_add。

struct irq_domain *irq_domain_add_simple(struct device_node *of_node,
					 unsigned int size, unsigned int first_irq,
					 const struct irq_domain_ops *ops, void *host_data);
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);
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
					 unsigned int size, const struct irq_domain_ops *ops,
					 void *host_data);
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
					 unsigned int max_irq, const struct irq_domain_ops *ops,
					 void *host_data);
static inline struct irq_domain *irq_domain_add_legacy_isa(
				struct device_node *of_node, const struct irq_domain_ops *ops,
				void *host_data);
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
					 const struct irq_domain_ops *ops, void *host_data);
static inline struct irq_domain *irq_domain_create_linear(struct fwnode_handle *fwnode,
					 unsigned int size, const struct irq_domain_ops *ops,
					 void *host_data);
static inline struct irq_domain *irq_domain_create_tree(struct fwnode_handle *fwnode,
					 const struct irq_domain_ops *ops, void *host_data);
extern struct irq_domain *irq_domain_create_hierarchy(struct irq_domain *parent,
			unsigned int flags, unsigned int size, struct fwnode_handle *fwnode,
			const struct irq_domain_ops *ops, void *host_data);

static inline struct irq_domain *irq_domain_add_hierarchy(struct irq_domain *parent,
					    unsigned int flags, unsigned int size, struct device_node *node,
					    const struct irq_domain_ops *ops, void *host_data);

1.1 irq_domain_add_simple & irq_domain_add_legacy,这两个接口实现irq domain的创建,且可以绑定用户已经申请的irq desc。不同的是,irq_domain_add_simple如果入参first_irq为0,则不会执行绑定操作,irq_domain_add_legacy则默认irq_base被配置为正确的irq desc。first_irq为用户已经申请过的virq rangs的初始编号。例:

	irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 32, numa_node_id());
	if (irq_base < 0) {
		err = irq_base;
		goto err_out;
	}

	port->domain = irq_domain_add_legacy(np, 32, irq_base, 0, &irq_domain_simple_ops, NULL);
	if (!port->domain) {
		err = -ENODEV;
		goto err_out;
	}

 1.2 irq_domain_add_hierarchy&irq_domain_create_hierarchy,这两个接口实现的是在父irq domain节点下继续创建一个irq domain。如GPIO中断可以该接口,将GPIO控制器的irq domain挂在GIC中断下。

	parent_np = of_irq_find_parent(dev->of_node);
	if (!parent_np)
		return -ENXIO;

	parent_domain = irq_find_host(parent_np);
	of_node_put(parent_np);
	if (!parent_domain)
		return -EPROBE_DEFER;

	priv->domain = irq_domain_create_hierarchy(
					parent_domain, 0, XXX_GPIO_IRQ_MAX_NUM,
					of_node_to_fwnode(dev->of_node), &xxx_gpio_irq_domain_ops, priv);

1.3 __irq_domain_add 申明如下,其中比较关键的几个参数为size, hwirq_max,direct_max。

size: 如果非0的话,则存储hwirq和virq的映射关系的空间将为线性的,关系映射存储地址为unsigned int linear_revmap[ ]。反之则为链式存储,存储地址为struct radix_tree_root revmap_tree。

hwirq_max:定义了此irq_domain的最大子中断号。该值限定在domain域下申请子中断时,最大的子中断号,如果超过该值,则产生一个WARN。

direct_max:该值仅用于不映射的情况下,hwirq即为virq的情况。如果子中断为hwirq的话,direct_max被配置为0。

struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
				    irq_hw_number_t hwirq_max, int direct_max,
				    const struct irq_domain_ops *ops,
				    void *host_data);

通过以上__irq_domain_add分析,便可以理解如下几个封装好的接口。

irq_domain_add_linear、irq_domain_create_linear:Virq与Hwirq的映射关系存储为线性区域的domain创建。其实现如下:

static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
					 unsigned int size,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), size, size, 0, ops, host_data);
}

irq_domain_add_tree、irq_domain_create_tree:与上面的接口不一样的是, 该接口的实现的映射关系存储为基数树radix_tree方式(基数树相关算法不详述),其映射关系以数的关系建立,可实现映射关系动态插入和删除,一般用于中断数目较多,且应用到的数目较少的情况,如GIC控制器实现了500个中断,但通常应用场景只使用了80个中断的情况。其实现如下:

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}

irq_domain_add_nomap: 不建立virq和hwirq的映射关系,hwirq即为virq。direct_max为max_irq,其实现如下:

static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
					 unsigned int max_irq,
					 const struct irq_domain_ops *ops,
					 void *host_data)
{
	return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}

irq_domain_add_legacy_isa:部分传统的ISA控制器只能使用预留的virq 0~15作为中断,所以内核提供相关的该接口,方便ISA控制器注册中断号0~15,NUM_ISA_INTERRUPTS值为16。其实现如下:

static inline struct irq_domain *irq_domain_add_legacy_isa(
				struct device_node *of_node,
				const struct irq_domain_ops *ops,
				void *host_data)
{
	return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops,
				     host_data);
}

2. 常用的Virq中断申请

irq_create_mapping:根据中断所属的irq_domain,直接map出来Virq。该接口的实现为先查询中断是否已经map,如果没有map则alloc一个irq desc,然后将desc的virq在domain中建立映射关系。

of_irq_get:使用dts的配置申请irq,首先找到interrput-parent的节点,如果domain中没有map这个中断号,则会调用irq_create_mapping接口去申请一个Virq,并绑定在父domain中,建立其map关系。

platform_get_irq:当device为一个platform device时,可通过该接口申请一个virq。该接口底层根据device的配置去申请中断。如果配置dts,则调用of_irq_get接口申请;如果创建device时,预先配置Virq到resource中的IORESOURCE_IRQ类型,则将resource中的值读出来返回;另外如果配置了acpi方式,也会使用acpi_irq_get等方式去申请Virq(此处不描述)。

gpio_to_irq:顾名思义将根据GPIO号获取一个virq。需要GPIO控制驱动端实现gpio_chip的to_irq的功能,有些驱动做在pinctrl端,有些做在gpio控制器端。

pci_irq_vector: 获取pci dev下的Virq,通常为msi或msi-X中断,pci dev的中断Virq会在dev枚举时,linux驱动框架提前map好(过程请参考pcie相关流程),此接口即返回对应的Virq。

pci_request_irq:不关心Virq,直接注册对应pci dev的中断服务,其核心是request_threaded_irq,其中Virq的参数使用pci_irq_vector来获取。

<think>好的,我现在需要回答用户关于GPIO中断处理中两个函数gpiochip_irqchip_add_nested和irq_domain_add_legacy的区别的问题。首先,我得回忆一下这两个函数的作用和它们在Linux内核中的使用场景。 首先,用户提到这两个函数都是用来设置IRQ中断域的,但具体区别是什么。我记得在Linux内核中,GPIO控制器通常使用irqchip来处理中断,而这两个函数可能属于不同的层次或者用途。 让我先想一下gpiochip_irqchip_add_nested。这个函数应该是用于将GPIO控制器的中断控制器(irqchip)添加到系统中,特别是当这个GPIO控制器是一个嵌套的中断控制器时。嵌套的中断控制器意味着它位于另一个中断控制器的下游,比如级联的中断。这种情况下,中断处理可能需要特殊的处理,比如线程化处理或者嵌套的锁机制。这时候,使用nested类型的irqchip可以确保中断处理程序正确执行,避免死锁等问题。 然后是irq_domain_add_legacy。这个函数属于irqdomain的API,用于创建传统的中断域。传统的中断映射通常是指固定、静态的中断号分配,比如在设备树或ACPI表中已经定义好的中断号。当硬件的中断线是静态配置且不需要动态分配时,可以使用legacy方式。例如,一些老旧的硬件或者某些需要保持向后兼容性的情况会用到这种方法。 接下来,我需要明确它们的区别。首先,应用场景不同。gpiochip_irqchip_add_nested用于GPIO控制器作为嵌套的中断控制器的情况,而irq_domain_add_legacy则是用于传统的中断映射方式。其次,结构层次不同:gpiochip的函数属于GPIO子系统,而irq_domain属于中断子系统更底层的部分。再者,用途不同:nested处理的是中断控制器的层次结构,而legacy处理的是中断号的静态映射。 可能还需要考虑它们的参数和使用方式。gpiochip_irqchip_add_nested需要关联到具体的gpio_chip结构,设置父中断控制器,处理函数等,而irq_domain_add_legacy需要指定中断号的范围、操作函数以及设备节点等信息。 另外,错误处理方面可能也有区别。嵌套的中断控制器需要确保父控制器的正确设置,而legacy域则可能涉及更多的静态配置,比如固定的中断号映射,不需要动态分配。 需要验证一下是否这两个函数在代码中的调用位置和参数确实如我所想。例如,gpiochip的函数可能在GPIO驱动初始化时被调用,而irq_domain的函数可能在更早的中断控制器注册阶段使用。同时,是否legacy方法已经被逐渐淘汰,或者在某些特定硬件中仍然必需。 总结来说,主要区别在于应用场景、所属子系统和中断处理机制的不同。一个是处理嵌套中断的GPIO控制器注册,另一个是创建传统静态中断映射的域。可能需要进一步查阅内核代码或文档来确认这些细节,但根据现有的知识,这样的区分是合理的。</think>在Linux内核中断子系统中,`gpiochip_irqchip_add_nested`和`irq_domain_add_legacy`虽然都涉及中断域设置,但设计目标和应用场景有本质区别: --- ### 一、定位层级不同 1. **gpiochip_irqchip_add_nested** - **所属子系统**:GPIO子系统 - **作用**:将GPIO控制器注册为**嵌套中断控制器**(Nested IRQ Chip),通常用于GPIO引脚级联到父中断控制器(如GIC)的场景。 - **典型场景**:GPIO引脚作为中断源,通过父中断控制器(如`irq-gic`)传递中断信号。 - **关键特征**: - 需要显式指定父中断控制器(`parent_handler`) - 支持嵌套中断处理(避免中断重入导致的死锁) - 通过`gpiochip_set_nested_irqchip()`启用嵌套模式 2. **irq_domain_add_legacy** - **所属子系统**:通用中断子系统(Generic IRQ) - **作用**:创建**传统静态映射**的中断域,用于固定硬件中断号的旧设备。 - **典型场景**:硬件中断号与Linux虚拟中断号(`virq`)是1:1固定映射(如x86 PIC)。 - **关键特征**: - 不涉及硬件级联关系 - 中断号预定义,不可动态分配 - 常用于兼容旧驱动程序或固定中断拓扑的硬件 --- ### 二、中断处理流程差异 1. **gpiochip_irqchip_add_nested** - 中断触发流程: ```text HW中断 → 父中断控制器 → GPIO子控制器 → 具体GPIO引脚的中断处理 ``` - 需要实现`irq_chip`操作(如`.irq_mask`, `.irq_unmask`) - 通过`gpiochip_to_irq()`将GPIO编号转换为虚拟中断号 2. **irq_domain_add_legacy** - 中断触发流程: ```text HW中断 → 直接映射到固定virq → 调用对应中断处理函数 ``` - 不需要级联关系,中断号由`irq_domain`静态分配 --- ### 三、代码结构对比 #### 1. `gpiochip_irqchip_add_nested` 示例 ```c struct gpio_chip *gc; struct irq_chip *irqchip; // 初始化GPIO控制器的irqchip gpiochip_irqchip_add_nested(gc, irqchip, 0, handle_edge_irq, IRQ_TYPE_NONE); // 设置嵌套模式 gpiochip_set_nested_irqchip(gc, irqchip, parent_irq); // 为每个GPIO引脚设置中断 for (i = 0; i < gc->ngpio; i++) irq_set_chip_data(virq + i, gpiochip_get_data(gc)); ``` #### 2. `irq_domain_add_legacy` 示例 ```c struct device_node *np; struct irq_domain *domain; // 创建传统中断域 domain = irq_domain_add_legacy(np, NUM_IRQS, 0, 0, &irq_domain_simple_ops, NULL); // 直接通过固定偏移获取virq int virq = irq_create_mapping(domain, hwirq); ``` --- ### 四、选择依据 | **场景** | **推荐函数** | |---------------------------|------------------------------| | GPIO引脚作为级联中断源 | `gpiochip_irqchip_add_nested` | | 旧设备固定中断号映射 | `irq_domain_add_legacy` | --- ### 五、总结 - **`gpiochip_irqchip_add_nested`**:面向GPIO控制器作为子中断控制器的级联场景,强调嵌套安全和父子中断协作。 - **`irq_domain_add_legacy`**:面向传统静态中断映射,适用于无需动态分配中断号的旧硬件。 两者在中断拓扑中的位置和作用层级完全不同,需根据硬件中断设计选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

奔跑的蜗牛87

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

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

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

打赏作者

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

抵扣说明:

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

余额充值