NVMe ドライブの操作

適用対象:

  • Windows 10
  • Windows Server 2016

Windows アプリケーションから高速 NVMe デバイスを操作する方法について説明します。 デバイス アクセスは、Windows Server 2012 R2 と Windows 8.1 で初めて導入されたインボックス ドライバーである StorNVMe.sys を介して有効になります。 また、KB 修正プログラムを適用すると、Windows 7 デバイスでも使用できます。 Windows 10 では、ベンダー固有の NVMe コマンドのパススルー メカニズムや既存の IOCTL の更新など、いくつかの新機能が導入されました。

このトピックでは、Windows 10 で NVMe ドライブにアクセスするために使用できる汎用 API の概要について説明します。 また、以下についても説明します。

NVMe ドライブを操作する API

Windows 10 で NVMe ドライブにアクセスするには、次の汎用 API を使用できます。 これらの 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_INSUFFICIENT_RESOURCES
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • 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 = ProtocolTypeNvme かつ DataType = NVMeDataTypeLogPage の場合、呼び出し元は 512 バイトのデータ チャンクを要求する必要があります。
    • StorageDeviceProtocolSpecificProperty

    これらのプロトコル固有のプロパティ ID のいずれかを STORAGE_PROTOCOL_SPECIFIC_DATA と組み合わせて使い、STORAGE_PROTOCOL_DATA_DESCRIPTOR 構造体のプロトコル固有のデータを取得します。

    • StorageAdapterTemperatureProperty
    • StorageDeviceTemperatureProperty

    これらの温度プロパティ ID のいずれかを使って STORAGE_TEMPERATURE_DATA_DESCRIPTOR 構造体の温度データを取得します。

  • STORAGE_PROTOCOL_SPECIFIC_DATA: STORAGE_PROPERTY_QUERYAdditionalParameters フィールドにこの構造体が使われていて、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 フィールド (ブール型) には、Threshold フィールドがしきい値を超えているかどうかを指定します (指定しない場合はしきい値未満です)。

パススルー メカニズム

NVMe 仕様に定義されていないコマンドは、ホスト OS にとって処理が最も難しいものです。コマンドがターゲット デバイス、公開されているインフラストラクチャ (名前空間やブロック サイズ)、その動作に与える可能性がある影響について、ホストは何の分析情報も持っていません。

このようなデバイス固有のコマンドを Windows ストレージ スタック経由でより適切に伝送するには、新しいパススルー メカニズムにより、ベンダー固有のコマンドをパイプ処理することができます。 このパススルー パイプは、管理ツールやテスト ツールの開発にも役立ちます。 ただし、このパススルー メカニズムでは、コマンド効果 (Command Effects) ログを使う必要があります。 さらに、StoreNVMe.sys では、パススルー コマンドだけでなく、すべてのコマンドをコマンド効果ログに記述する必要があります。

重要

デバイスに対するコマンドがコマンド効果ログに記述されていない場合、StorNVMe.sys と Storport.sys はそれをブロックします。

 

コマンド効果ログのサポート

コマンド効果ログ (NVMe 仕様 1.2 のセクション 5.10.1.5「Commands Supported and Effects」(サポートされるコマンドと効果) に記載されています) には、ベンダー固有のコマンドと仕様に定義されたコマンドの効果を一緒に記載できます。 これにより、コマンド サポートの検証とコマンド動作の最適化が容易になります。そのため、デバイスがサポートするすべてのコマンド セットに対して実装するようにしてください。 次の条件は、コマンド効果ログのエントリに基づいてコマンドを送信する方法について結果を説明したものです。

コマンド効果ログに記載されている特定のコマンドに対して...

While (次の条件を満たす間):

  • [Command Supported (CSUPP)] (サポートされているコマンド (CSUPP)) が、コマンドがコントローラーによってサポートされていることを示す「1」に設定されている (ビット 01)

    Note

    CSUPP が「0」に設定されている場合 (コマンドがサポートされていないことを示します)、コマンドはブロックされます

     

