二、实战篇-NVME SSD控制之ZYNQ实现(五)

发布时间:2026/7/18 19:22:21

二、实战篇-NVME SSD控制之ZYNQ实现(五) 本章节讲述如何使用ZYNQ纯C语言实现NVME SSD硬盘的读写控制。参考FPGA控制NVME开发流程-CSDN博客一、理论篇-NVME协议学习笔记-CSDN博客二、实战篇-NVME SSD控制之ZYNQ实现一-CSDN博客二、实战篇-NVME SSD控制之ZYNQ实现二-CSDN博客二、实战篇-NVME SSD控制之ZYNQ实现三-CSDN博客二、实战篇-NVME SSD控制之ZYNQ实现四-CSDN博客前面完成了PCIE的建链和枚举、FPGA和NVME SSD的PCIE CFG空间的配置以及NVME SSD的寄存器配置。创建提交队列和完成队列并完成NvmeIdentifyCtrl信息读取。今天讲使用ADMIN命令创建IO提交和完成队列Admin命令集Admin Command Set如下常用的命令前面读取NvmeIdentifyCtrl信息使用的操作码为06h宏定义所有需要使用的操作码// NVMe 命令操作码 #define Admin_CMD_Create_SQ 0x01 #define Admin_CMD_Create_CQ 0x05 #define Admin_CMD_IDENTIFY 0x06 #define Admin_CMD_SET_FEAT 0x09 #define Admin_CMD_GET_FEAT 0x0A前面完成了NvmeIdentifyCtrl信息读取下面进行设置IO队列数量和IO队列的创建1、设置IO队列数量设置IO队列数量使用设置特征命令DW11如上实际写入值为队列数量-1创建2个队列NCQR和NSQR都为1。// Set Features: 设置 I/O 队列数量 int NvmeSetIoQueueNum(u16 sq_num, u16 cq_num) { NvmeSqe sqe {0}; NvmeCqe cqe; sqe.cdw0 Admin_CMD_SET_FEAT; sqe.cdw10 FEAT_IO_QUEUES; sqe.cdw11 (cq_num 16) | sq_num; NvmeSubmitAdminCmd(sqe); usleep(2000); if(NvmePollAdminCqe(cqe) ! 0) { xil_printf(NvmeSetIoQueueNum 4. CQE轮询超时函数退出\r\n); return -1; } xil_printf(NvmeSetIoQueueNum 5. 成功读到CQE\r\n); u16 real_status (cqe.status 1) 0x7FFF; if(real_status ! 0) { xil_printf(Set Features failed, status0x%04X\r\n, real_status); return -1; } xil_printf(Set IO Queues Done: SQ%d, CQ%d\r\n, sq_num, cq_num); return 0; }2、创建IO完成队列定义IO队列存放在本地的基地址#define IO_WR_SQ_BASE 0x10102000 #define IO_WR_CQ_BASE 0x10103000 #define IO_RD_SQ_BASE 0x10104000 #define IO_RD_CQ_BASE 0x10105000// 创建 I/O Completion Queue int NvmeCreateIoCq(u16 cid, u16 cq_id, u16 depth, u64 cq_base_addr) { NvmeSqe sqe {0}; NvmeCqe cqe; // CID由外部传入拼接Opcode sqe.cdw0 (((u32)cid) 16) | Admin_CMD_Create_CQ; sqe.nsid 0; sqe.rsvd1 0; sqe.mptr 0; sqe.dptr[0] cq_base_addr; // PRP1CQ物理基地址 sqe.dptr[1] 0; // DW10高16位QSIZE(depth-1)低16位QID(cq_id)按NVMe标准修正高低位 sqe.cdw10 (((u32)(depth - 1)) 16) | (cq_id 0xFFFF); // DW11PC1(内存连续)IEN0(纯轮询关闭中断)IV0 sqe.cdw11 0x00000001; sqe.cdw12 0; sqe.cdw13 0; sqe.cdw14 0; sqe.cdw15 0; // 缓存刷新确保SSD读取完整SQE Xil_DCacheFlushRange(sqe, sizeof(NvmeSqe)); NvmeSubmitAdminCmd(sqe); usleep(2000); if(NvmePollAdminCqe(cqe) ! 0) { xil_printf(NvmeCreateIoCq 4. CQE轮询超时函数退出\r\n); return -1; } xil_printf(NvmeCreateIoCq 5. 成功读到CQE\r\n); u16 real_status (cqe.status 1) 0x7FFF; if(real_status ! 0) { xil_printf(Create CQ %d failed, CID%d status0x%04X\r\n, cq_id, cid, real_status); return -1; } xil_printf(Create IO CQ %d Done, CID%d\r\n, cq_id, cid); return 0; }3、创建IO提交队列// 创建 I/O Submission Queue int NvmeCreateIoSq(u16 cid, u16 sq_id, u16 cq_id, u16 depth, u64 sq_base_addr) { NvmeSqe sqe {0}; NvmeCqe cqe; // DW0高16bit CID低8bit Opcode(0x01 Create SQ) sqe.cdw0 (((u32)cid) 16) | Admin_CMD_Create_SQ; sqe.nsid 0; sqe.rsvd1 0; sqe.mptr 0; // DW10Bit31:16QSIZE(depth-1)Bit15:0SQID(sq_id) sqe.cdw10 (((u32)(depth - 1)) 16) | (sq_id 0xFFFF); // DW11Bit31:16CQID保留位0QPRIO0PC1 sqe.cdw11 (((u32)cq_id) 16) | 0x0001; // PRP1存放SQ物理基地址PRP2置0 sqe.dptr[0] sq_base_addr; sqe.dptr[1] 0; // 其余保留DWORD全部清零 sqe.cdw12 0; sqe.cdw13 0; sqe.cdw14 0; sqe.cdw15 0; // 刷新数据缓存保证SSD读取完整SQE Xil_DCacheFlushRange(sqe, sizeof(NvmeSqe)); NvmeSubmitAdminCmd(sqe); usleep(2000); if(NvmePollAdminCqe(cqe) ! 0) { xil_printf(NvmeCreateIoSq 4. CQE轮询超时函数退出\r\n); return -1; } xil_printf(NvmeCreateIoSq 5. 成功读到CQE\r\n); u16 real_status (cqe.status 1) 0x7FFF; if(real_status ! 0) { xil_printf(Create SQ %d failed, CID%d status0x%04X\r\n, sq_id, cid, real_status); return -1; } xil_printf(Create IO SQ %d Done, bind CQ%d, CID%d\r\n, sq_id, cq_id, cid); return 0; }main函数中调用NvmeSetIoQueueNum(1,1); // NSQR1, NCQR10基偏移实际分配 2套I/O队列(2SQ2CQ) //创建 I/O 队列 NvmeCreateIoCq(0x0300,1, IO_Q_DEPTH,IO_WR_CQ_BASE); NvmeCreateIoCq(0x0301,2, IO_Q_DEPTH, IO_RD_CQ_BASE); usleep(1000); NvmeCreateIoSq(0x0302,1, 1, IO_Q_DEPTH,IO_WR_SQ_BASE); NvmeCreateIoSq(0x0303,2, 2, IO_Q_DEPTH, IO_RD_SQ_BASE);IO队列深度这里设置的是8到这里初始化及配置基本上已经完成后面就可以进行数据读写了

相关新闻