使用 NVMe 磁片磁碟機
適用於:
- Windows 10
- Windows Server 2016
瞭解如何從 Windows 應用程式使用高速 NVMe 裝置。 裝置存取是透過 StorNVMe.sys 啟用,這是 Windows Server 2012 R2 和 Windows 8.1 中首次推出的內建驅動程式。 Windows 7 裝置也可以透過 KB 熱修復來使用。 在 Windows 10 中,引進了數項新功能,包括廠商特定 NVMe 命令的傳遞機制,以及現有 IOCTL 的更新。
本主題提供一般使用 API 的概觀,可讓您用來存取 Windows 10 中的 NVMe 磁片磁碟機。 它也會描述:
- 如何使用傳遞傳送廠商特定的 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 中的 Command Effects 記錄。 您可以將它與廠商特定的命令搭配使用。 如需詳細資訊,請參閱 傳遞機制 。
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_INSUFFICIENT_RESOURCES
- 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 擷取通訊協定特定和溫度資訊。
- 儲存體AdapterProtocolSpecificProperty :如果 ProtocolType = ProtocolTypeNvme 和 DataType = NVMeDataTypeLogPage,呼叫端應該要求 512 位元組的資料區塊。
- 儲存體DeviceProtocolSpecificProperty
使用下列其中一個通訊協定特定的屬性識別碼搭配 STORAGE_PROTOCOL_SPECIFIC_DATA,以擷取STORAGE_PROTOCOL_DATA_DESCRIPTOR 結構中的 通訊協定特定 資料。
- 儲存體AdapterTemperatureProperty
- 儲存體DeviceTemperatureProperty
使用下列其中一個溫度屬性識別碼來擷取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 欄位 (boolean) 會指定 Threshold 欄位是否 為超過臨界值(否則,其為臨界值下)。
傳遞機制
在 NVMe 規格中未定義的命令,是主機 OS 處理的最困難–主機無法深入瞭解命令在目標裝置、公開的基礎結構(命名空間/區塊大小)及其行為上可能具有的效果。
為了更妥善地透過 Windows 儲存體堆疊傳遞這類裝置特定命令,新的傳遞機制可讓廠商特定的命令透過管道傳送。 此傳遞管道也有助於開發管理和測試控管。 不過,此傳遞機制需要使用命令效果記錄檔。 此外,StoreNVMe.sys 需要命令記錄檔中所述的所有命令,而不只是傳遞命令。
重要
如果命令效果記錄檔中未描述 StorNVMe.sys 和 Storport.sys,則會封鎖裝置的任何命令。
支援命令效果記錄檔
命令效果記錄檔(如命令支援和效果中所述,NVMe 規格 1.2 的 5.10.1.5 節中所述)允許廠商特定命令與規格定義的命令之效果的描述。 這有助於命令支援驗證和命令列為優化,因此應該針對裝置支援的整個命令集實作。 下列條件描述如何根據命令效果記錄專案傳送命令的結果。
針對命令效果記錄檔中所述的任何特定命令...
期間 :
命令支援 (CSUPP) 設定為 '1',表示控制器支援命令 (位 01)
注意
當 CSUPP 設定為 '0' 時(表示不支援命令),命令將會遭到封鎖
如果已設定下列任一項:
控制器功能變更 (CCC) 設定為 '1',表示命令可能會變更控制器功能 (位 04)
命名空間清查變更 (NIC) 設定為 '1',表示命令可能會變更數位,或多個命名空間的功能 (位 03)
命名空間功能變更 (NCC) 設定為 '1',表示命令可能會變更單一命名空間的功能 (Bit 02)
命令提交和執行 (CSE) 設定為 001b 或 010b,表示當相同或任何命名空間沒有其他未處理的命令時,命令可能會提交,而且在此命令完成之前,不應將另一個命令提交至相同或任何命名空間(位 18:16)
然後 ,命令將會以配接器唯一未處理的命令傳送。
否則為 :
- 命令提交和執行 (CSE) 設定為 001b,表示當相同命名空間沒有其他未處理的命令時,可能會提交命令,而且在此命令完成之前,不應該將另一個命令提交至相同的命名空間(位 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 向下傳送。 IOCTL 會使用 ntddstor.h 中找到 的 STORAGE_PROTOCOL_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只應該用於廠商特定的命令。
請改用下列一般記憶體 IOCTL(在 Windows 10 中引進)以避免應用程式直接使用SCSI_miniport版本的韌體 IOCTL。 儲存體 驅動程式會將IOCTL轉譯為SCSI命令或IOCTL SCSI_miniport版本轉譯為迷你埠。
建議在 Windows 10 和 Windows Server 2016 中開發韌體升級工具:
為了取得記憶體資訊和更新韌體,Windows 也支援 PowerShell Cmdlet 快速執行這項操作:
Get-StorageFirmwareInfo
Update-StorageFirmware
注意
若要更新 Windows 8.1 中 NVMe 上的韌體,請使用 IOCTL_SCSI_MINIPORT_FIRMWARE。 此 IOCTL 未轉送至 Windows 7。 如需詳細資訊,請參閱 在 Windows 8.1 中升級 NVMe 裝置的韌體。
透過傳遞機制傳回錯誤
類似於 SCSI 和 ATA 傳遞 IOCTLs,當命令/要求傳送至迷你埠或裝置時,IOCTL 會在成功或未成功時傳回。 在 STORAGE_PROTOCOL_COMMAND 結構中,IOCTL 會透過 ReturnStatus 字段傳回狀態。
範例:傳送廠商特定的命令
在此範例中,會透過傳遞至 NVMe 磁碟驅動器,將任意廠商特定命令 (0xFF) 傳送。 下列程式代碼會配置緩衝區、初始化查詢,然後透過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 字段設定為 儲存體 AdapterProtocolSpecificProperty 或 儲存體 控制器或裝置/命名空間要求的 儲存體 DeviceProtocolSpecificProperty。
將 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 平行擷取通訊協定特定資訊的查詢。
重要
對於使用 儲存體 AdapterProtocolSpecificProperty STORAGE_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"));
}
}
重要
對於使用 儲存體 AdapterProtocolSpecificProperty STORAGE_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"));
}
呼叫端可以使用STORAGE_PROPERTY_ID 儲存體 AdapterProtocolSpecificProperty,且其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 字段分別設定為 儲存體 AdapterProtocolSpecificProperty 或 儲存體 DeviceProtocolSpecificProperty 控制器或裝置/命名空間要求。
- 以所需的值填入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 字段分別設定為控制器或裝置/命名空間要求的 儲存體 AdapterTemperatureProperty 或 儲存體 DeviceTemperatureProperty。
將 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) 中。
頭檔 | 描述 |
---|---|
ntddstor.h | 定義常數和類型,以從核心模式存取記憶體類別驅動程式。 |
nvme.h | 針對其他 NVMe 相關數據結構。 |
winioctl.h | 針對整體 Win32 IOCTL 定義,包括使用者模式應用程式的記憶體 API。 |