NVMe Revision 1.4: Other changes

# はじめに
　以下の予定で、2019/6/10にリリースされたNVM Express（以降NVMeと表記します）の最新仕様であるリビジョン1.4<sup>[1]</sup>について、新機能や注目（注意）が必要な変更点、をまとめています。今回が最終回になります。

1. NVM Setとその関連機能 → [2019/8/28投稿の記事](https://qiita.com/ken-yossy/items/495cb481e254eb306c55)
2. 新コマンドと新機能（NVM Setとその関連機能を除く） → [2019/9/11投稿の記事](https://qiita.com/ken-yossy/items/ae42c06d8c9face76934)
3. 既存機能に対する変更点のうち注意が必要なもの

　この記事では、NVMeリビジョン1.4における、既存機能に対する変更点のうち注意が必要なものをまとめます。

# サマリ
* 注意（注目）が必要だと思われる変更点がある機能は以下の通り
    * Controller Type
    * Controller Memory Buffer (CMB)
    * `Flush`コマンド
    * `Sanitize`コマンド
* 変更の理由と目的はおおむね理解できるものの、複雑になる方向の変更なので、注意が必要

# Controller Type
　NVMe over Fabrics (NVMe-oF)仕様との整合性をとるため、などの理由により、「コントローラタイプ」が導入されました。

　具体的には、**I/O Controller**、**Administrative Controller**、**Discovery Controller**の3つです。以下に、NVMe仕様書記載の図を引用します。

![controller-type.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/98d55023-1b47-dbdf-4f78-c7e96e8c0098.png)
図1：Controller Type（NVMe仕様書より引用）

　NVMeリビジョン1.4に準拠したコントローラは、自分がどのコントローラタイプなのか、を`Identify`コマンドの結果で表明しなければなりません。

　**I/O Controller**は、ReadやWriteといったコマンドをサポートし、不揮発性メディアへのアクセス手段を提供するコントローラを指します。リビジョン1.3dまでの仕様における「コントローラ」は、この「I/Oコントローラ」に相当します。

　**Administrative Controller**は、管理機能に特化したコントローラです。このため、このタイプのコントローラはI/Oキュー（I/O Submission QueueとI/O Completion Queue）を作成・削除するコマンドをサポートできません。したがって、このタイプのコントローラはI/Oコマンド（ReadやWriteなど）を受信することができません。

　**Discovery Controller**は、NVMe-oF環境で必要となるコントローラで、特定のログページ（`Get Log Page`コマンドで取得可能なデータ）を提供するコントローラであることのみが記載されています。このタイプのコントローラに関する詳細は、NVMe-oFの仕様を参照する必要があります。

# Controller Memory Buffer (CMB)
　Controller Memory Buffer (CMB) とは、NVMeドライブ（のコントローラ）が持つメモリ領域に各種キュー（I/O Submission Queueなど）やデータを配置することを可能にする機能です。

　このCMBについては、リビジョン1.4において設定項目が大幅に変更・増加され、以前と比較してCMBを有効にするまでの手順が複雑に（煩雑に）なりました。

　リビジョン1.3dまでは、ホストは以下の手順でCMBを使用することができました。

1. CMB領域のベースアドレスを取得する（`CMBLOC`レジスタ）
2. CMB領域のサイズを取得する（`CMBSZ`レジスタ）

　これに対して、リビジョン1.4では、CMBを使用するためにはホストは以下の手順を踏む必要があります。

1. コントローラがCMBをサポートしているかを確認する（`CAP`レジスタの`CMBS`フィールド）
2. コントローラがCMB設定レジスタを有効にしているかどうか確認する（`CMBMSC`レジスタ）
3. CMB領域がマップされているアドレス空間上の領域を確認する（`CMBLOC`レジスタ）
4. CMB領域のサイズを確認する（`CMBSZ`レジスタ）
5. コントローラにCMB領域へのアクセスを要求する時に発行するアドレス領域を設定する（`CMBMSC`レジスタ）
6. コントローラにCMB領域の有効化を要求する（`CMBMSC`レジスタ）
7. コントローラがCMBを有効にしたことを確認する（`CMBSTS`レジスタ）

　煩雑になっているのが一目瞭然ですね。
　仕様書を読む限り、この変更の目的は2つあると考えられます。

　ひとつは、**CMBが有効になるタイミングを明確化すること**、です。

　リビジョン1.3dまでの仕様では、極論すると、コントローラの設定レジスタが読めるようになったタイミングでCMBが有効である、と解釈することもできてしまいます。これは曖昧でかつコントローラにとっては大変なので、CMBが有効になるタイミングを明確に定義するとともに、CMBが有効になるタイミングをコントローラに委ねるようにしたのだと考えられます。

　もうひとつは、**仮想化環境への対応**、です。

　ゲストOSがCMBを利用していて、例えば`Read`や`Write`コマンドのデータ転送先・転送元としてCMB内のアドレスを指定したとします。この時、ゲストOSが指定したアドレスがゲストOSのアドレス空間上でのアドレス（のまま）になる可能性があります。
　一方で、SSDコントローラがCMBをマップしているアドレス空間はホストOSのアドレス空間です。この結果、SSDコントローラは誤ったアドレスに対してDMA転送を要求してしまいます（図2）。

![仮想化環境でのCMB利用時に不具合が生じるケース（リビジョン1.3dまで）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/142eaa3b-a8a4-59f9-6f04-378472a785ec.png)
図2：仮想化環境でのCMB利用時に不具合が生じるケース（リビジョン1.3dまで）

　この間違いを防止するためには、ハイパーバイザがゲストOS間でコンテキストを切り替える際に「ホストがCMB領域へのアクセスを要求する時に発行するアドレス領域」をゲストOSに合わせて設定する、という方法が考えられます（図3）。

![仮想化環境でのCMB利用（リビジョン1.4）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/04bef1f1-4ee6-11ba-3888-1ae4f8b5d419.png)
図3：仮想化環境でのCMB利用（リビジョン1.4）

　図3のようにすることで、コントローラは、ゲストOSがデータ転送先として指定しているアドレスはCMB領域の先頭からオフセット`A-B`のアドレスだ、ということがわかりますので、正しくデータ転送できます。

　リビジョン1.4では、この「CMB領域へのアクセスを要求する時に発行するアドレス領域」のベースアドレスを設定するフィールドが新設されました。
　CMBについては、上記仕様変更の他に、CMB上に配置するデータに関する設定項目が数多く追加されていますので、注意が必要です。

　なお、図2に示した不具合はPersistent Memory Region (PMR)でも発生し得ますので、PMRについても同様の設定項目が存在します。

# Flushコマンド
　`Flush`コマンドは、NVMe仕様におけるVolatile Write Cache (VWC)に格納されていて不揮発化されていないデータを不揮発化することを要求するコマンドです。`Read`コマンドや`Write`コマンドの次に発行頻度が高そうなコマンドでしょうか。

　この`Flush`コマンドでは、以前よりNamespace ID (NSID)フィールドに処理対象のNamespaceを指定する必要がありましたが、`0xFFFFFFFF`が指定された場合の挙動が未定義でした。

　そこでリビジョン1.4では、対象のNamespaceとして`0xFFFFFFFF`を指定した場合の挙動が定義されました。

　具体的には、`Flush`コマンドのNSIDとして`0xFFFFFFFF`が指定された時に「エラーにする」か「全Namespaceが対象と解釈する」かを、コントローラが選択します。
　そしてコントローラは、NSIDに`0xFFFFFFFF`を指定された`Flush`コマンドに対して前記2つのどちらの対応をするかについて、`Identify`コマンドに対するレスポンスであるIdentify Controller Data Structureで表明します（図4）。

![Identify Controller Data StructureのVolatile Write Cache (VWC)フィールド](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/c24a356d-f60d-24e5-d3e8-634450c96cd6.png)
図4：Identify Controller Data StructureのVolatile Write Cache (VWC)フィールド

　つまり、リビジョン1.4準拠を謳うコントローラは、上記VWCフィールドのビット[2:1]を10bか11bにしなければなりません。

　この仕様変更はリビジョン1.3dまでと互換性がありませんので、NVMeドライバソフトウェアには注意が必要です。

　なぜなら、リビジョン1.3dまでは`NSID`フィールドが`0xFFFFFFFF`でも`Flush`コマンドはエラーにならなかった可能性が高いですが、リビジョン1.4ではエラーになってしまう可能性があるからです。
　これを回避するためには、NVMeデバイスドライバは、接続しているNVMeデバイスが準拠するバージョンをきちんとチェックするか、もしくはNSIDに`0xFFFFFFFF`を指定しないようにしなければなりません。

　試しに、qemuを使用して、Windows 10 Home Build 18950（Insider Programで入手）に同梱されているMicrosoftのinboxデバイスドライバ(stornvme.sys)の挙動を調べてみました。デバイスドライバのバージョンは`10.0.18950.1000 (WinBuild 160101.0800)`です。

　ベンチマークソフトウェアを動かしたり、再起動を行ったりしましたが、その実験中に限って言えば、このデバイスドライバは、ドライブが準拠するNVMeのリビジョンが1.4でなくても、`Flush`コマンドの`NSID`フィールドに`0xFFFFFFFF`を入れることはなく、実在するNamespaceのIDを入れていました（図4）。

![Windows 10 Home (Build 18950)同梱NVMeドライバのFlushコマンドに関する挙動調査結果](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/38e3924a-3ee8-27b8-3ccf-9b378a4eb2d9.png)
図4：Windows 10 Home (Build 18950)同梱NVMeドライバのFlushコマンドに関する挙動調査結果

　これだけでは「NSIDに`0xFFFFFFFF`を入れることは絶対にない」とは断定できませんが、この挙動であれば、NVMeリビジョン1.4準拠のNVMeドライブを接続してもこの`Flush`コマンドの仕様変更に関する問題を起こす可能性は低そうです。

# Sanitizeコマンド
　`Sanitize`コマンドとは、ドライブ内のデータを破棄する（読めなくする）機能です。ユーザデータを記録しているNANDフラッシュメモリのブロックを消去(Erase)する方法(Block Erase)、データを上書きする方法(Overwrite)、そして暗号化に使用している鍵を変える方法(Crypto Erase)、の3種類が定義されています。

　リビジョン1.3dまでの仕様は、コントローラが上記3つの方法のうちどれをサポートしているのかを表明し、ホストが`Sanitize`コマンドで、`Sanitize`コマンドが成功した後にSanitizeしたLBAに対してDeallocateするかどうかを指定する、というものでした。

　これに対してリビジョン1.4の仕様では、`Sanitize`コマンドに関する設定項目が増えて、動作が複雑になりました。複雑になったのは（仕様変更の目的は）、以下の2点をより明確にするためだと思われます。

1. `Sanitize`コマンドの処理においてDeallocateするかどうか
2. `Sanitize`コマンド処理の「残り所要時間」（`Get Log Page`コマンドで取得できる）にメディア（例：NANDフラッシュメモリ）を変更する処理（仕様書には具体的な処理の内容は記載されていない）にかかる時間がふくまれているかどうか

　言葉で説明するのは難しいので、フローチャート（図5）にまとめました。

![Sanitizeコマンドに関する設定項目の関係](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/439683/c07b22b0-17d3-65be-4fa5-45301c3093e2.png)
図5：Sanitizeコマンドに関する設定項目の関係

　結局Deallocateするのかしないのか、フローチャートを最後まで辿らないと決まらないことがわかります。

　そもそも、"No Deallocate Inhibit"とか"No Deallocate Response Mode"など、否定形の設定項目がいくつも重なっていることがわかりにくくなっている原因に見えます。"No Deallocate Inhibit"に至っては二重否定ですし。

　`Sanitize`コマンドはそうそう頻繁に使うコマンドではないでしょうし、図5で示した場合分けのうち実際に用いられるパターンは限られるでしょうから、実用上問題にはならないのかもしれません。`Sanitize`コマンドをサポートするSSDベンダは大変ですが。

# まとめ
　この記事では、2019年6月10日に発行されたNVMeの最新仕様であるリビジョン1.4について、既存機能に対する変更点のうち注意が必要なものについて、その概要を説明しました。

　記事内でも触れたように、リビジョン1.4で既存機能に加えられたいくつかの変更については、エンタープライズやデータセンター環境だけでなくクライアント環境にも関係があり、またSSDとホストのデバイスドライバの間での相性問題を引き起こし兼ねないため、注意が必要です。

# リファレンス
[1] NVM Express, _"NVM Express<sup>TM</sup> Base Specification"_, Revision 1.4, June 10, 2019

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