网站客服:zx81024163 |

固态存储设备原理与通信协议

2023-10-08 14:05:03发布    浏览549次    信息编号:77662

友情提醒:凡是以各种理由向你收取费用,均有骗子嫌疑,请提高警惕,不要轻易支付。

固态存储设备原理与通信协议

1. 简介 1.1。 存储设备的发展历程

1725年,出现了打孔纸。 通过纸张上的孔描述图案,并用于控制纺织机的编织图案。 后来,打孔纸也被用来存储电报信息。 直到1928年,磁带才被发明,并用磁带和磁棒来记录信息。 它利用电和磁之间的相互转换来记录和输出声音电信号。 1932年,发明了缓冲存储器,即当今机械硬盘的前身,它是通过电磁原理实现的。

此时,存储器的转换单位遵循SI单位制。

1941年,世界上第一台电子计算机出现,其计算和存储采用传统的基数10系统。 1944年,冯·诺依曼提出了二进制计算和存储的理论。 1946年,世界上第一台电子二进制计算机问世。 由于它的计算和存储原理是二进制的,为了方便计算机计算,它的容量计算也做了相应的修改。 如:=。

此后,存储行业将1KB=的换算规则延续至今。 这就导致内存上标注的容量小于手机、电脑上显示的容量。

除了电磁技术存储设备外,人们还探索了电、光、声、磁等各种物理现象,开发了各种技术路径的存储设备,如光盘、光盘、软盘等。

后来随着半导体的发展,根据半导体的特性发明了很多存储设备。

1.2. 内存的分类 1.2.1. 随机存取存储器

随机存取存储器(RAM),RAM是一种读/写存储器,其特点是存储器的任意存储单元的内容都可以被随机存取,存取时间与存储器的物理位置无关。存储单元。 RAM 有两大类:静态 RAM (/SRAM) 和动态 RAM (RAM/DRAM)。

1.2.2. 只读存储器

只读存储器(Read Only,ROM) 只读存储器是指所存储的内容可以读出但不能随意改写的存储器。 只读存储器,传统上指EPROM、Nor Flash等,现在泛指,一般指那些存储启动代码(包括操作系统)的存储器,如:eMMC、UFS甚至机械硬盘、SSD等包括已安装的操作系统。

2. 固态存储设备原理 2.1. 固态器件简介

与原来的机械硬盘相比,固态存储设备(Solid State,SSS)是一种基于Flash介质的存储设备,不需要任何机械部件。 固态存储设备根据不同的用途有不同的表现形式。

例如,用于便携式存储的SD/TF卡有专门的SD传输协议和相应的物理接口,如下图:

U盘也用于便携式即插即用,基于USB传输协议。 接口类型有很多种,包括如下图常用的Type-A接口、Type-C接口、还有接口。

固态存储器还用于嵌入式设备,例如 eMMC 和 UFS。 它还具有支持eMMC传输协议和接口,如下所示。 UFS是eMMC的升级版,升级了传输协议,速度更快。

固态存储器也常用于PC中,称为SSD(固态硬盘)。 根据其接口协议的不同,有SATA接口的SSD和NVMe协议接口的SSD。

2.2. 固态存储设备的基本原理

USB闪存驱动器是最简单的固态存储设备。 它主要由三部分组成,如下图所示,1是控制芯片,2是USB物理接口,3是存储引入NAND Flash。

由于其特性,Nand Flash存在位错误,因此需要ECC纠错机制来保证其稳定性。 Nand Flash对于擦除、写入、读取都有特殊的时序接口要求,并不是每个块都是好的。

2.2.1. 坏块管理

当2GB U盘连接电脑时,显示的容量为2GB,为连续可用容量。 这种容量通常称为逻辑容量。 对应的Nand Flash可能有2.3GB。 由于里面有一些坏块,排除坏块后,​​它的最终可用容量只有2.2GB。

2.2.2. ECC机制

前面提到,由于Nand Flash的特性,使用一段时间后存在因电子逃逸而产生误码的可能性。 添加ECC纠错机制可以大大延长固态存储设备的生命周期。 不同的存储设备使用不同的ECC纠错算法。 U盘由于成本较低,可靠性要求较高,一般采用相对简单的BCH纠错算法。 根据校验位数据长度的不同,BCH一般可以纠错1到72位/DMA(DMA是MCU的单个传输数据模块,其大小可以是512到512位)。 如果采用72bit/512B纠错机制,可以达到最大的纠错效果。 但这种纠错机制也需要最大数量的校验位,需要(32B)空间来存储校验位。 因此,对于 2GB 的可用空间,所需的奇偶校验位空间为 128MB。 如果使用72bit/1024B纠错机制,奇偶校验空间减半为64MB。

