次の方法で共有


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

次の汎用 API を使用して、Windows 10 の NVMe ドライブにアクセスできます。 これらの API は、ユーザー モード アプリケーションの 場合は winioctl.h 、カーネル モード ドライバーの 場合は ntddstor.h にあります。 ヘッダー ファイルの詳細については、「 ヘッダー ファイル」を参照してください。

  • IOCTL_STORAGE_PROTOCOL_COMMAND : NVMe コマンドを発行するには、 STORAGE_PROTOCOL_COMMAND 構造でこの IOCTL を使用します。 この IOCTL は NVMe パススルーを有効にし、NVMe のコマンド効果ログをサポートします。 ベンダー固有のコマンドで使用できます。 詳細については、「 パススルー メカニズム」を参照してください。

  • STORAGE_PROTOCOL_COMMAND : この入力バッファー構造には、次の状態値を報告するために使用できる ReturnStatus フィールドが含まれています。

    • STORAGE_PROTOCOL_STATUS_PENDING
    • STORAGE_PROTOCOL_STATUS_SUCCESS
    • STORAGE_PROTOCOL_STATUS_ERROR
    • STORAGE_PROTOCOL_STATUS_INVALID_REQUEST
    • デバイスがありません(STORAGE_PROTOCOL_STATUS_NO_DEVICE)
    • STORAGE_PROTOCOL_STATUS_BUSY
    • STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
    • ストレージ_プロトコル_ステータス_リソース不足
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED(ストレージプロトコルがサポートされていません)
  • IOCTL_STORAGE_QUERY_PROPERTY : デバイス情報を取得するには、 STORAGE_PROPERTY_QUERY 構造でこの IOCTL を使用します。 詳細については、 プロトコル固有のクエリと Temperature クエリに関 する ページを参照してください。

  • STORAGE_PROPERTY_QUERY : この構造体には、クエリするデータを指定する PropertyId フィールドと AdditionalParameters フィールドが含まれます。 ファイルされた PropertyId で、 STORAGE_PROPERTY_ID 列挙型を使用してデータの種類を指定します。 [AdditionalParameters] フィールドを使用して、データの種類に応じて詳細を指定します。 プロトコル固有のデータの場合は、[AdditionalParameters] フィールドのSTORAGE_PROTOCOL_SPECIFIC_DATA構造体を使用します。 温度データの場合は、[AdditionalParameters] フィールドのSTORAGE_TEMPERATURE_INFO構造を使用します。

  • STORAGE_PROPERTY_ID : この列挙には、 IOCTL_STORAGE_QUERY_PROPERTY がプロトコル固有および温度の情報を取得できるようにする新しい値が含まれています。

    • StorageAdapterProtocolSpecificProperty: ProtocolType = ProtocolTypeNvmeDataType = NVMeDataTypeLogPage場合、呼び出し元は 512 バイトのデータ チャンクを要求する必要があります。

    • StorageDeviceProtocolSpecificProperty

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

      • ストレージアダプタ温度プロパティ
      • StorageDeviceTemperatureProperty (ストレージデバイス温度プロパティ)

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

  • STORAGE_PROTOCOL_SPECIFIC_DATA: STORAGE_PROPERTY_QUERYの AdditionalParameters フィールドにこの構造体が使用され、STORAGE_PROTOCOL_NVME_DATA_TYPE列挙値が指定されている場合は、NVMe 固有のデータを取得します。 STORAGE_PROTOCOL_SPECIFIC_DATA構造体の DataType フィールドには、次のいずれかのSTORAGE_PROTOCOL_NVME_DATA_TYPE値を使用します。

    • NVMeDataTypeIdentify を使用して、コントローラーの識別データまたは名前空間データの識別を取得します。
    • NVMeDataTypeLogPage を使用して、ログ ページ (SMART/正常性データを含む) を取得します。
    • NVMeDataTypeFeature を使用して、NVMe ドライブの機能を取得します。
  • STORAGE_TEMPERATURE_INFO : この構造は、特定の温度データを保持するために使用されます。 これは、温度クエリの結果を返すためにSTORAGE_TEMERATURE_DATA_DESCRIPTORで使用されます。

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : 温度しきい値を設定するには、 STORAGE_TEMPERATURE_THRESHOLD 構造でこの IOCTL を使用します。 詳細については、「 動作変更コマンド」を参照してください。

  • STORAGE_TEMPERATURE_THRESHOLD : この構造体は、温度しきい値を指定するための入力バッファーとして使用されます。 OverThreshold フィールド (ブール値) は、しきい値フィールドが超過しきい値かどうかを指定します (それ以外の場合は、しきい値未満の値です)。

