vxworks issue: pcie msi interrupt

1. 问题现象

在调试 Am5728 + vxworks时,风河反馈两个pcie插槽,只有pcie1能使用msi中断pcie2不能使用使用msi中断,后来只能把两个插槽都换成传统的int_x中断。

为什么pcie2不能使用msi中断?

2. 分析过程

AM5728对硬件中断处理部分可以参考"am5728_int_map_summary.pdf"。

Step 1. 分析传统的int_x中断流程

在初始化时注册中断:

LOCAL STATUS tiAm572xPcieAttach()
{
    ...

    /* (1) 获取pcie1/pcie2对应dts中的"interrupts"属性
        pcie1: "interrupts      = <40 0 4>;" 
        pcie2: "interrupts      = <188 0 4>;"
     */
    pDrvCtrl->pMsiIntRes = vxbResourceAlloc (pDev, VXB_RES_IRQ, 0);

    /* (2) 使用"interrupts"属性来注册pcie中断总入口 */
    if (ERROR == vxbIntConnect (pDev, pDrvCtrl->pMsiIntRes,
                                (VOIDFUNCPTR)tiAm572xPcieMsiHandler,
                                (void *)pDrvCtrl))
        {
        DEBUG_MSG (AM572X_DBG_ERR, "<tiAm572xPcieAttach>: vxbIntConnect "
                   "failed\n");
        goto errOut;
        }

    /* (3) 分配传统INT_X的中断槽位
        新建一个中断控制器,因为是静态中断,会在vxbIntCtrl[0 - 31] 中寻找一个空闲中断控制器槽位
        然后在vxbIntrVecs[]中分配4个中断向量槽位
     */
    if (ERROR == vxbIntRegister (pDev, pFdtDev->offset, AM572X_PCI_MAX_INT_LINE,
                                 0, &pDrvCtrl->intxVecBase))
        {
        DEBUG_MSG (AM572X_DBG_ERR, "<tiAm572xPcieAttach>: Register legacy intx "
                   "vectors failed\n");
        goto errOut;
        }

    /* (4) 分配MSI的中断槽位
        新建一个中断控制器,因为是动态中断,会在vxbIntCtrl[32 - 39] 中寻找一个空闲中断控制器槽位
        然后在vxbIntrVecs[]中分配32个中断向量槽位
     */
    if (ERROR == vxbDyncIntRegister (pDev, AM572X_PCIE_MSI_MAX_NUM,
                                     &pDrvCtrl->msiVecBase))
        {
        DEBUG_MSG (AM572X_DBG_ERR, "<tiAm572xPcieAttach>: Register msi vectors "
                   "failed\n");
        goto errOut;
        }

    ...
}

在中断总入口处理函数中,区分是传统INT_X或者MSI中断:

LOCAL void tiAm572xPcieMsiHandler()


    irqStatus = TI_CFG_READ_4 (pDrvCtrl, TI_CONF_IRQSTATUS_MSI);
    if (0 == irqStatus)
        {
        return;
        }

    /* (2.1) MSI中断 */
    if (0 != (irqStatus & TI_CONF_IRQSTATUS_MSI_MSI))
        {
        msiStatus = CSR_READ_4 (pDrvCtrl, PL_MSI_CTRL_INT_STATUS_0);
        if (0 != msiStatus)
            {
            for (i = 0; i < AM572X_PCIE_MSI_MAX_NUM; i++)
                {
                if (msiStatus & (1 << i))
                    {

                    /* (2.1.1) 提取出具体的MSI中断编号,并执行中断服务程序 */
                    CSR_WRITE_4 (pDrvCtrl, PL_MSI_CTRL_INT_STATUS_0, (1 << i));
                    vector = pDrvCtrl->msiVecBase + AM572X_PCI_MAX_INT_LINE + i;
                    if (vxbIntrVecs[vector]->flag & VXB_INTR_VEC_ENABLE)
                        {
                        VXB_INT_ISR_CALL (vector);
                        }
                    }
                }
            }
        }

    /* (2.2) 传统INT_X中断 */
    if (0 != (irqStatus & TI_CONF_IRQSTATUS_MSI_INTX))
        {
        for (i = 0; i < AM572X_PCI_MAX_INT_LINE; i++)
            {
            if (irqStatus & (1 << i))
                {
                /* (2.2.1) 提取出具体的INT_X中断编号,并执行中断服务程序 */
                vector = pDrvCtrl->intxVecBase + i;
                if (vxbIntrVecs[vector]->flag & VXB_INTR_VEC_ENABLE)
                    {
                    VXB_INT_ISR_CALL (vector);
                    }
                }
            }
        }

    TI_CFG_WRITE_4 (pDrvCtrl, TI_CONF_IRQSTATUS_MSI, irqStatus);
    }