2.2.3.超光速

FTL是Flash层的英文缩写。 FTL是一个软件中间层,最初由Intel提出,用于将闪存模拟成虚拟块设备,从而可以在闪存上实现FAT等块设备文件系统。 FTL包括地址映射、垃圾收集、磨损均衡等,从下图可以看出,FTL集成了坏块管理和ECC纠错算法,同时还具有底层驱动接口(即操作Nand Flash被抽象成通用接口供FTL使用,因此无论是SLC还是TLC,不同的驱动都可以实现详细的访问并提供相同的抽象接口供FTL使用)。

从文件系统层下来的地址(逻辑地址)可以是连续的。 逻辑地址处的数据最终会被写入Nand Flash中,即逻辑地址必须与Nand Flash上​​的地址(物理地址)有对应关系。 。 逻辑地址对应哪些物理地址,哪些物理地址是坏块地址,需要跳过。 这个过程类似于文件系统中FAT表对应的实际数据表。 这个映射表需要占用一定的空间,根据算法和容量的不同,占用的空间也不同。 例如,2GB 的可用逻辑容量可能需要 128MB 的映射表。 此时如果采用72bit/512B纠错算法,需要128MB的奇偶校验位空间,但实际物理可用容量只有2.2GM,还不够。 因此,此时只能选择72b/1024B纠错算法。

算法开销,除了映射表之外,还包括磨损均衡(Wear)。 如果用户始终只有前512MB,如果没有磨损平衡,前512MB使用次数过多就会损坏,而后1.5GB却没有被碰过,这显然是不合理的。 因此,需要磨损平衡。 在使用过程中,逻辑与物理的对应关系是动态调整的,而不是采用固定的对应关系。

比如有Trim机制,除了文件系统中的所有文件。 这时不可能立即清除所有存储的数据,因为擦除所有块可能需要几十秒的时间,这极大地影响了使用的即时性。 用户清除所有文件后,可能会用它来存储一个小文件,然后再做其他事情。 然后FTL校准检测到如果在一定时间内没有用户操作,FTL会自动执行一次碎片整理和垃圾收集()操作,擦除之前Trim命令标记的所有坏块。 这样的机器能够为用户提供更好的用户体验。

2.2.4. 单片机

MCU,微控制器,就是我们常说的控制芯片。 MCU主要做什么? USB控制芯片必须完成USB协议的管理,如差分信号线的处理、USB中断的处理等。

芯片由很多门电路组成,可以同时运行很多逻辑。 如果MCU不支持乘法指针,纯软件只能用加法来实现乘法,大大增加了时间复杂度。 由于MCU是并行的,因此可以同时进行多次加法计算,使得MCU可以像加法一样快速地执行乘法,并且可以在一个时钟周期内完成。 同样,ECC纠错算法也相对复杂。 如果用软件来实现,效率是非常高的。 因此,ECC算法也被集成到芯片中。 此外,芯片还提供了一些排序算法,可以更好地提高嵌入式固件的性能。 效力。

操作Flash时,不同的操作对应不同的时序。 一方面不方便,另一方面效率不高。 MCU还封装了Flash的操作,并提供了一些接口供固件使用。

2.2.5。 固件

一切都可以通过MCU本身来实现而不需要使用固件吗? 当然。 然而,MCU一次流片的成本非常高,而且每次变更的验证成本都非常高。 因此,一旦MCU稳定,就不会再更改。 如果全部由MCU完成,那么就不能做任何改变。 那么当有新的Flash到来时,MCU将不支持它,也不会支持新的容量。 即使发现了bug,也没有办法升级,这显然不合理。 因此,这个时候就需要固件(也可以理解为MCU上的操作系统代码)来完成一些改变任务,以支持不同容量、不同型号的Nand Flash,甚至升级固件来修复bug。

2.2.6. 概括