かつ if (条件) 次のいずれかが設定されている場合:

  • [Controller Capability Change (CCC)] (コントローラー機能変更 (CCC)) が「1」(コマンドでコントローラー機能を変更できることを示す) に設定されている (ビット 04)

  • [Namespace Inventory Change (NIC)] (名前空間インベントリの変更 (NIC)) が「1」(コマンドでその数値、または複数の名前空間の機能を変更できることを示す) に設定されている (ビット 03)

  • [Namespace Capability Change (NCC)] (名前空間機能の変更 (NCC)) が「1」(コマンドで 1 つの名前空間の機能を変更できることを示す) に設定されている (ビット 02)

  • [Command Submission and Execution (CSE)] (コマンドの送信と実行 (CSE)) が、同じまたは任意の名前空間に対して他の未処理のコマンドがない場合にコマンドを送信できることと、このコマンドが完了するまで同じまたは任意の名前空間に対して他のコマンドを送信すべきではないことを示す「001b」または「010b」に設定されている (ビット 18:16)

Then (上記の条件を満たす場合) このコマンドは未処理の唯一のコマンドとしてアダプターに送信されます。

Else if (上記の条件を満たさず、次の条件):

  • [Command Submission and Execution (CSE)] (コマンドの送信と実行 (CSE)) が、同じ名前空間に対して他の未処理のコマンドがない場合にコマンドを送信できることと、このコマンドが完了するまで同じ名前空間に対して他のコマンドを送信すべきではないことを示す「001b」に設定されている (ビット 18:16)

Then (上記の条件を満たす場合) このコマンドは未処理の唯一のコマンドとして論理ユニット番号オブジェクト (LUN) に送信されます。

Otherwise (それ以外の場合) コマンドは抑制されることなく、他の未処理のコマンドと共に送信されます。 たとえば、仕様で定義されていない統計情報を取得するために、ベンダー固有のコマンドがデバイスに送信された場合、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 入力バッファー構造体を使って設定されます。 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 はベンダー固有のコマンドを送信することを目的としています。 Identify のような他の管理者またはベンダー固有ではない NVMe コマンドを送信する場合は、このパススルー IOCTL を使わないでください。 たとえば、[Identify] (識別) または [Get Log Pages] (ログ ページの取得) には IOCTL_STORAGE_QUERY_PROPERTY を使う必要があります。 詳細については、次のセクションの「プロトコル固有のクエリ」を参照してください。

パススルー メカニズムを介してファームウェアを更新しないでください

ファームウェアのダウンロードとアクティビティのコマンドは、パススルーを使って送信しないでください。 IOCTL_STORAGE_PROTOCOL_COMMAND は、ベンダー固有のコマンドにのみ使う必要があります。

代わりに、次の一般的なストレージ IOCTL (Windows 10 で導入されました) を使って、アプリケーションがファームウェア IOCTL の SCSI_miniport バージョンを直接使わないようにしてください。 ストレージ ドライバーによって、IOCTL は SCSI コマンドに、または SCSI_miniport バージョンの IOCTL はミニポートに変換されます。

Windows 10 と Windows Server 2016 でファームウェア アップグレード ツールを開発するには、これらの IOCTL が推奨されます。

ストレージ情報の取得やファームウェアの更新については、これを迅速に実行するために、Windows は PowerShell コマンドレットもサポートしています。

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

Note

Windows 8.1 で NVMe のファームウェアを更新するには、IOCTL_SCSI_MINIPORT_FIRMWARE を使います。 この IOCTL は Windows 7 に移植されませんでした。 詳細については、Windows 8.1 での NVMe デバイス用ファームウェアのアップグレードに関する記事を参照してください。

 

パススルー メカニズムを介してエラーを返す

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 では、[Get Log Pages] (ログ ページの取得)[Get Features] (機能の取得)[Identify] (識別) などの一般的に要求される NVMe 機能をサポートするように IOCTL が拡張されました。 これにより、監視とインベントリを目的とした 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;

