vxworks issue: PCIE Bar Base Address Unalignment

1. 问题现象

在调试 Am5728 + vxworks + 外部pcie网卡(i210) 时,发现可以找到pcie设备,但是网口不通。

  • 现象1.可以在vxbus上看到pcie控制器和i210网卡的相关设备和驱动:
-> vxbDevShow
    ...
         ti,am572x-pcie-0 (refs: 1) on simpleBus0 (refs: 27)        /* pcie controller */
         ti,am572x-pcie-0: <TI AM572X PCI Ex driver> (0x2028674c) level(3)
            pcib-0 (refs: 1) on ti,am572x-pcie0 (refs: 1)           /* pcie bridge */
            pcib-0: <PCI generic bridge driver> (0x20287edc) level(4)
               gei-0 (refs: 1) on pcib0 (refs: 1)                   /* i210 pcie设备 */
               gei-0: <Intel PRO/1000 VxBus END driver> (0x20288518) level(5)
                  genericPhy-0 (refs: 0) on gei0 (refs: 1)          /* i210 phy设备 */
                  genericPhy-0: <Generic 10/100/1000 PHY driver> (0x202892ec) level(6)
    ...
  • 现象2.使用pcie的调试函数,可以看到i210网卡的pcie配置信息:
-> vxbPciCtrlShow
Pci controller (ti,am572x-pcie:0) devId=0x2028f048
value = 51 = 0x33 = '3'
-> vxbPciTopoShow(0x2028f048)
[0,0,0] - (104c 8888) type=P2P BRIDGE to [1,0,0]
        base/limit:
          mem=   0x20000000/0x201fffff
          preMem=0xfff00000/0x000fffff
          I/O=   0x0000/0x0fff
        status=0x0010 ( CAP DEVSEL=0 )
        command=0x0007 ( IO_ENABLE MEM_ENABLE MASTER_ENABLE )
        bar0 in prefetchable 32-bit mem space @ 0x0000000000000000
[1,0,0] - (8086 1533) type=NET_CNTLR
        status=0x0010 ( CAP DEVSEL=0 )
        command=0x0407 ( IO_ENABLE MEM_ENABLE MASTER_ENABLE )
        bar0 in 32-bit mem space @ 0x0000000020080000
value = 65535 = 0xffff
-> vxbPciHeaderShow(0x2028f048,1,0,0)
vendor ID =                   0x8086
device ID =                   0x1533
command register =            0x0407
status register =             0x0010
revision ID =                 0x03
class code =                  0x02
sub class code =              0x00
programming interface =       0x00
cache line =                  0x10
latency time =                0x00
header type =                 0x00
BIST =                        0x00
base address 0 =              0x20080000
base address 1 =              0x00000000
base address 2 =              0x00000001
base address 3 =              0x20120000
base address 4 =              0x00000000
base address 5 =              0x00000000
cardBus CIS pointer =         0x00000000
sub system vendor ID =        0x8086
sub system ID =               0x0000
expansion ROM base address =  0x00000000
interrupt line =              0x00
interrupt pin =               0x01
min Grant =                   0x00
max Latency =                 0x00
Capabilities - Power Management 
Capabilities - Message Signaled Interrupts: 0x50 control 0x181 Enabled, 64-bit, MME: 0 MMC: 0
        Address: 0000000000000000  Data: 0x0000
        Per-vector Mask: Support Mask Bit: 0x0, Pending Bit: 0x0 

Capabilities - MSI-X
Capabilities - PCIe: Endpoint, IRQ 0
        Device: Max Payload: 512 bytes, Extended Tag: 5-bit
                Acceptable Latency: L0 - <512ns, L1 - <64us
                Errors Enabled: Relaxed Ordering No Snoop
        Max Read Request 512 bytes
        Link: MAX Speed - 2.5Gb/s, MAX Width - by 1 Port - 4 ASPM - L0s & L1
                Latency: L0s - >4us, L1 - <16us
                ASPM - Disabled, RCB - 64bytes
                Speed - 2.5Gb/s, Width - by 1
