参考资料:

相关书籍下载方式:
链接:https://pan.baidu.com/s/1zzWWt9ujVTr9oJSaJNP_mA
提取码:ahax

PCIE的中断

MSI和MSI-X(重点)

X86的中断简述

因为不是描述X86体系结构的,这里只简述下X86的中断处理
详情可参考 Intel® 64 and IA-32 Architectures -Volume3:System programming guide,这部分有时间后续整理。

LAPIC

image.png
PC-X86之前使用PIC(UP-单处理器)进行中断处理,后来位置支持MP-多处理器,所以加入了APIC
PCIE interrupt Routing/IO APIC在收到中断后,先根据PRT和RTE等表找到中断信息,通过系统总线发送给特定的CPU LAPIC中。

image.png

X86下的MSI/MSI-X

MSI(Message SignaledInterrupt):有中断产生时在系统特定内存地址写入中断数据已通知CPU一个中断。该种方式脱离了中断引脚(PIN)带来的数目限制,并且延迟小、效率高
image.png

MSI and MSI-X Operation(协议部分)

协议部分:《PCI Express Base_r5_1》 6.1章节:

使用条件

  • 系统软件在配置设备时初始化MSI地址和MSG Data(这里叫vector)。
  • Interrupt latency 与当前的中断体系结构相关,MSI中断不提供中断等待时间的保证。
  • MSI和MSI-x允许同时支持,但禁止同时使用。
  • MSI和MSI-X机制通过执行内存写入事务来传递中断。 MSI和MSI-X是边沿触发的中断机制。 [PCI]或本规范均不支持电平触发的MSI / MSI-X中断。
  • MSI / MSI-X事务的请求者必须将 事务描述符 的“No Snoop ”和“Relaxed Ordering”属性设置为0b。 如果启用了使用IDO属性,则允许MSI / MSI-X事务的请求者设置基于ID的排序(IDO)属性。
  • 请注意,与INTx仿真消息不同,MSI / MSI-X事务不限于TC0流量类。

MSI

描述了MSI CAP的Message Control 的属性。
系统软件读MSI CAP的Message Control 中 Multiple Message Capable 属性,看支持多少个vector属性(最多32个)。
系统软件写MSI CAP的Message Control 中 Multiple Message Enable 属性,来使能msi。

Enabling Operation: 默认MSI和MSI-X都是禁止使能的。
Sending Messages:
一旦启用了MSI或MSI-X(设置Message Control 的MSI Enable位),则允许该功能发送消息。
为了发送消息,函数将 按照 “存储器写TLP包( DWORD 4字节)” 给Message Address写入 合适的数据 。

Extended Message Data Enable

相关字段参考: 《PCI Express Base_r5_1.pdf》 7.7.1-7.7.2 P773

对于MSI,当Extended Message Data Enable为“0”时,写入的 DWORD 由MSI Message Data register 中低两个字节的值和零组成。
对于MSI,当Extended Message Data Enable为“1”时,写入的 DWORD 由 MSI Message Data register 中低两个字节的值 和 高两个字节的 MSI Extended Message Data register 中的值组成。

Multiple Message Enable:
对于MSI,如果Multiple Message Enable不为零,则允许功能修改消息数据的低位以生成多个向量。
例如,010b的多消息启用编码表示允许该功能修改消息数据位1和0以生成最多四个唯一向量。如果“多消息启用”字段为000b,则不允许该功能修改消息数据。
对于MSI-X,MSI-X 能够为每个分配的 中断向量 至少包含一个Entry,并且消息中使用来自选定表条目的32位消息数据字段值,而无需对低位进行任何修改,功能。
函数如何使用多个向量(分配时)取决于设备。功能必须处理分配向量少于要求的数量。

