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中断模式工作。