共用方式為


使用 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 磁片磁碟機的 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_QUERYSTORAGE_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_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"));
        }
    }

  

重要

對於使用 儲存體 AdapterProtocolSpecificProperty STORAGE_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_ID 儲存體 AdapterProtocolSpecificProperty,且其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_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。