Pcie MSI/MSI-X
zhilu.zhang
zhilu.zhang
发布于 2023-10-14 / 540 阅读 / 0 评论 / 0 点赞

Pcie MSI/MSI-X

一、简述

pcie的MSI-X是如何实现的?为什么向一个地址写值就可以产生中断了呢?

带着这样的问题去探索。

二、MSI和MSI-X的区别

PCI Express (PCIe) 是一种计算机总线标准,用于连接各种硬件设备,如显卡、存储控制器、网络适配器等,以便它们可以与计算机的中央处理单元(CPU)通信。PCIe支持多种中断管理方式,其中包括MSI(Message Signaled Interrupts)和MSI-X(Message Signaled Interrupts eXtended)。以下是它们的详细介绍、实现机制以及区别和联系:

MSI (Message Signaled Interrupts):

  • 实现机制:

    • MSI 是一种中断分配机制,通过向设备发送专门的消息(消息标识符)来触发中断。

    • 在PCIe设备上,MSI的消息是由设备本身生成,不需要外部中断控制器。

    • 当设备需要触发中断时,它将向特定的中断向量发送一个消息,然后CPU可以识别该消息并响应中断。

  • 优点:

    • 减少了中断冲突,因为每个设备都可以拥有唯一的消息标识符。

    • 支持更多的中断源,因为不受中断线的限制。

MSI-X (Message Signaled Interrupts eXtended):

  • 实现机制:

    • MSI-X 是 MSI 的扩展版本,提供了更多的灵活性。

    • 它允许设备向CPU发送多个不同的中断消息,每个消息都有一个唯一的消息标识符。

    • 设备可以使用 MSI-X 表来管理中断消息,而不是使用单一的 MSI 表。

  • 优点:

    • 支持更多中断的管理和分配,因为可以有多个消息。

    • 允许更精细的中断控制,可以根据需要配置不同的中断处理程序。

区别和联系:

  1. 扩展性:

    • MSI-X 是 MSI 的扩展,支持更多的中断消息,因此更灵活。

    • MSI 通常只支持一个中断消息。

  2. 中断管理:

    • MSI-X 使用专门的 MSI-X 表来管理中断消息。

    • MSI 使用单一的 MSI 表。

  3. 唯一性:

    • 每个MSI消息(无论是MSI还是MSI-X)都有一个唯一的消息标识符,以防止中断冲突。

  4. 硬件支持:

    • MSI-X 需要更复杂的硬件支持,包括对MSI-X表的访问。

    • MSI 较为简单,通常在更早的PCIe设备上得到支持。

在实际应用中,选择使用MSI还是MSI-X通常取决于硬件设备的支持和操作系统的驱动程序。一些现代PCIe设备和操作系统可以同时支持两种中断机制,以提供更大的灵活性和性能优化。这些中断管理机制对于减少中断冲突、提高系统性能和可扩展性都非常重要

三、GIC

MSI和MSI-X的实现需要GIC的支持,先关注下GIC下的内容。

GICv3中有LPI(locality-specific peripheral)中断类型,和ITS(interrupt translatio service)。LPI是基于消息的边沿中断。中断信息不再使用中断线进行传递,节约了硬件资源。当外设向GIC/ITS的指定寄存器写入数据时,GIC就会发生一个LPI中断。LPI的中断号从8192开始,支持的数量由硬件配置决定(n bit)。 LPI的中断配置表(property table)和状态表(pending table)是存在memory中,并不是在寄存器内。实际上所有涉及到LPI的table都放在memory中,同时在GIC/ITS内部也是cache的,并且所有的表都在Non-secure physical address space,这些表和寄存器的关系:

  1. MBI(Message-Based Interrupts):MBI是GICv3中的一个重要概念,它代表了一种中断传递机制,其中中断信息通过消息传递的方式发送给处理器核心。MBI的引入可以提高中断的效率和可伸缩性,特别适用于多核系统,因为它允许中断只针对需要处理它的核心进行传递。MBI的使用减少了不必要的中断传递,有助于提高系统性能。

  2. LPI(Local Peripheral Interrupt):LPI是指针对特定外围设备的中断。与传统的中断控制器不同,GICv3引入了LPI,允许外围设备向处理器核心发送中断,而不需要全局的中断线。这减少了系统中断线的数量,提高了系统的可伸缩性和性能。每个处理器核心都有一个本地中断控制器,用于管理LPI。

  3. ITS(Interrupt Translation Service):ITS是GICv3中的另一个关键组件,它允许将外围设备产生的中断映射到逻辑中断号,然后将其传递给处理器核心。ITS有助于降低中断传递的复杂性,同时提供了一种更有效的中断路由机制。ITS的使用允许系统管理员配置中断的分配和映射,以满足系统需求。

