随手记——PCIe驱动同时支持MSI和MSIX两种中断模式

在学习资料满天飞的大环境下,知识变得非常零散,体系化的知识并不多,这就导致很多人每天都努力学习到感动自己,最终却收效甚微,甚至放弃学习。我的使命就是过滤掉大量的垃圾信息,将知识体系化,以短平快的方式直达问题本质,把大家从大海捞针的痛苦中解脱出来。

1 需求导入

PCIe驱动之前使用的中断模式一直是MSI。由于Linux内核只支持一个MSI中断号,所以需要在x86的BIOS中开启VT-d选项来支持32个MSI中断号以满足需求。

但是开启VT-d选项会引起其他相关问题,需要关闭。这样,只能将MSI中断方式修改为MSIX中断方式。为了平滑升级,Linux驱动需要同时支持两种中断模式。

2 MSIX与MSI的区别

简单列一下主要区别。

类别MSIMSIX
中断号连续性中断号必须连续中断号可以连续也可以不连续
中断个数最多支持32个中断最多支持2048个中断
中断信息配置位置配置空间配置空间中存放指针;MSIX-table存放在BAR空间

3 代码修改点

3.1 中断申请(修改probe)

修改点主要是申请中断接口的入参和获取中断号的方式。

/* 该函数被probe函数调用 */
static s32 setup_msi(struct pci_dev *pdev, DD_PCIE_ENDPOINT_ATTRIBUTE *pRes)
{
    ...   
    /* 首先尝试申请MSIX中断 */
    ret = pci_alloc_irq_vectors(pdev, MIN_MSI_NUMBER, MAX_MSI_NUMBER, PCI_IRQ_MSIX);
    if (ret <= 0) /* MSIX中断申请失败后,尝试申请MSI中断 */
        ret = pci_alloc_irq_vectors(pdev, MIN_MSI_NUMBER, MAX_MSI_NUMBER, PCI_IRQ_MSI);        
    if (ret <= 0)
    {
        dev_err(&pdev->dev, "Fail to enable MSI/MSIX interrupt ret=%#x.\n",ret);
        return ret;
    }
    
    number = ret; /* MSIX或者是MSI申请中断成功后均会返回申请的中断号个数 */

    for(i = 0; i < number; i++)
    {
        /* 对于MSI模式,其中断号的base可以从pdev->irq中获得;
           但是MSIX模式的中断号必须依靠pci_irq_vector接口获取,
           该接口是通用接口,也可以支持MSI中断号的获取 */
		irq_nr = pci_irq_vector(pdev, i); 
		...
        /* 注册中断号(irq)对应的中断服务例程(isr) */
        ret = devm_request_irq(&pdev->dev, irq_nr, endpoint_isr, 0,"pcie_irq", pdev);
		...
    }

3.2 中断卸载(修改remove)

中断卸载主要修改了irq的获取方式,以及增加了pci_free_irq_vectors接口,已完成PCIe中断去使能的完整流程。


void ep_remove(struct pci_dev *pdev)
{
    s32 d, u, index = 0;
    u32 irq_nr = 0;
    
    for(d = 0; d < MAX_PCIEEP_NUMBER; d++)
    {
        if (PcieHandle.EndPointAttribute[d].DeviceId == pdev->device)
        {
            if(pdev->device == 0x66) /*只有该设备id支持中断*/
            {
                for(u = 0; u < PcieHandle.EndPointAttribute[d].ReceMsiInfo.MsiNumbers; u++)
                {/*遍历所有中断号*/
                    for(index = 0; index < thread_index[pdev->irq + u]; index++)
                    {/*遍历每个中断号上绑定的阻塞队列*/
                        /* 唤醒并通知阻塞线程设备已经移除 */
                        pci_waitqueue_cancle_flag[pdev->irq + u][index] = 1;
                        wake_up_interruptible(&msi_intWaitQueue[pdev->irq + u][index]); 
                    }
                    /* 使用新的接口获取irq,用来释放内核分配的irq */
	                irq_nr = pci_irq_vector(pdev, u);
                    devm_free_irq(&pdev->dev, irq_nr, pdev);
                }
                pci_free_irq_vectors(pdev); /*用来disable PCIe设备的中断*/
            }
            pci_disable_device(pdev);
			...
            return;
        }
    }
}

3.3 BAR空间保护

MSIX的向量表存放在BAR空间中,但是这段地址只有内核去配置才是合理的,而不应该让应用层看到。因此,如果想使用mem_map来将该地址映射到用户空间时一定要注意把Vector table和PBA的地址空间“扣掉”,这样更加安全。

对于本例,endpoint虽然分配了1M的BAR空间,但是大部分地址空间并没有开辟对应的存储空间,因此如果访问这些地址也必定出错,因此对该部分地址空间也需要保护,千万不能用mem_map映射到用户空间。

        Region 0: Memory at 20fe8000000 (64-bit, prefetchable) [size=1M] #分配了1M BAR空间
        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: [60] MSI-X: Enable+ Count=32 Masked-
                Vector table: BAR=0 offset=00008000 #Vector table存放在BAR空间中
                PBA: BAR=0 offset=00008fe0

3.4 特殊情况处理

这里使用的PCIe endpoint是FPGA实现的,为了提升加载速度,FPGA版本被分割成了两个部分(A&B)。A部分重复加载之后就相当于拔插了设备,驱动会对应执行remove核probe操作。但是MSIX的逻辑实现在B部分中,probe执行MSIX初始化时B部分可能还没有加载完成。因此需要在FPGA版本B部分重新加载之后再执行一次驱动模块的rmmod和insmod操作,以便重新触发一次probe动作来解决上述问题。

    /* 在应用程序中,扫描设备后重新移除和加载KO模块*/
    cmd("rmmod endpoint"); /*移除ko*/
    ret = cmd("insmod /usr/software/endpoint_snr.ko"); /*加载ko*/
    if (0 != ret)
    {
        printf("PCIE: do insmod ko error. \n");
    }    

    sleep(1);

4 遇到的问题和解决方法

4.1 MSIX中断上报失败

调试发现MSIX中断上报失败,首先怀疑是endpoint设备的问题,因为endpoint设备是自己用FPGA实现的,嫌疑最大。

根据MSIX产生中的原理,首先检查MSIX的vector table是否可以访问,发现entry所在BAR空间不能访问。

然后查看BAR空间特性,发现是Memory位宽不匹配和FPGA内部配置有误,修改后可以收到MSIX中断。

# 错误配置
Region 0: Memory at 90200000 (32-bit, non-prefetchable) [size=1M]
# 正确配置(注:处理器侧的PCIe地址使用的是64bit地址) 
Region 0: Memory at 20fe8000000 (64-bit, prefetchable) [size=1M]

4.2 MSIX中断全部映射到第一个irq

接着又发现一个问题:32个中断(该例中申请了32个中断)全部映射到了第一个中断上。

# MSIX vector table 前12个中断配置
# Msg Addr(32bit); Msg Upper Addr(32bit); Msg Data(32bit); Vector Ctl(32bit)
D8 04 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
F8 04 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
18 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
38 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
58 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
78 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
98 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
B8 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
D8 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
F8 05 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
18 06 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 
38 06 E0 FE 00 00 00 00 00 00 00 00 00 00 00 00 

检查BAR空间的配置没有错误,后经定位发现是IP核只支持8个MSIX中断,如果配置空间强制使用32个就会发生该问题,需要FPGA自己编码实现MSIX中断的记录和触发逻辑。

FPGA修改代码后解决该问题。

4.3 热插拔endpoint导致处理器复位

定位将PCIe 中断修改为MSIX模式后,FPGA重复加载产生内核告警,该告警有时候会导致处理器复位

告警信息如下:

[ 1861.604019] WARNING: CPU: 0 PID: 1363 at kernel/irq/devres.c:146 devm_free_irq+0x68/0x80=====00
...
[ 1861.798673] RSP: 0018:ffffc90001e67cc0 EFLAGS: 0001028=====00
[ 1861.803249] =====00
[ 1861.804177] RAX: 00000000fffffffe RBX: 000000000000001f RCX: 0000000000000000=====00
[ 1861.810748] RDX: ffffc90001e67cc0 RSI: 0000000000000286 RDI: 0000000000000000=====00
[ 1861.817319] RBP: ffffc90001e67ce8 R08: 0000000000000000 R09: ffff88836fc00248=====00
[ 1861.823890] R10: 0000000000000040 R11: 0000000000000000 R12: ffff888142e13000=====00
[ 1861.830461] R13: 0000000000000020 R14: ffffffffa0322ca0 R15: 0000000000000000=====00
[ 1861.837032] FS:  0000000000000000(0000) GS:ffff88837c600000(0000) knlGS:0000000000000000=====00
[ 1861.844560] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033=====00
[ 1861.849742] CR2: 00007f613d48d000 CR3: 00000001768ea000 CR4: 0000000000340ef0=====00
[ 1861.856313] Call Trace:=====00
[ 1861.858199]  ep_remove+0x1e5/0x239 [endpoint_snr]=====00
[ 1861.862342]  pci_device_remove+0x3e/0xc0=====00
[ 1861.865702]  device_release_driver_internal+0x18f/0x250=====00
[ 1861.870367]  device_release_driver+0x12/0x20=====00
[ 1861.874073]  pci_stop_bus_device+0x6f/0x90=====00
[ 1861.877608]  pci_stop_and_remove_bus_device+0x12/0x20=====00
[ 1861.882099]  pciehp_unconfigure_device+0xb9/0x150=====00
[ 1861.886240]  pciehp_disable_slot+0x54/0xe0=====00
[ 1861.889771]  pciehp_power_thread+0x8a/0xa0=====00
[ 1861.893306]  process_one_work+0x1dd/0x440=====00
[ 1861.896753]  worker_thread+0x34/0x3f0=====00
[ 1861.899855]  kthread+0x121/0x140=====00
[ 1861.902519]  ? process_one_work+0x440/0x440=====00
[ 1861.906140]  ? kthread_flush_work_fn+0x20/0x20=====00
[ 1861.910020]  ret_from_fork+0x35/0x40=====00
[ 1861.913034] ---[ end trace 9a3b7288cdb80177 ]---=====00

原因是MSIX获取中断号的方式和MSI不同导致(在代码修改点一节中已经提到)。修改中断号获取方式,合理释放所有中断号之后解决。

4.4 热插拔endpoint导致MSIX中断失效

调试过程中发现FPGA再次加载之后,MSIX中断上报失败。

MSIX中断方式需要开辟相关的BAR空间用于存放MSIX 向量表,该部分代码FPGA在第二阶段的版本(FPGA加载分两个版本,分两个阶段加载)中实现。
这样会导致在加载第一阶段的版本时hotplug中断已经产生,内核驱动会调用probe进行对MSIX向量表的初始化,但此时,第二阶段的版本有可能还未加载完成,因而导致MSIX的向量表配置失败,MSIX中断无法上报。

修改驱动启动流程,在加载完成FPGA第二阶段之后做驱动程序的rmmod和insmod操作后解决。

4.5 rmmod失败

在解决上一个问题后发现在运行应用程序后,rmmod时会失败。

定位发现rmmod失败是应用层代码在设备移除后没有关闭文件句柄导致。在应用层合适的位置关闭即可。合适的位置是指设备移除之后。


5 复盘

调试下来给人整体的感觉是一个小特性的修改却涉及了很多零碎的问题。

5.1 反思

期间问题引入的根本原因概括起来只有两点:

  1. 先前的驱动程序和应用程序写的不够严谨,没有考虑到异常情况和后续的功能扩展。
  2. endpoint设备由两部分组成。这种设计是一种非常规设计,因此会产生按照正常逻辑所理解不了的问题,这就需要 根据基本工作原理来灵活设计 来适配特殊情况。

5.2 改进

由此,我们可以引发下述思考:

  1. 异常处理可扩展性 是我们在编程过程中一定要重视起来的问题。
  2. 我们在使用一项技术时除了会照猫画虎的用,还要深入理解其背后的原理,这样出现非常规问题时才能有解决问题的思路和灵活变通的方法

恭喜你又坚持看完了一篇博客,又进步了一点点!如果感觉还不错就点个赞再走吧,你的点赞和关注将是我持续输出的哒哒哒动力~~

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

穿越临界点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值