PL读不到PS写入DDR的数据

1、背景

平台:ZYNQ7020

CPU0的设置默认,CPU1工程bsp设置-DUSE_AMP=1,PL以AXI接口访问DDR。

CPU1往DDR中写数据后用Xil_DCacheFlushRange函数将数据回写到DDR,但是随后PL在DDR中读不到相关数据。

把CPU1中的相关代码放到CPU0中运行,PL能读到DDR中的相关数据。

2、原因分析

CPU1工程bsp中设置-DUSE_AMP=1只是让CPU1工程中涉及到共享资源初始化的代码段不被编译进应用程序,这样CPU1启动后就不会再进行共享资源的初始化1。但是由于CPU0工程bsp未设置-DUSE_AMP=1,所以这些共享资源的初始化代码会被编译进CPU0的应用程序。也就是说,在两个CPU启动之后,相关的共享资源会被CPU0初始化,所以CPU1不需要去重复初始化就可以使用相关的共享资源了(包括L2 Cache),防止两个CPU同时进行初始化时出现问题。

Xil_DCacheFlushRange函数源码中有预编译宏#ifndef USE_AMP,如下:

void Xil_DCacheFlushRange(INTPTR adr, u32 len)
{
	u32 LocalAddr = adr;
	const u32 cacheline = 32U;
	u32 end;
	u32 currmask;
	volatile u32 *L2CCOffset = (volatile u32 *)(XPS_L2CC_BASEADDR +
				    XPS_L2CC_CACHE_INV_CLN_PA_OFFSET);

	currmask = mfcpsr();
	mtcpsr(currmask | IRQ_FIQ_MASK);

	if (len != 0U) {
		/* Back the starting address up to the start of a cache line
		 * perform cache operations until adr+len
		 */
		end = LocalAddr + len;
		LocalAddr &= ~(cacheline - 1U);

		while (LocalAddr < end) {

	/* Flush L1 Data cache line */
#if defined (__GNUC__) || defined (__ICCARM__)
			asm_cp15_clean_inval_dc_line_mva_poc(LocalAddr);
#else
			{ volatile register u32 Reg
				__asm(XREG_CP15_CLEAN_INVAL_DC_LINE_MVA_POC);
			  Reg = LocalAddr; }
#endif
#ifndef USE_AMP
			/* Flush L2 cache line */
			*L2CCOffset = LocalAddr;
			Xil_L2CacheSync();
#endif
			LocalAddr += cacheline;
		}
	}
	dsb();
	mtcpsr(currmask);
}

即当CPU1工程bsp设置了-DUSE_AMP=1时,#ifndef USE_AMP ...... #endif部分代码便不会编译进应用程序中。而这部分代码又与L2 Cache有关,所以CPU1通过Xil_DCacheFlushRange函数往DDR中刷新数据时,只是将数据刷新到了L2 Cache,没有立即刷新到DDR,故PL在DDR中读不到数据。

3、解决办法

针对以上问题,我们可以将Xil_DCacheFlushRange函数改写,将其中的预编译宏#ifndef USE_AMP删除,如下:

void Amp_DCacheFlushRange(INTPTR adr, u32 len)
{
	u32 LocalAddr = adr;
	const u32 cacheline = 32U;
	u32 end;
	u32 currmask;
	volatile u32 *L2CCOffset = (volatile u32 *)(XPS_L2CC_BASEADDR +
				    XPS_L2CC_CACHE_INV_CLN_PA_OFFSET);

	currmask = mfcpsr();
	mtcpsr(currmask | IRQ_FIQ_MASK);

	if (len != 0U) {
		/* Back the starting address up to the start of a cache line
		 * perform cache operations until adr+len
		 */
		end = LocalAddr + len;
		LocalAddr &= ~(cacheline - 1U);

		while (LocalAddr < end) {

	/* Flush L1 Data cache line */
#if defined (__GNUC__) || defined (__ICCARM__)
			asm_cp15_clean_inval_dc_line_mva_poc(LocalAddr);
#else
			{ volatile register u32 Reg
				__asm(XREG_CP15_CLEAN_INVAL_DC_LINE_MVA_POC);
			  Reg = LocalAddr; }
#endif

			/* Flush L2 cache line */
			*L2CCOffset = LocalAddr;
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_SYNC_OFFSET, 0x0U);
			LocalAddr += cacheline;
		}
	}
	dsb();
	mtcpsr(currmask);
}