Ext Capabilities - Advanced Error Reporting. 0x100. Version 2. AER Control: 0xa0 
   Uncorrectable : Mask 0x0. Severity 0x462031 
   Uncorrectable Status:    Correctable : Mask 0x2000. 
   Correctable Status:          - Receiver Error 
         - Bad DLLP 

   HeaderLog: 
   Error Source Identification: 0x0 0x0 
Ext Capabilities - Device Serial Number. 0x140. Version 1 
       Serial Number: 0xdd  0x40  0x55  0xff  0xff  0x6a  0x4c  0x6c 
Ext Capabilities - TPH. 0x1a0. Version 1

value = 0 = 0x0
-> 
  • 现象3. 网卡配置完成后,ifconfig虽然能看到网卡,但是读出的mac地址为00:00:00:00:00:00正确mac为6c:4c:6a:55:40:dd,且ping不通:
-> ipcom_drv_eth_init("gei", 0, 0)
value = 0 = 0x0
-> ifconfig("gei0 172.16.90.81 netmask 255.255.255.0 up ")
value = 0 = 0x0
-> ifconfig("gei0")
gei0    Link type:Ethernet  HWaddr 00:00:00:00:00:00
        capabilities: TXCSUM TX6CSUM VLAN_MTU VLAN_TXHWTAG VLAN_RXHWTAG 
        inet 172.16.90.81  mask 255.255.255.0  broadcast 172.16.90.255
        inet6 unicast fe80::6e4c:6aff:fe55:40dd%gei0  prefixlen 64  tentative  automatic
        UP SIMPLEX BROADCAST MULTICAST 
        MTU:1500  metric:1  VR:0  ifindex:2
        RX packets:0 mcast:0 errors:0 dropped:0
        TX packets:0 mcast:0 errors:0
        collisions:0 unsupported proto:0
        RX bytes:0  TX bytes:0

value = 0 = 0x0
-> 

2. 分析过程

Step 1. 排查网卡驱动(vxbGei825xxEnd.c)

首先我们从i210的网卡驱动vxbGei825xxEnd.c开始分析。

实用技巧:因为修改vxbGei825xxEnd.c需要编译VSB工程非常的慢,我们直接把vxbGei825xxEnd.c拷贝到VIP目录下直接修改。后面的其他文件同理。

从i210的datasheet上可以看到,可以通过memory bar直接访问内部寄存器,或者通过io bar间接访问内部寄存器。
在这里插入图片描述
我们加上调试代码,目的是打印出memeory bar和io bar的映射情况:

LOCAL STATUS geiAttach
    (
    VXB_DEV_ID pDev
    )
{
    ...

    for (i = 0; i < VXB_MAXBARS; i++)
        {
        pRes = vxbResourceAlloc (pDev, VXB_RES_MEMORY, (UINT16)i);
        if (pRes != NULL)
            {
            pResAdr = (VXB_RESOURCE_ADR *)pRes->pRes;
            if (pResAdr != NULL)
                {
                pDrvCtrl->geiHandle = pResAdr->pHandle;
                pDrvCtrl->geiBar = (void *)pResAdr->virtual;
                pDrvCtrl->geiRes = pRes;
				_func_kprintf("Memory bar base addr(index = %d):\n", i);
				_func_kprintf("pResAdr->virtual: 0x%x.\n", pResAdr->virtual);
				_func_kprintf("pResAdr->start: 0x%llx.\n", pResAdr->start);
				_func_kprintf("pResAdr->size: 0x%x.\n", pResAdr->size);
                break;
                }
            else
                {
                (void) vxbResourceFree (pDev, pRes);
                }
            }
        }

    ...

    /* Map the I/O BAR too */

    for (i = 0; i < VXB_MAXBARS; i++)
        {
        pRes = vxbResourceAlloc (pDev, VXB_RES_IO, (UINT16)i);
        if (pRes != NULL)
            {
            pResAdr = (VXB_RESOURCE_ADR *)pRes->pRes;
            if (pResAdr != NULL)
                {
                pDrvCtrl->geiIoHandle = pResAdr->pHandle;
                pDrvCtrl->geiIoBar = (void *)pResAdr->virtual;
                pDrvCtrl->geiIoRes = pRes;
				_func_kprintf("IO bar base addr(index = %d):\n", i);
				_func_kprintf("pResAdr->virtual: 0x%x.\n", pResAdr->virtual);
				_func_kprintf("pResAdr->start: 0x%llx.\n", pResAdr->start);
				_func_kprintf("pResAdr->size: 0x%x.\n", pResAdr->size);
                break;
                }
            else
                {
                (void) vxbResourceFree (pDev, pRes);
                }
            }
        }

    ...
}

