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,消息信号中断)机制,主要区别如下:
功能和应用范围:
配置和实现:
关注点:
两者协同工作,确保 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 各字段说明:
list:链表头,用于管理和组织MSI描述符。
irq:该MSI描述符的基础中断号。
nvec_used:已经使用的MSI向量的数量。
dev:指向使用该MSI描述符的设备的指针。
msg:缓存的最后一个MSI消息,用于重用。
affinity:可选的CPU亲和性掩码指针。
write_msi_msg 和 write_msi_msg_data:当MSI消息的地址或数据发生变化时可能会调用的回调函数和数据参数。
msi_mask/msix_ctrl:MSI的缓存掩码位 / MSI-X的每向量控制位缓存。
is_msix:指示是否使用MSI-X。
multiple 和 multi_cap:分别表示已分配和支持的消息数的对数(log2)。
maskbit:指示是否支持Mask-Pending位。
is_64:指示地址大小是32位(0)还是64位(1)。
entry_nr:由该描述符描述的MSI-X条目编号。
default_irq:默认的非MSI预分配中断号。
mask_pos / mask_base:MSI的掩码寄存器位置 / MSI-X的掩码寄存器基地址。
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 各字段说明:
address_lo:MSI消息地址的低32位,是设备将写入以触发中断的内存地址的一部分。
arch_addrlo:架构特定的address_lo影子,通常用于驱动程序内部跟踪或修改这个值。
address_hi:MSI消息地址的高32位(仅当设备支持64位地址时使用)。
arch_addrhi:架构特定的address_hi影子。
data:MSI消息数据(通常是16位),当设备写入MSI地址时同时发送的数据值,可用于区分不同的中断源。
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)。
函数的主要流程如下:
参数检查:函数接收irq_domain指针、irq_fwspec指针,以及hwirq和type的输出指针。fwspec结构包含从GIC设备树节点中解析出来的参数。
处理简单情况:如果fwspec只有一个参数,且该参数小于16,则直接将此参数作为hwirq,并将中断类型设置为上升沿触发(IRQ_TYPE_EDGE_RISING)。
处理OF节点:根据fwspec的第一个参数(即中断类型)进行不同的处理:
处理IRQ芯片FW节点:确保fwspec有两个参数,hwirq设置为参数0,type设置为参数1。
错误处理:以上条件都不满足时,函数返回-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;}
针对PCIe中MSI/MSI-X中断的映射有单独的处理流程,是在整个PCIe枚举之后进行中断分配。