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);
}
至此,相关问题才算完美解决。
不过有些地方纯属个人猜测,如果有误,欢迎评论指正。
-
ZYNQ双核AMP开发详解(USE_AMP -DUSE_AMP=1 含义和作用详解) ↩︎
-
ZYNQ7000双核AMP工作方式下如何共享L2 Cache ↩︎文章来源:https://www.toymoban.com/news/detail-458143.html
-
Zynq7000 双核运行 L2Cache 寄存器配置 划分Cache ↩︎文章来源地址https://www.toymoban.com/news/detail-458143.html
到了这里,关于PL读不到PS写入DDR的数据的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!