我用的DSP和TI的TMS320C6678类似,但是是国防科大的6678。它的PCIe外设和TI的有很大不同,但也实现了基本的功能。虽然文档不太详细,而且有些功能有点问题,但基本还是能用的。
链路初始化
6678的PCIe相关的地址空间是下面这样的,初始化阶段要做的工作就是对本地和远端的PCIe配置空间进行配置。6678作为RC,FPGA端作为EP,FPGA上的PCIe接口相关的寄存器就需要DSP来设置。寄存器的表需要查DSP的文档、FPGA的文档和PCIe协议规范的文档。具体寄存器的功能这里就不细说了。
链路初始化的第一步是初始化PCIe链路相关的时钟。这个只要按照DSP文档里写的方式初始化就行。
第二步是设置需要的链路数量和链路速率,链路数量是x4,然后速率是5.0GT/s。
1 2 3 4 5 6 7 8 9 10
| #pragma CODE_SECTION (CSL_pcieRcConfig, ".text:csl_section:pcie"); void CSL_pcieRcConfig() { CSL_FINST(hPcieRc->LANE_CTL1 , PCIE_LANE_CTL1_LINKCAP, x4); CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_NUMOFLANE, 4); CSL_FINS(hPcieRc->LINK_CTL2, PCIE_LINK_CTL2_TGTSPD, 2); CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_DIRECTSPDCHG, 0); CSL_FINS(hPcieRc->LANE_CTL2, PCIE_LANE_CTL2_DIRECTSPDCHG, 1); return; }
|
后面的代码都是参考TI 的CSL库的格式写的。就是每个外设模块都相互独立;对于每个外设的寄存器都定义成一个结构体;每个寄存器里面的每个Field都定义了相应的Mask和Shift;一些默认的参数定义成Token。再有一组宏定义实现了可读性较强的代码风格。比如“CSL_FINS"这个宏定义实现的就是往寄存器的特定Field插入某个值的功能。“CSL_FINST”就是用预先定义好的Token来给特定的Field插入某个值。还有“CSL_FEXT”是提取某个Field 的值,“CSL_FMK”是生成某个寄存器的值。CSL_FINS在用的时候会先读寄存器,再写寄存器;有时候如果不用读或者不能读的话,就要用CSL_FMK来生成某个值直接赋给寄存器。
1 2 3 4 5 6 7 8
| #pragma CODE_SECTION (CSL_pcieLinkUp, ".text:csl_section:pcie"); Uint8 CSL_pcieLinkUp() { CSL_FINS(hPcieRc->CMD_STATUS, PCIE_CMD_STATUS_LTSSM_EN, 1); while(CSL_FEXT(hPcieRc->PCIE_CORE_STATUS8, PCIE_PCIE_CORE_STATUS8_LTSSM)!=0x11) ; return CSL_FEXT(hPcieRc->LINK_STAT_CTL, PCIE_LINK_STAT_CTL_CLINKSPD); }
|
相关链路的配置完成后,就只需要开始链路的初始化,并等待链路建立即可。链路建立后,可以读链路状态寄存器看当前的链路速率是否和设置的一致。
链路建立之后,还需要分别对RC和EP进一步初始化。DSP有一个InBound和OutBound的地址转换的功能,理论上它可以把在接收TLP包的时候把64bit 的PCIe地址,转换成DSP的32bit的AXI地址;也可以在发送TLP包的时候把DSP的32bit的AXI地址转换成64bit的PCIe地址。但是我试了却发现并没有这个效果,可能是我配置有问题。目前只能用那一段固定的0x61000000~0x6FFFFFFF地址空间。就是我如果往0x6100_0000写数据,在FPGA端就能收到PCIe地址是0x0000_0000_6100_0000的写数据请求,没有地址转换的效果。
EP端的BAR初始化是必不可少的,结合我的上一篇关于FPGA的PCIe的配置,我需要将上图中的红色基址写入EP端的对应的BAR寄存器中,然后就完成了BAR地址映射,为后面的大批量数据传输做好了准备。初始化过程还有一些东西需要配置,具体有哪些建议参考官方例程。而且寄存器就那么些,配置应该不麻烦,这里就不多说了,不同处理器的配置应该会有不一样。
MSI初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #pragma CODE_SECTION (CSL_pcieMsiInit, ".text:csl_section:pcie"); void CSL_pcieMsiInit(CSL_PcieMsiInfo* pMsgInfo) {
hPcieRc->MSI_CTRL_ADDR_LOW = 0x42000000; hPcieRc->MSI_CTRL_ADDR_HIGH = 0x00000000; hPcieRc->MSI_CTRL_INT_EN = 0xFFFFFFFF; hPcieRc->MSI_CTRL_INT_MASK = 0x00000000;
pMsgInfo->MsgCap = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_MESGCAP); pMsgInfo->NxtPtr = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_NXT); pMsgInfo->ID = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_ID); pMsgInfo->Addr64= CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_ADDR64); pMsgInfo->Maskable = CSL_FEXT(hPcieEp->MSI_CAP_CTRL_PTR, PCIE_MSI_CAP_CTRL_PTR_MASKABLE); hPcieEp->MSI_ADDR_LOW = 0x42000000; hPcieEp->MSI_ADDR_HIGH = 0x00000000; hPcieEp->MSI_DATA = 0x00000000; hPcieEp->MSI_CAP_CTRL_PTR = CSL_FMKT(PCIE_MSI_CAP_CTRL_PTR_MESGNUM, 32) | CSL_FMK(PCIE_MSI_CAP_CTRL_PTR_MSIEN, 1); }
|
MSI的初始化包括两部分,一部分是对RC的初始化,另一部分是对EP的配置。首先要设置DSP哪个地址用来接收MSI中断,然后打开32个MSi中断使能。EP端,也就是FPGA端,DSP可以读取相应的寄存器,查看它对MSI的支持情况,然后也需要设置同样的MSI地址,MSI个数,并开启使能。理论上来说总共有32个MSI中断,这32个中断向量分别分给8个核,中断向量0、8、16、24,分给核0;中断向量1、9、17、25分给核1,以此类推。但实际上测试发现每个中断向量都会被核0接收,而且产生的都是中断向量0的MSI中断。这个很奇怪,虽然有点影响使用,但是也还好。
PCIe DMA
DSP的PCIe模块带有DMA功能,而且数据量1B~4GB都可以,虽然只能实现连续的数据块的传输,但优点是使用方便。它有两个DMA读通道,两个DMA写通道。每次只需要给定DMA传输的起始地址、目的地址和字节大小就行。传输完成后会产生传输完成中断,下面是发起DMA读写的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #pragma CODE_SECTION (CSL_pcieDmaReadLocalInt, ".text:csl_section:pcie"); void CSL_pcieDmaWriteLocalInt( Uint8 chNum, Uint32 dmaSize, Uint32 srcAddr, Uint32 dstAddr ){ CSL_FINS(hPcieRc->DMA_WR_EN, PCIE_DMA_WR_EN, 1); hPcieRc->DMA_WR_INT_MASK = CSL_FMK(PCIE_DMA_INT_ABORT, 0) | CSL_FMK(PCIE_DMA_INT_DONE, 0); hPcieRc->DMA_VIEWPORT_SEL = CSL_FMKT(PCIE_DMA_VIEWPORT_SEL_CHANNEL_DIR, WRITE) | CSL_FMK(PCIE_DMA_VIEWPORT_SEL_CHANNEL_NUM, chNum); hPcieRc->DMA_CH_CTRL1 = CSL_FMK(PCIE_DMA_CH_CTRL1_TD, 1) | CSL_FMK(PCIE_DMA_CH_CTRL1_LIE, 1); hPcieRc->DMA_SIZE = dmaSize; hPcieRc->DMA_SRC_LOW = srcAddr; hPcieRc->DMA_SRC_HIGH = 0x00000000; hPcieRc->DMA_DST_LOW = dstAddr; hPcieRc->DMA_DST_HIGH = 0x00000000; hPcieRc->DMA_WR_DB = CSL_FMK(PCIE_DMA_WR_DB_WR_STOP, 0) | CSL_FMK(PCIE_DMA_WR_DB_DB_NUM, chNum); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #pragma CODE_SECTION (CSL_pcieDmaReadLocalInt, ".text:csl_section:pcie"); void CSL_pcieDmaReadLocalInt( Uint8 chNum, Uint32 dmaSize, Uint32 srcAddr, Uint32 dstAddr ) { CSL_FINS(hPcieRc->DMA_RD_EN, PCIE_DMA_RD_EN, 1); hPcieRc->DMA_RD_INT_MASK = CSL_FMK(PCIE_DMA_INT_ABORT, 0) | CSL_FMK(PCIE_DMA_INT_DONE, 0); hPcieRc->DMA_VIEWPORT_SEL = CSL_FMKT(PCIE_DMA_VIEWPORT_SEL_CHANNEL_DIR, READ) | CSL_FMK(PCIE_DMA_VIEWPORT_SEL_CHANNEL_NUM, chNum); hPcieRc->DMA_CH_CTRL1 = CSL_FMK(PCIE_DMA_CH_CTRL1_TD, 1) | CSL_FMK(PCIE_DMA_CH_CTRL1_LIE, 1); hPcieRc->DMA_SIZE = dmaSize; hPcieRc->DMA_SRC_LOW = srcAddr; hPcieRc->DMA_SRC_HIGH = 0x00000000; hPcieRc->DMA_DST_LOW = dstAddr; hPcieRc->DMA_DST_HIGH = 0x00000000; hPcieRc->DMA_RD_DB = CSL_FMK(PCIE_DMA_RD_DB_WR_STOP, 0) | CSL_FMK(PCIE_DMA_RD_DB_DB_NUM, chNum); }
|
EDMA
同样的数据传输,用EDMA也可以完成,而且EDMA更加灵活,可以做不连续的数据块的传输。我用EDMA的QDMA通道0做数据传输,做了如下的配置,使能了QDMA通道0和0号中断。在对应的PaRAM里面将QDMA完成中断指向0号中断。每次我往PaRAM的第7个字写的时候,EDMA1就会产生一个QDMA传输,并在传输完成后产生中断。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| void edma3QdmaConfig() { int i;
CSL_Status status;
CSL_Edma3ModuleAttr edma3Attr; CSL_Edma3HwSetup setup; CSL_Edma3HwDmaChannelSetup dmaChannelSetup[64]; CSL_Edma3HwQdmaChannelSetup qdmaChannelSetup[8];
CSL_pscModuleEnable(PSC_MD_DMA1, PSC_PWR_PERI);
hEdma3 = CSL_edma3Open(&edma3Obj, CSL_TPCC_1, &edma3Attr, &status);
for(i = 0; i<64; i++){ dmaChannelSetup[i].paramNum = 0; dmaChannelSetup[i].que = CSL_EDMA3_QUE_0; } for(i = 0; i<8; i++){ qdmaChannelSetup[i].paramNum = 0; qdmaChannelSetup[i].que = CSL_EDMA3_QUE_0; qdmaChannelSetup[i].triggerWord = 7; }
setup.dmaChaSetup = dmaChannelSetup; setup.qdmaChaSetup = qdmaChannelSetup; CSL_edma3HwSetup(hEdma3, &setup); CSL_edma3QDMAChannelEnable(hEdma3, CSL_EDMA3_REGION_GLOBAL, 0); CSL_edma3InterruptLoEnable(hEdma3, CSL_EDMA3_REGION_GLOBAL, 1<<qdmaIntChNum); return; }
|
EDMA写PaRAM配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, 1u) | CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 1) | CSL_FMK(TPCC_PARAM_OPT_TCC, qdmaIntChNum) | CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | CSL_FMK(TPCC_PARAM_OPT_SAM, 0); paramSetup.srcAddr = (Uint32)txData; paramSetup.aCntbCnt = 0x02000280; paramSetup.dstAddr = 0x68000000u; paramSetup.srcDstBidx = 0x02800280; paramSetup.linkBcntrld = 0x0280FFFF; paramSetup.srcDstCidx = 0x00000000; paramSetup.cCnt = 0x00000001;
|
EDMA读PaRAM配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| paramSetup.option = CSL_FMK(TPCC_PARAM_OPT_PRIV, 1u) | CSL_FMK(TPCC_PARAM_OPT_PRIVID, 0) | CSL_FMK(TPCC_PARAM_OPT_ITCCHEN, 0) | CSL_FMK(TPCC_PARAM_OPT_TCCHEN, 0) | CSL_FMK(TPCC_PARAM_OPT_ITCINTEN, 0) | CSL_FMK(TPCC_PARAM_OPT_TCINTEN, 1) | CSL_FMK(TPCC_PARAM_OPT_TCC, qdmaIntChNum) | CSL_FMK(TPCC_PARAM_OPT_TCCMOD, 0) | CSL_FMKT(TPCC_PARAM_OPT_FWID, 32) | CSL_FMK(TPCC_PARAM_OPT_STATIC, 0) | CSL_FMK(TPCC_PARAM_OPT_SYNCDIM, 1) | CSL_FMK(TPCC_PARAM_OPT_DAM, 0) | CSL_FMK(TPCC_PARAM_OPT_SAM, 0); paramSetup.srcAddr = 0x68000000; paramSetup.aCntbCnt = 0x02000280; paramSetup.dstAddr = (Uint32)rxData; paramSetup.srcDstBidx = 0x02800280; paramSetup.linkBcntrld = 0x0280FFFF; paramSetup.srcDstCidx = 0x00000000; paramSetup.cCnt = 0x00000001;
|
在用EDMA传输的时候,遇到了一个问题,就是如果把传输模式改成A-mode,她就不能正常完成读写了。只有把把PaRAM里OPT中的SYNDIM(Transfer synchronization dimension)改成1才能正常读写。
程序计时
后面准备对DMA传输的速度做个测试,所以需要比较准确的计时方式。之前在做SRIO速率测试的时候有用到过,就是用TSC(Time Stamp Counter Registers)寄存器。头文件需要调用<c6x.h>,然后需要两个无符号的long long 类型的变量来存TSCL和TSCH的值。给TSCL赋0来启动TSC。后面用_itoll()将两个32位寄存器拼成64bit的long long类型来获取当前计数值。将tStart和tStop作差即可求出总的时间开销,这一计数值的单位为系统时钟的一个周期,比如我的系统时钟是1GHz,那么这个计数值就是ns为单位。
1 2 3 4 5 6 7
| #include <c6x.h> unsigned long long tStart; unsigned long long tStop; TSCL= 0; tStart= _itoll(TSCH, TSCL); ...... tStop = _itoll(TSCH, TSCL);
|