当U盘连接到电脑后,MCU立即通过USB接口上电。 其内部的Boot机制(类似于计算机的Bios系统)会从Nand Flash的固定位置读取固件然后运行。 同时,当电脑主板上的USB Host控制器检测到有USB设备连接时,就会触发操作系统中的USB驱动程序,USB驱动程序就会开始通过USB接口向USB设备发送最基本的信息。 USB协议来查询访问情况。 设备信息。 MCU固件会提前配置设备的容量、类型(磁盘)、序列号等信息。 MCU会读取这些信息并通过USB协议将其发送回Host。 然后USB驱动会去Host的空间读取相关信息。 然后就会知道U盘的相关信息,然后U盘设备就会在系统中显示出来。 逻辑盘符、卷标等

用户写入文件时,通过USB驱动程序,再通过Host,再通过USB线到达U盘。 U盘MCU解析USB协议中的逻辑地址、数据和命令,知道是写命令。 MCU 通知固件 FTL 写入操作即将到来。 FTL根据P2L(To)表将对应逻辑地址的数据写入对应的物理地址(即Flash)。 读操作类似。

这样,U盘就完成了磁盘识别、写入、读取和存储的基本过程。

3. 通讯协议 3.1. USB 3.1.1 物理接口

物理接口仅用于不同场景,不影响其功能。 不同的协议有不同的内部电路。 但高级协议与低级协议兼容。

3.1.2. 传输层协议

USB传输协议有4种,这里我们主要关注大容量存储设备的Bluk传输协议。

传输层协议主要用于在传输过程中对信息进行验证,识别信息的类型,包括差分信号的处理等。在USB 3.1GEN2中,使用了非常好的线材来防止频率太高时的信号干扰。高,然后频率提升到最大,并且增加了一组输入输出信号线,直接比上一代提升了十倍的传输速度。 几次。

但由于PN结本身的电容性物理特性,现有半导体频率理论最高可达10G,实验室最高频率已达到8G。 频率已经提升到了峰值,想要提升传输速度,就只能加宽路面,将单车道增加为双车道。 USB3.2 Gen 2x2协议增加到双通道,速度直接提升一倍。

在USB数据通信过程中,并非所有在总线上传输的信息都是有效信息,还包括同步信号、类型标识、校验码、握手信号等各种协议信息,因此不可能得到实际数据传输速率达到总线传输的极限速度,480 Mbps。

为了提高传输层的信号传输效率,例如原来1K有效数据配置一次协议信息,现在可以修改为128K数据配置一次信息。 这会降低安全性。 如果传输过程中错误过多,就会影响性能。 为此,在USB协议升级的过程中,对线材的要求特别高,以尽量减少传输过程中信号干扰造成的错误。

随着PCI-E协议的发展,USB也紧随其后,提出了USB4.0。 在上一代的基础上,提供双链路,速度提升一倍,兼容之前的接口,支持100W快充。 。 这使得USB在PCIE协议蓬勃发展之后仍然是外设的最佳接口选择。

USB 协议的名称已更改。 最新版本不再以协议来区分,而是直接以速度和功率来区分。

3.1.3. 上层协议

上层协议主要定义设备是否支持热插拔,是否支持合并指令,比如是否支持NCQ。 USB3.2增加了UASP协议并开始支持NCQ。

3.1.4. 命令集

对于USB存储设备来说,上层应用层不能直接使用Bulk传输协议,只能以SCSI命令集的形式传输命令、地址和数据。 由于早期的计算机存储设备均采用SCSI协议,因此应用层也采用SCSI命令集的方式来传输数据。 后来的ATA接口和USB接口存储设备为了兼容上层应用,默认使用SCSI命令集来传输数据。 这样,再看应用层,无论底层是USB存储设备、ATA存储设备,甚至是SCSI存储设备,应用层都是一样的。

3.2. eMMC/UFS

MMC和UFS都是移动闪存的标准。 eMMC的全称是“MulTI Media Card”,即嵌入式多媒体存储卡。 eMMC从eMMC4.3发展到4.4、4.5到现在的5.0、5.1。 传输速度也从50MB/s一路飙升至200MB/s,直到目前eMMC5.1的600MB/s。 但在实际使用中,读取速度会低于理论值。 例如,搭载eMMC5.1的手机读取速度一般为250MB/s,但600MB/s已经是eMMC的极限,通过并行接口提升接口速率越来越困难。 为什么并行接口的速度没有提高? 这是因为并行接口需要同步信号来保证信号之间的一致性。 但随着频率的提高,不同信号线之间维持同步的开销太大,严格限制了频率的进一步提升。 速度。