在pcie bus遍历并添加设备设备时,如果设备的INT_PIN寄存器不为0,会从INT_0/1/2/3中给其分配一个传统INT_X中断:

tiAm572xPcieAttach() -> vxbPciAutoConfig() -> vxbPciBusAddDev() -> vxbPciResourceInit():

LOCAL STATUS vxbPciResourceInit()
{
    ...

    /* Acquire interrupt pin info */
    
    /* (5.1) 获取到pcie设备INT_PIN寄存器的值 */
    (void) VXB_PCI_CFG_READ(pDev, pIvars, PCI_CFG_DEV_INT_PIN, 1, &intPin);
    pIvars->pciIntPin = intPin;

    /*
     * Assign IRQ value and convert to VxBus resource. Note that
     * this sets up the legacy INTx interrupt only.
     */

    /* (5.2) 如果INT_PIN寄存器的值不为0,给其分配一个传统INT_X中断 */
    if (intPin != 0)
        {
        pIntr = (VXB_INTR_ENTRY *)vxbMemAlloc (sizeof(VXB_INTR_ENTRY));

        if (pIntr == NULL)
            goto fail;

        /* (5.2.1) 在pcie控制器中分配一个从INT_0/1/2/3中给其分配一个传统INT_X中断
             VXB_PCI_INT_ASSIGN()最后会调用到tiAm572xPcieIntAssign()函数
         */
        if (VXB_PCI_INT_ASSIGN(pDev, pIvars, intPin, &irq, pIntr) != OK)
            {
            vxbMemFree (pIntr);
            pIntr = NULL;
            goto skip;
            }

        /* Save it to the child's intLine register. */
        /* (5.2.2) 把分配得到的中断号保存到INT_LINE寄存器中 */
        (void) VXB_PCI_CFG_WRITE(pDev, pIvars, PCI_CFG_DEV_INT_LINE, 1, irq);

        /* Set up the VxBus resource for this IRQ. */

        /* (5.2.3) 把中断号包装成VXB_RESOURCE_IRQ,挂到设备的resource链表中 */
        pResIrq = (VXB_RESOURCE_IRQ *)vxbMemAlloc (sizeof(VXB_RESOURCE_IRQ));

        if (pResIrq == NULL)
            goto fail;

        pRes = (VXB_RESOURCE *)vxbMemAlloc (sizeof(VXB_RESOURCE));

        if (pRes == NULL)
            goto fail;

        pRes->pRes = (void *)pResIrq;
        pRes->id = VXB_RES_ID_CREATE(VXB_RES_IRQ, 0); /* INTX */

        pResIrq->hVec = irq;
        pResIrq->flag |= VXB_INT_FLAG_STATIC;
        pResIrq->pVxbIntrEntry = (void *)pIntr;
        pIntr = NULL;

        if (vxbResourceAdd (&pIvars->vxbResList, pRes) != OK)
            {
            vxbMemFree ((char *)pIntr);
            vxbMemFree ((char *)pRes);
            vxbMemFree ((char *)pResIrq);
            goto fail;
            }

        }

}

分配INT_X中断最核心的函数是tiAm572xPcieIntAssign():

tiAm572xPcieIntAssign() -> vxbFdtPciIntrGet():

