当前位置:首页>Linux>Linux MSI IRQ Domain深度解析:msi_desc/msi_msg结构体与hwirq到virq的映射机制 【第12篇·共15篇】

Linux MSI IRQ Domain深度解析:msi_desc/msi_msg结构体与hwirq到virq的映射机制 【第12篇·共15篇】

  • 2026-10-11 08:00:35
Linux MSI IRQ Domain深度解析:msi_desc/msi_msg结构体与hwirq到virq的映射机制 【第12篇·共15篇】
摘要

本文是《Linux中断子系统》系列第3章的第四篇,聚焦于Linux内核MSI IRQ Domain的关键数据结构与映射机制。首先对比CONFIG_GENERIC_MSI_IRQ_DOMAIN与PCIe MSI的概念区别;随后详细讲解内核态MSI管理的四大数据结构:msi_desc(MSI描述符,包含irq号、设备指针、MSI消息缓存、PCI MSI/MSI-X属性字段)、msi_msg(MSI消息,包含address_lo/hi和data字段)、msi_domain_info(MSI中断域配置,含chip/ops/handler等)和msi_domain_ops(MSI域操作回调集合)。接着通过its_pci_msi_init调用链,分析GICv3-ITS-PCI MSI Domain的创建过程。最后结合gic_irq_domain_translate函数,详解hwirq到virq的映射流程(针对SPI/PPI/LPI/ESPI等中断类型的地址计算),并以dmesg输出为例说明实际效果。


3.2 MSI IRQ Domain与hwirq-virq映射

3.2(续)MSI IRQ Domain 注册

在讲PCIe中断服务之前需要明确一下MSI中断注册中几个重要的结构体。

在Linux内核中定义了一个通用的MSI Irq Domain,这个主要是通过CONFIG_GENERIC_MSI_IRQ_DOMAIN来进行控制,相关代码在kernel\irq\msi.c;而PCIe设备中有PCI MSI中断机制,这是PCIe设备特有的MSI中断机制的特定实现,与具体的PCI相关,代码位于driver\pci\msi.c;ARM GIC V3支持LPIs(消息中断机制,即MSI),主要实现模块是ITS,同样也有针对PCI设备的适配,代码位置在driver\irqchip\irqchip-gic-v3-its-pci-msi.c。

CONFIG_GENERIC_MSI_IRQ_DOMAIN 和 PCIe 中的 MSI 在概念和应用上有所区别,但两者都涉及到 MSI(Message-Signaled Interrupts,消息信号中断)机制,主要区别如下:

  1. 功能和应用范围:

    • CONFIG_GENERIC_MSI_IRQ_DOMAIN 是 Linux 内核的一个配置选项,用于启用对 MSI IRQ 域的支持,确保内核能够正确地响应和处理 MSI 设备产生的中断。

    • PCIe 中的 MSI 是一种中断机制,允许 PCI Express 设备通过向内存中的特定地址写入消息来产生中断,而不使用传统的 INTx 引脚。

  2. 配置和实现:

    • CONFIG_GENERIC_MSI_IRQ_DOMAIN 在内核编译时通过配置工具进行配置,启用后内核将包含相应的 MSI IRQ 域支持代码。

    • PCIe 中的 MSI 在硬件级别实现,设备驱动程序与硬件设备协商确定用于 MSI 的内存地址,设备通过向这个地址写入消息来触发中断。

  3. 关注点:

    • CONFIG_GENERIC_MSI_IRQ_DOMAIN 更侧重于内核对 MSI 机制的支持和管理。

    • PCIe 中的 MSI 则更侧重于硬件设备和驱动程序之间的通信机制。

两者协同工作,确保 MSI 设备能够正确地与 Linux 内核进行中断交互。

内核态MSI管理相关的数据结构

msi_desc

