Windowsの標準NVMeドライバでNVMe SSDにアクセスする(Get Features)

# はじめに
　これまで、Windows 10（以降Windowsと記載します）の標準NVMeデバイスドライバを使用して、ユーザ空間で動作するプログラムからNVMe SSDに対していくつかのコマンドを発行する方法を紹介しました（[その1](https://qiita.com/ken-yossy/items/30f0dd206713aba770f7)、[その2](https://qiita.com/ken-yossy/items/d4cd6502fe475d78e872)、[その3](https://qiita.com/ken-yossy/items/ffecfd31d41d71f27dca)）。

　この記事では、NVMeが規定する機能の設定内容を取得する`Get Features`というコマンドを、Windowsの標準NVMeデバイスドライバを使用して発行する方法を説明します。

　`Get Features`コマンドと対を成す、「機能の設定」を行う`Set Features`コマンドも説明できればよかったのですが、Windowsの標準NVMeデバイスドライバを使う限り`Set Features`コマンドを使うことで設定内容を変更可能な機能がほとんどないようですので、ここでは`Get Features`コマンドのみ説明します。

　なお、この記事でまとめた内容の動作確認環境は、[以前の記事](https://qiita.com/ken-yossy/items/30f0dd206713aba770f7)の動作確認環境とほぼ同一ですので、そちらを参照してください。また、完全なコードは[Github](https://github.com/ken-yossy/nvmetool-win)に置いてありますのでそちらをご覧ください。

# まとめ
* `Get Features`コマンドを使用すると、NVMe SSDの機能の「現在の設定」を取得できる
* ただし、Windowsの標準NVMeデバイスドライバを使う場合、取得が可能な機能は限られる

# NVMe仕様における"Feature"とは
　NVMe仕様<sup>[1]</sup>における`Get Features`コマンドは、NVMeデバイス（面倒なので、以降「デバイス」＝「SSD」として"SSD"と記載します）の各種機能に関する、現時点での設定内容を取得するためのコマンドです。

　NVMe仕様は、`Get Features`コマンドの対象となる機能(Feature)として、以下のものを定義しています。

表1：NVMe仕様で定義されている機能（NVMe仕様書から抜粋）
![NVMe仕様で定義されている機能（NVMe仕様書から抜粋）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/e923a118-d904-89ae-f657-7c54a0762a87.png "NVMe仕様で定義されている機能（NVMe仕様書から抜粋）")

　表1内の機能のうち、例えば、Feature Identifier (FID) = 06hの`Volatile Write Cache`は揮発性ライトキャッシュ、FID = 02hの`Power Management`は省電力機能、そしてFID = 04hの`Temperature Threshold`は温度閾値に関する設定となります。

# "Feature"の仕組み
　`Get Features`コマンドの説明に入る前に、NVMe仕様が定義する「機能」の仕組みについて説明します。

　NVMe仕様における「機能(Feature)」は、下記のような特徴を持ち、それらの設定内容を`Get Features`コマンドで取得できます。

* 機能の設定内容（値）には、「現在の値(Current)」と「デフォルト値(Default)」と「保存された値(Saved)」の3種類がある
* 各機能に下記の「属性(Supported Capabilities)」が存在する
    * 設定を変更できるかどうか(Changeable)
    * 設定を保存できるかどうか(Saveable)
    * Namespace毎に適用される設定なのかコントローラ全体に適用される設定なのか

　これらの仕様は、仕様書5.13節"Get Features command"および5.21節"Set Features command"に記載されています。

　`Get Features`コマンドでどの内容を取得するかは、以下の部分で設定します（青四角で囲った部分）。

![Get Featuresコマンドで取得する値の設定方法（NVMe仕様書より抜粋）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/ac5e9497-55fe-84bb-7d3a-6b63295adc20.png "Get Featuresコマンドで取得する値の設定方法（NVMe仕様書より抜粋）")
図1：`Get Features`コマンドで取得する値の設定方法（NVMe仕様書より抜粋）

# `Get Features`コマンドの発行
　それでは、具体的に`Get Features`コマンドを発行する手順を見ていきます。

　以下のフローチャートは、Windowsの標準NVMeデバイスドライバを使用してNVMe SSDに`Get Features`コマンドを発行する手順です。

　といっても、これまでに説明した他のコマンドの発行手順と大差はありません。

![Get Featuresコマンド発行手順](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/b70b62d7-0fec-d8f3-8b53-5f4dfc8f8f21.png "Get Featuresコマンド発行手順")
図2：`Get Features`コマンド発行手順

　デバイスハンドルを取得する方法は、[以前の別記事](https://qiita.com/ken-yossy/items/30f0dd206713aba770f7)で説明した通りですので、説明は省略します。

　この記事では、機能(Feature)の例として`Volatile Write Cache`を取り上げます。

　「コマンド発行用構造体作成」以降の処理について、以下にコード片を示します。行頭の行番号は、説明のために付与したものです。エラー処理に関するコードは省略しています。

　このコードは、Microsoftの説明<sup>[2]</sup>にしたがって書き起こしたものです。

```c
 1: int     iResult = -1;
 2: PVOID   buffer = NULL;
 3: ULONG   bufferLength = 0;
 4: ULONG   returnedLength = 0;
 5: uint32_t resultData = 0;

 6: PSTORAGE_PROPERTY_QUERY query = NULL;
 7: PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolData = NULL;
 8: PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;
 9: NVME_CDW10_GET_FEATURES cdw10 = { 0 };

    // Allocate buffer for use.
10: bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
11: buffer = malloc(bufferLength);
12: ZeroMemory(buffer, bufferLength);

13: query = (PSTORAGE_PROPERTY_QUERY)buffer;
14: protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
15: protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;

16: query->PropertyId = StorageDeviceProtocolSpecificProperty;
17: query->QueryType = PropertyStandardQuery;

18: cdw10.FID = NVME_FEATURE_VOLATILE_WRITE_CACHE;
19: cdw10.SEL = NVME_FEATURE_VALUE_CURRENT;

20: protocolData->ProtocolType = ProtocolTypeNvme;
21: protocolData->DataType = NVMeDataTypeFeature;
22: protocolData->ProtocolDataRequestValue = (DWORD)(cdw10.AsUlong);
23: protocolData->ProtocolDataRequestSubValue = 0;
24: protocolData->ProtocolDataOffset = 0;
25: protocolData->ProtocolDataLength = 0;

    // Send request down.  
26: iResult = DeviceIoControl(_hDevice,
                              IOCTL_STORAGE_QUERY_PROPERTY,
                              buffer,
                              bufferLength,
                              buffer,
                              bufferLength,
                              &returnedLength,
                              NULL);

27: resultData = (uint32_t)(protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData);
```

　6行目に登場する構造体`STORAGE_PROPERTY_QUERY`は、ストレージに問い合わせ（クエリ）をするようなコマンドを発行する際に使用するデータ構造です。

　これは`Get Log Page`コマンドの発行時にも使ったもので、クエリタイプの設定（17行目）が必要です。

　9行目で定義している変数の型は、`Get Features`コマンド用に`nvme.h`で定義されている構造体です。

　`Get Features`コマンドでは、Feature IDなどをCommand DWord 10 (CDW10)に設定します。このCDW10の設定内容は`Get Features`コマンド専用ですので、`nvme.h`に構造体が定義されています。

　具体的なFeature IDと図1で示した取得内容は、18行目と19行目で設定して、22行目でコマンド発行用構造体に設定しています。

　サンプルコードでは、19行目に`NVME_FEATURE_VALUE_CURRENT`を指定しています。これは「現在の値(Current)」の取得を要求するための値です。「デフォルト値(Default)」や「保存された値(Saved)」そして「属性」に対しても、同様に`nvme.h`に定数が定義されていますので、それを指定します。

　今回は`Volatile Write Cache`ですので、FIDには`NVME_FEATURE_VOLATILE_WRITE_CACHE` (06h)を設定します。

　順序が逆になってしまいましたが、10行目でバッファサイズを計算して11行目でメモリを確保しているのが、コマンド発行用構造体です。

　`Volatile Write Cache`機能の場合、`Get Features`コマンドの結果としてSSDから受け取るデータはDWord（32ビット）のデータひとつだけですので、SSDから受け取るデータのためのメモリ領域を改めて確保する必要はありません。

　そして、21行目で`Get Features`コマンドであることを設定した上で、26行目で`DeviceIoControl()`を呼び出して、コマンド発行を要求します。

　`DeviceIoControl()`が成功すると、コマンド発行用構造体の中に、SSDから受け取ったデータが格納されますので、27行目のように参照します。

　受信したデータは、NVMe仕様書5.21.1節"Feature Specific Information"に記載されている各機能に関するデータ構造にしたがって解析します。

　以下に、とあるNVMe SSDから取得した`Volatile Write Cache`の設定内容を示します。なお、フィールド名や説明文の付与や出力の整形はプログラム中で行ったものです。

　解釈はNVMe仕様書の5.21.1.6節に従って行います。

```
[I] Volatile Write Cache:
        bit [      0] Volatile Write Cache Enable (WCE), (0) disabled, (1) enabled
                Current = 1
                Default = 1
                Saved   = 1
        Capabilities: this feature is
                bit [      2] 1 = (1) changeable, (0) not changeable
                bit [      1] 0 = (1) namespace specific, (0) for entire controller
                bit [      0] 0 = (1) saveable, (0) not saveable
```

　この結果から、このNVMe SSDの`Volatile Write Cache`機能は以下のようになっていることがわかります。

* 現在の設定値は「有効(`Current = 1`)」
* デフォルトの設定値は「有効(`Default = 1`)」
* 保存されている値は「有効(`Saved = 1`)」
* 設定値の変更は「可能(`changeable`)」
* この設定値は「コントローラ全体に適用される設定(`for entire controller`)」
* この設定値は「保存できない(`not saveable`)」

　わかりにくいですが、実際には、`Current`と`Default`と`Saved`の設定値を取得するためにそれぞれ`Get Features`コマンドを発行する必要があります。

　つまり、上記の結果を取得するために、属性(Capability)を取得するためのコマンド発行分と合わせて合計4回`Get Features`コマンドを発行しています。

# まとめ
　この記事では、NVMe仕様が定める「機能(Feature)」について説明し、Windowsの標準NVMeデバイスドライバを使って、`Get Features`コマンドでその機能の設定内容を取得する方法をまとめました。

　NVMe仕様が定める機能(Feature)は仕様が新しくなるにつれてどんどん数が増えているのですが、Windowsの標準NVMeデバイスドライバを使う方法では、`Set Features`コマンドによる設定内容の変更はもちろん、`Get Features`コマンドによる設定内容の取得ができない機能も多いです。

　とはいえ、この記事で紹介した方法は、Windowsの標準NVMeデバイスドライバを使用していますので、とても手軽に試せることが特徴です。

　なお、Windowsの標準NVMeデバイスドライバを使用した場合の、各機能についての設定取得可否については、[Github](https://github.com/ken-yossy/nvmetool-win)にまとめてあります。

# 参考文献
[1] NVM Express, _"NVM ExpressTM Base Specification"_, Revision 1.3d, March 20, 2019
[2] Microsoft, [_"Working with NVMe drives"_](https://docs.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices), 最終閲覧日2020年9月16日

# ライセンス表記
<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="クリエイティブ・コモンズ・ライセンス" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />この記事は<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">クリエイティブ・コモンズ 表示 - 継承 4.0 国際ライセンス</a>の下に提供されています。