STATUS vxbFdtPciIntrGet()
{

    /* (5.2.1.1) 根据bus/slot/func/pin来组织查询编号 */
    addr = (bus << 16) | (slot << 11) | (func << 8);
    childSpec[0] = addr;
    childSpec[1] = 0;
    childSpec[2] = 0;
    childSpec[3] = pin;

    mapLen = intrInfo->mapLen;
    mapPtr = intrInfo->map;

    /* (5.2.1.2) 使用编号在interrupt-map中查询对应的中断 */
    while (i < mapLen)
        {
        pOffset = vxFdtNodeOffsetByPhandle(vxFdt32ToCpu(mapPtr[childSpecCells]));

        pCell = vxFdtPropGet(pOffset, "#interrupt-cells", &cellSize);

        if (pCell == NULL)
            return ERROR;

        parIntrcells = vxFdt32ToCpu(*(UINT32 *)pCell);

        rowCells = childSpecCells + 1 +  parIntrcells;

        /* Apply mask and look up the entry in interrupt map. */

        for (j = 0; j < childSpecCells; j++)
            {
            masked[j] = childSpec[j] &
                vxFdt32ToCpu(intrInfo->mask[j]);

            if (masked[j] != vxFdt32ToCpu(mapPtr[j]))
                goto next;
            }

        /* Decode interrupt of the parent intr controller. */

        specIdx = childSpecCells + 1;

        *interrupt = (UINT8)vxFdt32ToCpu(mapPtr[specIdx]);

        pIntrEntry->node = pOffset;
        pIntrEntry->pProp = (UINT32 *)&mapPtr[specIdx + 1];
        pIntrEntry->numProp = (parIntrcells - 1);

        return OK ;

next:
        mapPtr += rowCells;
        i += (UINT32)(rowCells * sizeof(UINT32));
        }

    return ERROR;
    }

我们dts中的interrupt-map定义如下:

        pcie1: pcie@20000000
            {
            ...

            #interrupt-cells = <1>;
            interrupt-map-mask = <0xfff800 0 0 7>;
            interrupt-map = <0x0000 0 0 1 &pcie1 0       /* INT A */
                             0x0000 0 0 2 &pcie1 1       /* INT B */
                             0x0000 0 0 3 &pcie1 2       /* INT C */
                             0x0000 0 0 4 &pcie1 3>;     /* INT D */
            };

在pcie设备驱动中,例如i210网卡驱动中通过以下方法来得到这个中断:

geiEndStart() -> geiIvecsAlloc():

geiIvecsAlloc()
{
    ...

            if (ix == 0)
                {
                ivec->pRes = NULL;
#ifdef _WRS_KERNEL
                if (pDrvCtrl->geiDevType != GEI_DEVTYPE_PCIX)
                    {
#ifdef GEI_MSIX_SUPPORT
                    if (pDrvCtrl->geiDevId == INTEL_DEVICEID_82574L)
                        {
                        r = vxbPciMsiXAlloc (pDev, 1);
                        if (r == 1)
                            {

                            /* add GEI_ICR_OTHER for MSI-X mode */

                            ivec->events |= GEI_ICR_OTHER;
                            }
                        }
                    else
#endif /* GEI_MSIX_SUPPORT */
                        /* (1) 使用MSI分配动态资源,并作为VXB_RESOURCE_IRQ加入到设备的Resource链表当中 */
                        r = vxbPciMsiAlloc (pDev, 1);

                    if (r == 1)
                        /* (2) 如果MSI中断分配成功,获取设备Resource链表中的MSI中断
                                注意使用的是 VXB_RES_IRQ编号1,
                                VXB_RES_IRQ编号1中保存的是新分配的MSI中断
                                VXB_RES_IRQ编号0中保存的是设备创建时分配的INT_X中断
                         */
                        ivec->pRes = vxbResourceAlloc (pDev, VXB_RES_IRQ, 1);
                    }
#endif /* _WRS_KERNEL */

                /* Fall back to INTX */

                /* (3) 如果MSI中断分配失败,获取设备Resource链表中的INT_X中断
                        注意使用的是 VXB_RES_IRQ编号0
                 */
                if (ivec->pRes == NULL)
                    ivec->pRes = vxbResourceAlloc (pDev, VXB_RES_IRQ, 0);
                }
            else
                ivec->pRes = NULL;
            }
        }

    ...
}

