由于实在找不到MSI-X在x86上实现的教程或文档,只能分析Linux的PCI驱动程序了。希望能得到一些启发。本文基于linux 5.17.5进行分析
__pci_enable_msix_range
static int __pci_enable_msix_range(struct pci_dev *dev,
struct msix_entry *entries, int minvec,
int maxvec, struct irq_affinity *affd,
int flags)
这个函数的作用是,给定的pci设备,以及一些msix表的entry,申请向量号位于一个范围的中断向量。配置设备的MSI-X Capability结构体。
struct msix_entry
struct msix_entry {
u32 vector; /* Kernel uses to write allocated vector */
u16 entry; /* Driver uses to specify entry, OS writes */
};
pci_setup_msi_context
设置设备msi上下文
/*
* Ordering vs. devres: msi device data has to be installed first so that
* pcim_msi_release() is invoked before it on device release.
*/
static int pci_setup_msi_context(struct pci_dev *dev)
struct msi_device_data
msi设备数据结构体
struct msi_device_data {
unsigned long properties;
struct platform_msi_priv_data *platform_data;
struct mutex mutex;
struct xarray __store;
unsigned long __iter_idx;
};
msi_setup_device_data
为pci设备获取data结构体,并初始化。
pci_enable_msix
进行一些条件判断,然后跳转到capability_init
msix_capability_init
该函数初始化function的MSI-X capability structure.
其原型及注释如下:
/**
* msix_capability_init - configure device's MSI-X capability
* @dev: pointer to the pci_dev data structure of MSI-X device function
* @entries: pointer to an array of struct msix_entry entries
* @nvec: number of @entries
* @affd: Optional pointer to enable automatic affinity assignment
*
* Setup the MSI-X capability structure of device function with a
* single MSI-X IRQ. A return of zero indicates the successful setup of
* requested MSI-X entries with allocated IRQs or non-zero for otherwise.
**/
static int msix_capability_init(struct pci_dev *dev, struct msix_entry *entries,
int nvec, struct irq_affinity *affd)
其执行的流程如下:
msix_setup_interrupts
该函数初始化msix中断
原型如下:
static int msix_setup_interrupts(struct pci_dev *dev, void __iomem *base,
struct msix_entry *entries, int nvec,
struct irq_affinity *affd)
执行流程如下(其中每一步出错都会导致退出当前函数):
msix_setup_msi_descs
该函数用于初始化msi-x描述符,描述符只是一个内存中的结构。后面会把描述符交给具体的arch相关的函数去处理,他们读取这个描述符和dev header的内容,就可以去构造与具体体系架构相关的msi-x地址和msi-x数据。
函数原型如下:
static int msix_setup_msi_descs(struct pci_dev *dev, void __iomem *base,
struct msix_entry *entries, int nvec,
struct irq_affinity_desc *masks)
pci_msi_setup_msi_irqs
该函数原型如下:
int pci_msi_setup_msi_irqs(struct pci_dev *dev, int nvec, int type)
该函数首先获取msi的domain,校验是否为继承的(我还不明白这里是干啥意思的)
然后就会去调用pci_msi_legacy_setup_msi_irqs函数,去设置msix。
pci_msi_legacy_setup_msi_irqs
该函数的作用很简单:调用arch_setup_msi_irqs
方法,去设置msi,然后通过pci_msi_setup_check_result
函数去校验设置的结果,看看是否成功,若失败则会到sysfs之中,清除之前设置的一些数据。
arch_setup_msi_irqs
该方法位于pci/msi/legacy.c
中,它会去真正调用每个体系结构的处理器特有的arch_setup_msi_irq
方法,真正的生成与具体体系架构相关的msi-x地址和msi-x数据。
【重点】arch_setup_msi_irq
该函数是每个体系结构特有的函数,为不同平台编译时,将会编译不同的代码。
以x86-64为例,该函数位于arch/oa64/kernel/msi_ia64.c
中,它的内容如下:
int arch_setup_msi_irq(struct pci_dev *pdev, struct msi_desc *desc)
{
struct msi_msg msg;
unsigned long dest_phys_id;
int irq, vector;
irq = create_irq();
if (irq < 0)
return irq;
irq_set_msi_desc(irq, desc);
dest_phys_id = cpu_physical_id(cpumask_any_and(&(irq_to_domain(irq)),
cpu_online_mask));
vector = irq_to_vector(irq);
msg.address_hi = 0;
msg.address_lo =
MSI_ADDR_HEADER |
MSI_ADDR_DEST_MODE_PHYS |
MSI_ADDR_REDIRECTION_CPU |
MSI_ADDR_DEST_ID_CPU(dest_phys_id);
msg.data =
MSI_DATA_TRIGGER_EDGE |
MSI_DATA_LEVEL_ASSERT |
MSI_DATA_DELIVERY_FIXED |
MSI_DATA_VECTOR(vector);
pci_write_msi_msg(irq, &msg);
irq_set_chip_and_handler(irq, &ia64_msi_chip, handle_edge_irq);
return 0;
}
其实就是获取一个可用的irq号,然后生成message address以及message data,然后填写到msix table之中,仅此而已。
最后发现,在x86-64平台上边,MSI和MSI-X的消息数据和消息地址都是一样的。区别就是,这两个东西最后要写到哪里罢了。
msix_update_entries
该函数只是把之前生成的描述符中的irq号,更新到entry结构体之中。
一点思考
至此,为具体的设备初始化msi-x的工作就完成了。如果不考虑可移植性的话,确实不需要考虑那么多。
个人认为,在初步实现的时候,值得学习的点有:采用msi_desc和entry的两个抽象数据结构,方便将msi和MSI-X统一起来管理,减少代码冗余。并且也实现了系统其他模块所需要的中断的信息与具体的处理器架构的解耦。这样的一个抽象,可以减少将来不同架构之间的移植所耗费的时间。