适用于:
- Windows 10操作系统
- Windows Server 2016
了解如何从 Windows 应用程序使用高速 NVMe 设备。 设备访问是通过 StorNVMe.sys启用的,这是 Windows Server 2012 R2 和 Windows 8.1 中首次引入的内置驱动程序。 它还可通过 KB 热修复向 Windows 7 设备提供。 在 Windows 10 中,引入了多项新功能,包括针对特定于供应商的 NVMe 命令的直通机制,以及对现有 IOCTL 的更新。
本主题概述了可用于访问 Windows 10 中的 NVMe 驱动器的常规使用 API。 它还介绍了:
- 如何使用直通发送厂商特定的 NVMe 命令
- 如何将 “标识”、“获取功能”或“获取日志页”命令发送到 NVMe 驱动器
- 如何从 NVMe 驱动器获取温度信息
- 如何执行行为更改命令,例如 设置温度阈值
用于处理 NVMe 驱动器的 API
可以使用以下常规使用 API 访问 Windows 10 中的 NVMe 驱动器。 这些 API 可以在 winioctl.h 中查找用户模式应用程序,对于内核模式驱动程序,可以在 ntddstor.h 中找到。 有关头文件的详细信息,请参阅 头文件。
IOCTL_STORAGE_PROTOCOL_COMMAND :将此 IOCTL 与 STORAGE_PROTOCOL_COMMAND 结构一起使用,发出 NVMe 命令。 此 IOCTL 启用 NVMe 传递并支持 NVMe 中的命令效果日志。 可以将它与特定于供应商的命令一起使用。 有关详细信息,请参阅 直通机制。
STORAGE_PROTOCOL_COMMAND :此输入缓冲区结构包括一个 ReturnStatus 字段,可用于报告以下状态值。
- STORAGE_PROTOCOL_STATUS_PENDING (存储协议状态待定)
- STORAGE_PROTOCOL_STATUS_SUCCESS(存储协议状态成功)
- STORAGE_PROTOCOL_STATUS_ERROR
- STORAGE_PROTOCOL_STATUS_INVALID_REQUEST(无效请求)
- 存储协议状态_无设备 (STORAGE_PROTOCOL_STATUS_NO_DEVICE)
- STORAGE_PROTOCOL_STATUS_BUSY
- STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
- 存储协议状态:资源不足
- STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED(存储协议状态不支持)
IOCTL_STORAGE_QUERY_PROPERTY :将此 IOCTL 与 STORAGE_PROPERTY_QUERY 结构配合使用来检索设备信息。 有关详细信息,请参阅 特定于协议的查询 和 温度查询。
STORAGE_PROPERTY_QUERY :此结构包括 PropertyId 和 AdditionalParameters 字段,用于指定要查询的数据。 在 PropertyId 文件中,使用 STORAGE_PROPERTY_ID 枚举指定数据类型。 使用 AdditionalParameters 字段根据数据类型指定更多详细信息。 对于特定于协议的数据,请在 AdditionalParameters 字段中使用 STORAGE_PROTOCOL_SPECIFIC_DATA 结构。 对于温度数据,请在 AdditionalParameters 字段中使用 STORAGE_TEMPERATURE_INFO 结构。
STORAGE_PROPERTY_ID :此枚举包括允许 IOCTL_STORAGE_QUERY_PROPERTY 检索特定于协议和温度信息的新值。
StorageAdapterProtocolSpecificProperty:如果
ProtocolType=ProtocolTypeNvme和DataType=NVMeDataTypeLogPage,调用方应请求 512 字节的数据区块。存储设备协议特定属性
将这些协议特定的属性 ID 之一与 STORAGE_PROTOCOL_SPECIFIC_DATA 结合使用,以便在 STORAGE_PROTOCOL_DATA_DESCRIPTOR 结构中检索协议特定的数据。
- StorageAdapterTemperatureProperty
- 存储设备温度属性
使用这些温度属性 ID 之一检索 STORAGE_TEMPERATURE_DATA_DESCRIPTOR 结构中的温度数据。
STORAGE_PROTOCOL_SPECIFIC_DATA:当此结构用于STORAGE_PROPERTY_QUERY的 AdditionalParameters 字段并指定STORAGE_PROTOCOL_NVME_DATA_TYPE枚举值时,检索特定于 NVMe 的数据。 在STORAGE_PROTOCOL_SPECIFIC_DATA结构的 DataType 字段中使用以下STORAGE_PROTOCOL_NVME_DATA_TYPE值之一:
- 使用 NVMeDataTypeIdentify 获取标识控制器数据或标识命名空间数据。
- 使用 NVMeDataTypeLogPage 获取日志页(包括 SMART/health 数据)。
- 使用 NVMeDataTypeFeature 获取 NVMe 驱动器的功能。
STORAGE_TEMPERATURE_INFO :此结构用于保存特定的温度数据。 它用于 STORAGE_TEMERATURE_DATA_DESCRIPTOR 返回温度查询的结果。
IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD :将此 IOCTL 用于 STORAGE_TEMPERATURE_THRESHOLD 结构来设置温度阈值。 有关详细信息,请参阅 行为更改命令。
STORAGE_TEMPERATURE_THRESHOLD :此结构用作输入缓冲区来指定温度阈值。 OverThreshold 字段(布尔值)指定阈值字段是否超过阈值(否则为阈值下)。
直通机制
在 NVMe 规范中未定义的命令是主机 OS 处理的最困难 - 主机没有深入了解命令在目标设备上可能具有的效果、公开的基础结构(命名空间/块大小)及其行为。
为了更好地通过 Windows 存储堆栈传递此类设备特定命令,全新的直通机制允许将供应商特有命令通过管道传输。 此直通管道还将帮助开发管理和测试工具。 但是,此直通机制需要使用命令效果日志。 此外,StoreNVMe.sys 要求命令效果日志中介绍所有命令,而不仅仅是传递命令。
重要
StorNVMe.sys 和 Storport.sys 会阻止对设备的任何命令,如果这些命令在命令效果日志中没有被描述。
支持命令效果日志
命令效果日志(如命令支持和效果中所述, NVMe 规范 1.2 的第 5.10.1.5 节)允许描述特定于供应商的命令以及规范定义的命令的影响。 这有利于命令支持验证和命令行为优化,因此应该为设备支持的整个命令集实现。 以下条件描述基于命令效果日志条目发送命令的结果。
对于命令效果日志中描述的任何特定命令...
同时:
命令支持 (CSUPP) 设置为“1”,表示控制器支持该命令(Bit 01)
注释
当 CSUPP 设置为“0”(表示不支持该命令)时,将阻止该命令
而如果 设置了以下任一项:
- 控制器功能更改(CCC)设置为“1”,表示命令可能会更改控制器功能(Bit 04)
- 命名空间清单更改(NIC)设置为“1”,表示命令可能会更改多个命名空间的数字或功能(Bit 03)
- 命名空间功能更改(NCC)设置为“1”,表示命令可能会更改单个命名空间的功能(Bit 02)
- 命令提交和执行(CSE)被设置为 001b 或 010b,这意味着只有在没有其他未完成的命令发送到相同或任何命名空间时,才可以提交该命令,并且在该命令完成之前,不应提交另一个命令到相同或任何命名空间(位 18:16)。
然后 ,该命令将作为唯一未完成的命令发送到适配器。
否则如果:
- 命令提交和执行(CSE)设置为 001b,表示当同一命名空间没有其他未完成的命令时,该命令可能提交,在此命令完成之前,不应将另一个命令提交到同一命名空间(Bits 18:16)
然后 ,该命令将作为唯一未完成的命令发送到逻辑单元编号对象(LUN)。
否则,该命令将与其他未完成且不受阻碍的命令一起被发送。 例如,如果将特定于供应商的命令发送到设备以检索未定义规范的统计信息,则不应有更改设备的行为或执行 I/O 命令的能力的风险。 此类请求可以与 I/O 并行处理,无需暂停和恢复操作。
使用IOCTL_STORAGE_PROTOCOL_COMMAND发送命令
可以使用 Windows 10 中引入的 IOCTL_STORAGE_PROTOCOL_COMMAND 来执行直通操作。 此 IOCTL 设计为具有与现有 SCSI 和 ATA 直通 IOCTL 类似的行为,用于将嵌入式命令发送到目标设备。 通过此 IOCTL,可将直通发送到存储设备,包括 NVMe 驱动器。
例如,在 NVMe 中,IOCTL 允许发送以下命令代码。
- 供应商特定的管理员命令 (C0h - FFh)
- 供应商专用的 NVMe 命令(80h – FFh)
与所有其他 IOCTL 一样,使用 DeviceIoControl 向下发送传递 IOCTL。 在 ntddstor.h 中找到的 STORAGE_PROTOCOL_COMMAND 输入缓冲区结构用于填充 IOCTL。 使用特定于供应商的命令填充 Command 字段。
typedef struct _STORAGE_PROTOCOL_COMMAND {
ULONG Version; // STORAGE_PROTOCOL_STRUCTURE_VERSION
ULONG Length; // sizeof(STORAGE_PROTOCOL_COMMAND)
STORAGE_PROTOCOL_TYPE ProtocolType;
ULONG Flags; // Flags for the request
ULONG ReturnStatus; // return value
ULONG ErrorCode; // return value, optional
ULONG CommandLength; // non-zero value should be set by caller
ULONG ErrorInfoLength; // optional, can be zero
ULONG DataToDeviceTransferLength; // optional, can be zero. Used by WRITE type of request.
ULONG DataFromDeviceTransferLength; // optional, can be zero. Used by READ type of request.
ULONG TimeOutValue; // in unit of seconds
ULONG ErrorInfoOffset; // offsets need to be pointer aligned
ULONG DataToDeviceBufferOffset; // offsets need to be pointer aligned
ULONG DataFromDeviceBufferOffset; // offsets need to be pointer aligned
ULONG CommandSpecific; // optional information passed along with Command.
ULONG Reserved0;
ULONG FixedProtocolReturnData; // return data, optional. Some protocol, such as NVMe, may return a small amount data (DWORD0 from completion queue entry) without the need of separate device data transfer.
ULONG Reserved1[3];
_Field_size_bytes_full_(CommandLength) UCHAR Command[ANYSIZE_ARRAY];
} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;
需要发送的供应商特定命令应填充在上面突出显示的字段中。 再次请注意,必须为传递命令实现命令效果日志。 具体而言,这些命令需要在命令效果日志中报告为支持的命令(有关详细信息,请参阅上一节)。 另请注意,PRP 字段特定于驱动程序,因此发送命令的应用程序可以将这些字段保留为 0。
最后,此传递 IOCTL 用于发送特定于供应商的命令。 若要发送其他管理员或非供应商特定的 NVMe 命令(例如标识),不应使用此直通 IOCTL。 例如, IOCTL_STORAGE_QUERY_PROPERTY 应用于标识或获取日志页。 有关更多信息,请参阅下一部分,特定协议的查询。
不要通过直通机制更新固件
不应使用直通发送固件下载和激活命令。 IOCTL_STORAGE_PROTOCOL_COMMAND 应仅用于特定于供应商的命令。
请改用以下在 Windows 10 中引入的常规存储 IOCTL,以避免应用程序直接使用固件 IOCTL 的 SCSI_miniport 版本。 存储驱动程序会将 IOCTL 转换为 SCSI 命令,或将 IOCTL 的 SCSI_miniport 版本转换为小型端口。
建议使用这些 IOCTL 在 Windows 10 和 Windows Server 2016 中开发固件升级工具:
- IOCTL_STORAGE_FIRMWARE_GET_INFO
- IOCTL_STORAGE_FIRMWARE_DOWNLOAD
- IOCTL_STORAGE_FIRMWARE_ACTIVATE(存储固件激活命令)
为了获取存储信息和更新固件,Windows 还支持 PowerShell cmdlet 快速执行此作:
Get-StorageFirmwareInfoUpdate-StorageFirmware
通过传递机制返回错误
与 SCSI 和 ATA 传递 IOCTL 类似,当命令/请求发送到迷你端口或设备时,IOCTL 会返回操作是否成功。 在 STORAGE_PROTOCOL_COMMAND 结构中,IOCTL 通过 ReturnStatus 字段返回状态。
示例:发送特定于供应商的命令
在此示例中,将任意供应商特定命令(0xFF)通过直通机制发送到 NVMe 硬盘。 以下代码分配缓冲区、初始化查询,然后通过 DeviceIoControl 将命令向下发送到设备。
ZeroMemory(buffer, bufferLength);
protocolCommand = (PSTORAGE_PROTOCOL_COMMAND)buffer;
protocolCommand->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;
protocolCommand->Length = sizeof(STORAGE_PROTOCOL_COMMAND);
protocolCommand->ProtocolType = ProtocolTypeNvme;
protocolCommand->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;
protocolCommand->CommandLength = STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
protocolCommand->ErrorInfoLength = sizeof(NVME_ERROR_INFO_LOG);
protocolCommand->DataFromDeviceTransferLength = 4096;
protocolCommand->TimeOutValue = 10;
protocolCommand->ErrorInfoOffset = FIELD_OFFSET(STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
protocolCommand->DataFromDeviceBufferOffset = protocolCommand->ErrorInfoOffset + protocolCommand->ErrorInfoLength;
protocolCommand->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;
command = (PNVME_COMMAND)protocolCommand->Command;
command->CDW0.OPC = 0xFF;
command->u.GENERAL.CDW10 = 0xto_fill_in;
command->u.GENERAL.CDW12 = 0xto_fill_in;
command->u.GENERAL.CDW13 = 0xto_fill_in;
//
// Send request down.
//
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,
IOCTL_STORAGE_PROTOCOL_COMMAND,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
在此示例中,如果命令在设备上执行成功,则可以预期 protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS。
特定于协议的查询
Windows 8.1 引入了用于数据检索 的IOCTL_STORAGE_QUERY_PROPERTY 。 在 Windows 10 中,IOCTL 得到了增强,以支持常用的 NVMe 功能,例如获取日志页、获取功能和标识。 这允许检索 NVMe 特定信息以进行监视和库存。
此处显示了 IOCTL 的输入缓冲区, STORAGE_PROPERTY_QUERY (来自 Windows 10 及更高版本):
typedef struct _STORAGE_PROPERTY_QUERY {
STORAGE_PROPERTY_ID PropertyId;
STORAGE_QUERY_TYPE QueryType;
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;
使用 IOCTL_STORAGE_QUERY_PROPERTY 检索 STORAGE_PROTOCOL_DATA_DESCRIPTOR中特定于 NVMe 协议的信息时,请按如下所示配置 STORAGE_PROPERTY_QUERY 结构:
- 分配可以同时包含 STORAGE_PROPERTY_QUERY 和 STORAGE_PROTOCOL_SPECIFIC_DATA 结构的缓冲区。
- 将 PropertyID 字段分别设置为 StorageAdapterProtocolSpecificProperty 或 StorageDeviceProtocolSpecificProperty,分别用于控制器或设备/命名空间请求。
- 将 QueryType 字段设置为 PropertyStandardQuery。
- 使用所需值填充 STORAGE_PROTOCOL_SPECIFIC_DATA 结构。 STORAGE_PROTOCOL_SPECIFIC_DATA 的开头是 STORAGE_PROPERTY_QUERY的 AdditionalParameters 字段。
此处显示了STORAGE_PROTOCOL_SPECIFIC_DATA结构(来自 Windows 10 及更高版本):
typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {
STORAGE_PROTOCOL_TYPE ProtocolType;
ULONG DataType;
ULONG ProtocolDataRequestValue;
ULONG ProtocolDataRequestSubValue;
ULONG ProtocolDataOffset;
ULONG ProtocolDataLength;
ULONG FixedProtocolReturnData;
ULONG Reserved[3];
} STORAGE_PROTOCOL_SPECIFIC_DATA, *PSTORAGE_PROTOCOL_SPECIFIC_DATA;
若要指定特定于 NVMe 协议的信息类型,请按如下所示配置 STORAGE_PROTOCOL_SPECIFIC_DATA 结构:
- 将 ProtocolType 字段设置为 ProtocolTypeNVMe。
- 将 DataType 字段设置为由 STORAGE_PROTOCOL_NVME_DATA_TYPE定义的枚举值:
- 使用 NVMeDataTypeIdentify 获取标识控制器数据或标识命名空间数据。
- 使用 NVMeDataTypeLogPage 获取日志页(包括 SMART/health 数据)。
- 使用 NVMeDataTypeFeature 获取 NVMe 驱动器的功能。
当 ProtocolTypeNVMe 用作 ProtocolType 时,可以与 NVMe 驱动器上的其他 I/O 并行检索特定于协议的信息的查询。
重要
对于使用 StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID 的 IOCTL_STORAGE_QUERY_PROPERTY,如果其 STORAGE_PROTOCOL_SPECIFIC_DATA 或 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 结构被设置为 ProtocolType=ProtocolTypeNvme 和 DataType=NVMeDataTypeLogPage,则应将该结构中的 ProtocolDataLength 成员设置为至少 512 字节。
以下示例演示 NVMe 协议特定的查询。
示例:NVMe 识别查询
在此示例中, 标识 请求将发送到 NVMe 驱动器。 以下代码初始化查询数据结构,然后通过 DeviceIoControl 将命令向下发送到设备。
BOOL result;
PVOID buffer = NULL;
ULONG bufferLength = 0;
ULONG returnedLength = 0;
PSTORAGE_PROPERTY_QUERY query = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;
//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;
buffer = malloc(bufferLength);
if (buffer == NULL) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: allocate buffer failed, exit.\n"));
goto exit;
}
//
// Initialize query data structure to get Identify Controller Data.
//
ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageAdapterProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeIdentify;
protocolData->ProtocolDataRequestValue = NVME_IDENTIFY_CNS_CONTROLLER;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = NVME_MAX_LOG_SIZE;
//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);
//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - data descriptor header not valid.\n"));
return;
}
protocolData = &protocolDataDescr->ProtocolSpecificData;
if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < NVME_MAX_LOG_SIZE)) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - ProtocolData Offset/Length not valid.\n"));
goto exit;
}
//
// Identify Controller Data
//
{
PNVME_IDENTIFY_CONTROLLER_DATA identifyControllerData = (PNVME_IDENTIFY_CONTROLLER_DATA)((PCHAR)protocolData + protocolData->ProtocolDataOffset);
if ((identifyControllerData->VID == 0) ||
(identifyControllerData->NN == 0)) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Identify Controller Data not valid.\n"));
goto exit;
} else {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Identify Controller Data succeeded***.\n"));
}
}
重要
对于使用 StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID 的 IOCTL_STORAGE_QUERY_PROPERTY,如果其 STORAGE_PROTOCOL_SPECIFIC_DATA 或 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 结构被设置为 ProtocolType=ProtocolTypeNvme 和 DataType=NVMeDataTypeLogPage,则应将该结构中的 ProtocolDataLength 成员设置为至少 512 字节。
请注意,调用方需要分配包含STORAGE_PROPERTY_QUERY的单个缓冲区以及STORAGE_PROTOCOL_SPECIFIC_DATA的大小。 在此示例中,它使用相同的缓冲区进行属性查询的输入和输出。 这就是为什么分配的缓冲区的大小为“FIELD_OFFSET(STORAGE_PROPERTY_QUERY,AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE”。 尽管可以为输入和输出分配单独的缓冲区,但我们建议使用单个缓冲区来查询 NVMe 相关信息。
identifyControllerData->NN 是命名空间数量(NN)。 Windows 将命名空间视为物理驱动器。
示例:NVMe 获取日志页查询
在此示例中,根据上一个请求, 获取日志页 请求将发送到 NVMe 驱动器。 以下代码准备查询数据结构,然后通过 DeviceIoControl 将命令向下发送到设备。
ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeLogPage;
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
protocolData->ProtocolDataRequestSubValue = 0; // This will be passed as the lower 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue2 = 0; // This will be passed as the higher 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue3 = 0; // This will be passed as Log Specific Identifier in CDW11.
protocolData->ProtocolDataRequestSubValue4 = 0; // This will map to STORAGE_PROTOCOL_DATA_SUBVALUE_GET_LOG_PAGE definition, then user can pass Retain Asynchronous Event, Log Specific Field.
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);
//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
if (!result || (returnedLength == 0)) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. Error Code %d.\n"), GetLastError());
goto exit;
}
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n"));
return;
}
protocolData = &protocolDataDescr->ProtocolSpecificData;
if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
(protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n"));
goto exit;
}
//
// SMART/Health Information Log Data
//
{
PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log Data - Temperature %d.\n"), ((ULONG)smartInfo->Temperature[1] << 8 | smartInfo->Temperature[0]) - 273);
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***SMART/Health Information Log succeeded***.\n"));
}
调用方可以使用 StorageAdapterProtocolSpecificProperty 的STORAGE_PROPERTY_ID,其STORAGE_PROTOCOL_SPECIFIC_DATA或STORAGE_PROTOCOL_SPECIFIC_DATA_EXT结构设置为ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER请求供应商特定数据的 512 字节区块。
示例:NVMe 获取功能查询
在此示例中,根据上一个请求, 获取功能 请求将发送到 NVMe 驱动器。 以下代码准备查询数据结构,然后通过 DeviceIoControl 将命令向下发送到设备。
//
// Initialize query data structure to Volatile Cache feature.
//
ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;
query->PropertyId = StorageDeviceProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeFeature;
protocolData->ProtocolDataRequestValue = NVME_FEATURE_VOLATILE_WRITE_CACHE;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataOffset = 0;
protocolData->ProtocolDataLength = 0;
//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
if (!result || (returnedLength == 0)) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache failed. Error Code %d.\n"), GetLastError());
goto exit;
}
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache - data descriptor header not valid.\n"));
return;
}
//
// Volatile Cache
//
{
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache - %x.\n"), protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData);
_tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Get Feature - Volatile Cache succeeded***.\n"));
}
协议特定集
请注意,在 Windows 10 19H1 及更高版本中,IOCTL_STORAGE_SET_PROPERTY 已增强以支持 NVMe 设定功能。
此处显示了IOCTL_STORAGE_SET_PROPERTY的输入缓冲区:
typedef struct _STORAGE_PROPERTY_SET {
//
// ID of the property being retrieved
//
STORAGE_PROPERTY_ID PropertyId;
//
// Flags indicating the type of set property being performed
//
STORAGE_SET_TYPE SetType;
//
// Space for additional parameters if necessary
//
UCHAR AdditionalParameters[1];
} STORAGE_PROPERTY_SET, *PSTORAGE_PROPERTY_SET;
使用 IOCTL_STORAGE_SET_PROPERTY 设置 NVMe 功能时,请按如下所示配置 STORAGE_PROPERTY_SET 结构:
- 分配可以同时包含 STORAGE_PROPERTY_SET 和 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 结构的缓冲区;
- 将 PropertyID 字段分别设置为 StorageAdapterProtocolSpecificProperty 或 StorageDeviceProtocolSpecificProperty,分别用于控制器或设备/命名空间请求。
- 使用所需值填充 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 结构。 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT的开头是STORAGE_PROPERTY_SET的 AdditionalParameters 字段。
此处显示了STORAGE_PROTOCOL_SPECIFIC_DATA_EXT结构。
typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA_EXT {
STORAGE_PROTOCOL_TYPE ProtocolType;
ULONG DataType; // The value will be protocol specific, as defined in STORAGE_PROTOCOL_NVME_DATA_TYPE or STORAGE_PROTOCOL_ATA_DATA_TYPE.
ULONG ProtocolDataValue;
ULONG ProtocolDataSubValue; // Data sub request value
ULONG ProtocolDataOffset; // The offset of data buffer is from beginning of this data structure.
ULONG ProtocolDataLength;
ULONG FixedProtocolReturnData;
ULONG ProtocolDataSubValue2; // First additional data sub request value
ULONG ProtocolDataSubValue3; // Second additional data sub request value
ULONG ProtocolDataSubValue4; // Third additional data sub request value
ULONG ProtocolDataSubValue5; // Fourth additional data sub request value
ULONG Reserved[5];
} STORAGE_PROTOCOL_SPECIFIC_DATA_EXT, *PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT;
若要指定要设置的 NVMe 功能类型,请按如下所示配置 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 结构:
- 将 ProtocolType 字段设置为 ProtocolTypeNvme;
- 将 DataType 字段设置为由 STORAGE_PROTOCOL_NVME_DATA_TYPE 定义的枚举值 NVMeDataTypeFeature;
以下示例演示 NVMe 功能集。
示例:NVMe 设置功能
在此示例中,“设置功能”请求将发送到 NVMe 驱动器。 以下代码准备集合数据结构,然后通过 DeviceIoControl 将命令发送至设备。
PSTORAGE_PROPERTY_SET setProperty = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT protocolDataDescr = NULL;
//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_SET, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA_EXT);
bufferLength += NVME_MAX_LOG_SIZE;
buffer = new UCHAR[bufferLength];
//
// Initialize query data structure to get the desired log page.
//
ZeroMemory(buffer, bufferLength);
setProperty = (PSTORAGE_PROPERTY_SET)buffer;
setProperty->PropertyId = StorageAdapterProtocolSpecificProperty;
setProperty->SetType = PropertyStandardSet;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT)setProperty->AdditionalParameters;
protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeFeature;
protocolData->ProtocolDataValue = NVME_FEATURE_HOST_CONTROLLED_THERMAL_MANAGEMENT;
protocolData->ProtocolDataSubValue = 0; // This will pass to CDW11.
protocolData->ProtocolDataSubValue2 = 0; // This will pass to CDW12.
protocolData->ProtocolDataSubValue3 = 0; // This will pass to CDW13.
protocolData->ProtocolDataSubValue4 = 0; // This will pass to CDW14.
protocolData->ProtocolDataSubValue5 = 0; // This will pass to CDW15.
protocolData->ProtocolDataOffset = 0;
protocolData->ProtocolDataLength = 0;
//
// Send request down.
//
result = DeviceIoControl(m_deviceHandle,
IOCTL_STORAGE_SET_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL
);
温度查询
在 Windows 10 及更高版本中, IOCTL_STORAGE_QUERY_PROPERTY 还可用于查询 NVMe 设备的温度数据。
若要从 STORAGE_TEMPERATURE_DATA_DESCRIPTOR中的 NVMe 驱动器检索温度信息,请按如下所示配置 STORAGE_PROPERTY_QUERY 结构:
- 分配可以包含 STORAGE_PROPERTY_QUERY 结构的缓冲区。
- 将 PropertyID 字段设置为 StorageAdapterTemperatureProperty(用于控制器请求)或 StorageDeviceTemperatureProperty(用于设备/命名空间请求)。
- 将 QueryType 字段设置为 PropertyStandardQuery。
STORAGE_TEMPERATURE_INFO结构(在 Windows 10 及更高版本中可用)如下所示:
typedef struct _STORAGE_TEMPERATURE_INFO {
USHORT Index; // Starts from 0. Index 0 may indicate a composite value.
SHORT Temperature; // Signed value; in Celsius.
SHORT OverThreshold; // Signed value; in Celsius.
SHORT UnderThreshold; // Signed value; in Celsius.
BOOLEAN OverThresholdChangable; // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
BOOLEAN UnderThresholdChangable; // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
BOOLEAN EventGenerated; // Indicates that notification will be generated when temperature cross threshold.
UCHAR Reserved0;
ULONG Reserved1;
} STORAGE_TEMPERATURE_INFO, *PSTORAGE_TEMPERATURE_INFO;
行为改变命令
涉及操控设备属性或可能影响设备行为的命令对操作系统而言更难处理。 如果在处理 I/O 时设备属性在运行时发生更改,则在未正确处理的情况下,可能会出现同步或数据完整性问题。
NVMe Set-Features 命令是行为更改命令的一个很好的示例。 它允许更改仲裁机制和温度阈值的设置。 为了确保在行为影响集命令下发时,外部数据不会面临风险,Windows 会将所有 I/O 暂停到 NVMe 设备、清空队列和刷新缓冲区。 成功执行 set 命令后,I/O 将恢复(如果可能)。 如果无法恢复 I/O,可能需要设备重置。
设置温度阈值
Windows 10 引入了 IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD,这是用于获取和设置温度阈值的 IOCTL。 还可以使用它来获取设备的当前温度。 此 IOCTL 的输入/输出缓冲区是上一个代码部分中 STORAGE_TEMPERATURE_INFO 结构。
示例:设置超阈值温度
在此示例中,设置了 NVMe 驱动器的超阈值温度。 以下代码准备命令,然后通过 DeviceIoControl 将其发送到设备。
BOOL result;
ULONG returnedLength = 0;
STORAGE_TEMPERATURE_THRESHOLD setThreshold = {0};
setThreshold.Version = sizeof(STORAGE_TEMPERATURE_THRESHOLD);
setThreshold.Size = sizeof(STORAGE_TEMPERATURE_THRESHOLD);
setThreshold.Flags = STORAGE_TEMPERATURE_THRESHOLD_FLAG_ADAPTER_REQUEST;
setThreshold.Index = SensorIndex;
setThreshold.Threshold = Threshold;
setThreshold.OverThreshold = UpdateOverThreshold;
//
// Send request down.
//
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,
IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD,
&setThreshold,
sizeof(STORAGE_TEMPERATURE_THRESHOLD),
NULL,
0,
&returnedLength,
NULL
);
设置特定于供应商的功能
如果没有命令效果日志,驱动程序就不知道命令的影响。 这就是需要命令效果日志的原因。 它有助于作系统确定命令是否具有很高的影响,以及是否可以与其他命令并行发送到驱动器。
命令效果日志还不够精细,无法包含特定于供应商的 Set-Features 命令。 因此,尚无法发送特定于供应商的 Set-Features 命令。 但是,可以使用前面讨论的直通机制发送特定于供应商的命令。 有关详细信息,请参阅 直通机制。
头文件
以下文件与 NVMe 开发相关。 这些文件包含在 Microsoft Windows 软件开发工具包(SDK)中。
| 头文件 | DESCRIPTION |
|---|---|
| ntddstor.h | 定义用于从内核模式访问存储类驱动程序的常量和类型。 |
| nvme.h | 对于其他 NVMe 相关数据结构。 |
| winioctl.h | 总体 Win32 IOCTL 定义,包括用户模式应用程序的存储应用程序接口。 |