二、实战篇-NVME SSD控制之ZYNQ实现(六)-读写NVME SSD硬盘数据

本章节讲述如何使用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博客

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

前面完成了PCIE的建链和枚举、FPGA和NVME SSD的PCIE CFG空间的配置以及NVME SSD的寄存器配置。创建提交队列和完成队列,完成NvmeIdentifyCtrl信息读取,以及ADMIN命令创建IO提交和完成队列。本节进行NVME SSD硬盘数据读写

NVM命令集(NVM Command Set)

先定义操作码

cpp 复制代码
#define NVME_CMD_WRITE     0x01
#define NVME_CMD_READ      0x02

写数据和读数据的门铃寄存器和admin不同

cpp 复制代码
#define RegOffset_IO_SQ0TDBL 	0x1008
#define RegOffset_IO_CQ0HDBL 	0x100c
#define RegOffset_IO_SQ1TDBL 	0x1010
#define RegOffset_IO_CQ1HDBL 	0x1014

写数据、读数据提交队列函数,基本和ADMIN相同,只有写门铃寄存器位置不同

cpp 复制代码
// 提交命令到 IOWR SQ
void NvmeSubmitIOWRCmd(NvmeSqe *sqe)
{
    // 新增:刷新栈上临时SQE
    Xil_DCacheFlushRange(sqe, sizeof(NvmeSqe));
    IowrSq[Iowr_SqTail] = *sqe;
    Xil_DCacheFlushRange((void*)&IowrSq[Iowr_SqTail], sizeof(NvmeSqe));
    Iowr_SqTail = (Iowr_SqTail + 1) % IO_Q_DEPTH;
    // 写门铃:SQ Tail Doorbell (偏移 0x1000 + 4*SQID)
    XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_AXIBAR_0, RegOffset_IO_SQ0TDBL, Iowr_SqTail) ;
}
// 提交命令到 IORD SQ
void NvmeSubmitIORDCmd(NvmeSqe *sqe)
{
    // 新增:刷新栈上临时SQE
    Xil_DCacheFlushRange(sqe, sizeof(NvmeSqe));
    IordSq[Iord_SqTail] = *sqe;
    Xil_DCacheFlushRange((void*)&IordSq[Iord_SqTail], sizeof(NvmeSqe));
    Iord_SqTail = (Iord_SqTail + 1) % IO_Q_DEPTH;
    // 写门铃:SQ Tail Doorbell (偏移 0x1000 + 4*SQID)
    XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_AXIBAR_0, RegOffset_IO_SQ1TDBL, Iord_SqTail) ;
}

写数据、读数据完成队列函数

cpp 复制代码
// 轮询 IOWR CQE,等待命令完成
int NvmePollIOWRCqe(NvmeCqe *cqe)
{
    int timeout = 2000;
    while(timeout--)
    {
        // 失效缓存,读取硬件DMA更新后的CQ内存
        Xil_DCacheInvalidateRange((void*)&IowrCq[Iowr_CqHead], sizeof(NvmeCqe));
        *cqe = IowrCq[Iowr_CqHead];

        u8 curr_cqe_pha = cqe->status & 0x1;

        // 条件1:当前CQE Phase 和软件缓存的全局Phase不等 → 有效新条目
        if (curr_cqe_pha != IoWrCqLastPha)
        {
            // 读取成功,Head前进一格
        	Iowr_CqHead = (Iowr_CqHead + 1) % IO_Q_DEPTH;

            // 关键:判断是否走完一整圈队列,下一轮硬件Phase会翻转
            // 走完一圈后,下一条CQE的Phase必然取反,提前更新软件缓存
            if (Iowr_CqHead == 0)
            {
            	IoWrCqLastPha ^= 1;
            }

            // 更新CQ门铃,通知SSD主机已消费当前CQE
            XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_AXIBAR_0, RegOffset_IO_CQ0HDBL, Iowr_CqHead);
            return 0;
        }

        // 条件2:curr_cqe_pha = IoWrCqLastPha → 无新完成条目,继续等待
        usleep(500);
    }
    return -1; // 超时无CQE
}

// 轮询 IORD CQE,等待命令完成
int NvmePollIORDCqe(NvmeCqe *cqe)
{
    int timeout = 2000;
    while(timeout--)
    {
        // 失效缓存,读取硬件DMA更新后的CQ内存
        Xil_DCacheInvalidateRange((void*)&IordCq[Iord_CqHead], sizeof(NvmeCqe));
        *cqe = IordCq[Iord_CqHead];

        u8 curr_cqe_pha = cqe->status & 0x1;

        // 条件1:当前CQE Phase 和软件缓存的全局Phase不等 → 有效新条目
        if (curr_cqe_pha != IoRdCqLastPha)
        {
            // 读取成功,Head前进一格
        	Iord_CqHead = (Iord_CqHead + 1) % IO_Q_DEPTH;

            // 关键:判断是否走完一整圈队列,下一轮硬件Phase会翻转
            // 走完一圈后,下一条CQE的Phase必然取反,提前更新软件缓存
            if (Iord_CqHead == 0)
            {
            	IoRdCqLastPha ^= 1;
            }

            // 更新CQ门铃,通知SSD主机已消费当前CQE
            XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_AXIBAR_0, RegOffset_IO_CQ1HDBL, Iord_CqHead);
            return 0;
        }

        // 条件2:curr_cqe_pha = IoWrCqLastPha → 无新完成条目,继续等待
        usleep(500);
    }
    return -1; // 超时无CQE
}