/** * struct msi_desc - Descriptor structure for MSI based interrupts * @list:	List head for management * @irq:	The base interrupt number * @nvec_used:	The number of vectors used * @dev:	Pointer to the device which uses this descriptor * @msg:	The last set MSI message cached for reuse * @affinity:	Optional pointer to a cpu affinity mask for this descriptor * * @write_msi_msg:	Callback that may be called when the MSI message *			address or data changes * @write_msi_msg_data:	Data parameter for the callback. * * @msi_mask:	[PCI MSI]   MSI cached mask bits * @msix_ctrl:	[PCI MSI-X] MSI-X cached per vector control bits * @is_msix:	[PCI MSI/X] True if MSI-X * @multiple:	[PCI MSI/X] log2 num of messages allocated * @multi_cap:	[PCI MSI/X] log2 num of messages supported * @maskbit:	[PCI MSI/X] Mask-Pending bit supported? * @is_64:	[PCI MSI/X] Address size: 0=32bit 1=64bit * @entry_nr:	[PCI MSI/X] Entry which is described by this descriptor * @default_irq:[PCI MSI/X] The default pre-assigned non-MSI irq * @mask_pos:	[PCI MSI]   Mask register position * @mask_base:	[PCI MSI-X] Mask register base address */struct msi_desc {/* Shared device/bus type independent data */struct list_head		list;unsigned int			irq;unsigned int			nvec_used;struct device			*dev;struct msi_msg			msg;struct irq_affinity_desc	*affinity;#ifdef CONFIG_IRQ_MSI_IOMMUconst void			*iommu_cookie;#endifvoid (*write_msi_msg)(struct msi_desc *entry, void *data);void *write_msi_msg_data;union {/* PCI MSI/X specific data */struct {union {				u32 msi_mask;				u32 msix_ctrl;			};struct {				u8	is_msix		: 1;				u8	multiple	: 3;				u8	multi_cap	: 3;				u8	maskbit		: 1;				u8	is_64		: 1;				u8	is_virtual	: 1;				u16	entry_nr;unsigned default_irq;			} msi_attrib;union {				u8	mask_pos;void __iomem *mask_base;			};		};struct platform_msi_desc platform;struct fsl_mc_msi_desc fsl_mc;struct ti_sci_inta_msi_desc inta;	};};

msi_desc 各字段说明:

  1. list:链表头,用于管理和组织MSI描述符。

  2. irq:该MSI描述符的基础中断号。

  3. nvec_used:已经使用的MSI向量的数量。

  4. dev:指向使用该MSI描述符的设备的指针。

  5. msg:缓存的最后一个MSI消息,用于重用。

  6. affinity:可选的CPU亲和性掩码指针。

  7. write_msi_msg 和 write_msi_msg_data:当MSI消息的地址或数据发生变化时可能会调用的回调函数和数据参数。

  8. msi_mask/msix_ctrl:MSI的缓存掩码位 / MSI-X的每向量控制位缓存。

  9. is_msix:指示是否使用MSI-X。

  10. multiple 和 multi_cap:分别表示已分配和支持的消息数的对数(log2)。

  11. maskbit:指示是否支持Mask-Pending位。

  12. is_64:指示地址大小是32位(0)还是64位(1)。

  13. entry_nr:由该描述符描述的MSI-X条目编号。

  14. default_irq:默认的非MSI预分配中断号。

  15. mask_pos / mask_base:MSI的掩码寄存器位置 / MSI-X的掩码寄存器基地址。

  16. platform, fsl_mc, inta:为特定平台或设备类型提供的MSI描述符数据。

msi_msg

/** * msi_msg - Representation of a MSI message * @address_lo:		Low 32 bits of msi message address * @arch_addrlo:	Architecture specific shadow of @address_lo * @address_hi:		High 32 bits of msi message address *			(only used when device supports it) * @arch_addrhi:	Architecture specific shadow of @address_hi * @data:		MSI message data (usually 16 bits) * @arch_data:		Architecture specific shadow of @data */struct msi_msg {	union {		u32			address_lo;		arch_msi_msg_addr_lo_t	arch_addr_lo;	};	union {		u32			address_hi;		arch_msi_msg_addr_hi_t	arch_addr_hi;	};	union {		u32			data;		arch_msi_msg_data_t	arch_data;	};};

msi_msg 各字段说明:

  1. address_lo:MSI消息地址的低32位,是设备将写入以触发中断的内存地址的一部分。

  2. arch_addrlo:架构特定的address_lo影子,通常用于驱动程序内部跟踪或修改这个值。

  3. address_hi:MSI消息地址的高32位(仅当设备支持64位地址时使用)。

  4. arch_addrhi:架构特定的address_hi影子。

  5. data:MSI消息数据(通常是16位),当设备写入MSI地址时同时发送的数据值,可用于区分不同的中断源。

  6. arch_data:架构特定的data影子。

msi_domain_info

/** * struct msi_domain_info - MSI interrupt domain data * @flags:		Flags to decribe features and capabilities * @ops:		The callback data structure * @chip:		Optional: associated interrupt chip * @chip_data:		Optional: associated interrupt chip data * @handler:		Optional: associated interrupt flow handler * @handler_data:	Optional: associated interrupt flow handler data * @handler_name:	Optional: associated interrupt flow handler name * @data:		Optional: domain specific data */struct msi_domain_info {	u32			flags;	struct msi_domain_ops	*ops;	struct irq_chip		*chip;void			*chip_data;	irq_flow_handler_t	handler;void			*handler_data;const char		*handler_name;void			*data;};

msi_domain_info 各字段说明:

  • flags:描述MSI中断域的功能和特性的标志字段。

  • ops:回调函数数据结构,包含用于操作MSI中断域的函数指针。

  • chip:可选字段,指向与MSI中断域关联的中断控制器芯片。

  • chip_data:可选字段,存储与中断控制器芯片关联的数据。

  • handler:可选字段,指向与MSI中断域关联的中断处理函数。

  • handler_data:可选字段,存储与中断处理函数关联的数据。

  • handler_name:可选字段,存储中断处理函数的名称(用于调试)。

  • data:可选字段,存储与MSI中断域关联的特定域数据。

msi_domain_ops

/** * struct msi_domain_ops - MSI interrupt domain callbacks * @get_hwirq:		Retrieve the resulting hw irq number * @msi_init:		Domain specific init function for MSI interrupts * @msi_free:		Domain specific function to free a MSI interrupts * @msi_check:		Callback for verification of the domain/info/dev data * @msi_prepare:	Prepare the allocation of the interrupts in the domain * @msi_finish:		Optional callback to finalize the allocation * @set_desc:		Set the msi descriptor for an interrupt * @handle_error:	Optional error handler if the allocation fails * @domain_alloc_irqs:	Optional function to override the default allocation * @domain_free_irqs:	Optional function to override the default free */struct msi_domain_ops {irq_hw_number_t	(*get_hwirq)(struct msi_domain_info *info,				     msi_alloc_info_t *arg);int		(*msi_init)(struct irq_domain *domain,				    struct msi_domain_info *info,				    unsigned int virq, irq_hw_number_t hwirq,				    msi_alloc_info_t *arg);void		(*msi_free)(struct irq_domain *domain,				    struct msi_domain_info *info,				    unsigned int virq);int		(*msi_check)(struct irq_domain *domain,				     struct msi_domain_info *info,				     struct device *dev);int		(*msi_prepare)(struct irq_domain *domain,				       struct device *dev, int nvec,				       msi_alloc_info_t *arg);void		(*msi_finish)(msi_alloc_info_t *arg, int retval);void		(*set_desc)(msi_alloc_info_t *arg,				    struct msi_desc *desc);int		(*handle_error)(struct irq_domain *domain,struct msi_desc *desc, int error);int		(*domain_alloc_irqs)(struct irq_domain *domain,					     struct device *dev, int nvec);void		(*domain_free_irqs)(struct irq_domain *domain,					    struct device *dev);};

msi_domain_ops 各回调函数说明:

  • get_hwirq:用于获取硬件中断号(hwirq number)。

  • msi_init:MSI中断域的初始化函数。

  • msi_free:用于释放MSI中断资源的函数。

  • msi_check:用于验证MSI中断域、中断信息以及设备数据的回调函数。

  • msi_prepare:准备MSI中断在域中的分配。

  • msi_finish:可选的回调函数,用于完成MSI中断的分配。

  • set_desc:设置MSI描述符的函数。

  • handle_error:可选的错误处理函数,用于处理MSI中断分配过程中的错误。

  • domain_alloc_irqs 和 domain_free_irqs:用于覆盖默认MSI中断分配和释放函数。

MSI IRQ Domain 初始化过程

在 irq-gic-v3-its-pci-msi.c 中会定义一个实例:

static struct of_device_id its_device_id[] = {	{	.compatible	= "arm,gic-v3-its",	},	{},};static struct msi_domain_info its_pci_msi_domain_info = {    .flags  =  MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |               MSI_FLAG_MULTI_PCI_MSI   | MSI_FLAG_PCI_MSIX,    .ops    =  &its_pci_msi_ops,    .chip   =  &its_msi_irq_chip,    // 这个成员是给irq domain 用的};static struct msi_domain_ops its_pci_msi_ops = {    .msi_prepare    = its_pci_msi_prepare,};static struct msi_domain_ops pci_msi_domain_ops_default = {	.set_desc	= pci_msi_domain_set_desc,	.msi_check	= pci_msi_domain_check_cap,	.handle_error	= pci_msi_domain_handle_error,};

创建一个 MSI IRQ PCI domain

GICv3-ITS-PCI msi-domain 初始化过程 early_initcall(its_pci_msi_init);

its_pci_msi_init	its_pci_of_msi_init		its_pci_msi_init_onepci_msi_create_irq_domain(handle, &its_pci_msi_domain_info, parent)				pci_msi_domain_update_dom_ops(info);					...pci_msi_domain_ops_defaultdomain = msi_create_irq_domain(fwnode, info, parent);					msi_domain_update_dom_ops(info);						...msi_domain_ops_defaultirq_domain_create_hierarchy(fwnode, &msi_domain_ops, info)						irq_domain_create_tree(fwnode, ops, host_data);							__irq_domain_add(fwnode, 0, ~0, 0, ops, host_data)								domain->host_data = host_data;// host_data 是 its_pci_msi_domain_infostatic struct msi_domain_info its_pci_msi_domain_info = {    .flags  =  MSI_FLAG_USE_DEF_DOM_OPS | MSI_FLAG_USE_DEF_CHIP_OPS |               MSI_FLAG_MULTI_PCI_MSI   | MSI_FLAG_PCI_MSIX,    .ops    =  &its_pci_msi_ops,    .chip   =  &its_msi_irq_chip,};static struct msi_domain_ops its_pci_msi_ops = {    .msi_prepare    = its_pci_msi_prepare,};static struct msi_domain_ops pci_msi_domain_ops_default = {	.set_desc	= pci_msi_domain_set_desc,	.msi_check	= pci_msi_domain_check_cap,	.handle_error	= pci_msi_domain_handle_error,};

硬件中断号(hwirq)和软件中断号(virq)的映射过程

在具体的硬件初始化过程中,通常会获取到对应的hwirq(通过irq_of_parse_and_map),然后转换为virq,最后用于注册对应的中断。硬件中断号一般是通过解析设备树或者直接用板级数据获取,获取之后通过irq_of_parse_and_map函数转换为virq,再去注册中断。

gic_of_init    |- gic_of_setup_kvm_info  (在虚拟化平台下会调用)            |- irq_of_parse_and_map                    |- of_irq_parse_one                    |- irq_create_of_mapping                        |- irq_create_fwspec_mapping                            |- irq_domain_translate                                |- d->ops->translate                                    |- gic_irq_domain_translatestatic const struct irq_domain_ops gic_irq_domain_ops = {	.translate = gic_irq_domain_translate,	.alloc = gic_irq_domain_alloc,	.free = gic_irq_domain_free,	.select = gic_irq_domain_select,};

如何获取硬件中断号:

gic_irq_domain_translate 函数用于将设备树(Device Tree)中的中断规格(IRQ specification)转换为硬件中断号(hwirq)和中断类型(type)。

函数的主要流程如下:

  1. 参数检查:函数接收irq_domain指针、irq_fwspec指针,以及hwirq和type的输出指针。fwspec结构包含从GIC设备树节点中解析出来的参数。

  2. 处理简单情况:如果fwspec只有一个参数,且该参数小于16,则直接将此参数作为hwirq,并将中断类型设置为上升沿触发(IRQ_TYPE_EDGE_RISING)。

  3. 处理OF节点:根据fwspec的第一个参数(即中断类型)进行不同的处理:

    • SPI:hwirq设置为参数1加32。

    • PPI:hwirq设置为参数1加16。

    • ESPI和EPPI:hwirq设置为参数1加上对应的基准中断ID。

    • LPI和PARTITION:hwirq设置为参数1(PARTITION类型有额外偏移处理)。

  4. 处理IRQ芯片FW节点:确保fwspec有两个参数,hwirq设置为参数0,type设置为参数1。

  5. 错误处理:以上条件都不满足时,函数返回-EINVAL。

static int gic_irq_domain_translate(struct irq_domain *d,				    struct irq_fwspec *fwspec,				    unsigned long *hwirq,				    unsigned int *type){if (fwspec->param_count == 1 && fwspec->param[0] < 16) {		*hwirq = fwspec->param[0];		*type = IRQ_TYPE_EDGE_RISING;return 0;	}if (is_of_node(fwspec->fwnode)) {if (fwspec->param_count < 3)return -EINVAL;switch (fwspec->param[0]) {case 0:			/* SPI */			*hwirq = fwspec->param[1] + 32;break;case 1:			/* PPI */			*hwirq = fwspec->param[1] + 16;break;case 2:			/* ESPI */			*hwirq = fwspec->param[1] + ESPI_BASE_INTID;break;case 3:			/* EPPI */			*hwirq = fwspec->param[1] + EPPI_BASE_INTID;break;case GIC_IRQ_TYPE_LPI:	/* LPI */			*hwirq = fwspec->param[1];break;case GIC_IRQ_TYPE_PARTITION:			*hwirq = fwspec->param[1];if (fwspec->param[1] >= 16)				*hwirq += EPPI_BASE_INTID - 16;else				*hwirq += 16;break;default:return -EINVAL;		}		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;		WARN_ON(*type == IRQ_TYPE_NONE &&			fwspec->param[0] != GIC_IRQ_TYPE_PARTITION);return 0;	}if (is_fwnode_irqchip(fwspec->fwnode)) {if(fwspec->param_count != 2)return -EINVAL;		*hwirq = fwspec->param[0];		*type = fwspec->param[1];		WARN_ON(*type == IRQ_TYPE_NONE);return 0;	}return -EINVAL;}

通过dmesg打印出来:

针对PCIe中MSI/MSI-X中断的映射有单独的处理流程,是在整个PCIe枚举之后进行中断分配。

最新文章

随机文章