然后再CPU1中以Amp_DCacheInvalidateRange函数替代Xil_DCacheFlushRange函数,以上问题便可解决。

4、深入挖掘

Xil_DCacheInvalidateRange函数源码如下:

void Xil_DCacheInvalidateRange(INTPTR adr, u32 len)
{
	const u32 cacheline = 32U;
	u32 end;
	u32 tempadr = adr;
	u32 tempend;
	u32 currmask;
	volatile u32 *L2CCOffset = (volatile u32 *)(XPS_L2CC_BASEADDR +
				    XPS_L2CC_CACHE_INVLD_PA_OFFSET);

	currmask = mfcpsr();
	mtcpsr(currmask | IRQ_FIQ_MASK);

	if (len != 0U) {
		end = tempadr + len;
		tempend = end;
		/* Select L1 Data cache in CSSR */
		mtcp(XREG_CP15_CACHE_SIZE_SEL, 0U);

		if ((tempadr & (cacheline-1U)) != 0U) {
			tempadr &= (~(cacheline - 1U));

			Xil_L1DCacheFlushLine(tempadr);
#ifndef USE_AMP
			/* Disable Write-back and line fills */
			Xil_L2WriteDebugCtrl(0x3U);
			Xil_L2CacheFlushLine(tempadr);
			/* Enable Write-back and line fills */
			Xil_L2WriteDebugCtrl(0x0U);
			Xil_L2CacheSync();
#endif
			tempadr += cacheline;
		}
		if ((tempend & (cacheline-1U)) != 0U) {
			tempend &= (~(cacheline - 1U));

			Xil_L1DCacheFlushLine(tempend);
#ifndef USE_AMP
			/* Disable Write-back and line fills */
			Xil_L2WriteDebugCtrl(0x3U);
			Xil_L2CacheFlushLine(tempend);
			/* Enable Write-back and line fills */
			Xil_L2WriteDebugCtrl(0x0U);
			Xil_L2CacheSync();
#endif
		}

		while (tempadr < tempend) {
#ifndef USE_AMP
			/* Invalidate L2 cache line */
			*L2CCOffset = tempadr;
			Xil_L2CacheSync();
#endif

	/* Invalidate L1 Data cache line */
#if defined (__GNUC__) || defined (__ICCARM__)
			asm_cp15_inval_dc_line_mva_poc(tempadr);
#else
			{ volatile register u32 Reg
				__asm(XREG_CP15_INVAL_DC_LINE_MVA_POC);
			  Reg = tempadr; }
#endif
			tempadr += cacheline;
		}
	}

	dsb();
	mtcpsr(currmask);
}

可见Xil_DCacheInvalidateRange函数中也用预编译宏#ifndef USE_AMP预编译了L2 Cache的相关代码,那CPU1在用Xil_DCacheInvalidateRange函数获取PL写到DDR中的数据为什么又可以呢?

这应该与CPU0有关,因为L2 Cache是CPU0和CPU1共享的,CPU0也在不断地从DDR中获取指令和数据,这个过程中可能就将CPU1从DDR中获取并缓存在L2 Cache中的数据给覆盖掉。当CPU1要获取PL写到DDR中的数据时,调用Xil_DCacheInvalidateRange函数会先使L1 Cache中的数据无效,然后再到L2 Cache中去找。因为已经被CPU0的数据给覆盖了,所以找不到,然后直接到DDR中获取数据。

所以CPU1在调用Xil_DCacheInvalidateRange函数时,最好将其中的预编译宏#ifndef USE_AMP给删掉,如下:

void Amp_DCacheInvalidateRange(INTPTR adr, u32 len)
{
	const u32 cacheline = 32U;
	u32 end;
	u32 tempadr = adr;
	u32 tempend;
	u32 currmask;
	volatile u32 *L2CCOffset = (volatile u32 *)(XPS_L2CC_BASEADDR +
				    XPS_L2CC_CACHE_INVLD_PA_OFFSET);

	currmask = mfcpsr();
	mtcpsr(currmask | IRQ_FIQ_MASK);

	if (len != 0U) {
		end = tempadr + len;
		tempend = end;
		/* Select L1 Data cache in CSSR */
		mtcp(XREG_CP15_CACHE_SIZE_SEL, 0U);

		if ((tempadr & (cacheline-1U)) != 0U) {
			tempadr &= (~(cacheline - 1U));

			Xil_L1DCacheFlushLine(tempadr);
			/* Disable Write-back and line fills */
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_DEBUG_CTRL_OFFSET, 0x3U);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_CLEAN_PA_OFFSET, tempadr);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_INVLD_PA_OFFSET, tempadr);
			dsb();
			/* Enable Write-back and line fills */
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_DEBUG_CTRL_OFFSET, 0x0U);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_SYNC_OFFSET, 0x0U);

			tempadr += cacheline;
		}
		if ((tempend & (cacheline-1U)) != 0U) {
			tempend &= (~(cacheline - 1U));

			Xil_L1DCacheFlushLine(tempend);

			/* Disable Write-back and line fills */
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_DEBUG_CTRL_OFFSET, 0x3U);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_CLEAN_PA_OFFSET, tempend);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_INVLD_PA_OFFSET, tempend);
			dsb();
			/* Enable Write-back and line fills */
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_DEBUG_CTRL_OFFSET, 0x0U);
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_SYNC_OFFSET, 0x0U);

		}

		while (tempadr < tempend) {
			/* Invalidate L2 cache line */
			*L2CCOffset = tempadr;
			Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_SYNC_OFFSET, 0x0U);

	/* Invalidate L1 Data cache line */
#if defined (__GNUC__) || defined (__ICCARM__)
			asm_cp15_inval_dc_line_mva_poc(tempadr);
#else
			{ volatile register u32 Reg
				__asm(XREG_CP15_INVAL_DC_LINE_MVA_POC);
			  Reg = tempadr; }
#endif
			tempadr += cacheline;
		}
	}

	dsb();
	mtcpsr(currmask);
}

此时,基本上就没什么问题了。

除了性能的不确定性。2

因为L2 Cache是CPU0和CPU1共享的,同时使用时肯定会存在竞争问题,从而给CPU的运行速度带来不确定性。

此时我们可以考虑将L2 Cache均分给CPU0和CPU1使用,使两者互不影响。3

我们可以在CPU0应用程序入口处调用如下函数将L2 Cache均分给CPU0和CPU1:

//L2 CACHE在cpu0与cpu1进行等分
void L2CacheCut(void)
{
	u32 RegValue;
	
	RegValue = 0x0000000f;
    Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_DLCKDWN_0_WAY_OFFSET, RegValue);
    Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_ILCKDWN_0_WAY_OFFSET, RegValue);

    RegValue = 0x000000f0;
    Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_DLCKDWN_1_WAY_OFFSET, RegValue);
    Xil_Out32(XPS_L2CC_BASEADDR + XPS_L2CC_CACHE_ILCKDWN_1_WAY_OFFSET, RegValue);
}

至此,相关问题才算完美解决。

不过有些地方纯属个人猜测,如果有误,欢迎评论指正。


  1. ZYNQ双核AMP开发详解(USE_AMP -DUSE_AMP=1 含义和作用详解) ↩︎

  2. ZYNQ7000双核AMP工作方式下如何共享L2 Cache ↩︎

  3. Zynq7000 双核运行 L2Cache 寄存器配置 划分Cache ↩︎

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值