飞思卡尔 powerpc pci msi 中断实现和x86有点不相同,在SylixOS 实现msi 中断驱动参考了《PCI+EXPRESS体系结构导读》
和一个 博客链接,不知道这个博主叫啥名字,但是多谢他指导我分开调试msi功能。
不同体系结构在pci msi实现在会在msi 配置空间address 和data两个地方不同。x86的address是内容如下图:
在飞思卡尔 powerpc 上address 是指向MSIIR寄存器,在p4080 上有三个MSIIR寄存器,分别是MSIIRA,MSIIRB ,MSIIRC。
在SylixOS 上已经存在MSI的框架,通过针对不同体系结构实现不同address 和 data 的填写。
1.MSI使能
在SylixOS中使用MSI功能首先要使用API_PciDevMsiEnableSet函数使能MSI功能。
INT API_PciDevMsiEnableSet (PCI_DEV_HANDLE hHandle, INT iEnable)
{
INT iRet = PX_ERROR;
UINT32 uiMsiCapOft = 0;
if (hHandle == LW_NULL) {
return (PX_ERROR);
}
if (!iEnable) {
iRet = API_PciIntxEnableSet(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction, 1);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
} else {
iRet = API_PciIntxEnableSet(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction, 0);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
}
iRet = API_PciCapFind(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction,
PCI_CAP_ID_MSI,
&uiMsiCapOft);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
iRet = API_PciMsiEnableSet(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction,
uiMsiCapOft,
iEnable);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
hHandle->PCIDEV_iDevIrqMsiEn = iEnable;
return (ERROR_NONE);
}
API_PciDevMsiEnableSet首先会判断是否MSI使用,如果MSI使用能关闭INTX中断使用,也就是在INTX和MSI中断只能选择一个使用,不能同时使用。在使用和关闭INTX中断功能时会使用总线号,设备号,功能号访问对应设备的配置空间。在x86和powerpc上都有两个专门的寄存器来存放要访问的地址和读取的数据。以下是p4080手册中的pci设备访问地址寄存器
可以看到对设备的配置空间访问也是通过总线号,设备号,功能号实现。
在API_PciIntxEnableSet 函数中首先读取了pcie配置空间的commond 寄存器的值,从下图一看看出第10位是INTX使能位,函数中对该位进行设置。
在关闭INTX中断后,需要探测是被是否支持MSI中断,设备如果支持MSI中断会再扩展空间中存在ID为05。
在pcie中有一个地址是0x34 ,这个位置是pcie的扩展空间。
扩展空间结构如图,在查找到ID为0x05时,表明设备支持MSI能力。此时设备也会找到当前MSI在配置空间的偏移值。判断支持MSI后系统会查找MSI control。在MSI扩展空间如下图:
Message Control 包含了一些重要信息。
截图来自 《PCI+EXPRESS体系结构导读》的第10章,书写的非常好。SylixOS系统首先会对第0位使能。启用设备的MSI功能。
使能工作到这里就结束了。
2.MSI中断数量
API_PciDevMsiRangeEnable函数是为设备申请中断数量,在Message control 中第0-3位是设备申请使用的中断数量,系统会读出当前硬件申请的中断数量。第4-6位是软件分配完成分配中断后回写值。这个值可能比硬件申请的中断数量少,这个是系统根据中断使用情况分配中断的个数。
NT API_PciDevMsiRangeEnable (PCI_DEV_HANDLE hHandle, UINT uiVecMin, UINT uiVecMax)
{
INT i, j, iRet = PX_ERROR;
UINT8 ucMsiEn = 0;
UINT32 uiMsiCapOft = 0;
UINT32 uiVecNum = 0;
PCI_MSI_DESC_HANDLE hMsgHandle = LW_NULL;
if (hHandle == LW_NULL) {
return (PX_ERROR);
}
if ((uiVecMin > 32) || (uiVecMax > 32) ||
(uiVecMax < uiVecMin) || (uiVecMin < 1)) {
return (PX_ERROR);
}
iRet = API_PciCapFind(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction,
PCI_CAP_ID_MSI,
&uiMsiCapOft);
if ((iRet != ERROR_NONE) ||
(!PCI_DEV_MSI_IS_EN(hHandle))) {
return (PX_ERROR);
}
for (i = 0; i < 6; i++) {
j = 1 << i;
if (j >= uiVecMin) {
break;
}
}
uiVecMin = j;
for (i = 0; i < 6; i++) {
j = 1 << i;
if (j >= uiVecMax) {
break;
}
}
uiVecMax = j;
iRet = API_PciMsiVecCountGet(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction,
uiMsiCapOft, &uiVecNum);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
if (uiVecNum < uiVecMin) {
return (PX_ERROR);
} else if (uiVecNum > uiVecMax) {
uiVecNum = uiVecMax;
}
hMsgHandle = &hHandle->PCIDEV_pmdDevIrqMsiDesc;
hMsgHandle->PCIMSI_uiNum = uiVecNum;
__reget:
iRet = API_PciDevInterMsiGet(hHandle, hMsgHandle);
if (iRet != ERROR_NONE) {
hMsgHandle->PCIMSI_uiNum >>= 1;
if (hMsgHandle->PCIMSI_uiNum < uiVecMin) {
return (PX_ERROR);
}
goto __reget;
}
hHandle->PCIDEV_uiDevIrqMsiNum = hMsgHandle->PCIMSI_uiNum;
hHandle->PCIDEV_ulDevIrqVector = hMsgHandle->PCIMSI_ulDevIrqVector;
/*
* MSI can support only 1, 2, 4, 8, 16, 32 number of vectors
*/
switch (hHandle->PCIDEV_uiDevIrqMsiNum) {
case 1:
ucMsiEn = 0;
break;
case 2:
ucMsiEn = 1;
break;
case 4:
ucMsiEn = 2;
break;
case 8:
ucMsiEn = 3;
break;
case 16:
ucMsiEn = 4;
break;
case 32:
ucMsiEn = 5;
break;
default:
return (PX_ERROR);
}
iRet = API_PciMsiMsgWrite(hHandle->PCIDEV_iDevBus,
hHandle->PCIDEV_iDevDevice,
hHandle->PCIDEV_iDevFunction,
uiMsiCapOft,
ucMsiEn,
&hHandle->PCIDEV_pmdDevIrqMsiDesc.PCIMSI_pmmMsg);
if (iRet != ERROR_NONE) {
return (PX_ERROR);
}
return (ERROR_NONE);
}
上面代码中获取硬件申请的中断个数后,会传递给API_PciDevInterMsiGet 函数,此函数会调用驱动层提供好的中断获取函数。在SylixOS pci 框架里针对不同平台访问配置空间和获取中断以及MSI的address和data不同,将读写函数和中断获取函数抽象为驱动层。
MSI中断支持将放在_pciIrqGet函数中。
3.freescale powerpc msi 寄存器
以下介绍参考了《PCI+EXPRESS体系结构导读》 和p4080 硬件手册。
在freescale powerpc中MSI 结构的address 填写的是MSIIR寄存器的地址。MSIIR寄存器偏移地址在P4080手册中有。MSIIR的基地址是CCSRBAR寄存器的值,这个值也是p4080寄存器配置的基地址,这个值并不是固定值,在p4080中默认是0xfe000000。 系统不同,可能对这个值进行修改。
但是在freescale powerpc 中pci域无法直接访问存储空间地址,需要进行inbound 和outbound设置才可以,在p4080中uboot已经分配了pcie设备配置空间的基地址和inbound,oubound。 所以系统中没有在进行单独设置。在p4080中往MSIIR写入时提供了一个PEXCSRBAR寄存器。也就是把bar0换了个名字。
msi address 实际上是PEXCSRBAR + MSIIR寄存器的偏移值。 这个PEXCSRBAR我之前一直以为是当前设备上的PEXCSRBAR。我的pci网卡设备这个位置是0x1000,这个地方卡主了我两天,数据移植无法写入到MSIIR寄存器。后来在网上看到别人这个PEXCSRBAR 寄存器的值是0xff000000 。我发现p4080 bus0,dev 0,function 0 设备的PEXCSRBAR 是0xff00000 ,把pci网卡 msi address地址改为 us0,dev 0,function 0 PEXCSRBAR 的值加上MSIIR寄存器偏移值能够写入到MSIIR寄存器中。
在p4080中把数据写入到MSIIIR寄存器中并没有结束。
数据写入后会触发MSIRn寄存器对应的位 ,系统想知道那个MSIR寄存器被触发了,需要读取MSIIR寄存器来获取,有一个需要注意的是在pcie配置空间里数据都是小端存放的。但是p4080是大端的。计算起来比较费脑。 MSI data 发现p4080好像只取了8位数据写到MSIIIR寄存器,这个地方比较疑惑。比如data值是0x20 其实触发的是MSIR1寄存器。
有个MSIVPR寄存器,这个寄存器存放的是当前MSIRn寄存器对用的中断号,中断优先级,是否使能。此寄存器uboot会根据设备树设置的MSI中断进行设置。每个MSIRn对应的是MSIVPRn寄存器中中断号,所以系统要从MSIVPR在中读取相应的中断号,好触发中断函数。有个需要注意的是MSIR寄存器必须读一次才能清零,否则一直触发中断。《PCI+EXPRESS体系结构导读》对这些寄存器介绍更为详细。
4.SylixOS p4080 实现
首先从uboot传进ftd树中读取msi中断相应的中断号,保存到数组中。
在中断获取函数中为MSI设备申请中断
通过data数据,反查MSIVPR寄存器中的中断号,然后映射为系统中断,填充到address 和data, API_PciDevMsiRangeEnable会判断msi配置空间是否是64位然后将address和data填写到msi配置空间中。MSIVPR寄存器是可读可写的,理论上说直接写入中断号应该也能触发,但是我自己写入不触发,也不知道为啥。用uboot配置好的MSIVPR的值是没问题的。
调试心得: 在调试freescale powerpc msi功能时应该先手动往MSIIR寄存器写入数据,然后打印MSIR,MSISR寄存器的值,如果写入MSIIR寄存器值正确,会触发MSIR和MSISR寄存器相应的位变化。每次只能读取一次,读取会清零。还要注意powerpc p4080是大端的,写入时大小端要处理好,要不可能和想象的不符合。在往MSIIR写入数据触发中断没问题后,在调试在pci设备中填些msi 的address和data。