1. 消息中断

外设,不在通过专用中断线,向gic发送中断,而是写gic的寄存器,来发送中断。

这样的一个好处是,可以减少中断线的个数。

为了支持消息中断,gicv3,增加了LPI,来支持消息中断。并且为他分配了特别多的中断号,从8192开始,移植到16777216。

LPI,locality-specific peripheral interrupts。spec中,用了一章,来介绍这个LPI。

2. LPI介绍

LPI是一种基于消息的边沿中断。也就是,中断信息,不在通过中断线,进行传递,而是通过memory。gic内部,提供一个寄存器,当外设往这个地址,写入数据时,就往gic发送了一个中断。

在soc系统中,外设想要发送中断给gic,是需要一根中断线的。如果现在一个外设,需要增加一个中断,那么就要增加一根中断线,然后连接到gic。这样,就需要修改设计。而引入了LPI之后,当外设需要增加中断,只需要使用LPI方式,传输中断即可,不需要修改soc设计。

引入了LPI之后,gicv3中,还加入了ITS组件,interrupt translation service。ITS将接收到的LPI中断,进行解析,然后发送到对应的redistributor,再由redistributor将中断信息,发送给cpu interface。

以下是带LPI的框图。外设,通过写 GITS_TRANSLATER 寄存器,来传递消息中断。

LPI,和SPI,PPI,SGI有些差别,LPI的中断的配置,以及中断的状态,是保存在memory的表中,而不是保存在gic的寄存器中的。

  • GICR_PROPBASER:保存LPI中断配置表的基地址

  • GICR_PENDBASER: 保存LPI中断状态表的基地址

这里,就涉及到两个表:

LPI中断配置表

该表,保存在memory中。基地址,由GICR_PROPBASER寄存器决定。

该寄存器描述如下:

其中的Physical_Address字段,指定了LPI中断配置表的基地址。

对于LPI配置表,每个LPI中断,占用1个字节,指定了该中断的使能和中断优先级。

当外部发送LPI中断给redistributor,redistributor首先要查该表,也就是要访问memory来获取LPI中断的配置。为了加速这过程,redistributor中可以配置cache,用来缓存LPI中断的配置信息。

因为有了cache,所以LPI中断的配置信息,就有了2份拷贝,一份在memory中,一份在redistributor的cache中。如果软件修改了memory中的LPI中断的配置信息,需要将redistributor中的cache信息给无效掉。

LPI中断状态表

该表,处于memory中,保存了LPI中断的状态,是否pending状态。

LPI中断的状态,不是保存在寄存器中,而是保存在memory中的pending表中。该状态表,由redistributor来进行更改。而该table的基地址,是由软件来设置的。

软件通过设置 GICR_PENDBASER 寄存器来设置。

该寄存器,设置LPI状态表的基地址,该状态表的memory的属性,如shareability,cache属性等。

每个LPI中断,占用一个bit空间

  • 0: 该LPI中断,没有处于pending状态

  • 1: 该LPI中断,处于pending状态

该状态表,由redistributor来设置。软件如果修改该表,会引发unpredictable行为。

3. LPI的实现方式