端口串行提供了更高速度的可能性,这就是后来的UFS标准。 UFS使用高速串行接口代替并行接口,并采用全双工模式,因此可以同时发送和接收数据。 与eMMC相比,JEDEC发布了新的USF 2.0标准,大大提高了接口速率。 新的USF 2.0有两个版本。 UFS 2.0 HS-G2的理论带宽为5.8Gbps,约740MB/s,速度更快。 UFS 2.1 HS-G3的理论带宽达到11.6Gbps,约1.5GB/s。

2018年1月,UFS 3.0问世,有了新标准; 在高性能、低功耗和速度提升方面有了显着的变化。 UFS支持双通道双向读写。 UFS 3.0最大接口带宽速率可达23.2Gbps,即2.9GB/s; UFS3.0的单通道速率是UFS2.1的两倍以上。 可以说,UFS3.0是UFS2.1的升级版本。

UFS 4.0继续扩展通道,可以达到UFS 3.0两倍的速度。

3.3. SATA

SATA 是主流海量存储设备的接口和传输层协议。

3.3.1. 物理接口

SATA、U.2、M.2、mSATA等接口形式。

3.3.2. 传输协议

SATA 1.0于2003年1月7日推出,作为第一代SATA接口,传输速度为1.5Gbit/s; SATA 2.0于2004年正式推出,符合ATA-7规范,传输速度为1.5Gbit/s; 2009年5月 26日,SATA 3.0完成最终规格发布,速度较上一代提升至6Gbit/s。 后来也推出了SATA 3.1、SATA 3.2、SATA 3.3,但速度并没有提高,只是增加了一些新功能。 SATA的频率已经提升到最高水平。 如果不实施多渠道策略,速度将不再具有竞争力。 目前SATA无法与PCIE+NVMe竞争,估计SATA协议已经放弃升级了。

3.3.3. 上层协议

SATA设备的上层使用AHCI。 AHCI(ATA Host)串行ATA高级主机接口/高级主机控制器接口)是在Intel指导下多家公司共同制定的接口标准。 它允许存储驱动程序启用高级串行 ATA 功能,例如本机命令队列和热插拔。 SATA接口(ATA),即 ATA。 它是一种计算机总线,其主要功能是在主板和大量存储设备(例如硬盘驱动器和光盘驱动器)之间传输数据。 SATA总线采用嵌入式时钟信号,具有更强的纠错能力。 数据传输采用差分信号。 因此,尽管SATA的数据线比IDE少,但传输频率却高得多,这就导致了SATA数据传输。 比 IDE 更快。 早期的主板需要手动设计并在Bios中选择AHCI模式。 我们展开设备中的IDE ATA/ATAPI控制器,找到AHCI相关字样,说明AHCI模式已经开启,如下图。 SATA共有7条数据线、2条差分写信号线、2条差分读信号线和3条地线。 AHCI 是 PCI 类设备,充当系统内存和 SATA 设备之间的数据移动引擎。

3.3.4. 命令集

应用层也兼容SCSI命令集。 此外,它还可以发送ATA命令集。 为了更好的兼容应用,常用的命令集一般采用SCSI命令。 SATA驱动程序在底层会将SCSI命令转换为相应的ATA命令。

3.4.PCIe

PCIe直接与CPU相连,这样应用程序访问存储设备时无需经过内存传输,速度更快。

3.4.1. 物理接口

PCIe标准接口,U.2、M.2都有,只是形式要求不同。 每个都有自己的优点和缺点。

M.2的出现结束了mSATA接口。 它可以同时支持SATA和NVMe协议。 目前M.2 SSD较为常见的是后者,具有体积小、性能强的特点。

U.2接口在企业级和数据中心固态硬盘中更为常见。 它和SATA硬盘一样都是2.5英寸硬盘,但相对较厚,可以堆叠更多的Nand Flash,从而带来更快的读写速度。

一般主板上没有U.2接口。 一般需要一块PCIE转U.2转换板来适配。 如下所示:

对于多通道接口,数据线的数量也会相应增加。

上述PCIE接口对应PC主板上的PCIe插槽。

3.4.2. 传输协议

PCIe传输协议是最快的。 它最大限度地提高传输频率,然后最大限度地减少传输过程中的CRC校验开销。 此外,它还在继续添加多个频道。 例如 PCI-E x1/x4/x8/x16/x32。 目前还不清楚未来是否会有 x64、x128 甚至更多。

3.4.3. 上层协议

上层协议采用NVMe,主要支持热插拔、NCQ等功能。