STORAGE_PROTOCOL_DATA_DESCRIPTORIOCTL_STORAGE_QUERY_PROPERTY を使って NVMe プロトコル固有の情報を取得する場合、STORAGE_PROPERTY_QUERY 構造体を次のように構成します。

  • STORAGE_PROPERTY_QUERYSTORAGE_PROTOCOL_SPECIFIC_DATA 構造体の両方を含むことができるバッファーを割り当てます。

  • コントローラー要求の場合は PropertyID フィールドを StorageAdapterProtocolSpecificProperty に、デバイス/名前空間要求の場合は StorageDeviceProtocolSpecificProperty にそれぞれ設定します。

  • QueryType フィールドを PropertyStandardQuery に設定します。

  • STORAGE_PROTOCOL_SPECIFIC_DATA 構造体に必要な値を入力します。 STORAGE_PROTOCOL_SPECIFIC_DATA の先頭は STORAGE_PROPERTY_QUERYAdditionalParameters フィールドです。

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 ドライブの機能を取得します。

ProtocolTypeNVMeProtocolType として使う場合、プロトコル固有の情報に対するクエリは、NVMe ドライブ上の他の I/O と並列で取得できます。

重要

StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID を使い、STORAGE_PROTOCOL_SPECIFIC_DATA または STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 構造体が ProtocolType=ProtocolTypeNvmeDataType=NVMeDataTypeLogPage に設定されている IOCTL_STORAGE_QUERY_PROPERTY の場合、その同じ構造体の ProtocolDataLength メンバーを最小値の 512 (バイト) に設定します。

次の例は、NVMe プロトコル固有のクエリを示しています。

例: NVMe の [Identify] (識別) クエリ

この例では、Identify 要求が 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 を使い、STORAGE_PROTOCOL_SPECIFIC_DATA または STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 構造体が ProtocolType=ProtocolTypeNvmeDataType=NVMeDataTypeLogPage に設定されている IOCTL_STORAGE_QUERY_PROPERTY の場合、その同じ構造体の ProtocolDataLength メンバーを最小値の 512 (バイト) に設定します。

呼び出し元は、STORAGE_PROPERTY_QUERY と STORAGE_PROTOCOL_SPECIFIC_DATA のサイズを含む 1 つのバッファーを割り当てる必要があることに注意してください。 この例では、プロパティ クエリからの入力と出力に同じバッファーを使っています。 そのため、割り当てられたバッファーのサイズは "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE" です。 入力と出力の両方に個別のバッファーを割り当てることもできますが、NVMe 関連の情報のクエリを実行するには 1 つのバッファーを使うことをお勧めします。

identifyControllerData->NN は名前空間の数 (NN) です。 Windows は、名前空間を物理ドライブとして検出します。

例: NVMe の [Get Log Pages] (ログ ページの取得) クエリ

この例では、前の例に基づいて、[Get Log Pages] (ログ ページの取得) 要求が 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"));
    }

呼び出し元は StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID を使用でき、その STORAGE_PROTOCOL_SPECIFIC_DATA または STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 構造体は、ベンダー固有データの 512 バイト チャンクを要求するように、ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER に設定されます。

例: NVMe の [Get Features] (機能の取得) クエリ

この例では、前の例に基づいて、[Get Features] (機能の取得) 要求が 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 以降、NVMe の [Set Features] (機能の設定) をサポートするように IOCTL_STORAGE_SET_PROPERTY が拡張されました。

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 の [Set Features] (機能の設定)

この例では、[Set Features] (機能の設定) 要求が 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 は NVMe デバイスへのすべての I/O を一時停止し、キューをドレインし、バッファーをフラッシュします。 セット コマンドが正常に実行されると、I/O が再開されます (可能な場合)。 I/O を再開できない場合は、デバイスのリセットが必要になることがあります。

温度しきい値の設定

Windows 10 では、温度しきい値を取得および設定する IOCTL である IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD が導入されました。 また、これを使って、デバイスの現在の温度を取得することもできます。 この 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 ユーザー モード アプリケーション用のストレージ API を含む、Win32 IOCTL の全体的な定義の場合。