Hardware/Software Synchronization
如果某个功能在被 软件接收之前 多次发送具有相同vector的消息,则只能保证提供一条消息。
如果必须维护所有消息,则需要设备驱动程序握手。
换句话说,一旦函数发送了向量A,它就无法再次发送向量A,除非其设备驱动程序明确启用了向量A(前提是必须为所有消息提供服务)。
如果某些消息可能丢失,则不需要设备驱动程序握手。
对于支持多个向量的函数,一个函数可以发送多个唯一向量,并保证将为每个唯一消息提供服务。 例如,一个函数可以发送向量A,然后发送向量B,而无需任何设备驱动程序握手(将为向量A和向量B提供服务)。

MSI和MSI-X

入门必看-MSI/MSI-x中断及代码分析: 这个总结的很详细,甚至包含了代码部分。
入门必看-MSI 中断
入门必看-PCI-E配置MSI中断流程解析
入门必看-msi-howto.rst: 内核关于MSI的使用文档
入门必看-x86_64中断处理流程分析(防删备份:x8664中断处理流程分析 Lauren·weblog.pdf

其实,看完这几部分,应该对MSI/MSI-X已经了解了,甚至代码也明细了。

PCIe有三种中断,分别为INTx中断,MSI中断,MSI-X中断,其中INTx是可选的,MSI/MSI-X是必须实现的,甚至MSI和MSI-X可以同时支持,但同一时间只有一个启用

MSI和MSI-X中断区别

MSI MSI-X
中断向量数 32 2048
中断号约束 必须连续 可以随意分配
MSI信息存放 capability寄存器 MSI-X Table(BAR空间)

MSI Capability结构(重点)

因为当前能找到的设备大部分都是MSI Capability,所以MSI-X的设备后续在整理。
详情参看: 《PCI Express Base_r5_1.pdf》 7.7.1-7.7.2 P773

MSI Capability结构共有四种组成方式:32和64位的Message结构,32位和64位带中断Masking的结构
image.png
image.png
Capability ID = 5
NextPointer 下一个Capability地址

Message Control[16-bit]数值(一般简单的设备为1,使能即可)参考 :
《PCI Express Base_r5_1》7.7.1-7.7.2
image.png
image.png
image.png
注:Multiple Message Capable就是lspci查看到的 MSI Count信息;**

Message Address字段。当MSI Enable位有效时,该字段存放MSI存储器写事务的目的地址的低32位。该字段的31:2字段有效,系统软件可以对该字段进行读写操作;该字段的第1~0位为0。

Message Data字段,该字段可读写。当MSI Enable位有效时,该字段存放MSI报文使用的数据。该字段保存的数值与处理器系统相关,在PCIe设备进行初始化时,处理器将初始化该字段,而且不同的处理器填写该字段的规则并不相同。如果Multiple Message Enable字段不为0b000时(即该设备支持多个中断请求时),PCIe设备可以通过改变Message Data字段的低位数据发送不同的中断请求。

问题:这两个怎么填??? 后边重点解释。 参考X86如何处理MSI/MSI-X中断、

MSI-X Capability结构(暂时先不整理)

使用MSI-X Table存放该设备使用的所有Message Address和Message Data字段,这个表格存放在该设备的BAR空间中,从而PCIe设备可以使用MSI-X机制时,中断向量号可以并不连续,也可以申请更多的中断向量号

MSI-X Capability结构的组成方式如图6‑2所示。
image.png
Capability ID==11
NextPointer 下一个Capability地址
Message Control[16-bit]数值。参考:
image.png

  • Table BIR(BAR Indicator Register)。该字段存放MSI-X Table所在的位置,PCIe总线规范规定MSI-X Table存放在设备的BAR空间中。该字段表示设备使用BAR0~5寄存器中的哪个空间存放MSI-X table。该字段由三位组成,其中0b000~0b101与BAR0~5空间一一对应。
  • Table Offset字段。该字段存放MSI-X Table在相应BAR空间中的偏移。
  • PBA(Pending Bit Array) BIR字段。该字段存放Pending Table在PCIe设备的哪个BAR空间中。在通常情况下,Pending Table和MSI-X Table存放在PCIe设备的同一个BAR空间中。
  • PBA Offset字段。该字段存放Pending Table在相应BAR空间中的偏移。

注:在SR-IOV中可以看到,MSI-X和PBA位置不能冲突,否则会有问题。

MSI-X Tbale

image.png
注:DWORD=4字节,WORD=2字节

MSI-X Table由多个Entry组成,其中每个Entry与一个中断请求对应。其中每一个Entry中有四个参数,其含义如下所示。

  • Msg Addr。当MSI-X Enable位有效时,该字段存放MSI-X存储器写事务的目的地址的低32位。该双字的31:2字段有效,系统软件可读写;1:0字段复位时为0,PCIe设备可以根据需要将这个字段设为只读,或者可读写。不同的处理器填入该寄存器的数据并不相同。
  • Msg Upper Addr,该字段可读写,存放MSI-X存储器写事务的目的地址的高32位。
  • Msg Data,该字段可读写,存放MSI-X报文使用的数据。其定义与处理器系统使用的中断控制器和PCIe设备相关。
  • Vector Control,该字段可读写。该字段只有第0位(即Per Vector Mask位)有效,其他位保留。当该位为1时,PCIe设备不能使用该Entry提交中断请求;为0时可以提交中断请求。该位在复位时为0。Per Vector Mask位的使用方法与MSI机制的Mask位类似。 ```bash

    使能msi-x之后,一个设备的状态

    baiy@inno-MS-7B89:pcie-test$ sudo lspci -s 26:00.0 -vvvv 26:00.0 Memory controller: Xilinx Corporation Device 9032 (rev 03) Subsystem: Xilinx Corporation Device 9032 Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+ Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- SERR- <PERR- INTx- Latency: 0, Cache Line Size: 64 bytes Interrupt: pin A routed to IRQ 96 Region 0: Memory at f7a60000 (32-bit, non-prefetchable) [size=128K] Region 2: Memory at f7a40000 (32-bit, non-prefetchable) [size=128K] Expansion ROM at f7ae0000 [disabled] [size=2K]

    Capabilities: [60] MSI-X: Enable+ Count=2 Masked-

    1. Vector table: BAR=0 offset=00000040
    2. PBA: BAR=0 offset=00000100

root@inno-MS-7B89:test02# ./app_pcie_test -r -b 0 -l 100 -o 0x40 [main 106]: version 0.0.0.0 cmd is r, barnum is 0, len 0x64,offset is 0x40, value is 0x5a, count is 0x1024 00000000: 00 00 e0 fe 00 00 00 00 00 00 00 00 fe ff ff ff

  1. <a name="dC6Hy"></a>
  2. ##### Pending Table
  3. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2819254/1604581115566-0110a97b-6d09-496c-ade0-c632745f0ee5.png#align=left&display=inline&height=118&margin=%5Bobject%20Object%5D&name=image.png&originHeight=118&originWidth=518&size=28393&status=done&style=none&width=518)<br />在Pending Table中,一个Entry由64位组成,其中每一位与MSI-X Table中的一个Entry对应,即Pending Table中的每一个Entry与MSI-X Table的64个Entry对应。与MSI机制类似,Pending位需要与Per Vector Mask位配置使用。<br />当Per Vector Mask位为1时,PCIe设备不能立即发送MSI-X中断请求,而是将对应的Pending位置1;当系统软件将PerVector Mask位清零时,PCIe设备需要提交MSI-X中断请求,同时将Pending位清零
  4. <a name="CCWCt"></a>
  5. ### X86如何处理MSI/MSI-X中断
  6. <a name="Y7T4T"></a>
  7. #### X86的MSI描述
  8. [《Intel® 64 and IA-32 Architectures》](https://software.intel.com/content/www/us/en/develop/articles/intel-sdm.html#three-volume)中10.11章总结
  9. > MSI是一项可选功能,可使PCI设备通过将 system-specified message 写入系 system-specified address(PCI DWORD存储器)来请求服务。
  10. <a name="H3uIR"></a>
  11. ##### Message Address Register Format
  12. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/2819254/1606112711699-68350e33-a6cb-425d-93b2-4078c5ba3944.png#align=left&display=inline&height=75&margin=%5Bobject%20Object%5D&name=image.png&originHeight=100&originWidth=594&size=11589&status=done&style=none&width=446)<br />[31:20]:—这些位包含中断消息的固定值(0FEEH)。 该值将中断定位在基地址为4G – 18M的1 MB区域。 所有对该区域的访问都将作为中断消息。 必须注意:确保没有其他设备声明该区域为I / O空间。
  13. ```bash
  14. baiy@inno-MS-7B89:Documentation$ sudo cat /proc/iomem
  15. fee00000-fee00fff : Local APIC

当PCIE设备对0x0FEE_XXXX这段地址写入数据时,MCH/ICH会进行地址转换,然后触发MSI中断。
Destination ID:8bit 目标ID,
Redirection hint indication(RH) :
Destination mode (DM):
image.png
注:看本地的大部分参数,如果没有特指CPU,一半用0xfee0_0000就好了。

  1. 00:00.2 IOMMU: Advanced Micro Devices, Inc. [AMD] Device 1481
  2. ......
  3. Capabilities: [64] MSI: Enable+ Count=1/4 Maskable- 64bit+
  4. Address: 00000000fee04000 Data: 4021 // Destination ID==4
  5. 00:01.3 PCI bridge: Advanced Micro Devices, Inc. [AMD] Device 1483 (prog-if 00 [Normal decode])
  6. Capabilities: [a0] MSI: Enable+ Count=1/1 Maskable- 64bit+
  7. Address: 00000000fee00000 Data: 0000

Message Data Register Format

image.png

  • Vector : 这个8位字段包含与消息关联的中断向量。 值范围从010H到0FEH(其实0x0-0x1F已被占用)。 软件一定不能用00H到0FH。
  • Delivery Mode: 这个3位字段指定如何处理中断接收。 传送模式只能与指定的触发模式结合使用。 正确的触发模式必须由软件保证。
  • Triger Mode[15:14]:中断触发方式,0b0x是边沿触发(不推荐,但兼容Legacy mode),0b10低电平触发,0b11高电平触发。
  • Vector:中断向量,FSB在检测到中断时可直接获取到中断向量信息。

注:我们知道,每个PCI设备最多支持32个MSI中断,所以Vector的分配方式

  • [4:0]:中断向量 2^5 = 32 个中断向量 <==>支持32个
  • [7:5]:每个设备的编号, 8个funcnum

其实,经过测试发现:在不考虑IOMMU的中断分发情况,比如 address=0x0000_0000_FEE0_5000,data=0x4022,那么只需要通过PCIE的DMA,给0x0000_0000_FEE0_5000 地址写入0x4022,即可触发一次MSI中断。
在《QEMU/KVM源码解析及应用》P381页,有过IOMMU的一段描述很不错:

在Intel架构上,MSI中断是通过写一段地址完成的。任何DMA的发起者都能够写任意数据,这就会导致虚拟机内部的攻击者能够让外设产生不属于它的中断。VT-D技术中的另一个要解决的问题是 中断重定向,设备产生的中断会经过中断重定向器来判断中断是否合法以及是否重定向到虚拟机内部。

入门必看-x86_64中断处理流程分析 中 好像可以看到,masi-data也是由软件配置的?

  1. static struct irq_chip pci_msi_controller = {
  2. .name = "PCI-MSI",
  3. .irq_unmask = pci_msi_unmask_irq,
  4. .irq_mask = pci_msi_mask_irq,
  5. .irq_ack = irq_chip_ack_parent,
  6. .irq_retrigger = irq_chip_retrigger_hierarchy,
  7. .irq_compose_msi_msg = irq_msi_compose_msg,
  8. .irq_set_affinity = msi_set_affinity,
  9. .flags = IRQCHIP_SKIP_SET_WAKE,
  10. };
  11. irq_do_set_affinity
  12. msi_set_affinity
  13. irq_msi_update_msg


先看两篇文章:

背景知识

在X86中断注册中,描述了如下结构关系
初始的两个全局 struct irq_domain结构

  1. start_kernel
  2. early_irq_init();
  3. early_irq_init // irqdesc.c 中CONFIG_SPARSE_IRQ=y
  4. arch_early_irq_init
  5. struct irq_domain *x86_vector_domain; // 第一个irq_domain的分配-->总入口
  6. fn = irq_domain_alloc_named_fwnode("VECTOR");
  7. x86_vector_domain = irq_domain_create_tree(..., // 基于x86_vector_domain_ops接口添加默认domain
  8. __irq_domain_add // 初始化irq_domain,fwid->type==IRQCHIP_FWNODE_NAMED
  9. arch_init_msi_domain(x86_vector_domain); // 第二个irq_domain的分配--->PCI设备
  10. irq_domain_alloc_named_fwnode("PCI-MSI");
  11. msi_default_domain = pci_msi_create_irq_domain(fn, &pci_msi_domain_info // 创建MSI默认domain和默认ops
  12. msi_create_irq_domain // 更新msi_default_domain的ops接口
  13. irq_domain_create_hierarchy
  14. irq_domain_create_tree(fwnode, ops, host_data); // 第二个irq_domain添加msi_default_domain

这部分最终结果如下图:
image.png
image.png

PCI中断的域配置(默认irq_domain都为NULL)

其实,在测试中发现,X86主机好像所有的PCI设备的irq_domain都为NULL。

  1. pci_device_add
  2. pci_set_msi_domain(dev); // 这里我跟踪了下代码,也尝试遍历了X86主机pci设备的irq_domain,都为NULL
  3. d = pci_dev_msi_domain(dev);
  4. pci_msi_get_device_domain(dev); // 这里其实都是根据设备树查找的,X86下没设备树,最终都为NULL
  5. u32 rid = PCI_DEVID(pdev->bus->number, pdev->devfn); // rid==>BDF
  6. of_msi_map_get_device_domain(&pdev->dev, rid);
  7. __of_msi_map_rid(dev, &np, rid); // 不支持设备树,跳过,np = NULL
  8. irq_find_matching_host(np, DOMAIN_BUS_PCI_MSI);
  9. irq_find_matching_fwnode(of_node_to_fwnode(node), bus_token); // of_node_to_fwnode(node) == NULL
  10. irq_find_matching_fwspec
  11. dev_set_msi_domain
  12. pcidev->device->msi_domain = NULL
  13. irq_find_matching_fwspec // fwnode = NULL
  14. 遍历irq_domain_list下所有的irq_domain
  15. 调用对应irq_domain->ops下的selectmatch来匹配(注意,不是msi_domain_ops)
  16. dev_set_msi_domain(&dev->dev, d); // 配置对应设备的msi_domain
  17. static const struct irq_domain_ops msi_domain_ops = {
  18. .alloc = msi_domain_alloc,
  19. .free = msi_domain_free,
  20. .activate = msi_domain_activate,
  21. .deactivate = msi_domain_deactivate,
  22. };

代码流程

其实,PCI的MSI中断使用方式很简单
步骤一:为设备分配irq号(对MSIX,会有多个),为该中断分配执行CPU和它使用的vector,并通过对中断控制器的设置,确保对应的中断信号和vector匹配
对于使用INT#类型的中断,通常通过pci_enable_device/pci_enable_device_mem/pci_enable_device_io中对函数pcibios_enable_device的调用来完成(只有在没有开启MSI/MSIX的时候才会为INT#做配置),
而要配置MSI/MSIX中断要使用的是pci_enable_msix。
步骤二:request_irq为该设备的irq指定对应的中断处理例程,把irq号和驱动定义ISR关联

  1. #if 0
  2. pci_enable_msi / __pci_enable_msix 已不推荐使用,使用申请irq向量
  3. #else
  4. err = pci_alloc_irq_vectors(pdev, 1,1, PCI_IRQ_MSI | PCI_IRQ_MSIX);
  5. #endif
  6. request_irq(pci_irq_vector(pdev, 0),
  7. demo_pci_msi_isr, 0, DEV_NAME, pdata);

申请中断向量

因为不使用PIN IRQ,所以这里有几个操作(参考 Linux中断机制:硬件处理,初始化和中断处理 的概览部分):

中断让外设能够通知CPU他需要获得服务(让CPU执行指定的中断服务例程ISR)。 为了达到这个目的,首先要为中断执行做好准备,完成初始化相关的操作。 包括:
1、 初始化中断控制器等相关器件(OS初始化过程中完成);
2、 配置并使能外部设备(比如使用pci_enable_msix),得到irq号;

在这个操作过程中,内核需要完成的大致操作是: 1、 确定该中断的执行CPU,并在对应CPU上建立vector和irq号的对应关系(利用全局per-cpu变量vector_irq),配置中断控制器(I/OAPIC、PIR等),可能还需要设置外部设备(比如设置MSI Capacity registers); 2、 为对应的irq_desc初始化正确的handle_irq接口(通用逻辑接口); 3、 为对应的irq_desc初始化正确的底层chip操作接口。 3、 使用request_irq号为该中断号指定一个服务例程;

完成了以上的初始化操作,在外设中断到来的时候,为该中断指定的ISR(Interrupt Service Routines)就能得到执行,这个执行过程大致如下: 1、 外设根据各自的配置,产生中断信号或者中断消息(MSI,INT# message)。 2、 中断控制器从外设获取中断电信号或者中断消息,把它翻译为vector(CPU使用这个参数来决定是谁发生了中断,要如何处理)并提交到CPU。
3、 对X86系统,CPU利用从中断控制器获取到的vector为索引,查询IDT (interrupt descriptor table)得到该中断的处理接口(对linux,是在entry_64.s中定义的函数common_interrupt接口)并执行。
4、 在linux定义的common_interrupt接口中,执行完中断执行环境建立后,会进入generic interrupt layer执行,其首先通过vector查找到irq和对应的irq_desc结构,并执行该结构的handle_irq接口,这个接口就是generic interrupt layer的通用逻辑接口,比如handle_edge_irq/handle_level_irq等;在中断执行的通用逻辑接口中,会通过irq_desc::action调用外设指定的ISR。
5、 在linux中可以通过/proc/interrupts查看当前系统中所有中断的统计信息,在/proc/irq/xxx(中断号)下面,可以看到该中断的详细信息。

pci_enable_msi的过程
非重点部分

  1. pci_enable_msi // 虽然不推荐,但也是很好的入口
  2. __pci_enable_msi_range(dev, 1, 1, NULL);
  3. __pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec,
  4. const struct irq_affinity *affd)
  5. pci_msi_supported(dev, minvec) // 判断设备和桥是否支持msi
  6. // 从msi capability的control判断支持多少vec,0-对应1,最大32个,
  7. // lspci可以看到,具体可以查看pcie的msi cap结构
  8. nvec = pci_msi_vec_count(dev);
  9. for (;;) {
  10. if (affd) { // 先不考虑中断亲和性问题,这里参数是NULL
  11. nvec = irq_calc_affinity_vectors(minvec, nvec, affd);
  12. }
  13. msi_capability_init(dev, nvec, affd); // 初始化msi信息
  14. }

重点部分

  1. msi_capability_init (重点)
  2. pci_msi_set_enable(dev, 0); // disable msi, 设置capability的enable位
  3. // 设置中断向量入口,解析cap结构,初始化msi_desc结构,nvec就是当前MSI支持的个数
  4. entry = msi_setup_entry(dev, nvec, affd); // 分配一个 struct msi_desc *entry;
  5. mask = msi_mask(entry->msi_attrib.multi_cap);
  6. pci_msi_setup_msi_irqs(dev, nvec, PCI_CAP_ID_MSI); // 建立vector和irq号的对应关系
  7. 支持虚拟化domain分支(可能在虚拟化中有用,但上边描述了,非虚拟化环境中这个domain都为NULL
  8. domain = dev_get_msi_domain(&dev->dev);
  9. irq_domain_is_hierarchy(domain);
  10. msi_domain_alloc_irqs(domain, &dev->dev, nvec);
  11. 不支持虚拟化(真实使用)
  12. arch_setup_msi_irqs(dev, nvec, type)
  13. x86_msi.setup_msi_irqs(dev, nvec, type); // 调用 struct x86_msi_ops x86_msi __ro_after_init 中的接口
  14. native_setup_msi_irqs
  15. /* (关键节点)1. 先找到对应的irq_domain,默认用IOMMU下的msi_domain或者用msi_default_domain */
  16. irq_remapping_get_irq_domain
  17. remap_ops->get_irq_domain(info); // remap_ops在 irq_remapping_prepare 函数中被初始化
  18. get_irq_domain // 这里因为使用AMD机器,看AMD接口: amd_iommu_irq_ops
  19. /* ****(重点) TYPE=X86_IRQ_ALLOC_TYPE_MSI,info->msi_dev就是注册的pci_dev */
  20. devid = get_device_id(&info->msi_dev->dev); // *** 会发现devid就是当前pci_dev的BDF
  21. iommu = amd_iommu_rlookup_table[devid]; // 根据BDF寻找对应的amd_iommu设备->和intel_iommu一个东西,都是DRHD硬件
  22. return iommu->msi_domain; // 每个iommu下有自己的msi_domain
  23. 如果还没找到irq_domain,就用:msi_default_domain
  24. /* (关键节点)2. 从对应的irq_domain中分配irq号,这里使用 msi_default_domain 分析
  25. msi_domain_alloc_irqs(重点接口-Allocate interrupts from a MSI interrupt domain)
  26. msi_domain_prepare_irqs(domain, dev, nvec, &arg); // 总结:bzero(msi_alloc_info_t arg)清0
  27. // msi_default_domain 对应的是 msi_domain_ops ( msi.c)中,
  28. // 注意:这个全局遍历的方法缺省值不全,使用msi_create_irq_domain 添加了几个方法
  29. struct msi_domain_ops *ops = info->ops;
  30. ops->msi_check(domain, info, dev); // ops->msi_check = msi_domain_ops_default.msi_check; 函数直接返回0
  31. ops->msi_prepare(domain, dev, nvec, arg) // ops->msi_prepare = msi_domain_ops_default.msi_prepare; // bzero(arg)
  32. ops->set_desc(&arg, desc); // ops->set_desc = msi_domain_ops_default.set_desc;作用: msi_alloc_info_t arg.desc = desc
  33. __irq_domain_alloc_irqs // Allocate IRQs from domain *****
  34. struct msi_domain_ops *ops = info->ops; // pci_msi_domain_ops
  35. domain->ops->alloc // msi_domain_alloc, 其中 irq_hw_number_t hwirq = ops->get_hwirq(info, arg); // pci_msi_get_hwirq,arg->msi_hwirq;
  36. virq = irq_domain_alloc_descs; // virq=-1,nr_irqs=desc->nvec_used,realloc=false,affinity=desc->affinity=NULL
  37. __irq_alloc_descs // irq=-1,from=hint=1, cnt=desc->nvec_used
  38. alloc_descs(start, cnt, node, affinity, owner); // 实际分配IRQ的地方
  39. for (i = 0; i < cnt; i++) {
  40. desc = alloc_desc(start + i, node, flags, mask, owner); // 给每一个irq分配desc,并初始化
  41. irq_insert_desc(start + i, desc); // 建立搜索树,所以request_irq中通过 desc = irq_to_desc(irq); 来获取desc
  42. irq_sysfs_add(start + i, desc); // /sys/kernel/irq/ irqnum在sysfs的体现
  43. }
  44. pci_intx_for_msi(dev, 0); // 关闭int中断
  45. pci_msi_set_enable(dev, 1); // 使能MSI
  46. dev->irq = entry->irq;

image.png

  1. irq_domain_alloc_irqs_hierarchy // hierarchy 级别
  2. domain->ops->alloc(domain, irq_base, nr_irqs, arg); // 假设domain为 msi_default_domain
  3. msi_domain_alloc

TBD

MSI中断管理
http://www.lihanlu.cn/x86-intr-1/
https://www.pcietech.com/313.html/
https://blog.csdn.net/ye1223/article/details/85050498