可以看到打印出来的memory bar和io bar的基地址情况:

Memory bar base addr(index = 0):
pResAdr->virtual: 0x222cd000.       /* cpu侧虚拟地址 */
pResAdr->start: 0x0.                /* cpu侧物理地址 */
pResAdr->size: 0x80000.             /* bar的大小 */

IO bar base addr(index = 2):
pResAdr->virtual: 0x2234d000.       /* cpu侧虚拟地址 */
pResAdr->start: 0x20090000.         /* cpu侧物理地址 */
pResAdr->size: 0x20.                /* bar的大小 */

这里可以看到,memory bar的cpu侧物理地址为0x0其实已经异常,但是因为这个时候对am5728的pcie和cpu物理地址相互映射的机制还没理解透。所以还不是十分肯定。

我们通过memoey bar来直接访问内部寄存器,看看寄存器的情况:

LOCAL STATUS geiAttach
    (
    VXB_DEV_ID pDev
    )
{
    ...
	_func_kprintf("memory bar direct read:\n");
	for (i=0; i<0x100; i+=4)
	{
		if (i%0x10 == 0)
		{
			_func_kprintf("\n 0x%x: ", i);
		}
		
		reg = CSR_READ_4(pDrvCtrl, i);
		_func_kprintf("0x%x ", reg);
	}
	_func_kprintf("\n\n");
    ...
}

读出的i210内部寄存器情况:

memory bar direct read:

 0x0: 0xc744375 0xc5e815b7 0x9ffcf73b 0x95ef1d23 
 0x10: 0xf1afab3f 0x2498399d 0x7d989bec 0xc5bcfeb4 
 0x20: 0xddd1b99c 0xbead6bcd 0xdb5efb5b 0xcf5eb878 
 0x30: 0x6fb92ecd 0xb82e2f76 0x6dbb5e1f 0x1c297df8 
 0x40: 0x9d9abeb9 0xee97c976 0xa9ea5b7b 0xd4aefc74 
 0x50: 0x27745a85 0xfe36f8da 0x867f5e57 0x3132ee53 
 0x60: 0xbdfff74 0x2fb5eefb 0xffdbfd91 0x97b27d90 
 0x70: 0x70f2af3e 0x2765e9da 0x37fef72f 0x7823e897 
 0x80: 0x9c8fdd49 0xab3733bf 0xbe6799d7 0xfc2c897 
 0x90: 0xaff68478 0xcde7314e 0x61db877a 0x3df799ce 
 0xa0: 0xaef2765d 0x8a0dbd1f 0xd6677e75 0x3b8256ff 
 0xb0: 0x68fba56f 0xaf98e07b 0x7bf85ff 0x53fe5edf 
 0xc0: 0x5e3d0782 0x8f5f7b70 0x4ff309fc 0x6b194a7d 
 0xd0: 0x96bf3e16 0xbf7ed6dc 0xffffffff 0x1f987be8 
 0xe0: 0xdf7f37df 0xedba4fc2 0xff6ffc3d 0xdf9778f4 
 0xf0: 0xecbd6a7d 0x2bfd0b36 0x88c4046 0xd9b92976 

可以看到寄存器非常的不正常。例如内部寄存器在0x28偏移处是Flow Control Address Low寄存器,它是一个常量为0x00C28001,可以当我们判断寄存器是否正确的锚点。但是我们读出的寄存器完全不对。

因为io bar也可以访问寄存器,这个时候想到可以通过io bar来间接访问内部寄存器,看看寄存器的情况:

LOCAL STATUS geiAttach
    (
    VXB_DEV_ID pDev
    )
{
    ...

	_func_kprintf("io bar indirect read:\n");
	ioBar = pDrvCtrl->geiIoBar;
	ioHandle = pDrvCtrl->geiIoHandle;
	for (i=0; i<0x100; i+=4)
	{
		if (i%0x10 == 0)
		{
			_func_kprintf("\n 0x%x: ", i);
		}
		vxbWrite32 (ioHandle, ioBar, i);
        reg = vxbRead32 (ioHandle, ioBar + 1);

		_func_kprintf("0x%x ", reg);
	}
	_func_kprintf("\n\n");
    ...
}

读出的i210内部寄存器情况:

io bar indirect read:

 0x0: 0x81c0241 0x81c0241 0x280780 0x0 
 0x10: 0x6082b40 0x2 0x1400c0 0x8000a 
 0x20: 0x18001400 0x0 0xc28001 0x100 
 0x30: 0x8808 0x200 0x81008100 0x0 
 0x40: 0x556a4c6c 0x8000dd40 0x0 0x0 
 0x50: 0x0 0x0 0x0 0x0 
 0x60: 0x0 0x0 0x0 0x0 
 0x70: 0x0 0x0 0x0 0x0 
 0x80: 0x0 0x0 0x0 0x0 
 0x90: 0x0 0x0 0x0 0x0 
 0xa0: 0x0 0x0 0x0 0x0 
 0xb0: 0x0 0x0 0x0 0x0 
 0xc0: 0x40004 0x0 0x0 0x0 
 0xd0: 0x0 0x0 0x0 0x0 
 0xe0: 0x0 0x0 0x0 0x0 
 0xf0: 0x0 0x0 0x0 0x0 

可以看到读出的寄存器看起来比较正常,锚点0x28寄存器值为0xc28001,非常吻合。

总结:从上面的操作可以看到,通过memory bar访问寄存器异常,通过io bar访问寄存器正常。初步怀疑是memory bar的地址映射过程中出了问题。

Step 2. 综合分析(相关知识)

  • pcie bar地址的映射

上一步我们怀疑memory bar地址在映射过程中出错,在系统中关于这部分的映射一共有3种地址:cpu侧虚拟地址、cpu侧物理地址、pcie侧物理地址。

关于memory bar的这几个地址,通过vxbPciHeaderShow()命令我们看到pcie侧物理地址为0x20080000,通过上一步我们加的打印可以看到cpu侧虚拟地址为0x222cd000、cpu侧物理地址为0x0。

cpu侧虚拟地址 cpu侧物理地址 pcie侧物理地址
0x222cd000 0x0 0x20080000

可以看到cpu侧物理地址为0x0出错的嫌疑非常大,我们进一步追查其中的映射过程。

  • pcie 初始化过程

pcie的初始化首先会来到pcie控制器的驱动,在vxbFdtTiAM572xPcie.c:

LOCAL VXB_DRV_METHOD pciExMethodList[] =
    {
    { VXB_DEVMETHOD_CALL(vxbDevProbe),          (FUNCPTR)tiAm572xPcieProbe},
    { VXB_DEVMETHOD_CALL(vxbDevAttach),         (FUNCPTR)tiAm572xPcieAttach},
    { VXB_DEVMETHOD_CALL(vxbDevIoctl),          (FUNCPTR)vxbPciBusIoctl},
    { VXB_DEVMETHOD_CALL(vxbPciIntAssign),      (FUNCPTR)tiAm572xPcieIntAssign},
    { VXB_DEVMETHOD_CALL(vxbPciCfgRead),        (FUNCPTR)tiAm572xPcieCfgRead},
    { VXB_DEVMETHOD_CALL(vxbPciCfgWrite),       (FUNCPTR)tiAm572xPcieCfgWrite},
    { VXB_DEVMETHOD_CALL(vxbResourceAlloc),     (FUNCPTR)tiAm572xPcieResAlloc},
    { VXB_DEVMETHOD_CALL(vxbResourceFree),      (FUNCPTR)tiAm572xPcieResFree},
    { VXB_DEVMETHOD_CALL(vxbResourceListGet),   (FUNCPTR)tiAm572xPcieResListGet},
    { VXB_DEVMETHOD_CALL(vxbIntEnable),         (FUNCPTR)tiAm572xPcieIntEnable},
    { VXB_DEVMETHOD_CALL(vxbIntDisable),        (FUNCPTR)tiAm572xPcieIntDisable},
    { VXB_DEVMETHOD_CALL(vxbIntBind),           (FUNCPTR)tiAm572xPcieIntBind},
    { VXB_DEVMETHOD_CALL(vxbIntAlloc),          (FUNCPTR)tiAm572xPcieIntAlloc},
    { VXB_DEVMETHOD_CALL(vxbIntFree),           (FUNCPTR)tiAm572xPcieIntFree},
    { 0, NULL }
    };