3.4.4. 应用层

NVMe应用层还支持SCIS命令集,NVMe命令集较少,同时也兼容SCSI命令集。

3.5. 概括

4、通讯方式 4.1. USB 散装

对于USB存储设备来说,没有通过USB Bulk传输协议提供接口。 它是一个跨平台的开源项目,可以获取USB列表和各种描述符。 还可以通过批量传输直接与 USB 设备进行通信。 需要搭配.sys驱动,即可以自己构造一个SCSI传输内容包,通过Bulk进行传输,也可以直接通过Bulk传输自定义报文,这需要USB设备的配合。

4.2. SCSI 命令集

通过设置UGH命令码可以发送SCSI命令。 通过 SCSI 完成的写入和读取具有相同的效果。

if ( DeviceIoControl(
    IOCTL_SCSI_PASS_THROUGH,
    &sptwb,
    sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS),
    &sptwb,
    sizeof(SCSI_PASS_THROUGH_WITH_BUFFERS),
    &returned
			) && (sptwb.Spt.ScsiStatus == 0) )

4.3. ATA命令集

对于SATA设备,可以直接使用SCSI命令来完成一些基本操作。 对于一些特殊的ATA命令,ATA命令还可以通过SCSI中提供的附加命令来发送。 另外,还可以直接通过发送ATA命令。

bool AtaCommand(ATA_REGISTER &reg, READWRITE read_write, bool dma, BYTE * buf, JCSIZE secs)
{
    LOG_STACK_TRACE();
    LOG_TRACE(_T("Send ata cmd: X, X, X, X, X, X, X")
              , reg.feature, reg.sec_count, reg.lba_low, reg.lba_mid, reg.lba_hi, reg.device, reg.command);
    
    JCASSERT(m_dev);
    JCSIZE buf_len = secs * SECTOR_SIZE;
    
    // 申请一个4KB对齐的内存。基本思想:申请一块内存比原来多4KB的空间,
    // 然后将指针移到4KB对齐的位置。移动最多不会超过4KB。
    static const JCSIZE ALIGNE_SIZE = 4096;		// Aligne to 4KB
    stdext::auto_array<BYTE>	_buf(buf_len + 2 * ALIGNE_SIZE);	// 
    #if 1	// aligne
    BYTE * __buf = (BYTE*)_buf;
    JCSIZE align_mask = (~ALIGNE_SIZE) + 1;
    BYTE * align_buf = (BYTE*)((UINT)(__buf) & align_mask) + ALIGNE_SIZE;			// 4KBB aligne
    #else
    BYTE * align_buf = buf;
    #endif
    
    JCSIZE hdr_size = sizeof(ATA_PASS_THROUGH_DIRECT);
    
    ATA_PASS_THROUGH_DIRECT	hdr_in;
    memset(&hdr_in, 0, hdr_size);
    
    hdr_in.DataTransferLength = buf_len;
    hdr_in.DataBuffer = align_buf;
    if (write == read_write)
    {
        hdr_in.AtaFlags = ATA_FLAGS_DATA_OUT;
        if (buf_len > 0) memcpy_s(align_buf, buf_len, buf, buf_len);
    }
    else							
    {
        hdr_in.AtaFlags = ATA_FLAGS_DATA_IN;
    }
    if (dma)	hdr_in.AtaFlags |= ATA_FLAGS_USE_DMA;
    else		hdr_in.AtaFlags |= ATA_FLAGS_NO_MULTIPLE;
    hdr_in.AtaFlags |= ATA_FLAGS_DRDY_REQUIRED;
    
    hdr_in.Length = hdr_size;
    hdr_in.TimeOutValue = 5;
    memcpy_s(hdr_in.CurrentTaskFile, 8, &reg, 8);
    
    DWORD readsize = 0;
    BOOL br = DeviceIoControl(m_dev, IOCTL_ATA_PASS_THROUGH_DIRECT, &hdr_in, hdr_size, &hdr_in, hdr_size, &readsize, NULL);
    if (!br)
    {
        stdext::CWin32Exception err(GetLastError(), _T("failure on ata command "));
        LOG_ERROR( err.WhatT() );
    }
    memcpy_s(&reg, 8, hdr_in.CurrentTaskFile, 8);
    if ( (read == read_write) && (buf_len > 0) )			memcpy_s(buf, buf_len, align_buf, buf_len);
    LOG_DEBUG(_T("Returned ata cmd: X, X"), reg.error, reg.status);
    
    if (!br) THROW_WIN32_ERROR(_T("failure in invoking ata command."));
    
    return true;
}

