尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Linux 6.6内核中断深度解析(五):MSI/MSI-X 的 irq 层分配链

Linux 6.6内核中断深度解析(五):MSI/MSI-X 的 irq 层分配链 〇、这篇文章讲什么PCIe 系列的 MSI/MSI-X 篇讲的是PCIe 硬件层pci_alloc_irq_vectors申请、降级链、msi_desc怎么建、message 怎么写进配置空间 / MSI-X 表。那篇在pci_msi_setup_msi_irqs这里留了个口子说走 irq domain 分配 IRQ就跳过了。这篇把那个口子补上讲irq 层驱动申请到 MSI 之后内核的 irq domain 框架怎么把一个设备想用 N 个中断这件事变成N 个就绪的irq_desc N 个 vector 号 写进硬件的 message。一句话主线msi_descPCIe 层建→ 递归走 irq domain 分配irq_desc vector→ activate 组合 message 写进硬件。PCIe 层建好 msi_descmsi_index、affinity入口pci_msi_setup_msi_irqsirqdomain.c:11遍历 msi_desc__msi_domain_alloc_irqs msi.c:1250每个 desc 递归分配__irq_domain_alloc_irqsMSI domain.alloc算 hwirq → 递归 parentVECTOR domain.alloc分配 vectorx86_vector_alloc_irqsmsi_init设 irq_chip handle_edge_irq分配 irq_descirq_domain_alloc_descsactivatecompose message 写硬件msi_domain_activate msi.c:664一、边界PCIe 层建 msi_descirq 层消费它先划清和 PCIe 篇的边界避免重复层干什么关键产物PCIe 层PCIe 篇已讲pci_alloc_irq_vectors降级选型、建msi_desc、写 message 到硬件、set_enablemsi_descirq 层本篇拿msi_desc走 irq domain 分配irq_desc vector、组合 messageirq_desc vector messagemsi_desc是两层之间的连接PCIe 层填好它msi_index是设备的第几个向量、affinity是想绑哪个核irq 层读它翻译成 irq domain 的分配请求。一句话PCIe 层负责设备要几个中断irq 层负责给这几个中断分配内核资源。1.1 先看全貌MSI 和 MSI-X 在 irq 层共用一条链先给一个全局认识免得读者以为 MSI 和 MSI-X 在 irq 层要分开理解。实际上它们共用同一套分配链只在碰硬件的几个irq_chip回调上有差异一样的部分完全复用不一样的部分irq_chip回调分配链pci_msi_setup_msi_irqs→ 递归 VECTOR 分配 vector—handler都是handle_edge_irq边沿msi.c:259—hwirq 编码都是pci_msi_domain_calc_hwirq—activate都是 compose write message—mask—MSI 写 multi-mask 位MSI-X 写表项write_msi_msg—MSI 多消息只写首向量MSI-X 每向量独立域标志—MSI_FLAG_MULTI_PCI_MSIvsMSI_FLAG_PCI_MSIX为什么能这样因为 irq domain 把 MSI/MSI-X 抽象成同一个东西——“一个设备想用 N 个中断源”。上层的分配链完全不关心底下是 MSI 还是 MSI-X只有最底层的irq_chip回调知道mask 位在配置空间还是在 BAR 表里、message 是共享地址还是每向量独立。差异发生在 PCIe 硬件层到 irq 层被压缩成 3 个回调——下面第五、六、七节分别展开的 mask 和 write 就是这些差异其余全部共用。二、入口pci_msi_setup_msi_irqs 是分界点pci_msi_setup_msi_irqs()drivers/pci/msi/irqdomain.c:11是 PCIe 层调进来的第一个 irq 层函数intpci_msi_setup_msi_irqs(structpci_dev*dev,intnvec,inttype){structirq_domain*domain;domaindev_get_msi_domain(dev-dev);// ① 拿这个设备的 MSI domainif(domainirq_domain_is_hierarchy(domain))// ② 层次化 domainreturnmsi_domain_alloc_irqs_all_locked(dev-dev,MSI_DEFAULT_DOMAIN,nvec);returnpci_msi_legacy_setup_msi_irqs(dev,nvec,type);// ③ 老式架构走 legacy}dev_get_msi_domain拿到的是这个设备的 MSI domain。前面初始化篇讲过v6.6 的 x86 上VECTOR domain 自己兼任 MSI parentnative_create_pci_msi_domain只给它打IRQ_DOMAIN_FLAG_MSI_PARENT标记不建独立 domain。所以这里拿到的 domain其 parent 一路往上就是 VECTOR domain——vector 号这个最底层资源在那里分配。三、hwirq 编码怎么唯一标识哪个设备的第几个 MSIirq domain 框架里每个中断要有一个整数hwirq硬件中断号。但 MSI 的硬件号没有现成的整数——它是哪个设备的第几个向量。内核用pci_msi_domain_calc_hwirq()irqdomain.c:58把它编码出来staticirq_hw_number_tpci_msi_domain_calc_hwirq(structmsi_desc*desc){structpci_dev*devmsi_desc_to_pci_dev(desc);return(irq_hw_number_t)desc-msi_index|// 设备的第几个向量低位pci_dev_id(dev)11|// 设备 IDbus8|devfn(pci_domain_nr(dev-bus)0xFFFFFFFF)27;// PCI domain高位}三段拼成一个整数domain_nrbit 27 起的高位段32 位→ 设备 IDbit 11-2616 位 bus8|devfn→ msi_index低 11 位MSI-X 最多 20482^11 个向量。这样整个系统里每个 MSI 都有一个唯一的 hwirqirq domain 的 radix tree 才能用它做索引。这个 hwirq 只在 irq domain 内部用驱动永远看不到。四、递归分配链irq_desc vector 怎么来这是本篇的核心。msi_domain_alloc_irqs_all_locked()kernel/irq/msi.c:1432往下是一条递归的分配链。4.1 遍历 msi_desc__msi_domain_alloc_irqs()msi.c:1250遍历 PCIe 层建好的每个msi_desc逐个分配xa_for_each_range(xa,idx,desc,ctrl-first,ctrl-last){if(!msi_desc_match(desc,MSI_DESC_NOTASSOCIATED))continue;ops-set_desc(arg,desc);// ① 算出 hwirq上一节那个编码virq__irq_domain_alloc_irqs(domain,-1,desc-nvec_used,dev_to_node(dev),arg,false,desc-affinity);// ② ★ 递归分配...for(i0;idesc-nvec_used;i){irq_set_msi_desc_off(virq,i,desc);// ③ 建立 irq → msi_desc 反向映射msi_init_virq(domain,virqi,vflags);}}注意 ② 的第三个参数desc-nvec_used一个msi_desc可能对应多个 vectormulti-MSI第五节展开所以一次分配nvec_used个。4.2 通用递归分配irq_desc 从哪来__irq_domain_alloc_irqs()kernel/irq/irqdomain.c是 irq domain 的通用分配入口irq_domain_alloc_irqs_locked()irqdomain.c:1468做三件事virqirq_domain_alloc_descs(irq_base,nr_irqs,0,node,affinity);// ① 分配 irq_desc 拿 irq 号irq_domain_alloc_irq_data(domain,virq,nr_irqs);// ② 分配 irq_datairq_domain_alloc_irqs_hierarchy(domain,virq,nr_irqs,arg);// ③ 递归到 domain-ops-alloc三件事缺一不可irq_domain_alloc_descs——从全局稀疏位图里抠出nr_irqs个空闲的 Linux irq 号为每个分配irq_desc。这就是Linux 中断号诞生的地方/proc/interrupts里看到的那个号。irq_domain_alloc_irq_data——分配irq_data它是 irq domain 框架里一个中断在这个 domain 的视图。irq_domain_alloc_irqs_hierarchy——调domain-ops-alloc也就是 MSI domain 的 alloc。4.3 MSI domain 的 alloc先递归拿 vector再设 chipmsi_domain_alloc()msi.c:684是 MSI domain 的 alloc 回调staticintmsi_domain_alloc(structirq_domain*domain,unsignedintvirq,unsignedintnr_irqs,void*arg){hwirqops-get_hwirq(info,arg);// ① 上一节的 hwirq 编码if(domain-parent){retirq_domain_alloc_irqs_parent(domain,virq,nr_irqs,arg);// ② ★ 递归 parentif(ret0)returnret;}for(i0;inr_irqs;i){retops-msi_init(domain,info,virqi,hwirqi,arg);// ③ 设 chip handler...}}关键在 ②MSI domain 自己不分配 vector而是递归到 parent domainVECTOR domain。这是层次化 irq domain 的核心思想——每一层只管自己那一层的资源vector 号这种硬件最底层资源只有 VECTOR domain 有资格分。递归到最底层后x86_vector_alloc_irqs()arch/x86/kernel/apic/vector.c:542干两件事staticintx86_vector_alloc_irqs(structirq_domain*domain,unsignedintvirq,unsignedintnr_irqs,void*arg){for(i0;inr_irqs;i){apicdalloc_apic_chip_data(node);// ① 分配 apic_chip_data存 vector 号apicd-irqvirqi;irqd-chiplapic_controller;// ② 底层 irq_chip 是 lapic_controllerirqd-chip_dataapicd;...errassign_irq_vector_policy(irqd,info);// ③ 从 vector 矩阵分配一个 vector 号}}assign_irq_vector_policy最终调assign_irq_vectorvector.c:269从 x86 的 vector 分配矩阵里挑一个空闲 vector写进apic_chip_data并建立 per-CPU 的vector_irq[vector] → irq_desc映射——这条映射正是投递篇里common_interrupt用来vector 号转 irq_desc的那张表。4.4 回到 MSI domain设 irq_chip 和 handle_irq递归返回后msi_domain_ops_init()msi.c:755给每个新分配的 virq 设置处理函数和 chipstaticintmsi_domain_ops_init(structirq_domain*domain,structmsi_domain_info*info,unsignedintvirq,irq_hw_number_thwirq,msi_alloc_info_t*arg){irq_domain_set_hwirq_and_chip(domain,virq,hwirq,info-chip,info-chip_data);if(info-handlerinfo-handler_name){__irq_set_handler(virq,info-handler,0,info-handler_name);// MSI → handle_edge_irq...}}info-handler对 MSI 是handle_edge_irq边沿触发——这正是投递篇讲的desc-handle_irq多态点MSI 走边沿流程只 ack、循环处理INTx 走电平流程。到这里irq_desc有了 handler、有了 chip、有了 vector中断骨架搭好了。五、multi-MSI一个 capability 分多个 vector普通 MSI 一个 capability 可以分多个 vector2 的幂最多 32 个这就是multi-MSI。它在 irq 层的体现是desc-nvec_used 1——一个msi_desc对应一串连续的 vector。两个关键点连续约束MSI不是 MSI-X要求多个 vector 的 irq 号必须连续。因为 MSI 只有一个 message addressdata 里放 vector 号硬件靠连续来推算第 N 个向量的 data 值。per-vector maskmask 第几个向量靠pci_irq_mask_msi()irqdomain.c:151用 irq 号差算出来staticvoidpci_irq_mask_msi(structirq_data*data){structmsi_desc*descirq_data_get_msi_desc(data);pci_msi_mask(desc,BIT(data-irq-desc-irq));// 第几个向量 → 对应 mask 位}data-irq - desc-irq算出这是 desc 的第几个向量BIT(...)得到 multi-mask 里的对应位。这就是为什么 MSI 的 mask 是一个 32 位掩码而 MSI-X 是每个表项一个 mask bit。六、MSI-X 的 mask直接写表项MSI-X 没有multi-mask 位这个概念每个向量是独立表项mask 就是写表项的控制位// drivers/pci/msi/irqdomain.c:195staticvoidpci_irq_mask_msix(structirq_data*data){pci_msix_mask(irq_data_get_msi_desc(data));// 写 MSI-X 表项的 mask bit}对比一下两种 mask 的 irq 层差异MSIMSI-Xmask 实现pci_msi_mask(desc, BIT(n))写 multi-mask 位pci_msix_mask(desc)写表项控制位定位第几个data-irq - desc-irq算偏移每个 desc 独立无需算存储配置空间 capability 的 mask 位BAR 里表项的 VECTOR_CTRL七、activatemessage 怎么组合 写进硬件分配alloc只建软件结构真正写 message 到硬件是在 activate 阶段。msi_domain_activate()msi.c:664staticintmsi_domain_activate(structirq_domain*domain,structirq_data*irq_data,bool early){structmsi_msgmsg[2]{[1]{},};BUG_ON(irq_chip_compose_msi_msg(irq_data,msg));// ① 组合 message填 vector/destmsi_check_level(irq_data-domain,msg);irq_chip_write_msi_msg(irq_data,msg);// ② 写 message 到硬件return0;}两步对应两个 irq_chip 的分工irq_chip_compose_msi_msg——调lapic_controllerVECTOR domain 的 chip的irq_compose_msi_msg最终到__irq_msi_compose_msg()arch/x86/kernel/apic/apic.c:2448。它读irq_cfgvector 号、目标 CPU 的 dest_apicid填进 message 的 address/data——投递给谁由它决定。irq_chip_write_msi_msg——调 MSI domain chip 的irq_write_msi_msg默认是pci_msi_domain_write_msg()irqdomain.c:40最终到 PCIe 篇讲过的__pci_write_msi_msg写配置空间或 MSI-X 表——写到哪里由它决定。这就是层次化 irq domain 的美妙之处底层 chiplapic知道中断该投给哪个 CPU 哪个 vector上层 chipPCI-MSI知道怎么把这个信息塞进设备两个 chip 各管一段compose和write把它们串起来。另外注意pci_msi_domain_write_msgirqdomain.c:40里的一个细节if(desc-irqirq_data-irq)__pci_write_msi_msg(desc,msg);注释说MSI-X 每个 desc 独立写但MSI 多消息里只有第一个向量desc-irq irq_data-irq才真正写 message——因为 multi-MSI 共享一个 address写一次就够了其余向量靠 data 连续推算。八、完整调用链把申请 MSI → 中断就绪的 irq 层链路串成带行号的图【分配链msi_desc → irq_desc vector】 pci_msi_setup_msi_irqs() irqdomain.c:11 └─ msi_domain_alloc_irqs_all_locked() msi.c:1432 └─ __msi_domain_alloc_irqs() msi.c:1250 遍历 msi_desc └─ 每个 desc: __irq_domain_alloc_irqs() irqdomain.c └─ irq_domain_alloc_irqs_locked() irqdomain.c:1468 ├─ irq_domain_alloc_descs() 分配 irq_desc irq 号 └─ irq_domain_alloc_irqs_hierarchy() → msi_domain_alloc └─ msi_domain_alloc() msi.c:684 ├─ get_hwirq() pci_msi_domain_calc_hwirq irqdomain.c:58 ├─ irq_domain_alloc_irqs_parent() 递归 VECTOR │ └─ x86_vector_alloc_irqs() vector.c:542 │ └─ assign_irq_vector_policy() vector.c:599 分配 vector └─ msi_domain_ops_init() msi.c:755 设 chip handle_edge_irq └─ msi_init_virq() → irq_domain_activate_irq() └─ msi_domain_activate() msi.c:664 ├─ irq_chip_compose_msi_msg() → __irq_msi_compose_msg apic.c:2448 └─ irq_chip_write_msi_msg() → pci_msi_domain_write_msg irqdomain.c:40 └─ __pci_write_msi_msg() msi.c:235 ★ 写硬件PCIe 篇抓住这条链路的三个分界点分界点位置意义PCIe/irq 分界pci_msi_setup_msi_irqs上面是 PCIe 层desc下面是 irq 层分配分配/写硬件分界msi_domain_activate上面建软件结构这里开始 composewrite message软件/硬件分界__pci_write_msi_msg写配置空间 / MSI-X 表真正碰硬件九、关键函数 / 文件索引函数位置作用pci_msi_setup_msi_irqsirqdomain.c:11irq 层入口PCIe/irq 分界msi_domain_alloc_irqs_all_lockedmsi.c:1432分配入口__msi_domain_alloc_irqsmsi.c:1250遍历 msi_desc 逐个分配pci_msi_domain_calc_hwirqirqdomain.c:58编码 hwirqdomain/devid/msi_indexirq_domain_alloc_irqs_lockedirqdomain.c:1468分配 irq_desc 递归 allocmsi_domain_allocmsi.c:684MSI domain alloc递归 parent msi_initmsi_domain_ops_initmsi.c:755设 irq_chip handle_edge_irqx86_vector_alloc_irqsvector.c:542分配 vector 建 vector_irq 映射msi_domain_activatemsi.c:664组合 message 写硬件pci_msi_domain_write_msgirqdomain.c:40写 messagemulti-MSI 只写首向量pci_irq_mask_msiirqdomain.c:151MSI per-vector maskpci_irq_mask_msixirqdomain.c:195MSI-X 表项 mask文件v6.6关键内容kernel/irq/msi.cMSI domain 框架、分配链、activatekernel/irq/irqdomain.c通用 irq domain 递归分配drivers/pci/msi/irqdomain.cPCIe/irq 衔接、hwirq 编码、maskarch/x86/kernel/apic/vector.cvector 分配、lapic_controllerarch/x86/kernel/apic/msi.cx86 MSI domain ops、affinityarch/x86/kernel/apic/apic.c__irq_msi_compose_msg组合 message十、记住三点① 递归是 irq domain 的灵魂MSI 分配不一步到位而是msi_domain_alloc → irq_domain_alloc_irqs_parent → VECTOR domain → 分配 vector层层递归。每一层只管自己的资源——MSI domain 算 hwirq、设 chipVECTOR domain 分配 vector 号底层lapic_controller负责写 local APIC。irq_desc由irq_domain_alloc_descs从全局位图抠 irq 号。② hwirq 是把哪个设备的第几个压成一个整数pci_msi_domain_calc_hwirq用msi_index | pci_dev_id 11 | domain_nr 27编码是 irq domain radix tree 的索引。msi_desc是 PCIe 层和 irq 层的连接——PCIe 层填msi_index/affinityirq 层读算 hwirq、分配。③ 分配alloc和写硬件activate是两段靠两个 irq_chip 分工alloc 只建irq_desc/vector/映射这些软件结构activate 才compose_msi_msglapic chip 填 vector/dest决定投给谁write_msi_msgMSI chip 写配置空间/表决定写哪。multi-MSI 共享 address只写第一个向量MSI-X 每表项独立 mask。
返回列表