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

# はじめに
　これまで、Windows 10（以降Windowsと記載します）の標準NVMeデバイスドライバを使用して、ユーザ空間で動作するプログラムからNVMe SSDに対して以下のコマンドを発行する方法を紹介しました。

* `Read`コマンド、`Write`コマンド、`Dataset Management`コマンド(Deallocate)<sup>[1]</sup>
* `Identify`コマンド<sup>[2]</sup>

　これらのコマンドを使うことで、NVMe SSDの基本情報の取得と、NVMe SSDに対するデータの読み書きができることになります。

　NVMe仕様が備えるこの他の機能のうち、ユーザに直接関係しそうな機能としては、Self-Monitoring Analysis and Reporting Technology(S.M.A.R.T)情報に代表される**ログ**と、**機能設定**が挙げられます。

　このうち、ログを取得するには`Get Log Page`というコマンドを使用します。この`Get Log Page`コマンドを使用してNVMe SSDから様々な情報を取得してしまおう、というのがこの記事の目的になります。

　ちなみに、二つ目の「機能の設定」には、`Set Features`というコマンドを使用します。`Identify`コマンドで取得できる機能の情報は、基本的に「コントローラが持つ能力」であって、一部の項目を除いて現在の「設定内容」ではありません。加えて、`Identify`コマンドでは設定内容の「変更」ができません。
　そこでNVMeでは、「設定内容」の変更のために`Set Features`コマンドを用意しています。この`Set Features`コマンドについては、対になる`Get Features`コマンドとあわせて今後紹介する予定です。

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