LOCAL VXB_FDT_DEV_MATCH_ENTRY pciExMatch[] =
    {
        {
        TI_AM572X_DRV_NAME,                  /* compatible */
        NULL      
        },
        {}                                   /* empty terminated list */
    };

/* globals */

VXB_DRV vxbFdtTiAm572xPcieDrv =
    {
    {NULL} ,
    TI_AM572X_DRV_NAME,                 /* Name */
    "TI AM572X PCI Ex driver",          /* Description */
    VXB_BUSID_FDT,                      /* Class */
    0,                                  /* Flags */
    0,                                  /* Reference count */
    pciExMethodList                     /* Method table */
    };

VXB_DRV_DEF(vxbFdtTiAm572xPcieDrv)

在attach函数tiAm572xPcieAttach()中开始pcie控制器的初始化工作:

LOCAL STATUS tiAm572xPcieAttach
    (
    VXB_DEV_ID  pDev
    )
{
    ...

    /* (1) 从DTS中读取pcie控制器的相关配置 */

    /* (2) 初始化pcie控制器 */

    /* (3) 自动扫描并配置PCIE总线上的设备 */
    /* PCI autoconfiguration and announce device */
    if (OK != vxbPciAutoConfig (pDev))
        {
        DEBUG_MSG (AM572X_DBG_ERR, "<tiAm572xPcieAttach>: vxbPciAutoConfig"
                   " failed\n");
        goto errOut;
        }

    ...
}

tiAm572xPcieAttach()最后会调用vxbPciAutoConfig()来遍历pcie总线上的设备,并且调用vxbPciBusAddDev()函数把扫描到的pcie设备转成VxBus Device来添加。在vxbPci.c:

STATUS vxbPciAutoConfig
    (
    VXB_DEV_ID  pDev
    )
{
    ...

    /* (3.1) 扫描并配置pcie总线上的设备,
        _func_vxbPciAutoConfig = vxbPciAutoConfigFunc 
     */
    if (pSoftc->autoConfig == TRUE)
        {
        if (_func_vxbPciAutoConfig != NULL)
            _func_vxbPciAutoConfig (pDev);
        else
            return (ERROR);
        }

    /* (3.2) 把扫描并配置好的pcie设备,当成vxbus device注册 */
    return (vxbPciBusAddDev (pDev, pRes->baseBusNumber));
}

Step 3. 排查PCIE物理地址和CPU物理地址转换(vxbPci.c)

我们继续怀疑是不是pcie侧物理地址cpu侧物理地址转换的过程中出错了?

转换函数在:vxbPciBusAddDev() -> vxbPciResourceInit() -> vxbPciAddr2Cpu()。

LOCAL PHYS_ADDR vxbPciAddr2Cpu
    (
    VXB_DEV_ID     pDev,        /* device info */
    UINT64         pciAddr      /* PCI address */
    )
    {
    UINT64 phyAdjust = 0;
    ...

    for (i = 0; i < pSoftc->segCount; i++)
        {
        pciRootRes = (PCI_ROOT_RES *)&pSoftc->pRootRes[i];

        for (j = 0; j < PCI_ROOT_RES_MAX_IDX; j++)
            {
            if ((pciAddr >= pciRootRes->barRes[j].start) &&
                (pciAddr < pciRootRes->barRes[j].end))
                {
                phyAdjust = pciAddr - pciRootRes->barRes[j].start;
                phyAdjust += pciRootRes->barRes[j].cpuBase;
                break;
                }
            }
        }

    return ((PHYS_ADDR)phyAdjust);
    }

