本章节讲述如何使用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, status=0x%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; // PRP1:CQ物理基地址 sqe.dptr[1] = 0; // DW10:高16位QSIZE(depth-1),低16位QID(cq_id),按NVMe标准修正高低位 sqe.cdw10 = (((u32)(depth - 1)) << 16) | (cq_id & 0xFFFF); // DW11:PC=1(内存连续),IEN=0(纯轮询关闭中断),IV=0 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 status=0x%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; // DW10:Bit31:16=QSIZE(depth-1),Bit15:0=SQID(sq_id) sqe.cdw10 = (((u32)(depth - 1)) << 16) | (sq_id & 0xFFFF); // DW11:Bit31:16=CQID,保留位0,QPRIO=0,PC=1 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 status=0x%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); // NSQR=1, NCQR=1(0基偏移),实际分配 2套I/O队列(2SQ+2CQ) //创建 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
到这里初始化及配置基本上已经完成,后面就可以进行数据读写了