二、实战篇-NVME SSD控制之ZYNQ实现(二)
本章节讲述如何使用ZYNQ纯C语言实现NVME SSD硬盘的读写控制。参考FPGA控制NVME开发流程-CSDN博客一、理论篇-NVME协议学习笔记-CSDN博客二、实战篇-NVME SSD控制之ZYNQ实现一-CSDN博客上一节完成了PCIE的建链和枚举使ZYNQ能够通过PCIE扫描到NVME SSD硬盘本节主要讲述怎么进行NVME SSD的PCIE CFG空间的配置。上一节我们主要使用官方给的例程完成设备ID、ClassCode等信息的读取官方的代码也是通过读写寄存器方式实现的下面分析一下官方代码后面通过自己读写寄存器方式也可以替换官方代码官方例程代码主要由PcieInitRootComplex和PCIeEnumerateFabric两个函数组成完成建链和枚举一、PcieInitRootComplex函数这个函数主要用于设备作为RC端时的初始化完整代码int PcieInitRootComplex(XAxiPcie *AxiPciePtr, u16 DeviceId) { int Status; u32 HeaderData; u32 InterruptMask; u8 BusNumber; u8 DeviceNumber; u8 FunNumber; u8 PortNumber; XAxiPcie_Config *ConfigPtr; ConfigPtr XAxiPcie_LookupConfig(DeviceId); Status XAxiPcie_CfgInitialize(AxiPciePtr, ConfigPtr, ConfigPtr-BaseAddress); if (Status ! XST_SUCCESS) { xil_printf(Failed to initialize PCIe Root Complex IP Instance\r\n); return XST_FAILURE; } if(!AxiPciePtr-Config.IncludeRootComplex) { xil_printf(Failed to initialize...AXI PCIE is configured as endpoint\r\n); return XST_FAILURE; } /* See what interrupts are currently enabled */ XAxiPcie_GetEnabledInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently enabled are %8X\r\n, InterruptMask); /* Make sure all interrupts disabled. */ XAxiPcie_DisableInterrupts(AxiPciePtr, XAXIPCIE_IM_ENABLE_ALL_MASK); /* See what interrupts are currently pending */ XAxiPcie_GetPendingInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently pending are %8X\r\n, InterruptMask); /* Just if there is any pending interrupt then clear it.*/ XAxiPcie_ClearPendingInterrupts(AxiPciePtr, XAXIPCIE_ID_CLEAR_ALL_MASK); /* * Read enabled interrupts and pending interrupts * to verify the previous two operations and also * to test those two API functions */ XAxiPcie_GetEnabledInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently enabled are %8X\r\n, InterruptMask); XAxiPcie_GetPendingInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently pending are %8X\r\n, InterruptMask); /* Make sure link is up. */ Status XAxiPcie_IsLinkUp(AxiPciePtr); if (Status ! TRUE ) { xil_printf(Link is not up\r\n); return XST_FAILURE; } xil_printf(Link is up\r\n); /* * Read back requester ID. */ XAxiPcie_GetRequesterId(AxiPciePtr, BusNumber, DeviceNumber, FunNumber, PortNumber); xil_printf(Bus Number is %02X\r\n Device Number is %02X\r\n Function Number is %02X\r\n Port Number is %02X\r\n, BusNumber, DeviceNumber, FunNumber, PortNumber); /* Set up the PCIe header of this Root Complex */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData); HeaderData | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN | PCIE_CFG_CMD_IO_EN | PCIE_CFG_CMD_PARITY | PCIE_CFG_CMD_SERR_EN); XAxiPcie_WriteLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData); /* * Read back local config reg. * to verify the write. */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData); xil_printf(PCIe Local Config Space is %8X at register CommandStatus\r\n, HeaderData); /* * Set up Bus number */ HeaderData PCIE_CFG_PRIM_SEC_BUS; XAxiPcie_WriteLocalConfigSpace(AxiPciePtr, PCIE_CFG_PRI_SEC_BUS_REG, HeaderData); /* * Read back local config reg. * to verify the write. */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_PRI_SEC_BUS_REG, HeaderData); xil_printf(PCIe Local Config Space is %8X at register Prim Sec. Bus\r\n, HeaderData); /* Now it is ready to function */ xil_printf(Root Complex IP Instance has been successfully initialized\r\n); return XST_SUCCESS; }这里面我们主要关注哪些函数对寄存器进行了操作。这里是关于中断interrupts的当前未使用中断模式所以就不用配置也行/* See what interrupts are currently enabled */ XAxiPcie_GetEnabledInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently enabled are %8X\r\n, InterruptMask); /* Make sure all interrupts disabled. */ XAxiPcie_DisableInterrupts(AxiPciePtr, XAXIPCIE_IM_ENABLE_ALL_MASK); /* See what interrupts are currently pending */ XAxiPcie_GetPendingInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently pending are %8X\r\n, InterruptMask); /* Just if there is any pending interrupt then clear it.*/ XAxiPcie_ClearPendingInterrupts(AxiPciePtr, XAXIPCIE_ID_CLEAR_ALL_MASK); /* * Read enabled interrupts and pending interrupts * to verify the previous two operations and also * to test those two API functions */ XAxiPcie_GetEnabledInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently enabled are %8X\r\n, InterruptMask); XAxiPcie_GetPendingInterrupts(AxiPciePtr, InterruptMask); xil_printf(Interrupts currently pending are %8X\r\n, InterruptMask);下面一个函数是检测PCIE链路是否LINKUP/* Make sure link is up. */ Status XAxiPcie_IsLinkUp(AxiPciePtr); if (Status ! TRUE ) { xil_printf(Link is not up\r\n); return XST_FAILURE; } xil_printf(Link is up\r\n);这里按ctrl键鼠标左键点进去看函数定义#define XAxiPcie_IsLinkUp(InstancePtr) \ (XAxiPcie_ReadReg((InstancePtr)-Config.BaseAddress, \ XAXIPCIE_PHYSC_OFFSET) XAXIPCIE_PHYSC_LINK_UP_MASK) ? TRUE : FALSE底层也是在读寄存器寄存器地址XAXIPCIE_PHYSC_OFFSET这里可以通过手册pg055查看原理bit11为1表示link up 0为Link down下面这个函数为获取FPGA 在PCIE总线位置(BDF值),默认都为0/* * Read back requester ID. */ XAxiPcie_GetRequesterId(AxiPciePtr, BusNumber, DeviceNumber, FunNumber, PortNumber); xil_printf(Bus Number is %02X\r\n Device Number is %02X\r\n Function Number is %02X\r\n Port Number is %02X\r\n, BusNumber, DeviceNumber, FunNumber, PortNumber);查看XAxiPcie_GetRequesterId函数定义也是通过读寄存器获取地址XAXIPCIE_BL_OFFSET为手册中定义接下来为配置RC端FPGA的PCIE配置空间中的命令寄存器/* Set up the PCIe header of this Root Complex */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData); HeaderData | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN | PCIE_CFG_CMD_IO_EN | PCIE_CFG_CMD_PARITY | PCIE_CFG_CMD_SERR_EN); XAxiPcie_WriteLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData);XAxiPcie_ReadLocalConfigSpace也是读寄存器函数不过偏移地址*4了实际访问地址为0x04这里就是把这些使能都打开后面这个是回读验证/* * Read back local config reg. * to verify the write. */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_CMD_STATUS_REG, HeaderData); xil_printf(PCIe Local Config Space is %8X at register CommandStatus\r\n, HeaderData);后面这个设置扫描的总线号范围以及回读验证/* * Set up Bus number */ HeaderData PCIE_CFG_PRIM_SEC_BUS; XAxiPcie_WriteLocalConfigSpace(AxiPciePtr, PCIE_CFG_PRI_SEC_BUS_REG, HeaderData); /* * Read back local config reg. * to verify the write. */ XAxiPcie_ReadLocalConfigSpace(AxiPciePtr, PCIE_CFG_PRI_SEC_BUS_REG, HeaderData); xil_printf(PCIe Local Config Space is %8X at register Prim Sec. Bus\r\n, HeaderData);这里读写地址为6*40x24,FPGA作为RC使用的是TYPE1型配置空间配置的值PCIE_CFG_PRIM_SEC_BUS为将寄存器配置为0x00070100代表RC 主总线 Bus0 直接下游总线起点 Bus1 系统最多支持 7 级总线Bus1~Bus7到这里这个函数就解析完了最后通过串口打印成功信息xil_printf(Root Complex IP Instance has been successfully initialized\r\n); return XST_SUCCESS;二、PCIeEnumerateFabric函数这个函数主要进行设备枚举扫描出PCIE总线下挂载的EP设备NVME SSD完整函数代码如下void PCIeEnumerateFabric(XAxiPcie *AxiPciePtr) { u32 ConfigData; u32 PCIeHeaderType; u32 PCIeMultiFun; u32 PCIeBusNum; u32 PCIeDevNum; u32 PCIeFunNum; u16 PCIeVendorID; u32 RegVal; xil_printf(Start Enumeration of PCIe Fabric on This System\r\n); /* Scan PCIe Fabric */ for (PCIeBusNum 0; PCIeBusNum PCIE_CFG_MAX_NUM_OF_BUS; PCIeBusNum) { for (PCIeDevNum 0; PCIeDevNum PCIE_CFG_MAX_NUM_OF_DEV; PCIeDevNum) { for (PCIeFunNum 0; PCIeFunNum PCIE_CFG_MAX_NUM_OF_FUN; PCIeFunNum) { /* Vendor ID */ XAxiPcie_ReadRemoteConfigSpace( AxiPciePtr,PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_ID_REG, ConfigData); PCIeVendorID (u16) (ConfigData 0xFFFF); if (PCIeVendorID PCIE_CFG_FUN_NOT_IMP_MASK) { if (PCIeFunNum 0) /* * We dont need to look * any further on this device. */ break; } else { xil_printf(PCIeBus is %02X\r\n PCIeDev is %02X\r\n PCIeFunc is %02X\r\n, PCIeBusNum, PCIeDevNum, PCIeFunNum); xil_printf(Vendor ID is %04X \r\n, PCIeVendorID); /* Header Type */ XAxiPcie_ReadRemoteConfigSpace( AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CAH_LAT_HD_REG, ConfigData); PCIeHeaderType ConfigData PCIE_CFG_HEADER_TYPE_MASK; PCIeMultiFun ConfigData PCIE_CFG_MUL_FUN_DEV_MASK; if (PCIeHeaderType PCIE_CFG_HEADER_O_TYPE) { /* This is an End Point */ xil_printf(This is an End Point\r\n); /* * Initialize this end point * and return. */ XAxiPcie_ReadRemoteConfigSpace( AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CMD_STATUS_REG, ConfigData); ConfigData | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN); XAxiPcie_WriteRemoteConfigSpace (AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CMD_STATUS_REG, ConfigData); /* * Write Address to * PCIe BAR0 */ ConfigData (PCIE_CFG_BAR_0_ADDR | PCIeBusNum | PCIeDevNum | PCIeFunNum); XAxiPcie_WriteRemoteConfigSpace (AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_BAR_0_REG, ConfigData); xil_printf(End Point has been enabled\r\n); } else { /* This is a bridge */ xil_printf(This is a Bridge\r\n); } } if ((!PCIeFunNum) (!PCIeMultiFun)) { /* * If it is function 0 and it is not a * multi function device, we dont need * to look any further on this devie */ break; } } /* Functions in one device */ } /* Devices on the same bus */ } /* Buses in the same system */ xil_printf(End of Enumeration of PCIe Fabric on This system\r\n); /* Bridge enable */ XAxiPcie_GetRootPortStatusCtrl(AxiPciePtr, RegVal); RegVal | XAXIPCIE_RPSC_BRIDGE_ENABLE_MASK; XAxiPcie_SetRootPortStatusCtrl(AxiPciePtr, RegVal); return; }这里主要就是一个for循环根据不同的BusDevFunc号进行设备VendorID读取VendorID不是0xFFFF的话就代表这个BDF下挂载了一个EP设备挂载一块NVME SSD的情况下默认Bus1Dev0Func0。后面这部分主要是读取一些EP设备的基本信息xil_printf(PCIeBus is %02X\r\n PCIeDev is %02X\r\n PCIeFunc is %02X\r\n, PCIeBusNum, PCIeDevNum, PCIeFunNum); xil_printf(Vendor ID is %04X \r\n, PCIeVendorID); /* Header Type */ XAxiPcie_ReadRemoteConfigSpace( AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CAH_LAT_HD_REG, ConfigData); PCIeHeaderType ConfigData PCIE_CFG_HEADER_TYPE_MASK; PCIeMultiFun ConfigData PCIE_CFG_MUL_FUN_DEV_MASK; if (PCIeHeaderType PCIE_CFG_HEADER_O_TYPE) { /* This is an End Point */ xil_printf(This is an End Point\r\n);下面这个是配置NVME SSD设备PCIE配置空间的命令寄存器和BAR寄存器/* * Initialize this end point * and return. */ XAxiPcie_ReadRemoteConfigSpace( AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CMD_STATUS_REG, ConfigData); ConfigData | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN); XAxiPcie_WriteRemoteConfigSpace (AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_CMD_STATUS_REG, ConfigData); /* * Write Address to * PCIe BAR0 */ ConfigData (PCIE_CFG_BAR_0_ADDR | PCIeBusNum | PCIeDevNum | PCIeFunNum); XAxiPcie_WriteRemoteConfigSpace (AxiPciePtr, PCIeBusNum, PCIeDevNum, PCIeFunNum, PCIE_CFG_BAR_0_REG, ConfigData); xil_printf(End Point has been enabled\r\n);最后这个是开启Bridge Enable 桥使能Bridge Enable该位置 1 时AXI BAR 空间的读写流量可以转发到 PCIe 总线。软件完成 PCIe 枚举流程后必须向该位写 1 开启桥转发三、PCIE CFG空间配置下面展示不调用官方函数直接使用读写寄存器进行配置。1、读取RC端FPGA的PCIE配置空间先读取FPGA (RC端)CFG配置寄存器里面的信息确保链路通信正常u32 ClassCode; u32 bar0addr,bar1addr,ep_bar0; u32 DEVICE_ID,CMD,CFG_PRIM; DEVICE_ID XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000000); xil_printf(RC_DEVICE_ID is %08X \r\n, DEVICE_ID); CMD XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000004); xil_printf(RC_CMD is %08X \r\n, CMD); ClassCode XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000008); xil_printf(RC_ClassCode ID is %08X \r\n, ClassCode); bar0addr XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000010); xil_printf(RC_bar0addr is %08X \r\n, bar0addr); bar1addr XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000014); xil_printf(RC_bar1addr is %08X \r\n, bar1addr); CFG_PRIM XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000018); xil_printf(CFG_PRIM is %08X \r\n, CFG_PRIM);2、配置RC端FPGA的PCIE配置空间主要配置命令CMD寄存器(0x04)和设备枚举范围寄存器0x18然后再进行回读验证CMD | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN | PCIE_CFG_CMD_IO_EN | PCIE_CFG_CMD_PARITY | PCIE_CFG_CMD_SERR_EN); XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x000004, CMD) ; CMD XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000004); xil_printf(PCIe Local Config Space is %08X at register Prim Sec. Bus\r\n, CMD); XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x000018, PCIE_CFG_PRIM_SEC_BUS) ; CFG_PRIM XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000018); xil_printf(CFG_PRIM PCIe Local Config Space is %08X at register Prim Sec. Bus\r\n, CFG_PRIM);3、读取EP端NVME SSD的PCIE配置空间读取NVME SSD(EP端)CFG配置寄存器里面的信息检查设备ID、厂商ID等确保链路通信正常DEVICE_ID XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100000); xil_printf(DEVICE_ID is %08X \r\n, DEVICE_ID); CMD XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100004); xil_printf(CMD is %08X \r\n, CMD); ClassCode XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100008); xil_printf(ClassCode ID is %08X \r\n, ClassCode); bar0addr XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100010); xil_printf(bar0addr is %08X \r\n, bar0addr); bar1addr XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100014); xil_printf(bar1addr is %08X \r\n, bar1addr);4、配置EP端NVME SSD的PCIE配置空间这里主要配置命令CMD寄存器(0x100004)、BAR空间地址寄存器0x100010、0x100014并回读验证//MEM_EN BUSM_EN CMD | (PCIE_CFG_CMD_BUSM_EN | PCIE_CFG_CMD_MEM_EN ); XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x100004, CMD) ; CMD XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100004); xil_printf(PCIe Local Config Space is %08X at register Prim Sec. Bus\r\n, CMD); u32 bar0_val, bar1_val; // 1. 配置64位BARBAR0低32位 BAR1高32位 // 地址与 Vivado 映射 0x80000000 对齐 XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x100010, 0x80000004) ; usleep(1000); XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x100014, 0x00000000) ; usleep(1000); // 2. 回读BAR配置寄存器校验 bar0_val XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100010); bar1_val XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x100014); xil_printf(EP BAR0 Reg: 0x%08X\r\n, bar0_val); xil_printf(EP BAR1 Reg: 0x%08X\r\n, bar1_val);5、配置IP核寄存器配置根端口状态 / 控制寄存器寄存器偏移地址0x148打开Bridge Enable 桥使能/* Bridge enable */ u32 RegVal; RegVal XAxiPcie_ReadReg((XPAR_AXI_PCIE_0_BASEADDR),0x000148); RegVal | XAXIPCIE_RPSC_BRIDGE_ENABLE_MASK; XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_BASEADDR, 0x000148, RegVal) ; xil_printf(Bridge enable \r\n);