am572x_idk_ca15.dts中定义了两组pcie侧物理地址cpu侧物理地址转换:

            ranges = <0x2000000 0 0x200A0000 0x200A0000 0 0x0FF50000
                      0x1000000 0 0x00000000 0x20090000 0 0x00010000>;
窗口类型 pcie侧物理地址 cpu侧物理地址 窗口大小
memory窗口 0x200A0000 0x200A0000 0x0FF50000
io窗口 0x00000000 0x20090000 0x00010000

从上述函数可以看到,我们memory bar0读出的pcie侧物理地址为0x20080000,那么上述的两个窗口我们都不能命中。我们只能得到默认值0。符合step 1中我们读出的cpu侧物理地址0x0。

总结:从上述分析看,vxbPciAddr2Cpu()这部分的地址转换的逻辑是没有问题的。出问题可能性最大的还是0x20080000这个地址有问题。

Step 4. 排查PCIE物理地址的配置(vxbPciAutoCfg.c)

我们深入映射的核心vxbPciAutoCfg.c来分析pcie侧物理地址具体配置过程。:

LOCAL STATUS vxbPciAutoConfigFunc
    (
    VXB_DEV_ID  busCtrlID
    )
{
    ...

        (void) vxbPciDeviceScan (busCtrlID, pciRootRes, pciRootRes->baseBusNumber, pPciDev);
    
        if (!DLL_EMPTY(&pPciDev->children))
            {
            vxbPciResAdjust (busCtrlID, pciRootRes, pPciDev);
    
            vxbPciResourceSort (busCtrlID, pPciDev, 0);
    
            vxbPciResourceAssign (busCtrlID, pciRootRes, pPciDev);
    
            vxbPciProgramBar(busCtrlID, pPciDev);
            }

    ...
}

首先我们打开文件的调试开关:

/* Debug macro */
#undef  PCI_DEBUG
#define  PCI_DEBUG
#ifdef  PCI_DEBUG

#include <private/kwriteLibP.h>

LOCAL int debugLevel = 1000;

默认打印出了一些信息:

Insert resource1 (1:0:0) BAR (0) to (0:0:0) type (0) size (0x80000) align(0x80000) preBar (0x6)
Insert resource1 (1:0:0) BAR (2) to (0:0:0) type (1) size (0x20) align(0x20) preBar (0x7)
Insert resource1 (1:0:0) BAR (3) to (0:0:0) type (0) size (0x4000) align(0x4000) preBar (0x6)
Insert resource2 (0:0:0) BAR (6) to (255:255:255) type (0) size (0x100000) align(0x100000) preBar (0x2028f328)
Insert resource2 (0:0:0) BAR (7) to (255:255:255) type (1) size (0x1000) align(0x1000) preBar (0x2028f328)
(255:255:255) bar(6) request (0x0:0x0) size(0x100000) align(0x100000) addr(0x200a0000)
(255:255:255) bar(7) request (0x0:0x0) size(0x1000) align(0x1000) addr(0x0)
(0:0:0) bar(6) request (0x200a0000:0x100000) size(0x80000) align(0x80000) addr(0x200a0000)
(0:0:0) bar(6) request (0x200a0000:0x100000) size(0x4000) align(0x4000) addr(0x20120000)
(0:0:0) bar(7) request (0x0:0x1000) size(0x20) align(0x20) addr(0x0)
ProgramBar bridge(000): BAR6:[0x200a0000-0x2019ffff] align 0x100000 size 0x100000
ProgramBar bridge(000): BAR7:[0x0-0xfff] align 0x1000 size 0x1000
ProgramBar device(100): BAR0:[0x200a0000-0x2011ffff] align 0x80000 size 0x80000
ProgramBar device(100): BAR2:[0x0-0x1f] align 0x20 size 0x20
ProgramBar device(100): BAR3:[0x20120000-0x20123fff] align 0x4000 size 0x4000

可以看到我们关注的i210的memory bar0,分配到的pcie侧物理地址为0x200a0000,大小为0x80000,对齐为0x80000。

