一、简述
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 表。
优点:
支持更多中断的管理和分配,因为可以有多个消息。
允许更精细的中断控制,可以根据需要配置不同的中断处理程序。
区别和联系:
扩展性:
MSI-X 是 MSI 的扩展,支持更多的中断消息,因此更灵活。
MSI 通常只支持一个中断消息。
中断管理:
MSI-X 使用专门的 MSI-X 表来管理中断消息。
MSI 使用单一的 MSI 表。
唯一性:
每个MSI消息(无论是MSI还是MSI-X)都有一个唯一的消息标识符,以防止中断冲突。
硬件支持:
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,这些表和寄存器的关系:
MBI(Message-Based Interrupts):MBI是GICv3中的一个重要概念,它代表了一种中断传递机制,其中中断信息通过消息传递的方式发送给处理器核心。MBI的引入可以提高中断的效率和可伸缩性,特别适用于多核系统,因为它允许中断只针对需要处理它的核心进行传递。MBI的使用减少了不必要的中断传递,有助于提高系统性能。
LPI(Local Peripheral Interrupt):LPI是指针对特定外围设备的中断。与传统的中断控制器不同,GICv3引入了LPI,允许外围设备向处理器核心发送中断,而不需要全局的中断线。这减少了系统中断线的数量,提高了系统的可伸缩性和性能。每个处理器核心都有一个本地中断控制器,用于管理LPI。
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.txt
、drivers\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;