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

本章节讲述如何使用ZYNQ(纯C语言)实现NVME SSD硬盘的读写控制。

参考:

FPGA控制NVME开发流程-CSDN博客

一、理论篇-NVME协议学习笔记-CSDN博客

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

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

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

前面完成了PCIE的建链和枚举、FPGA和NVME SSD的PCIE CFG空间的配置以及NVME SSD的寄存器配置。

下面介绍提交队列和完成队列,流程如下:

定义提交队列和完成队列结构体

cpp 复制代码
// ===================== 队列项结构体 =====================
// SQE: Submission Queue Entry 64Byte
typedef struct {
    u32 cdw0;
    u32 nsid;
    u64 rsvd1;
    u64 mptr;
    u64 dptr[2];
    u32 cdw10;
    u32 cdw11;
    u32 cdw12;
    u32 cdw13;
    u32 cdw14;
    u32 cdw15;
} __attribute__((aligned(64))) NvmeSqe;

// CQE: Completion Queue Entry 16Byte
typedef struct {
    u32 cdw0;
    u32 rsvd1;
    u16 sqhd;
    u16 sqid;
    u16 cid;
    u16 status;
} __attribute__((aligned(16))) NvmeCqe;

①指令写入内部DDR提交队列地址中

这一步不会向NVME SSD发送数据,只将指令存入指定内部空间,等待NVME读取

②发起提交门铃

向提交门铃寄存器(Admin为0x80001000)写提交队列尾指针。

③判断是否接收到完成命令

根据P位相位翻转判断是否接受到新的完成指令。状态Status不为0表示出错。

④发起完成门铃

向完成门铃寄存器(Admin为0x80001004)写完成队列头指针。

cpp 复制代码
#define ADMIN_SQ_BASE      0x10100000
#define ADMIN_CQ_BASE      0x10101000
static NvmeSqe *AdminSq = (NvmeSqe *)ADMIN_SQ_BASE;
static NvmeCqe *AdminCq = (NvmeCqe *)ADMIN_CQ_BASE;

提交Admin队列函数:

cpp 复制代码
// 提交命令到 Admin SQ
void NvmeSubmitAdminCmd(NvmeSqe *sqe)
{
    // 新增:刷新栈上临时SQE
    Xil_DCacheFlushRange(sqe, sizeof(NvmeSqe));
    AdminSq[AdminSqTail] = *sqe;
    Xil_DCacheFlushRange((void*)&AdminSq[AdminSqTail], sizeof(NvmeSqe));
    AdminSqTail = (AdminSqTail + 1) % ADMIN_Q_DEPTH;
    // 写门铃:SQ Tail Doorbell (偏移 0x1000 + 4*SQID)
    XAxiPcie_WriteReg(XPAR_AXI_PCIE_0_AXIBAR_0, RegOffset_Admin_SQTDBL, AdminSqTail) ;
}

完成Admin队列函数:

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

        u8 curr_cqe_pha = cqe->status & 0x1;

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

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

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

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

下面用读取Identify中Controller信息进行举例

Identify信息共4094字节。主要读取NVME SSD硬盘的ID、MDTS信息等

先申请一片空间存放Identify信息,并定义NvmeIdentifyCtrl格式

cpp 复制代码
#define IDENTIFY_CTRL_BASE   0x10200000

// Identify Controller 数据结构(简化)
typedef struct {
    u8  vid[2];
    u8  ssvid[2];
    u8  sn[20];
    u8  mn[40];
    u8  fr[8];
    u8  rab;
    u8  ieee[3];
    u8  mic;
    u8  mdts;
    u16 cntlid;
    u32 ver;
    u32 rtd3r;
    u32 rtd3e;
    u32 oaes;
    u32 ctratt;
    u8  rsvd[4096 - (2+2+20+40+8+1+3+1+1+2+4+4+4+4+4)];
} NvmeIdentifyCtrl;

main函数中调用

cpp 复制代码
static NvmeIdentifyCtrl * const g_id_ctrl = (NvmeIdentifyCtrl *)IDENTIFY_CTRL_BASE;
int ret;
// 2. Identify 读取控制器信息
ret = NvmeIdentifyController(g_id_ctrl);