为了实现LPI,gicv3定义了以下两种方法来实现:

  • 使用ITS,将外设发送到eventID,转换成LPI 中断号

  • forwarding方式,直接访问redistributor的寄存器GICR_SERLPIR,直接发送LPI中断

forwarding方式

这种方式,比较简单,主要由下面几个寄存器来实现:

  • GICR_SERLPIR

  • GICR_CLRLPIR

  • GICR_INVLPIR

  • GICR_INVALLR

  • GICR_SYNCR

其gic框图如下所示:

GICR_SERLPIR,将指定的LPI中断,设置为pending状态。

GICR_INVLPIR,将指定的LPI中断,清除pending状态。寄存器内容和GICR_SERLPIR一致。

GICR_INVLPIR,将缓存中,指定LPI的缓存给无效掉,使GIC重新从memory中载入LPI的配置。

GICR_INVALLR,将缓存中,所有LPI的缓存给无效掉,使GIC重新从memory中,载入LPI中断的配置。

GICR_SYNCR,对redistributor的操作是否完成。

寄存器,只有第0bit是有效的。如果为0,表示当前对redistributor的操作是完成的,如果为1,那么是没有完成的。

ITS方式

理解了forwarding方式,那么理解ITS方式,就要容易了。forwarding方式,是直接得到了LPI的中断号。

但是对于ITS方式,是不知道LPI的中断号的。需要将外设发送的DeviceID,eventID,通过一系列查表,得到LPI的中断号以及该中断对应的target redistributor,然后将LPI中断,发送给对应的redistributor。

下图是带有ITS的gic框图:

外设,通过写GITS_TRANSLATER寄存器,发起LPI中断。写操作,给ITS提供2个信息:

  • EventID:值保存在GITS_TRANSLATER寄存器中,表示外设发送中断的事件类型

  • DeviceID:表示哪一个外设发起LPI中断。该值的传递,是实现自定义,例如,可以使用AXI的user信号来传递。

ITS将DeviceID和eventID,通过一系列查表,得到LPI中断号,再使用LPI中断号查表,得到该中断的目标cpu。

ITS将LPI中断号,LPI中断对应的目标cpu,发送给对应的redistributor。redistributor再将该中断信息,发送给CPU。

4. ITS

ITS是一个组件,用来提供给外设,发送LPI中断的,然后将LPI中断,发送给redistributor。

ITS处理流程

ITS使用三类表格,实现LPI的转换和映射:

  • device table: 映射deviceID到中断转换表

  • interrupt translation table:映射EventID到INTID。以及INTID属于的collection组

  • collection table:映射collection到redistributor

当外设往GITS_TRANSLATER寄存器中写数据后,ITS做如下操作:

  • 使用DeviceID,从设备表(device table)中选择索引为DeviceID的表项。从该表项中,得到中断映射表的位置

  • 使用EventID,从中断映射表中选择索引为EventID的表项。得到中断号,以及中断所属的collection号

  • 使用collection号,从collection表格中,选择索引为collection号的表项。得到redistributor的映射信息

  • 根据collection表项的映射信息,将中断信息,发送给对应的redistributor

以上是物理LPI中断的ITS流程。虚拟LPI中断的ITS流程与之类似。以下是处理流程图:

ITS命令

ITS操作,会涉及到很多表,而这些表的创建,维护是通过ITS命令,来实现的。虽然这些表,是在内存中的,但是GICv3和GICv4,不支持直接访问这些表,而是要通过ITS命令,来配置这些表。

ITS的操作,是通过命令,来控制的。外部通过发送命令给ITS,ITS然后去执行命令,每个命令,占32字节。

ITS有command队列,命令写在这个队列里面。ITS会自动的按照队列顺序,一一执行。

每个命令占32个字节。

命令,存放在内存中,GITS_CBASE,保存命令的首地址。GITS_CREADR,是由ITS控制,表示下一个命令的地址。GITS_CWRITER,是下一个待写命令的地址。软件往GITS_CWRITER地址处,写入命令,之后ITS就会执行这个命令。