Step 2. 排查MSI中断流程

MSI中断和INT_X中断的流程大致相似,最大的区别还是在中断的分配时机和方法上。

INT_X中断是在pci auto config创建设备时就已经创建好了,设备驱动直接vxbResourceAlloc()编号0的VXB_RES_IRQ就能得到。

MSI中断需要设备驱动自己来调用vxbPciMsiAlloc()来分配,分配完成后vxbResourceAlloc()编号1的VXB_RES_IRQ就能得到。

我们来看看其中关键vxbPciMsiAlloc()的实现:

vxbPciMsiAlloc() -> vxbIntAlloc() -> _func_dyncIntAlloc() -> vxbDyncIntAlloc():

LOCAL int vxbDyncIntAlloc
    (
    UINT32               count,      /* number of interrupt to request */
    VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry /* dynamic interrupt entry */
    )
    {
    int ix, num;
    int iy;
    int base;

    VXB_DEV_ID pIntCtrl;

    for (ix = 0; ix < VXB_DYNAMIC_INT_CTRL_MAX; ix++)
        {

        /* (1.1) 找到中断控制器数组中,第一个能支持动态中断分配的控制器 */
        if (!(vxbIntCtrl[ix + VXB_INT_CONTRL_MAX].flag & VXB_INT_FLAG_ALLOCATED))
            continue;
        base = vxbIntCtrl[ix + VXB_INT_CONTRL_MAX].base;

        pIntCtrl = (VXB_DEV_ID)vxbIsrPicGet(base);

        if (pIntCtrl == NULL)
            continue;

        /* (1.2) 调用中断控制器的动态中断分配函数
                这里会调用到tiAm572xPcieIntAlloc()函数 
         */
        if ((num = VXB_INT_ALLOC(pIntCtrl, count, pVxbDyncIntEntry)) > 0)
            {
            for (iy = 0; iy < num; iy++)
                {
                /* (1.3) 根据分配到的MSI中断编号,加上中断控制器的基地址
                        得到中断的逻辑向量编号 
                 */
                pVxbDyncIntEntry[iy].lVec = pVxbDyncIntEntry[iy].hVec + \
                    vxbIntCtrl[ix + VXB_INT_CONTRL_MAX].base;
                }
            return num;
            }
        else
            continue;
        }

    return 0;
    }

我们看到上述函数有一个明显的漏洞,它只会找第一个空闲的中断控制器,而不是根据设备挂载到哪个pcie控制器下面来找对应的MSI中断控制器。这会导致所有的MSI中断都注册到第一个MSI中断控制器上,即pcie1的MSI中断控制器上。

我们使用isrShow命令来查看实际的中断注册情况:

Pcie1的中断基地址:
<tiAm572xPcieAttach>: Register legacy intx, intxVecBase = 208 
<tiAm572xPcieAttach>: Register msi vectors, msiVecBase =212

Pcie2的中断基地址:
<tiAm572xPcieAttach>: Register legacy intx, intxVecBase = 244 
<tiAm572xPcieAttach>: Register msi vectors, msiVecBase =248

-> isrShow
ISR ID     Name                        Tag        HandlerRtn
---------- --------------------------- ---------- ------------------------------
…
0x2028da38 isr14                       40         tiAm572xPcieMsiHandler        
0x2034f890 isr15                       216        geiEndLegacyMsiInt            // pcie1网卡msi中断
0x20296040 isr16                       188        tiAm572xPcieMsiHandler        
0x2040b250 isr17                       217        geiEndInt                     // pcie2网卡msi中断
…
value = 0 = 0x0

