MSI 向量必须连续?
前言
MSI 物理条件,MSI 中断产生的逻辑是RC初始化的时候,由软件将配置写入到 EP 的 2 个寄存器中,这两个寄存器一个指示的是地址 Message Address,一个指示的是数据 Message Data。当 EP 试图触发中断的时候,将会发起一个 MW 事务,往对应的 Message Address 写入 Message Data 的值。
引入问题
MSI 可以配置多达 32 个中断,那么如果是一个相同的 MW 事务,往对应的 Message Address 写入 Message Data 的值,而 Message Data 的值一直是 RC 初始化时配置的值,那么 RC 怎么可以区分不同的中断呢?
分析问题
上面的问题,写入同样的 Message Data 肯定是不能解决的。所以 EP 写的 Data 肯定是变化的,这样 RC 才能区分不同的中断。所以修改代码进行验证。
验证步骤
- 硬件 CPU: FT E2000 PCIe: NVME 硬盘 (CPU 使用的 ITS 的中断方式来处理 MSI 中断)
- 修改代码逻辑
__pci_write_msi_msg
为最后的将 msi_msg 写入 EP 配置空间的操作函数。
- 修改1:struct msi_msg *msg 传入的数据结构里面 Message Address 是 ITS 的某个寄存器的地址,Message Data 是
its_get_event_id
获取的 id 。申请一个非缓存的内存空间__phys_addr
来冒充这个 Message Address。随机选取了 Message Data 为 0xF0 来观察变化。
// 分配非缓存空间
noncache_memory = kmalloc(ALLOC_SIZE, GFP_KERNEL | __GFP_HIGHMEM);
if (!noncache_memory) {
printk(KERN_ERR "Failed to allocate non-cacheable memory\n");
return -ENOMEM;
}
__phys_addr = virt_to_phys(noncache_memory);
printk(KERN_INFO "Non-cacheable memory allocated. Physical Address: %pa\n", &__phys_addr);
- 修改 2:创建一个定时器,来定时查询这个
__phys_addr
的值有没有被修改
#define ALLOC_SIZE 128
static void* noncache_memory;
static phys_addr_t __phys_addr;
static void __iomem *its_virtual_address;
#include <linux/timer.h>
struct irq_data * irq_data_save[128] = {0};
//irq_chip_retrigger_hierarchy
// 内核线程函数
static struct timer_list my_timer;
#define DATA_MSI 0xF0
static void my_kernel_thread(struct timer_list *timer) {
int *paddr = noncache_memory;
if (*paddr != 0) {
printk("msi data change :0x%x irq_data:%px", *paddr, (void*)irq_data_save[97 + (*paddr-(DATA_MSI))]);
irq_chip_retrigger_hierarchy(irq_data_save[97 + (*paddr-DATA_MSI)]);
*paddr = 0;
}
// 重新设置定时器,以实现循环定时
mod_timer(timer, jiffies + msecs_to_jiffies(1));
}
代码中使用 irq_chip_retrigger_hierarchy
来触发 ITS 的中断。97 是写死了,是人为查看系统中 MSI 中断的号起始。
实验结果
nvme 可以正常使用,CPU 是 2 核心,系统中创建了 3 个 MSI 的中断号,97 98 99 一共 3 个中断。
通过观察打印,可以分析得出,EP试图触发中断时,会往刚刚修改的 __phys_addr
的这个内存地址写入数据,有 0xF0, 0xF1, 0xF2 。