ITS提供的命令,有很多,可以查阅GIC手册获取更多。

以下是CLEAR命令。

ITS table

ITS包括很多个表,这些表均处于 non-secure区域。

GITS_BASER<n>,指定ITS表的基地址和大小。软件,在使用ITS之前,必须要配置。

其中的Physical_Address字段,就指定了表的基地址所在位置。

以下是各个表的基地址,对应的寄存器。

四、MSI/MSI-X实现

硬件实现

MSI支持32个中断向量,MSI-X支持2048个中断向量,但是MSI-X的相关寄存器在PCIE的配置空间中占用的空间却更小。这是因为中断向量信息并不直接存储在寄存器中,而是存在BAR中。通过BIR(这个是GIC的寄存器吗?)来确定其在BAR中的具体位置。无论是MSI还是MSI-X,其本周都是基于Memor Write,RC Controller收到这条Memory write请求后,会按照硬件的实现,产生本地中断给SOC。

可以通过Bus + Device + function转换为Device ID,接着把Device ID设置到ITS中。Event ID可以来自MSI/MSI-X data,设置到ITS的GITS_TRANSLATER寄存器。

EP把ITS的GITS_TRANSLATER寄存器映射到BAR中,RC直接写GITS_TRANSLATER,触发EP的LPI。

软件实现

设备树文件rk3399.dtsi中,可以看到ITS的基地址是0xfee20000:

its: interrupt-controller@fee20000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        reg = <0x0 0xfee20000 0x0 0x20000>;
                };
​

IHI0069G_gic_architecture_specification.pdf中有ITS寄存器的偏移地址:

GITS_TRANSLATER寄存器的CPU地址是:0xfee20000 + 0x010000 + 0x0040 = 0xfee30040。

对应的PCI地址也是0xfee30040(驱动程序里为例方便,故意使得CPU地址跟PCI地址相同,这2个地址属于不同地址空间),

所以下图中PCI地址都是0xfee30040。

使用RK3399开发板,插上了NVMe SSD固态硬盘。

执行lspci -vvv,得到如下信息:

01:00.0 Non-Volatile memory controller: Silicon Motion, Inc. Device 2263 (rev 03) (prog-if 02 [NVM Express])
        Subsystem: Silicon Motion, Inc. Device 2263
        Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Latency: 0
        Interrupt: pin A routed to IRQ 231
        Region 0: Memory at fa000000 (64-bit, non-prefetchable) [size=16K]
        Capabilities: [40] Power Management version 3
                Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0-,D1-,D2-,D3hot-,D3cold-)
                Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-
        Capabilities: [50] MSI: Enable- Count=1/8 Maskable+ 64bit+
                Address: 0000000000000000  Data: 0000
                Masking: 00000000  Pending: 00000000
        Capabilities: [70] Express (v2) Endpoint, MSI 00
                DevCap: MaxPayload 128 bytes, PhantFunc 0, Latency L0s unlimited, L1 unlimited
                        ExtTag- AttnBtn- AttnInd- PwrInd- RBE+ FLReset+ SlotPowerLimit 0.000W
                DevCtl: Report errors: Correctable- Non-Fatal- Fatal- Unsupported-
                        RlxdOrd+ ExtTag- PhantFunc- AuxPwr- NoSnoop- FLReset-
                        MaxPayload 128 bytes, MaxReadReq 512 bytes
                DevSta: CorrErr- UncorrErr- FatalErr- UnsuppReq- AuxPwr+ TransPend-
                LnkCap: Port #0, Speed 8GT/s, Width x4, ASPM L1, Exit Latency L0s <1us, L1 <8us
                        ClockPM+ Surprise- LLActRep- BwNot- ASPMOptComp+
                LnkCtl: ASPM Disabled; RCB 64 bytes Disabled- CommClk-
                        ExtSynch- ClockPM- AutWidDis- BWInt- AutBWInt-
                LnkSta: Speed 2.5GT/s, Width x4, TrErr- Train- SlotClk+ DLActive- BWMgmt- ABWMgmt-
                DevCap2: Completion Timeout: Range ABCD, TimeoutDis+, LTR+, OBFF Not Supported
                DevCtl2: Completion Timeout: 50us to 50ms, TimeoutDis-, LTR-, OBFF Disabled
                LnkCtl2: Target Link Speed: 8GT/s, EnterCompliance- SpeedDis-
                         Transmit Margin: Normal Operating Range, EnterModifiedCompliance- ComplianceSOS-
                         Compliance De-emphasis: -6dB
                LnkSta2: Current De-emphasis Level: -3.5dB, EqualizationComplete-, EqualizationPhase1-
                         EqualizationPhase2-, EqualizationPhase3-, LinkEqualizationRequest-
        Capabilities: [b0] MSI-X: Enable+ Count=16 Masked-
                Vector table: BAR=0 offset=00002000
                PBA: BAR=0 offset=00002100
        Capabilities: [100 v2] Advanced Error Reporting
                UESta:  DLP- SDES- TLP- FCP- CmpltTO- CmpltAbrt- UnxCmplt- RxOF- MalfTLP- ECRC- UnsupReq- ACSViol-
                UEMsk:  DLP- SDES- TLP- FCP- CmpltTO- CmpltAbrt- UnxCmplt- RxOF- MalfTLP- ECRC- UnsupReq- ACSViol-
                UESvrt: DLP+ SDES+ TLP- FCP+ CmpltTO- CmpltAbrt- UnxCmplt- RxOF+ MalfTLP+ ECRC- UnsupReq- ACSViol-
                CESta:  RxErr- BadTLP- BadDLLP- Rollover- Timeout- NonFatalErr-
                CEMsk:  RxErr- BadTLP- BadDLLP- Rollover- Timeout- NonFatalErr+
                AERCap: First Error Pointer: 00, GenCap+ CGenEn- ChkCap+ ChkEn-
        Capabilities: [158 v1] #19
        Capabilities: [178 v1] Latency Tolerance Reporting
                Max snoop latency: 0ns
                Max no snoop latency: 0ns
        Capabilities: [180 v1] L1 PM Substates
                L1SubCap: PCI-PM_L1.2+ PCI-PM_L1.1+ ASPM_L1.2+ ASPM_L1.1+ L1_PM_Substates+
                          PortCommonModeRestoreTime=10us PortTPowerOnTime=10us
                L1SubCtl1: PCI-PM_L1.2- PCI-PM_L1.1- ASPM_L1.2- ASPM_L1.1-
                           T_CommonMode=0us LTR1.2_Threshold=0ns
                L1SubCtl2: T_PwrOn=10us
        Kernel driver in use: nvme
​

从上述信息可以看到:

        Region 0: Memory at fa000000 (64-bit, non-prefetchable) [size=16K]
        Capabilities: [b0] MSI-X: Enable+ Count=16 Masked-
                Vector table: BAR=0 offset=00002000
                PBA: BAR=0 offset=00002100
​

这表示:

MSI-X: Enable+:使用MSI-X功能 Vector table: BAR=0 offset=00002000:MSI的向量在BAR 0偏移地址0x00002000处 Region 0: Memory at fa000000: BAR 0的PCI地址是0xfa000000, 驱动程序里为了方便令CPU地址等于PCI地址,所以BAR的CPU地址也是0xfa000000。 我们可以去读取 0xfa000000 + 0x00002000开始的向量表,验证里:

  • msg addr为0xfee30040

  • msg data为0、1、……

验证MSI-X信息

使用MSI/MSI-X

参考内核文档:Documentation\PCI\MSI-HOWTO.txtdrivers\nvme\host\pci.c

主要函数是这2个:

int pci_enable_msix_range(struct pci_dev *dev, struct msix_entry *entries,
              int minvec, int maxvec);
int pci_enable_msi_range(struct pci_dev *dev, int minvec, int maxvec);
​

