.. Kenneth Lee 版权所有 2021
:Authors: Kenneth Lee :Version: 1.0 :Date: 2021-06-15 :Status: Release :Abstract: 本文分析MSI/MSI-X这些消息中断在软硬件中的处理逻辑,硬件部分的行为主 要从PCIE 5的规范和qemu的实现中分析,软件部分主要从Linux主线内核5.x中 分析。
消息中断
:index:msi
消息中断的目的是用内存访问协议取代线连的中断,这对于PCI/PCIe系的标准来说是显而 易见的选择:PCIe协议族本来就是基于消息的一个通讯通道,既然中断也不过就是一种消 息通知,何必要做两套?
所以,合理的选择是,在RC上放一个内应,设备要发生中断了,就从设备上向这个内应发 一个特定的消息,说明自己的中断号,让这个内应在计算子系统中产生一个中断就可以了 。
又,整个PCI协议族的重点是把消息语义封装为内存语义,所以这个消息如果也能解释为内 存语义,这个问题就不需要专门进行额外的发明了。最终一推演,就成了向特定的地址写 特定的值,在这个值上给定中断号,这个消息传递就可以完成了。所以,最终这个协议就 变成了让设备向特定的物理地址中写特定的值。
设备需要发中断,它需要提供的数据是自己需要几个中断,计算子系统需要告诉设备这每个 中断应该写哪个物理地址。这个交换通过capability完成:设备提供一个表,软件填充这个 表,说明要写哪个地址(Message Address),具体要写的内容是什么。
设备按这个要求写出去了,RC负责决定怎么捕获这个写入过程。一种纯软的方法是:就真让 这个请求写到真的内存中,由软件去检查里面的数据写对没有,然后主动产生中断。
另一种方法当然是靠硬件来完成。这也有两种方法,一种是在这个地址上放一个真的硬件, 硬件的MMIO空间被写了,就产生对应的中断。另一种方法是让所有的地址访问请求都通过 RC上的设备过滤,过滤到了对应的地址,就产生中断。
按上面这个逻辑,消息中断只能是边缘触发中断,而不是电平中断。
Qemu中的设备通过msi_notify发MSI中断,实现上就是一个普通的内存访问操作,操作的地址 封装成MSIMessage消息:
.. code-block:: C
struct MSIMessage { uint64_t address; uint32_t data; };
两个参数都直接从设备的配置空间获取。这一点印证了:PCI子系统不关心谁来处理这个地 址访问,它只关心往这个地址写东西。
但这个地址访问可以经过IOMMU,因为它使用的是设备的地址空间,这里面是可以有IOMMU MR的:
.. code-block:: C
address_space_stl_le(&dev->bus_master_as, msg.address, msg.data, attrs, NULL);
这个具体地址的处理过程,我们看一些实现。
首先是amd_iommu。它注册了一个IO MR,但凡写到这个MR的,对地址做一次IR REMAP(AMD 的MSI转义功能),得到一个新的MSI消息,然后调用APIC的send_msi发出去,而在这个实现 中,就是解释msi消息的data域,决定发出什么中断出去。
再看ARM的ITS的做法,原理也是一样的,直接就是一个IO空间,写进来就开始send_msi。 只是ARM没有这个IR Remap的功能。
Linux中PCI设备通过这个函数使能设备的msi功能:
.. code-block:: C
int pci_alloc_irq_vectors_affinity(struct pci_dev dev, unsigned int min_vecs, unsigned int max_vecs, unsigned int flags, struct irq_affinity affd);
它的原理是给每个设备创建一个msi_list,保存所有msi中断的信息(包括在PCI配置空间 中的capa位置),然后找设备所在的irqdomain\ [1]\,从这个domain上分配irq。这样 设备的MSI中断就和系统的IRQ关联在一起了。
.. [1] irq_domain是Linux中管理中断控制器中断(hwirq)和全局中断(irq)关系的数 据结构。它是个树状分层结构,顶层是irq_default_domain(全局变量),下面是 一个个irqchip创建的domain,分层加上去,下层的地址总向上层申请,这样ID分 配就没有冲突了。
irq_find_mapping(domain, hwirq)可以从hwirq求irq。
irq_to_desc(irq)->irq_data->hwirq可以从irq求hwirq。
每个dev有一个msi_domain记住设备所在的msi专用domain,由支持MSI的设备注册
上来(比如IOMMU)。
现在让我们综合一下这个逻辑:
软件控制着两类设备:中断控制器和产生消息中断的设备。产生消息中断的设备不知道要 写什么地址,软件通过MSI或者MSI-X的Capa告诉它要写什么地址。软件要知道这个地址, 就要看中断控制器承认的地址在哪里。这由中断控制器决定,中断控制器可以是纯正的中 断控制器,也可以是IOMMU,用哪个看你想从读写中过滤出中断消息,还是想守株待兔,就 等在某个地址上。
中断控制器靠在irq_domain在整个系统的中断号分配上占据一个空间,它自己实现在它在 接受的hwirq和它被分配的系统irq之间的对应关系。