共用方式為


與 NVMe 磁碟驅動器合作工作

適用於:

  • Windows 10
  • Windows 伺服器 2016

瞭解如何從 Windows 應用程式使用高速 NVMe 裝置。 裝置存取是透過 StorNVMe.sys啟用,這是 Windows Server 2012 R2 和 Windows 8.1 中首次推出的內建驅動程式。 Windows 7 裝置也可以透過 KB 修正程式來取得此功能。 在 Windows 10 中,引進了數項新功能,包括廠商特定 NVMe 命令的傳遞機制,以及現有 IOCTL 的更新。

本主題提供一般使用 API 的概觀,可讓您用來存取 Windows 10 中的 NVMe 磁碟驅動器。 它也會描述:

使用 NVMe 磁碟驅動器的 API

您可以使用下列一般用途 API 來存取 Windows 10 中的 NVMe 磁碟驅動器。 這些 API 可在 winioctl.h 中找到,適用於使用者模式的應用程式;而針對核心模式驅動程式的 API 則可在 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_DATA_OVERRUN)
    • 存儲協議狀態資源不足
    • 儲存協議狀態不支援
  • IOCTL_STORAGE_QUERY_PROPERTY :使用此 IOCTL 搭配 STORAGE_PROPERTY_QUERY 結構來擷取裝置資訊。 如需詳細資訊,請參閱 通訊協定特定查詢溫度查詢

  • STORAGE_PROPERTY_QUERY :此結構包含 PropertyIdAdditionalParameters 欄位,以指定要查詢的數據。 在 PropertyId 提交中,使用 STORAGE_PROPERTY_ID 列舉來指定數據類型。 根據數據類型,使用 [AdditionalParameters] 字段來指定更多詳細數據。 針對通訊協定特定數據,請使用 [AdditionalParameters] 字段中的 STORAGE_PROTOCOL_SPECIFIC_DATA 結構。 針對溫度數據,請使用 [AdditionalParameters] 字段中的 STORAGE_TEMPERATURE_INFO 結構。

  • STORAGE_PROPERTY_ID :此列舉包含新的值,可讓 IOCTL_STORAGE_QUERY_PROPERTY 擷取通訊協定特定和溫度資訊。

    • StorageAdapterProtocolSpecificProperty:如果 ProtocolType = ProtocolTypeNvmeDataType = NVMeDataTypeLogPage,呼叫端應該要求 512 位元組的數據區塊。

    • 存儲設備協定特定屬性

      使用下列其中一個通訊協定特定屬性標識碼搭配 STORAGE_PROTOCOL_SPECIFIC_DATA ,擷取 STORAGE_PROTOCOL_DATA_DESCRIPTOR 結構中的通訊協定特定數據。

      • 储存配接器温度属性
      • 儲存裝置溫度屬性

      使用下列其中一個溫度屬性標識符來擷取 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/健康 數據)。
    • 使用 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.sysStorport.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)。

然後, 命令會作為唯一等待處理的命令傳送到配接器。

如果 ,則為Else:

  • 指令提交與執行(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版轉譯為迷你埠。

建議使用這些 IOCTLs 在 Windows 10 和 Windows Server 2016 中開發韌體升級工具:

為了取得記憶體資訊和更新韌體,Windows 也支援 PowerShell Cmdlet 快速執行這項作:

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

透過傳遞機制傳回錯誤

類似於 SCSI 和 ATA 傳遞 IOCTLs,當命令/要求傳送至迷你埠或裝置時,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_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/健康 數據)。
    • 使用 NVMeDataTypeFeature 來取得 NVMe 磁碟驅動器的功能。

ProtocolTypeNVMe 被用作 ProtocolType時,可以在 NVMe 磁碟驅動器進行其他 I/O 操作的同時,並行查詢通訊協定特定資訊。

重要

對於使用 StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID的IOCTL_STORAGE_QUERY_PROPERTY,且其STORAGE_PROTOCOL_SPECIFIC_DATASTORAGE_PROTOCOL_SPECIFIC_DATA_EXT結構設定為 ProtocolType=ProtocolTypeNvmeDataType=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_DATASTORAGE_PROTOCOL_SPECIFIC_DATA_EXT結構設定為 ProtocolType=ProtocolTypeNvmeDataType=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_IDStorageAdapterProtocolSpecificProperty,並將STORAGE_PROTOCOL_SPECIFIC_DATASTORAGE_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_SETSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT 結構的緩衝區;
  • 將 [PropertyID] 字段分別設定為 StorageAdapterProtocolSpecificPropertyStorageDeviceProtocolSpecificProperty 控制器或裝置/命名空間要求。
  • 以所需的值填入 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 結構。 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT的開頭是 STORAGE_PROPERTY_SETAdditionalParameters 字段。

此處會顯示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] 欄位設定為 StorageAdapterTemperaturePropertyStorageDeviceTemperatureProperty,以分別應用於控制器或裝置/命名空間的請求。
  • 將 [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 會暫停所有通向 NVMe 裝置的 I/O 操作,清空佇列並刷新緩衝區。 成功執行 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。

NVMe 規格 1.2