示例代码如下:

    // 分配 msix_entry 数组,每一数组项用来保存一个中断的信息
    dev->entry = kzalloc_node(num_possible_cpus() * sizeof(*dev->entry),
                            GFP_KERNEL, node);
​
    // 先尝试使用MSI-X
    vecs = pci_enable_msix_range(pdev, dev->entry, 1, nr_io_queues);
    if (vecs < 0) {
        // 再尝试使用MSI
        vecs = pci_enable_msi_range(pdev, 1, min(nr_io_queues, 32));
        if (vecs < 0) {
            vecs = 1;
        } else {
            for (i = 0; i < vecs; i++)
                dev->entry[i].vector = i + pdev->irq;
        }
    }
​
    // request_irq: 中断号都保存在dev->entry[i].vector里
    for (i = 0; i < vecs; i++)
        request_irq(dev->entry[i].vector, ...);
​

注意,在pci_enable_msix_range或者pci_enable_msi_range函数中

  • minvec从1开始

  • 对于pci_enable_msix_range,中断号保存在entries[i].vector里

  • 对于pci_enable_msi_range,第1个中断号保存在pdev->irq里

MSI/MSI-X中断源码分析

IRQ Domain创建流程

从PCI设备触发,涉及三个IRQ Domain:

  • drivers\irqchip\irq-gic-v3-its-pci-msi.c

  • drivers\irqchip\irq-gic-v3-its.c

  • drivers\irqchip\irq-gic-v3.c

GIC

设备树

        gic: interrupt-controller@fee00000 {
                compatible = "arm,gic-v3";
                #interrupt-cells = <4>;
                #address-cells = <2>;
                #size-cells = <2>;
                ranges;
                interrupt-controller;
​
                reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
                      <0x0 0xfef00000 0 0xc0000>, /* GICR */
                      <0x0 0xfff00000 0 0x10000>, /* GICC */
                      <0x0 0xfff10000 0 0x10000>, /* GICH */
                      <0x0 0xfff20000 0 0x10000>; /* GICV */
                interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
                its: interrupt-controller@fee20000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        reg = <0x0 0xfee20000 0x0 0x20000>;
                };
​