我们可以看到pcie1和pcie2网卡的中断都注册到了pcie1上,符合我们的推测。

3. 结论

综上所述:

该问题的根因是Vxworks的MSI机制有问题,造成pcie1和pcie2网卡的中断都注册到了pcie1上。

我们修改Vxworks的MSI中断分配机制,最核心的修改如下:

diff --git a/vxbDyncIntLib.c b/vxbDyncIntLib.c
index 99be397..ff2c0d9 100644
--- a/vxbDyncIntLib.c
+++ b/vxbDyncIntLib.c
@@ -26,15 +26,19 @@ modification history

 #include <vxWorks.h>
 #include <hwif/vxBus.h>
-#include <subsys/int/vxbIntLib.h>
-#include <subsys/int/vxbDyncIntLib.h>
+#include <vxbIntLib.h>
+#include <vxbDyncIntLib.h>
 #include <hwif/methods/vxbIntMethod.h>

+#include <string.h>
+#include <private/kwriteLibP.h>         /* _func_kprintf */
+
+
 IMPORT VXB_INT_CONTRL vxbIntCtrl[];
 UINT32 vxbDyncIntCtrlNum;

-LOCAL int vxbDyncIntAlloc (UINT32 count, VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry);
-LOCAL void vxbDyncIntFree (UINT32 count, VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry);
+LOCAL int vxbDyncIntAlloc (VXB_DEV_ID pDev, UINT32 count, VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry);
+LOCAL void vxbDyncIntFree (VXB_DEV_ID pDev, UINT32 count, VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry);

 LOCAL int init = 0;

@@ -130,6 +134,7 @@ STATUS vxbDyncIntRegister

 LOCAL int vxbDyncIntAlloc
     (
+       VXB_DEV_ID               pDev,       /* device id */
     UINT32               count,      /* number of interrupt to request */
     VXB_DYNC_INT_ENTRY * pVxbDyncIntEntry /* dynamic interrupt entry */
     )
@@ -137,6 +142,8 @@ LOCAL int vxbDyncIntAlloc
     int ix, num;
     int iy;
     int base;
+       char parent_path[100];
+       char child_path[100];

     VXB_DEV_ID pIntCtrl;

@@ -150,6 +157,20 @@ LOCAL int vxbDyncIntAlloc

         if (pIntCtrl == NULL)
             continue;
+
+               vxbDevPathGet(pIntCtrl, parent_path, 128);
+               vxbDevPathGet(pDev, child_path, 128);
+               //(* _func_kprintf)("parent_path: %s \n", parent_path);
+               //(* _func_kprintf)("child_path: %s \n", child_path);
+
+               //(* _func_kprintf)("parent_path len: %d \n", strlen(parent_path));
+               //(* _func_kprintf)("child_path len: %d \n", strlen(child_path));
+
+               //(* _func_kprintf)("comp 1: %d \n", strcmp(parent_path, child_path));
+               //(* _func_kprintf)("comp 2: %d \n", strcmp(child_path, parent_path));
+
+               if (strcmp(child_path, parent_path) < strlen(parent_path))
+                       continue;

         if ((num = VXB_INT_ALLOC(pIntCtrl, count, pVxbDyncIntEntry)) > 0)
             {

思想就是让pcie1网卡和pcie2网卡从各自的pcie MSI中断控制器中分配中断。

修改后中断分配效果如下:

-> isrShow
ISR ID     Name                        Tag        HandlerRtn
---------- --------------------------- ---------- ------------------------------
…
0x202873c0 isr12                       40         tiAm572xPcieMsiHandler        
0x20342250 isr13                       216        geiEndLegacyMsiInt            // pcie1网卡msi中断
0x20288e90 isr14                       188        tiAm572xPcieMsiHandler        
0x203fdb60 isr15                       252        geiEndInt                     // pcie2网卡msi中断
…
value = 0 = 0x0

两路网卡都能使用MSI中断模式工作。

posted @ 2020-11-01 10:40  pwl999  阅读(158)  评论(0编辑  收藏  举报