　また、先日NVMeから、[最新仕様となるリビジョン1.4の発行についてプレスリリースが行われました](https://nvmexpress.org/new-nvm-express-inc-specifications-bolster-cloud-and-enterprise-advancements/)。ですが、この記事でご紹介する内容については一つ前のリビジョン(1.3d)ベースとさせていただきます。リビジョン1.4の内容については、今後ご紹介する予定です。

# まとめ
* `Get Log Page`コマンドを使用すると、NVMe SSDの動作ログを取得できる
    * いわゆる"S.M.A.R.T"情報もこのコマンドで取得する
* `Get Log Page`コマンドでは、他にもいくつかの情報を取得することができる
    * SSDがサポートしているベンダコマンド（メーカー固有のコマンド）の存在もわかる！？

# NVMeのGet Log Pageコマンドとその発行方法
## 概要
　NVMe仕様<sup>[2]</sup>における`Get Log Page`コマンドとは、NVMeデバイス（面倒なので、以降「デバイス」＝「SSD」として"SSD"と記載します）の各種機能に関する、現時点での状態もしくは現時点までの累積・履歴情報（いわゆるログ情報）を取得するためのコマンドです。

　「ログ情報」として最も有名なものは、やはりストレージデバイスとしては外せない"Self-Monitoring Analysis and Reporting Technology (S.M.A.R.T)"情報でしょうか。もちろん、NVMe SSDのS.M.A.R.T情報は、この`Get Log Page`コマンドで取得できます。

　上記S.M.A.R.T情報の他、NVMe仕様の`Get Log Page`コマンドでは、発生したエラーの（履歴）情報や、コマンドのサポート状況、さらにはSSDの自己診断機能の結果を取得できます。

　取得できる内容に違いはありますが、このコマンドは、SATA SSD/HDDなどで使用できるATA Command Set (ACS)<sup>[3]</sup>で定義されている`(SMART) READ LOG`コマンドや、SCSI Primary Commands (SPC)<sup>[4]</sup>で定義されている`LOG SENSE`コマンドと、同様の機能を持つコマンドだと言えます。

## 基本的な仕組み
　NVMeにおける、`Get Log Page`コマンドを使用したログ情報の取得方法は、読み出したい情報（「ログページ」と呼ぶ）を指定して`Get Log Page`コマンドを発行する、という仕組みです。

　指定可能なログページは、下記のようにNVMe仕様に規定されています（以下の表はNVMe仕様からの抜粋）。

![lid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/3ab751d7-945e-b72e-ba9b-1005402c8988.png "ログページ一覧")

　コマンド発行時にログページを指定する際は、表中の"Log Identifier"を使います。つまり、S.M.A.R.T情報を取得する場合には"Log Identifier"として02hを、発生したエラーの情報を取得したい場合は01hを、SSDの自己診断結果を取得したい場合には06hを指定すれば良い、ということになります。

　この記事では、S.M.A.R.T情報（上記の表中の青枠）と、"Command Supported and Effects"情報（上記の表中の緑枠）を取得する方法とその内容について説明します。

## Get Log Pageコマンドの発行
　早速、`Get Log Page`コマンドを発行してその結果を受け取る方法の説明に移ります。

　以下のフローチャートは、Windowsの標準NVMeデバイスドライバを使用してNVMe SSDに`Get Log Page`コマンドを発行する手順です。
　記事[2]で説明した`Identify`コマンドを発行する手順とほぼ同じです。

![get-log-page-flowchart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/fe11fc68-204d-edaf-1a36-144a57eaa6a1.png "Get Log Pageコマンド発行フローチャート")

　デバイスハンドルを取得する方法は、別記事<sup>[1]</sup>で説明した通りですので、説明は省略します。

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

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

```c
01: BOOL    result = false;
02: PVOID   buffer = NULL;
03: ULONG   bufferLength = 0;
04: ULONG   returnedLength = 0;
05: PSTORAGE_PROPERTY_QUERY query = NULL;
06: PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolData = NULL;
07: PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;
08: PNVME_HEALTH_INFO_LOG pData = NULL;
09: // PNVME_COMMAND_EFFECTS_LOG pData = NULL;

10: bufferLength = offsetof(STORAGE_PROPERTY_QUERY, AdditionalParameters)
        + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)
        + sizeof(NVME_HEALTH_INFO_LOG);
    //  + sizeof(NVME_COMMAND_EFFECTS_LOG);
11: buffer = malloc(bufferLength);

12: ZeroMemory(buffer, bufferLength);
13: query = (PSTORAGE_PROPERTY_QUERY)buffer;
14: protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;

15: query->PropertyId = StorageAdapterProtocolSpecificProperty;
16: query->QueryType = PropertyStandardQuery;
17: protocolData->ProtocolType = ProtocolTypeNvme;
18: protocolData->DataType = NVMeDataTypeLogPage;
19: protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;
20: // protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_COMMAND_EFFECTS;
21: protocolData->ProtocolDataRequestSubValue = NVME_NAMESPACE_ALL;
22: protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
23: protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);
24: // protocolData->ProtocolDataLength = sizeof(NVME_COMMAND_EFFECTS_LOG);

25: result = DeviceIoControl(_hDevice, IOCTL_STORAGE_QUERY_PROPERTY,
    buffer, bufferLength, buffer, bufferLength, &returnedLength, NULL);

26: pData = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);
27: // pData = (PNVME_COMMAND_EFFECTS_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);
```

　5行目に登場する構造体`STORAGE_PROPERTY_QUERY`は、ストレージに問い合わせ（クエリ）をするコマンドを発行する際に使用するデータ構造です。これは`Identify`コマンドの発行時<sup>[3]</sup>と同じです。15行目のプロパティIDの設定や16行目のクエリタイプ設定、17行目のプロトコル(NVMe)設定も同じです。

　`Identify`コマンド発行時と異なるのは、問い合わせた結果として受け取るデータが`Get Log Page`コマンドのデータ構造であること（18行目）と、取得する内容（19行目もしくは20行目）、NVMe SSDから取得するデータサイズの設定（23行目もしくは24行目）、です。

　S.M.A.R.T情報などを取得する場合は19行目と23行目を有効に、Command Supported and Effects情報を取得する場合はコメントアウトしている20行目と24行目を有効にします。

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

　`Get Log Page`コマンドを発行する場合、コマンド発行用構造体のサイズは取得する情報によって変わります。例えば、S.M.A.R.T情報などを取得する場合は上記コード片のように構造体`NVME_HEALTH_INFO_LOG`のサイズを足せばよく、Command Supported and Effects情報を取得する場合は構造体`NVME_COMMAND_EFFECTS_LOG`のサイズを足せばよいことになります（コメントアウトしてあります）。

　構造体`NVME_HEALTH_INFO_LOG`や`NVME_COMMAND_EFFECTS_LOG`は、Windows Driver Kit (WDK)に含まれているヘッダファイル`nvme.h`内で定義されている、それぞれのログページの内容に沿ったデータが定義された構造体です。

　なお、21行目で`NVME_NAMESPACE_ALL`という値を設定しているのは、S.M.A.R.T情報などを取得する場合（Log Identifierに01hを指定する場合）に取得対象を設定する必要があるためです（ログページ一覧の表参照）。ここでは、コントローラの情報を取得するためにこの値（内容は0xFFFFFFFF）を指定しています。Command Supported and Effects情報を取得する場合には不要です（設定したままでも構いません）。この`NVME_NAMESPACE_ALL`も`nvme.h`内で定義されています。

　これらの設定を行い、25行目で`DeviceIoControl()`を呼び出して、コマンド発行を要求します。

　`DeviceIoControl()`が成功すると、11行目で確保したメモリ領域内に、SSDから取得した情報が格納されます。そこで、26行目もしくは27行目のように、取得したデータに該当する構造体にキャストして参照します。

　受信した情報は、仕様書"5.14.1.2 SMART / Health Information (Log Identifier 02h)"もしくは"5.14.1.5 Commands Supported and Effects (Log Identifier 05h)"に記載されているデータ構造にしたがって解析します。

# 取得した内容の見方
## SMART / Health Informationの内容
　Log Identifierに02hを指定した場合に取得できる情報の内容は、仕様書"5.14.1.2 SMART / Health Information (Log Identifier 02h)"に記載されています。

　以下の内容は、実際にとあるNVMe SSDから取得した"SMART / Health Information"の内容を、仕様に従って解釈したものです。「★」マーク以降の記述は、説明のために追加した、各項目の説明です。

```
[I] Critical Warning:
        bit [      4] 0 = Volatile memory backup device has not failed. ★ 揮発メモリをバックアップする装置は故障していない
        bit [      3] 0 = The media has not been placed in read only mode. ★ Read Only Mode（Writeできない状態）ではない
        bit [      2] 0 = The NVM subsystem reliability has not been degraded. ★ 信頼性は低下していない
        bit [      1] 0 = The temperature threshold has not been encountered. ★ 温度閾値に到達していない
        bit [      0] 0 = The available spare capacity has not fallen below the threshold. ★ 利用可能予備領域（サイズ）は閾値未満になっていない
[I] Composite Temperature: 318 (K), 45 (C) ★ 合成温度
[I] Available Spare: 100 (%) ★ 利用可能予備領域（サイズ）
[I] Available Spare Threshold: 10 (%) ★ 予備領域閾値（サイズ）
[I] Percentage Used: 1 (%) ★ 使用済度（100は寿命に達したことを示す）
[I] Data Units Read (Lower 8 bytes): 10742979 (x 512 byte = 5500405248 byte = 5245 MiB) ★ Readデータサイズ
[I] Data Units Written (Lower 8 bytes): 15386344 (x 512 byte = 7877808128 byte = 7512 MiB) ★ Writeデータサイズ
[I] Host Read Commands (Lower 8 bytes): 336185337 (commands) ★ ホストから受信したReadコマンド数
[I] Host Write Commands (Lower 8 bytes): 96688280 (commands) ★ ホストから受信したWriteコマンド数
[I] Controller Busy Time (Lower 8 bytes): 574 (minutes) (= 9 hours) ★ コントローラの稼働時間
[I] Power Cycles (Lower 8 bytes): 242 (times) ★ 電源投入回数
[I] Power On Hours (Lower 8 bytes): 442 (hours) (= 18 days) ★ 電源オン時間
[I] Unsafe Shutdowns (Lower 8 bytes): 52 (times) ★ 正規の手順を経ない電源オフ回数
[I] Media and Data Integrity Errors (Lower 8 bytes): 0 (times) ★ メディア／データ整合性エラー発生回数
[I] Number of Error Information Log Entries (Lower 8 bytes): 0 ★ エラー情報数
[I] Warning Composite Temperature Time: 0 (minutes) (= 0 hours) ★ 合成温度が警告値(WCTEMP)を超えていた時間
[I] Critical Composite Temperature Time: 0 (minutes) (= 0 hours) ★ 合成温度が危険値(CCTEMP)を超えていた時間
[I] Temperature Sensor 1: (not implemented) ★ 温度センサの値
[I] Temperature Sensor 2: (not implemented)
[I] Temperature Sensor 3: (not implemented)
[I] Temperature Sensor 4: (not implemented)
[I] Temperature Sensor 5: (not implemented)
[I] Temperature Sensor 6: (not implemented)
[I] Temperature Sensor 7: (not implemented)
[I] Temperature Sensor 8: (not implemented)
[I] Thermal Management Temperature 1 Transition Count: 0 (times) ★ 温度による制御を行う設定温度1 (TMT1)を超えた回数
[I] Thermal Management Temperature 2 Transition Count: 0 (times) ★ 温度による制御を行う設定温度2 (TMT2)を超えた回数
[I] Total Time For Thermal Management Temperature 1: 0 (seconds) ★ 設定温度1 (TMT1)を超えたことによるに制御をした時間
[I] Total Time For Thermal Management Temperature 2: 0 (seconds) ★ 設定温度2 (TMT1)を超えたことによるに制御をした時間
```

　上記の内容が、`Get Log Page`コマンドで取得できるS.M.A.R.T / Health情報です。
　ドライブの状態や、ホストから受信した総コマンド数とそのコマンドで読み書きした総データサイズ、電源オン時間、そして残り寿命に関する情報が取得できることがわかります。

　なお、残り寿命に関する情報("Percentage Used")は、SSDメーカー独自の計算方法により寿命到達時に100になるように正規化された値です。「何をすれば値が1増えるか」は製品依存ですので注意が必要です。

　加えて、温度に関する情報が多いこともわかります。

　よく知られているように、PCIe / NVMe SSDはそのピーク性能で動作し続けるとコントローラやDRAMやNANDフラッシュメモリがかなり発熱してそれら素子の温度が上昇します。最近ではヒートシンクや冷却シートなどによる温度上昇抑制機構を備えたマザーボードやSSD製品も多くなりました。
　温度の上昇はコントローラ（チップ）の動作不良を招くだけでなく、DRAMおよびNANDフラッシュメモリのデータ保持能力に悪影響を与えます。したがって、NVMe SSDでは温度による動作制御および管理が必要です。

　S.M.A.R.T情報を含めたこのログページの情報は、SSDが使う情報ではなくホストがSSDから取得する情報ですので、例えば温度情報であれば、ホストがSSDの温度制御を抑える措置（高温時にコマンド発行を控える、など）を実施するための情報、もしくはSSDでなんらかの不具合があった時にその原因を調査するための情報、ということになります。

　なお、上記情報中にある"Thermal Management Temperature 1 (TMT1)"や"Thermal Management Temperature 2 (TMT2)"は、今後ご紹介予定の`Set Features`コマンドで設定可能な値（閾値温度）です。

## Commands Supported and Effectsの内容
　Log Identifierに05hを指定した場合に取得できる情報の内容は、仕様書"5.14.1.5 Commands Supported and Effects (Log Identifier 05h)"に記載されています。

　この情報は、`Get Log Page`コマンドで取得可能な他の情報とは性質が異なります。説明より先に実際の情報を見ていただいたほうがわかりやすいと思いますので、まず実際に取得した情報をご紹介します。

　以下の内容は、とあるNVMe SSDから取得した"Commands Supported and Effects"の内容の一部を、仕様に従って解釈したものです。

```
[I] Command Supported and Effect Log: NVM Command
[I] Opcode = 0x01  [ Write ] Supported (bit 0: 1)
        bit [ 18: 16] 0 = No command submission or execution restriction
        bit [      4] 0 = This command does not modify controller capabilities
        bit [      3] 0 = This command does not modify the number of namespaces or capabilities for multiple namespaces
        bit [      2] 0 = This command does not modify any namespace capabilities for the specified namespace
        bit [      1] 1 = This command may modify logical block content in one or more namespaces
```

　この情報から、このSSDについて、以下のことがわかります。

* NVM Command SetのOpcode = 01hのコマンド（`Write`コマンド）について
  * このコマンドは、サポートされている
  * このコマンドは、発行と実行に制約はない
  * このコマンドは、コントローラの機能を変更しない
  * このコマンドは、ネームスペースの数や機能を変更しない
  * このコマンドは、（操作対象）ネームスペースの機能を変更しない
  * このコマンドは、（一つ以上の）ネームスペースの論理ブロック（セクタ）データを変更する可能性がある

　いかがでしょうか。

　そうなのです、この"Command Supported and Effects"という情報は、SSDがサポートしているコマンドの情報なのです。この情報を見れば、SSDがどの（オペコードの）コマンドをサポートしているのか、どのコマンドをサポートしていないのか、サポートしている場合のコマンド発行に関する制約や副作用の有無、がすべてわかるのです（正直に表明していれば、ですが）。

　なぜこの情報をご紹介したのかというと、Windowsの標準NVMeデバイスドライバを使用してNVMe SSDにアクセスする場合、この"Command Supported and Effects"の情報が非常に重要だからです。

　重要である理由は、Microsoftによる説明<sup>[6]</sup>の中にある以下の記述に依ります。

> StorNVMe.sys and Storport.sys will block any command to a device if it is not described in the Command Effects Log.
>（中略）
> When CSUPP is set to ‘0’ (signifying that the command is not supported) the command will be blocked

　つまり、Windowsの標準NVMeデバイスドライバを使用してNVMe SSDにアクセスする場合、この"Command Supported and Effects"の情報に「サポートしている」と表明されているコマンドしか発行できないのです。

　逆に言えば、この"Command Supported and Effects"の情報に「サポートしている」と表明されているコマンドであれば、たとえそれがベンダ固有のコマンドであっても、Windowsの標準NVMeデバイスドライバを使用して発行できる（はず）、ということになります。

　例えば、手元のNVMe SSDについて調べてみると、Admin Command Setのコマンドについて、以下のような情報が取得できました。

```
[I] Opcode = 0xE5  [ (Vendor specific) ] Supported (bit 0: 1)
        bit [ 18: 16] 0 = No command submission or execution restriction
        bit [      4] 0 = This command does not modify controller capabilities
        bit [      3] 0 = This command does not modify the number of namespaces or capabilities for multiple namespaces
        bit [      2] 0 = This command does not modify any namespace capabilities for the specified namespace
        bit [      1] 1 = This command may modify logical block content in one or more namespaces
```

　NVMe仕様上、Admin Command SetのOpcode C0hからFFhのコマンドは、"Vendor Specific"つまり「ベンダ固有」と定義されています。

　したがって、上記Opcode = E5hのコマンドはベンダ固有コマンドなのですが、この"Command Supported and Effects"の情報に「サポートしている」と表明されているので、Windowsの標準NVMeデバイスドライバを使用して発行可能、ということになります。

　もちろん、ベンダ固有コマンドなので、コマンド発行に際してどんな情報が必要なのか、コマンドを発行すると何が起きるのか、コマンドを発行した結果何か情報を受け取るのか、もし情報を受け取る場合のサイズやデータ構造、などは全くわかりません。

　しかも、例に挙げた上記コマンドの場合、「一つ以上のネームスペースの論理ブロック（セクタ）データを変更する可能性がある」と記述されているため、コマンドを発行した結果データを失う可能性があり、迂闊に試すことはできません。

　もしコマンド発行に関する上記「制約」や「副作用」が「何もない」と表明されていれば、試してみることはできるかもしれません。もちろん、試すときは十分に注意する必要があります。

# まとめ
　この記事では、NVMe SSDの現時点での状態もしくは現時点までの累積・履歴情報（いわゆるログ情報）を取得するためのコマンドである`Get Log Page`コマンドを、Windowsの標準NVMeデバイスドライバを使用して発行する方法をまとめました。

　`Get Log Page`コマンドを使うことで、ストレージデバイスとしては一般的な"Self-Monitoring Analysis and Reporting Technology (S.M.A.R.T)"情報のほか、発生したエラーの（履歴）情報や、コマンドのサポート状況、さらにはSSDの自己診断機能の結果を取得できます。

　この記事で紹介した方法は、Windowsの標準NVMeデバイスドライバを使用していますので、とても手軽に試せることが特徴です。もしご興味があれば、ぜひ試してみてください。

# 参考文献
[1] ["Windowsの標準NVMeドライバでNVMe SSDにアクセスする(Read/Write/Dataset Management)"](https://qiita.com/ken-yossy/items/30f0dd206713aba770f7), July 3, 2019 
[2] ["Windowsの標準NVMeドライバでNVMe SSDにアクセスする(Identify)"](https://qiita.com/ken-yossy/items/d4cd6502fe475d78e872), July 17, 2019 
[3] NVM Express, _"NVM ExpressTM Base Specification"_, Revision 1.3d, March 20, 2019
[4] T13 / International Committee for Information Technology Standards (INCITS), _"ATA Command Set - 4 (ACS-4)"_, Revision 14, October 14, 2016
[5] T10 / International Committee for Information Technology Standards (INCITS), _"SCSI Primary Commands - 4 (SPC-4)"_, Revision 23, February 9, 2010
[6] Microsoft, [_"Working with NVMe drives"_](https://docs.microsoft.com/en-us/windows/desktop/fileio/working-with-nvme-devices), 最終閲覧日2019年5月14日

# ライセンス表記
<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>の下に提供されています。