パススルー メカニズム

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

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

Von Bedeutung

StorNVMe.sys コマンド効果ログで説明されていない場合、 Storport.sys はデバイスへのコマンドをブロックします。

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

コマンド効果ログ (「サポートされるコマンドと効果」の NVMe 仕様 1.2 のセクション 5.10.1.5 で説明) を使用すると、ベンダー固有のコマンドの効果と仕様定義コマンドの説明が可能になります。 これにより、コマンド サポートの検証とコマンド動作の最適化の両方が容易になります。そのため、デバイスがサポートする一連のコマンド全体に対して実装する必要があります。 次の条件は、コマンド効果ログエントリに基づいてコマンドを送信する方法に関する結果を示しています。

コマンド効果ログで説明されている特定のコマンドについて...

同時に:

  • コマンドサポート (CSUPP) が '1' に設定されています。これは、コマンドがコントローラーでサポートされていることを示します (ビット 01)

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

また、 次のいずれかが設定されている場合:

  • コントローラー機能の変更 (CCC) が "1" に設定されている。これは、コマンドによってコントローラーの機能が変更される可能性があることを示しています (ビット 04)
  • 名前空間インベントリ変更 (NIC) が '1' に設定されている。これは、コマンドによって複数の名前空間の数または機能が変更される可能性があることを示しています (ビット 03)
  • 名前空間機能変更 (NCC) が '1' に設定されている。これは、コマンドによって 1 つの名前空間の機能が変更される可能性があることを示しています (ビット 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 は、ベンダー固有のコマンドを送信することを目的としています。 Identify などの他の管理者またはベンダー以外の NVMe コマンドを送信するには、このパススルー IOCTL を使用しないでください。 たとえば、 IOCTL_STORAGE_QUERY_PROPERTY を [識別] または [ログ ページの取得] に使用する必要があります。 詳細については、次のセクション「 プロトコル固有のクエリ」を参照してください。

パススルー メカニズムを使用してファームウェアを更新しない

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

代わりに、次の一般的なストレージ IOCTL (Windows 10 で導入) を使用して、ファームウェア IOCTL の SCSI_miniport バージョンを直接使用するアプリケーションを回避します。 記憶域ドライバーは、IOCTL を SCSI コマンドまたは IOCTL のSCSI_miniportバージョンのいずれかにミニポートに変換します。

Windows 10 および Windows Server 2016 でファームウェア アップグレード ツールを開発するには、次の IOCTL をお勧めします。

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

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

NVMe のファームウェアを更新するには、IOCTL_SCSI_MINIPORT_FIRMWAREを使用します。 詳細については、「 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 では、ログ ページの取得、機能の 取得識別など、一般的に要求される 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;

IOCTL_STORAGE_QUERY_PROPERTYを使用してSTORAGE_PROTOCOL_DATA_DESCRIPTORで 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 と並行してプロトコル固有の情報のクエリを取得できます。

Von Bedeutung

IOCTL_STORAGE_QUERY_PROPERTY で、STORAGE_PROPERTY_ID として StorageAdapterProtocolSpecificProperty を使用し、STORAGE_PROTOCOL_SPECIFIC_DATA または STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 構造体が ProtocolType=ProtocolTypeNvme および DataType=NVMeDataTypeLogPage に設定されている場合、その構造体の ProtocolDataLength メンバーを最小値 512 バイトに設定します。

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

例:NVMe 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"));
    }
}

Von Bedeutung

StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_IDを使用し、そのSTORAGE_PROTOCOL_SPECIFIC_DATAまたはSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT構造体がProtocolType=ProtocolTypeNvmeおよびDataType=NVMeDataTypeLogPageに設定されているIOCTL_STORAGE_QUERY_PROPERTYは、その同じ構造体の 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 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構造体がProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIERに設定され、ベンダー固有のデータの 512 バイト チャンクが要求されます。

例: NVMe 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 セット機能をサポートするように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_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 フィールドをそれぞれ 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 を一時停止し、キューをドレインし、バッファーをフラッシュします。 set コマンドが正常に実行されると、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 定義全体の場合。

NVMe 仕様 1.2