驱动代码:`drivers\irqchip\irq-gic-v3.c

ITS

设备树:

its: interrupt-controller@fee20000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        reg = <0x0 0xfee20000 0x0 0x20000>;
                };
​

驱动代码:drivers\irqchip\irq-gic-v3-its.c

PCI MSI

对应的设备节点跟ITS驱动使用的一样的:

its: interrupt-controller@fee20000 {
                        compatible = "arm,gic-v3-its";
                        msi-controller;
                        reg = <0x0 0xfee20000 0x0 0x20000>;
                };
​

驱动代码:drivers\irqchip\irq-gic-v3-its-pci-msi.c,它只是在ITS下面再增加了一个处理层:

PCIe控制器

设备树:

        pcie0: pcie@f8000000 {
                compatible = "rockchip,rk3399-pcie";
                #address-cells = <3>;
                #size-cells = <2>;
                aspm-no-l0s;
                clocks = <&cru ACLK_PCIE>, <&cru ACLK_PERF_PCIE>,
                         <&cru PCLK_PCIE>, <&cru SCLK_PCIE_PM>;
                clock-names = "aclk", "aclk-perf",
                              "hclk", "pm";
                bus-range = <0x0 0x1f>;
                max-link-speed = <1>;
                linux,pci-domain = <0>;
                msi-map = <0x0 &its 0x0 0x1000>;
​

里面的msi-map = <0x0 &its 0x0 0x1000>;是用来把PCIe设备映射到MSI控制器,它的格式为:

msi-map = <rid-base &msi-controller msi-base length>;
  • rid-base:第1个Request ID,就是使用<bus, dev, function>组成的一个数字

  • msi-controller:这个PCIe设备映射到哪个MSI控制器?

  • msi-base:第1个PCIe设备映射到MSI控制器哪个中断?

  • length:能映射多少个设备

分配中断

代码:drivers\nvme\host\pci.c

nvme_probe > nvme_probe_work > nvme_setup_io_queues 
        pci_enable_msix_range
            pci_enable_msix(dev, entries, nvec);
                msix_capability_init(dev, entries, nvec);
        
        pci_enable_msi_range
                msi_capability_init(dev, nvec);
    
msix_capability_init/msi_capability_init
    pci_msi_setup_msi_irqs
        pci_msi_domain_alloc_irqs
            msi_domain_alloc_irqs
                ret = ops->msi_prepare(domain, dev, nvec, &arg); // its_pci_msi_prepare
                    its_pci_msi_prepare // irq-gic-v3-its-pci-msi.c
                        // rid = (bus << 8) | (dev << 4) | function
                        info->scratchpad[0].ul = pci_msi_domain_get_msi_rid(domain, pdev);  
                        return msi_info->ops->msi_prepare(...) // 上一层irq-gic-v3-its.c
                                its_msi_prepare
                                    dev_id = info->scratchpad[0].ul;  // rid
                                    its_dev = its_create_device(its, dev_id, nvec);
                                                // 从ITS全局的位图里找到空闲位 chunk
                                                // 一个chunk表示32个中断
                                                // its的hwirq = (chunk << 5) + 8192
                                                // 这也是GIC的hwirq
                                                lpi_map = its_lpi_alloc_chunks(nvecs, &lpi_base, &nr_lpis);
                                                        // 等于(chunk << 5) + 8192 
                                                        dev->event_map.lpi_base = lpi_base;
                                    
                __irq_domain_alloc_irqs
                    irq_domain_alloc_irqs_recursive
                        ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
                                its_irq_domain_alloc
                                    err = its_alloc_device_irq(its_dev, &hwirq);
                                                *hwirq = dev->event_map.lpi_base + idx;
                                    irq_domain_set_hwirq_and_chip
                                        irq_data->hwirq = hwirq;
                                        irq_data->chip = chip ? chip : &no_irq_chip;
                irq_domain_activate_irq(irq_data);
                    domain->ops->activate(domain, irq_data);
                        msi_domain_activate
                            irq_chip_compose_msi_msg(irq_data, &msg)    
                                   // 构造msg,里面含有MSI或msi-x的addr/val
                                   its_irq_compose_msi_msg
                                        addr = its->phys_base + GITS_TRANSLATER;
                                        msg->address_lo     = addr & ((1UL << 32) - 1);
                                        msg->address_hi     = addr >> 32;
                                        // its_get_event_id:
                                        // d->hwirq - its_dev->event_map.lpi_base;
                                        msg->data       = its_get_event_id(d);                   
                            // 设置msi-x的entry地址  
                            irq_chip_write_msi_msg(irq_data, &msg);
                            data->chip->irq_write_msi_msg(data, msg);
                            pci_msi_domain_write_msg
                                __pci_write_msi_msg(desc, msg);
​
__pci_write_msi_msg(desc, msg);
    // 对于MSI-X
    writel(msg->address_lo, base + PCI_MSIX_ENTRY_LOWER_ADDR);
    writel(msg->address_hi, base + PCI_MSIX_ENTRY_UPPER_ADDR);
    writel(msg->data, base + PCI_MSIX_ENTRY_DATA);
​
    // 对于MSI
    pci_write_config_word(dev, pos + PCI_MSI_FLAGS, msgctl);
    pci_write_config_dword(dev, pos + PCI_MSI_ADDRESS_LO,
                       msg->address_lo);
​
​
// 为PCI设备确定hwirq
its_domain_ops.alloc
its_irq_domain_alloc
    its_alloc_device_irq
        *hwirq = dev->event_map.lpi_base + idx;
​
​

五、参考资料

https://blog.csdn.net/jia_weihui/category_12184747.html

http://www.lujun.org.cn/?p=3921