提交和完成队列函数写好后,下面在读数据和写数据函数中调用

写数据函数

cpp 复制代码
// NVMe 写命令
int NvmeWrite(u32 nsid, u64 lba, u16 nlb, void *buf)
{
    NvmeSqe sqe = {0};
    NvmeCqe cqe;

    sqe.cdw0  = NVME_CMD_WRITE;
    sqe.nsid  = nsid;
    sqe.dptr[0] = (u64)buf;
    sqe.dptr[1] = 0;
    sqe.cdw10 = (u32)lba;
    sqe.cdw11 = (u32)(lba >> 32);
    sqe.cdw12 = (nlb - 1) & 0xFFFF;

    Xil_DCacheFlushRange(buf, 4096);

    NvmeSubmitIOWRCmd(&sqe);
    usleep(2000);
    if(NvmePollIOWRCqe(&cqe) != 0)
    {
        xil_printf("NvmeWrite 4. CQE轮询超时,函数退出\r\n");
        return -1;
    }
    xil_printf("NvmeWrite 5. 成功读到CQE\r\n");

    u16 real_status = (cqe.status >> 1) & 0x7FFF;
       if(real_status != 0)
       {
           xil_printf("NvmeWrite  failed, status=0x%04X\r\n", real_status);
           return -1;
       }

    xil_printf("NvmeWrite  Done\r\n");
    return 0;
}

读数据函数

cpp 复制代码
// NVMe 读命令
int NvmeRead(u32 nsid, u64 lba, u16 nlb, void *buf)
{
    NvmeSqe sqe = {0};
    NvmeCqe cqe;

    sqe.cdw0  = NVME_CMD_READ;
    sqe.nsid  = nsid;
    sqe.dptr[0] = (u64)buf;
    sqe.dptr[1] = 0;
    sqe.cdw10 = (u32)lba;
    sqe.cdw11 = (u32)(lba >> 32);
    sqe.cdw12 = (nlb - 1) & 0xFFFF;

    NvmeSubmitIORDCmd(&sqe);
      usleep(2000);
      if(NvmePollIORDCqe(&cqe) != 0)
      {
          xil_printf("NvmeRead 4. CQE轮询超时,函数退出\r\n");
          return -1;
      }
      xil_printf("NvmeRead 5. 成功读到CQE\r\n");

      u16 real_status = (cqe.status >> 1) & 0x7FFF;
         if(real_status != 0)
         {
             xil_printf("NvmeRead failed, status=0x%04X\r\n",  real_status);
             return -1;
         }

      xil_printf("NvmeRead  Done\r\n");
      return 0;

}

先定义读数据和写数据的地址空间

cpp 复制代码
// 数据缓冲区(和队列彻底分开)
#define WRITE_DATA_BUF     0x20000000
#define READ_DATA_BUF      0x28000000

在main函数中调用如下

cpp 复制代码
u8 *w_buf = (u8 *)WRITE_DATA_BUF;
u8 *read_buf = (u8 *)READ_DATA_BUF;
// 填充测试数据
 memset(w_buf, 0x5A, 4096);
 ret = NvmeWrite(1, 0, 1, w_buf);
if(ret != 0) goto err_end;
  //read
	   if(NvmeRead(1, 0, 1, read_buf) == 0)
	   {
	       // 读取成功,打印前若干字节验证
	       for(int i = 0; i < 16; i++)
	       {
	           xil_printf("0x%02X ", read_buf[i]);
	       }
	       xil_printf("\r\n");
	   }
	   else
	   {
	       xil_printf("Read failed\r\n");
	   }

本次读写空间为一个LB,512字节

先将数据0x5A写入NVME SSD,再读取打印前16个数据验证是否为0x5A。

相关推荐
千寻xun2 小时前
二、实战篇-NVME SSD控制之ZYNQ实现(三)
fpga开发·nvme ssd·nvme协议
忆锦紫4 小时前
vivado报错及解决【十】
fpga开发·fpga·vivado
喵喵苗4 小时前
FPGA 核心资源解析:FF 触发器(Flip-Flop)—— 时序逻辑的“记忆心脏”
fpga开发
硅农深芯6 小时前
LVDS DC参数详解
fpga开发·lvds·dc参数
忆锦紫9 小时前
vivado报错及解决【十一】
fpga开发·fpga·vivado·xilinx
Saniffer_SH1 天前
NAND技术(二):从 Channel、Die/LUN、P/E Cycle 到 LDPC,一次讲透 NAND 里那些最容易误解的概念
人工智能·驱动开发·嵌入式硬件·测试工具·fpga开发·计算机外设·压力测试
千寻xun1 天前
二、实战篇-NVME SSD控制之ZYNQ实现(四)
fpga开发·nvme·nvme ssd
ALINX技术博客1 天前
【黑金云课堂】FPGA技术教程Linux开发:系统进阶-PS DMA
linux·fpga开发
喵喵苗1 天前
FPGA Verilog 入门避坑:寄存器与锁存器的本质区别 & 为什么时序逻辑缺 else 不会生成锁存器
fpga开发