NvmeIdentifyController函数:

cpp 复制代码
int NvmeIdentifyController(NvmeIdentifyCtrl *id_ctrl)
{
    NvmeSqe sqe = {0};
    NvmeCqe cqe;

    // 填充 Identify 命令
    sqe.cdw0  = Admin_CMD_IDENTIFY;
    sqe.nsid  = 0;
    sqe.dptr[0] = (u64)id_ctrl;  // 数据缓冲区地址
    sqe.dptr[1] = 0;
    sqe.cdw10  = 1;              // CNS=1: Identify Controller
    // 刷新SQE Cache,保证SSD读到真实命令
    Xil_DCacheFlushRange((void*)&sqe, sizeof(NvmeSqe));
    NvmeSubmitAdminCmd(&sqe);
    usleep(2000);
    if(NvmePollAdminCqe(&cqe) != 0)
    {
        xil_printf("Identify Timeout\r\n");
        return -1;
    }
    u16 real_status = (cqe.status >> 1) & 0x7FFF;
    if(real_status != 0)
    {
        xil_printf("Identify real error: 0x%04X\r\n", real_status);
        return -1;
    }
    //刷新数据区,CPU读到SSD写入的真实数据
    Xil_DCacheInvalidateRange((void*)id_ctrl, sizeof(NvmeIdentifyCtrl));

    xil_printf("===== Controller Info =====\r\n");
    xil_printf("Model Name: %.40s\r\n", id_ctrl->mn);
    xil_printf("Firmware: %.8s\r\n", id_ctrl->fr);
    xil_printf("NVMe Ver: 0x%08X\r\n", id_ctrl->ver);

    u16 mqes = *(u16 *)((u8 *)id_ctrl + 0x4D);
    xil_printf("MQES = %d (max queue depth)\n", mqes + 1);
    return 0;
}

这里进行提交队列命令填充,并调用NvmeSubmitAdminCmd函数发起提交门铃

这里等待接收到完成命令,并判断是否出错

后面进行 Controller Info 打印

相关推荐
国科安芯13 小时前
卫星星务计算机数据管理中SEU容错机制的技术实现研究——以AS32S601存储器ECC架构为例
网络·单片机·算法·fpga开发·架构·云计算·risc-v
国科安芯14 小时前
卫星电推进系统阀门驱动控制中的宽温域高可靠MCU应用研究——基于AS32S601电气特性与辐照试验数据的工程分析
单片机·嵌入式硬件·fpga开发·云计算·信息与通信·卫星·抗辐射芯片
郝旭帅电子设计团队1 天前
图像处理是什么
图像处理·fpga开发·郝旭帅电子设计团队·郝旭帅
weixin_452600691 天前
D2632大电流低差压可调线性稳压器介绍,D2632可替换MIC29302
fpga开发·机器人·无人机·电动工具·车载摄像头
ARM|X86+FPGA工业主板厂家2 天前
基于ARM+FPGA平台在精密测控领域的应用:ZYNQ千兆以太网高速数据记录仪
arm开发·fpga开发
cmc10282 天前
275.需要双边沿触发时,直接对时钟取反会不会出问题
fpga开发
ARM|X86+FPGA工业主板厂家2 天前
基于ARM+FPGA平台在精密测控领域的应用:ZYNQ FPGA图像预处理加速系统
arm开发·fpga开发
LCMICRO-133108477463 天前
国产长芯微LPS7118完全pin-pin替代ADP7118,是一款采用 CMOS 技术设计的低噪声、高 PSRR、快速瞬态响应、低压差线性稳压器
arm开发·单片机·嵌入式硬件·fpga开发·硬件工程·线性稳压器·adp7118
LCMICRO-133108477463 天前
国产长芯微LM3042完全P2P替代LT3042,是一款高性能低压差线性稳压器
arm开发·单片机·嵌入式硬件·fpga开发·硬件工程·线性稳压器·lt3042
ARM|X86+FPGA工业主板厂家3 天前
基于ARM+FPGA平台在精密测控领域的应用:ZYNQ高精度温振压多参融合采集系统
fpga开发