二、实战篇-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。

相关推荐
星夜离尘5613 小时前
UART 串口通信:从帧格式到收发回显(附完整 Verilog 实操)
fpga开发
仲南音18 小时前
Xilinx FPGA—— DDR4读写
fpga开发
思尔芯S2C1 天前
思尔芯RCF RTL自动分割工具,助力大规模AI/HPC芯片原型验证
人工智能·fpga开发·内存模型·自动编译·ddr5·prototyping·原型验证
Terasic友晶科技2 天前
答疑解惑|Agilex 3 FPGA QSPI时钟设置注意事项
fpga开发·de23-lite·terasic·qspi flash
国科安芯2 天前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
国科安芯2 天前
卫星电推进系统阀门驱动控制中的宽温域高可靠MCU应用研究——基于AS32S601电气特性与辐照试验数据的工程分析
单片机·嵌入式硬件·fpga开发·云计算·信息与通信·卫星·抗辐射芯片
郝旭帅电子设计团队2 天前
图像处理是什么
图像处理·fpga开发·郝旭帅电子设计团队·郝旭帅
ARM|X86+FPGA工业主板厂家3 天前
基于ARM+FPGA平台在精密测控领域的应用:8路同步IEPE振动采集系统
arm开发·fpga开发
weixin_452600693 天前
D2632大电流低差压可调线性稳压器介绍,D2632可替换MIC29302
fpga开发·机器人·无人机·电动工具·车载摄像头
ARM|X86+FPGA工业主板厂家3 天前
基于ARM+FPGA平台在精密测控领域的应用:ZYNQ千兆以太网高速数据记录仪
arm开发·fpga开发