4.4. NVMe命令

PCIe设备出现时间不长,基本操作也可以通过SCSI完成。 早期,一些特殊的操作,比如SMART信息等,还没有完全统一。 微软并没有提供直接的相关操作方法。 所以每个人都按照自己的方式来做,有的通过驱动程序完成,有的通过SCSI(扩展命令)完成。 Win10之后,微软提供了通用的NVMe驱动程序和一套标准的相关操作,用于查询各种信息、升级固件、发出私有命令。

YSNVMe::PSTORAGE_PROTOCOL_COMMAND pProtocolCmd = NULL;
YSNVMe::PNVME_COMMAND pCmd = NULL;
BYTE* bTmpBuf = NULL;
DWORD dwTmpBufSize = 0;
DWORD dwRtnLength = 0;
dwTmpBufSize = FIELD_OFFSET(YSNVMe::STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME + sizeof(YSNVMe::NVME_ERROR_INFO_LOG) + srtCommandSet.dwDataLength;
bTmpBuf = (BYTE *)::malloc((size_t)dwTmpBufSize);
if(bTmpBuf == NULL)
    return False;
if(srtCommandSet.ulTimeout == 0)
    srtCommandSet.ulTimeout = 10;
::ZeroMemory(bTmpBuf, dwTmpBufSize);
pProtocolCmd = (YSNVMe::PSTORAGE_PROTOCOL_COMMAND)bTmpBuf;
pProtocolCmd->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;
pProtocolCmd->Length = sizeof(YSNVMe::STORAGE_PROTOCOL_COMMAND);
pProtocolCmd->ProtocolType = YSNVMe::ProtocolTypeNvme;
pProtocolCmd->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;
pProtocolCmd->CommandLength = STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
pProtocolCmd->ErrorInfoLength = sizeof(YSNVMe::NVME_ERROR_INFO_LOG);
pProtocolCmd->TimeOutValue = srtCommandSet.ulTimeout;
pProtocolCmd->ErrorInfoOffset = FIELD_OFFSET(YSNVMe::STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
pProtocolCmd->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;
if(srtCommandSet.bProtocolField == 2)
{
    pProtocolCmd->DataFromDeviceTransferLength = srtCommandSet.dwDataLength;
    pProtocolCmd->DataFromDeviceBufferOffset = pProtocolCmd->ErrorInfoOffset + pProtocolCmd->ErrorInfoLength;
}
else if(srtCommandSet.bProtocolField == 3)
{
    pProtocolCmd->DataToDeviceTransferLength = srtCommandSet.dwDataLength;
    pProtocolCmd->DataToDeviceBufferOffset = pProtocolCmd->ErrorInfoOffset + pProtocolCmd->ErrorInfoLength;
    
    ::CopyMemory(((PCHAR)pProtocolCmd + pProtocolCmd->DataToDeviceBufferOffset), bIOBuf, srtCommandSet.dwDataLength);
}
pCmd = (YSNVMe::PNVME_COMMAND)pProtocolCmd->Command;
pCmd->CDW0.OPC = srtCommandSet.bOpcode;
pCmd->NSID = srtCommandSet.dwNSID;
pCmd->u.GENERAL.CDW10 = srtCommandSet.dw10;
pCmd->u.GENERAL.CDW11 = srtCommandSet.dw11;
pCmd->u.GENERAL.CDW12 = srtCommandSet.dw12;
pCmd->u.GENERAL.CDW13 = srtCommandSet.dw13;
pCmd->u.GENERAL.CDW14 = srtCommandSet.dw14;
pCmd->u.GENERAL.CDW15 = srtCommandSet.dw15;
boRtnStatus = ::DeviceIoControl(hDeviceIOCTL, IOCTL_STORAGE_PROTOCOL_COMMAND, bTmpBuf, dwTmpBufSize, bTmpBuf, dwTmpBufSize, &dwRtnLength, NULL);
if(boRtnStatus && (srtCommandSet.bProtocolField == 2))
    ::CopyMemory(bIOBuf, ((PCHAR)pProtocolCmd + pProtocolCmd->DataToDeviceBufferOffset) + 0xD0, srtCommandSet.dwDataLength);
		::free(bTmpBuf);

提醒:请联系我时一定说明是从星空网上看到的!