ProgramBar device(100): BAR0:[0x200a0000-0x2011ffff] align 0x80000 size 0x80000

这里就非常奇怪了,为什么这里分配到的pcie侧物理地址为0x200a0000,但是我们通过vxbPciHeaderShow()命令读出来的值为0x20080000?

我们首先排查寄存器的配置过程,在具体的配置函数vxbPciProgramBar()中加入调试信息:

LOCAL void vxbPciProgramBar
    (
    VXB_DEV_ID     busCtrlID,
    PCI_DEVICE   * pPciDev
    )
{
    ...

							PCI_LOG_MSG(600, "bar %d: write val = 0x%x .\n", i, (UINT32)pPciDev->resource[i].start);
							
                            (void) VXB_PCI_CFG_WRITE(busCtrlID, &hardInfo,
                                PCI_CFG_BASE_ADDRESS_0 + i*4, 4,
                                (UINT32)pPciDev->resource[i].start);
								
							(void) VXB_PCI_CFG_READ(busCtrlID, &hardInfo,
                                PCI_CFG_BASE_ADDRESS_0 + i*4, 4,
                                &reg);
								
							PCI_LOG_MSG(600, "bar %d: read val = 0x%x .\n", i, reg);

    ...
}

打印出信息如下:

bar 0: write val = 0x200a0000 .
bar 0: read val = 0x20080000 .

可以看到bar 0 base address寄存器,写进去的值为0x200a0000,但是读出来的值为0x20080000。这就非常奇怪了,为什么会这样?

后来想到是不是pci bar alignment的问题,因为bar0大小为0x80000,它分配的地址也要求0x80000对齐的。但是0x200a0000这个地址是不符合0x80000对齐的,这说明pcie侧物理地址的分配策略有问题。

我们查看具体的分配函数vxbPciBarAlloc():

LOCAL STATUS vxbPciBarAlloc
    (
    PCI_RES *          pRes, /* parent resource*/
    PCI_MMIO_DESC    * pDesc
    )
    {
    PCI_BAR_ADR_POOL * pPool = &pRes->pool;

    /* (1) 情况1:在pRes->pool->list中分配符合对齐和大小条件的地址空间 */
    for (pCur  = (PCI_ADDR_RESOURCE *) DLL_FIRST (&pPool->list);
         pCur != NULL;
         pCur  = pNext)
        {
        nodeIndex++;

        alignStart =
            ROUND_UP (pCur->start + pCur->size, pDesc->align);

        pNext = (PCI_ADDR_RESOURCE *) DLL_NEXT (&pCur->node);

        if ((pNext != NULL) &&
            (pNext->start > alignStart) &&
            ((pNext->start - alignStart) >= pDesc->size))
            break;
        }

    /* (2) 情况2:如果pRes->pool->list为空,直接使用pPool->base */
    if (nodeIndex == 0)
        alignStart = pPool->base;

    ...

    }

可以看到我们刚好是情况2,所以我们获得的地址是没有做地址对齐的。

3. 结论

综上分析,问题的结论是:

pcie控制器驱动在自动配置pci设备的过程中,会探测pci设备bar空间的大小,并根据bar空间的大小和对齐分配一个合适的pcie侧物理地址给它。但是vxworks的地址空间分配函数vxbPciBarAlloc(),在pRes->pool->list为空的情况下不会做地址对齐操作。

bar0大小为0x80000对齐为0x80000,但是分配了一个没有0x80000对齐的地址0x200a0000。

地址0x200a0000写入bar0,低位会被强制清零,变成了0x20080000。这样就造成了后续的一系列映射出错。

解决补丁:

--- a/vxbPciAutoCfg.c
+++ b/vxbPciAutoCfg.c
@@ -180,7 +180,7 @@ LOCAL STATUS vxbPciBarAlloc
         }

     if (nodeIndex == 0)
-        alignStart = pPool->base;
+        alignStart = ROUND_UP(pPool->base, pDesc->align);

     pNew = (PCI_ADDR_RESOURCE *) vxbMemAlloc (sizeof (PCI_ADDR_RESOURCE));

猜你喜欢

转载自blog.csdn.net/pwl999/article/details/109412225
bar