CXLエミュレーション環境について（補足編）

# 0. はじめに

## 0.1 今年を振り返って
この記事は[Fujitsu Advent Calendar 2024](https://qiita.com/advent-calendar/2024/fujitsu) の20日目の記事です。
なお、本記事は個人の意見に基づくものであり、組織を代表するものではありません。

今年もこの季節がやってきました。皆様いかがお過ごしでしょうか？私にとっては今年はなんというか地味に苦しい年だったような気がします。とはいっても、あまり愚痴を言っても仕方ないので早速本題に入りましょうか…。

今年の話題は[Open Source Summit Japanでの発表内容](https://ossaidevjapan24.sched.com/event/1jKCR/exploring-cxl-memory-configuration-and-emulation-yasunori-goto-fsas-technologies-inc)だった[CXL Emulation環境の作成](https://speakerdeck.com/yasunorigoto1/exploring-cxl-memory-configuration-and-emulation)について、時間内に収まらなかった話などを中心に、いくつかの事柄について記載していきたいと思います。

# 0.1 CXL仕様 Revision 3.2のリリース

表題の件に入る前に、まずは今月のニュースの話から入りましょう。
日本時間の12/4[^1]にCXL Revision 3.2（以下v3.2)の仕様が新たに公開されました。

[^1]: なお、仕様書の日付は10/2となっているので、2か月前には内容がほぼfixされていたようですね。


[リリースノート](https://computeexpresslink.org/wp-content/uploads/2024/12/CXL_3.2-Spec-Announcement_FINAL-1.pdf)によると、更新個所は以下とされています。

---
__Highlights of the CXL 3.2 Specification:__
+ Optimized CXL Memory Device monitoring and management
    - New CXL Hot-Page Monitoring Unit (CHMU) for memory tiering
    - Common event record
    - Compatibility with PCIe Management Message Pass Through (MMPT)
    - CXL online firmware (FW) activation capabilities
+ Enhanced functionality of CXL Memory Devices for OS and Application
    - Post Package Repair (PPR) enhancements
    - Additional performance monitoring events for CXL Memory Devices
+ Extends security with the Trusted Security Protocol (TSP)
    - New Meta-bits Storage Feature for Host-only Coherent Host-Managed Device Memory (HDM-H) address regions
    - Improved security by expanding IDE protection
    - Increases security of Host-only Coherent Device-Managed Memory with Back-Invalidation (HDM-DB) memory devices
    - Enhances compliance tests for interoperability
+ Full backward compatibility with all previous CXL specifications
---

この中で、個人的な注目点は __New CXL Hot-Page Monitoring Unit (CHMU) for memory tiering__ です。これは、[Memory Tiering](https://speakerdeck.com/yasunorigoto1/compute-express-link-cxl-the-next-generation-interconnect-overview-and-the-status-of-linux-open-source-summit-japan-2023-ver-dot?slide=32)の機能をハード的に支援してくれる機能になります。

これまではPromote[^promote]する判定をOSがソフト的に行っていましたが、これにはある制約と引き換えでした。それは、Promoteする対象となるアクセス頻度の高いhotなページを判定するために、__OSがアプリのメモリのメモリアクセスをいったん禁止し、実際にメモリアクセスされたらpage faultを発生させ、それをトラップする__ という操作が必要になることです。本来はすぐにアクセスできるところを余分にpage faultの処理が入るわけですから、どうしても性能上は遅くなってしまいます。

[^promote]: CXLメモリは従来のDDRメモリよりもレイテンシー性能がやや遅いことが予想されています。このため、CXLメモリの中でアクセス頻度の高いHotなメモリの内容をDDRメモリへ移す動作をPromoteと呼びます。逆にDDRメモリ中でアクセス頻度が低いメモリをCXLメモリに移動させる動作をDemoteと呼びます。

しかし、このhot判定をハードが行えるようにすれば、余計なpage fault処理が不要となるため、その分性能劣化を防ぐことができます。この機能に対応したハードとkernelが出てくれば、Memory Tieringの機能は一層強化されることでしょう。

他の機能については私もまだ学習中ですが、タイトルだけで判断すると、ファームウェアの強化、性能モニタリングの強化、セキュリティ機能の強化などがなされているようです。

# 1. CXLエミュレーション環境におけるCXLメモリのhotplug
さて、それでは本題に入りましょう。まずはエミュレーション環境下でのhotplugの方法です。

:::note info
ここからの記述は、今年のOpen Source Summit Japanで話した内容の続きになります。まだご覧になってない方は、先に上記の発表資料をご覧ください。
:::

## 1.1 CXLメモリデバイスのHotAdd
実は、QEMUにおけるHot Addの方法はそれほど難しくはありません。
その説明のために、まずは一旦シンプルなCXLエミュレーションの設定例を再掲[^2]しましょう。

[^2]: この設定、実は自宅の環境における設定ですが、Slideで話していた私の会社での環境と違って、何故かvNICの設定が上手くいきませんでした…。今回の本題から外れるので諦めて、操作はQEMUコンソールの画面から直接行っています。このQEMUのオプション指定のままだとNICの設定が抜けているので、ネットワークがつながらないので注意してください。

~~~bash
# 変更前のシンプルな設定
sudo /home/goto/qemu/build/qemu-system-x86_64 \
-drive file=/var/lib/libvirt/images/fedora.qcow2,format=qcow2,index=0,media=disk,id=hd \
-m 16G,slots=8,maxmem=32G -machine type=q35,accel=tcg,cxl=on -smp 16 \
-nographic \
-object memory-backend-ram,id=vmem0,share=on,size=1G \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=0,bus=cxl.1,id=root_port13,chassis=0,slot=2 \
-device cxl-type3,bus=root_port13,volatile-memdev=vmem0,id=cxl-vmem0 \   <--- 後からHotAddするためにこの行を削除する
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=32G
~~~

このままだと、ゲストマシンはシステム起動時からCXLメモリデバイスを認識している形となりますが、これを後からゲストマシンにhot-addする形に変更しましょう。
まず、起動時にゲストマシンに当該メモリデバイスを認識させないようにする…つまり、CXLメモリデバイス(-device cxl-type3)の定義の行を1行削除してQEMUを起動します。

~~~bash
# 変更後
sudo /home/goto/qemu/build/qemu-system-x86_64 \
-drive file=/var/lib/libvirt/images/fedora.qcow2,format=qcow2,index=0,media=disk,id=hd \
-m 16G,slots=8,maxmem=32G -machine type=q35,accel=tcg,cxl=on -smp 16 \
-nographic \
-object memory-backend-ram,id=vmem0,share=on,size=1G \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=0,bus=cxl.1,id=root_port13,chassis=0,slot=2 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=32G
~~~

起動したら"CTRL+a"⇒"c"の順に入力して、QEMU consoleを起動しましょう

~~~console
Fedora Linux 41 (Forty One)
Kernel 6.12.0+ on an x86_64 (ttyS0)

fedora login: QEMU 9.1.92 monitor - type 'help' for more information
(qemu)
~~~

ここで、device_addのコマンドと共に、先ほど削除した1行の情報を入力します。
~~~
(qemu) device_add cxl-type3,bus=root_port13,volatile-memdev=vmem0,id=cxl-vmem0
~~~
そうすると、CXLメモリデバイスのHotAddができます。コンソールにPCIeの表示が多数流れますが、CXLデバイスのhotplugの仕様はPCIeのhotplugの仕様をそのまま用いているからです。
~~~
(qemu) [  104.529348] pcieport 0000:0c:00.0: pciehp: Slot(2): Button press: will power on in 5 sec
[  104.530858] pcieport 0000:0c:00.0: pciehp: Slot(2): Card present
[  104.531242] pcieport 0000:0c:00.0: pciehp: Slot(2): Link Up
[  104.659492] pci 0000:0d:00.0: [8086:0d93] type 00 class 0x050210 PCIe Endpoint
[  104.660940] pci 0000:0d:00.0: BAR 0 [mem 0x00000000-0x0000ffff 64bit]
[  104.662026] pci 0000:0d:00.0: BAR 2 [mem 0x00000000-0x00000fff 64bit]
[  104.662599] pci 0000:0d:00.0: BAR 4 [mem 0x00000000-0x00000fff]
[  104.663509] pci 0000:0d:00.0: enabling Extended Tags
[  104.688323] pcieport 0000:0c:00.0: bridge window [io  0x1000-0x0fff] to [bus 0d] add_size 1000
[  104.691686] pcieport 0000:0c:00.0: bridge window [io  size 0x1000]: can't assign; no space
[  104.692548] pcieport 0000:0c:00.0: bridge window [io  size 0x1000]: failed to assign
[  104.693774] pcieport 0000:0c:00.0: bridge window [io  size 0x1000]: can't assign; no space
[  104.694545] pcieport 0000:0c:00.0: bridge window [io  size 0x1000]: failed to assign
[  104.696286] pci 0000:0d:00.0: BAR 0 [mem 0xfe800000-0xfe80ffff 64bit]: assigned
[  104.697500] pci 0000:0d:00.0: BAR 2 [mem 0xfe810000-0xfe810fff 64bit]: assigned
[  104.698040] pci 0000:0d:00.0: BAR 4 [mem 0xfe811000-0xfe811fff]: assigned
[  104.698813] pcieport 0000:0c:00.0: PCI bridge to [bus 0d]
[  104.705682] pcieport 0000:0c:00.0:   bridge window [mem 0xfe800000-0xfe9fffff]
[  104.711352] pcieport 0000:0c:00.0:   bridge window [mem 0xc040000000-0xc05fffffff 64bit pref]
[  104.841217] cxl_pci 0000:0d:00.0: enabling device (0000 -> 0002)
~~~

厳密にいうと、この段階で行われたのはPCI expressのドライバーによるCXLメモリデバイスの認識の動作となります。まだ、OSやプロセスがこの領域のメモリを使える状態にはなっていません。OSのメモリ管理系がメモリホットプラグの動作を行うのは、実際にはcxl create-regionなどの操作の後になります。

言い換えると、この後は、上記発表スライドで紹介したとおり、`cxl create-region`コマンドを投入すれば、OSとしてCXLメモリが使えるようになります。

ただし、この後続けてHotRemoveの操作を行うため、create-regionの前にもうひと手間かける必要があります。
昨年のメモリホットプラグの記事では、[auto_online_blocksの機能を説明](https://qiita.com/YasunoriGoto1/items/657fd0dcd6887881f9a9#52-%E8%87%AA%E5%8B%95%E3%81%A7online%E3%81%97%E3%81%9F%E3%81%A8%E3%81%8D%E3%81%AE%E3%83%87%E3%83%95%E3%82%A9%E3%83%AB%E3%83%88%E3%81%AEzone%E3%81%AE%E6%8C%87%E5%AE%9A9)しています。`cxl create-region`コマンドでCXLメモリを利用する前にこの`auto_onlne_blocks`の設定（もしくはそのほかのZONE_MOVABLEを作る設定）をしておかないと、CXLメモリをHotRemoveできなくなる可能性があります.

`cxl create-region`によってCXLメモリを利用する前に、まずはこの設定をやっておきましょう。
```console
# echo online_movable > /sys/devices/system/memory/auto_online_blocks
# cat /sys/devices/system/memory/auto_online_blocks
online_movable
```
それでは、改めて`cxl create-region`で、このCXLメモリの領域をOSに追加しましょう。
なお、本コマンドはQEMUのコンソール上で実施しているので、"cxl region0: Bypassing..."のメッセージ[^3]は、コンソールに出力されたメッセージがそのまま出力されたものです。

[^3]: このメッセージは、スライド中で述べた`CONFIG_REGION_INVALIDATION_TEST`が有効になっている時に出るものです。本来ベアメタル環境では必ず実行しないといけないCPU命令を、テスト用のゲスト環境であるためにバイパスしていることを示しています。

```console
# cxl create-region -d decoder0.0 -m mem0 -t ram
[  333.212435] cxl region0: Bypassing cpu_cache_invalidate_memregion() for testing!
{
  "region":"region0",
  "resource":"0xa90000000",
  "size":"1024.00 MiB (1073.74 MB)",
  "type":"ram",
  "interleave_ways":1,
  "interleave_granularity":256,
  "decode_state":"commit",
  "mappings":[
    {
      "position":0,
      "memdev":"mem0",
      "decoder":"decoder2.0"
    }
  ],
  "qos_class_mismatch":true
}
cxl region: cmd_create_region: created 1 region
```
なお、コンソールにはほかにも以下のようにメモリが追加された時のメッセージが出ています
```console
[  333.546658] Fallback order for Node 1: 0
[  333.549632] Built 1 zonelists, mobility grouping on.  Total pages: 4065737
[  333.551509] Policy zone: Normal
[  333.572212] Fallback order for Node 0: 0 1
[  333.572315] Fallback order for Node 1: 1 0
[  333.573059] Built 2 zonelists, mobility grouping on.  Total pages: 4098505
[  333.573716] Policy zone: Normal
[  333.606321] Demotion targets for Node 0: preferred: 1, fallback: 1
[  333.607798] Demotion targets for Node 1: null
```

これで、OSやプロセスから追加したCXLメモリが使える状態となりました。

## 1.2 CXLメモリのHotRemove

では、HotRemoveの操作に入りましょう。HotAddでは`cxl create-region`の後は一気にOSのメモリの認識までできるようになりましたが、HotRemoveでは、それよりも段階を踏まえた操作が必要になります。まずは、以下のコマンドを投入します。

```console
# cxl disable-region region0
cxl region: cmd_disable_region: disabled 1 region
```

```console
[ 1202.915942] Fallback order for Node 0: 0
[ 1202.916028] Fallback order for Node 1: 0
[ 1202.916039] Built 2 zonelists, mobility grouping on.  Total pages: 4065737
[ 1202.916979] Policy zone: Normal
[ 1202.965516] Demotion targets for Node 0: null
```

regionをdisable すなわち無効化するコマンドになりますが、この際OSのmemory offlineの処理が働きます。CXLメモリ上で利用中のメモリはmemory migrationの機能によって、他の領域に移動させられます。メモリの使用量が多い場合は、多くのメモリの内容を移動しないといけないため、このコマンドの実行に時間がかかるかもしれません。

なお、前述のように`auto_online_blocks`の設定をやり忘れていると、memory offlineの処理ができないため、以下のようにこのコマンドは失敗してしまいます。kernelなどのモジュールが追加されたCXLメモリを早速使ってしまったりすると、offlineすることができず失敗してしまうのです。

```console
# cxl disable-region region0
[  249.540976] page: refcount:1 mapcount:0 mapping:0000000000000000 index:0xffff89db50007800 pfn:0xa90000
[  249.543873] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
[  249.546004] flags: 0x57ffffc0000240(workingset|head|node=1|zone=2|lastcpupid=0x1fffff)
[  249.549780] page_type: f5(slab)
[  249.552709] raw: 0057ffffc0000240 ffff89d1c0045300 ffff89d1d2d8ddd0 ffff89d1d2d8ddd0
[  249.555275] raw: ffff89db50007800 0000000000100002 00000001f5000000 0000000000000000
[  249.558134] head: 0057ffffc0000240 ffff89d1c0045300 ffff89d1d2d8ddd0 ffff89d1d2d8ddd0
[  249.560246] head: ffff89db50007800 0000000000100002 00000001f5000000 0000000000000000
[  249.561249] head: 0057ffffc0000003 fffff5cd6a400001 ffffffffffffffff 0000000000000000
[  249.562097] head: 0000000000000008 0000000000000000 00000000ffffffff 0000000000000000
[  249.562934] page dumped because: unmovable page
libdaxctl: offline_one_memblock: dax0.0: Failed to offline /sys/devices/system/node/node1/memory338/state: Device or resource busy
cxl region: disable_region: region0: unable to offline dax0.0: Device or resource busy
cxl region: decoder_region_action: region0: failed: Device or resource busy
cxl region: region_action: one or more failures, last failure: Device or resource busy
cxl region: cmd_disable_region: disabled 0 regions
```

また、`cxl disable-region`に対応するHotAdd時のコマンドは`cxl enable-region`なのですが、このコマンドに相当する処理は`cxl create-region`の時に一気に行われます。このため、`cxl enable-region`のコマンドは使う機会が限られたものとなっています。

さて、次に実行するコマンドは`cxl destroy-region`です。これにより、region0を解放します。
```console
# cxl destroy-region region0
cxl region: cmd_destroy_region: destroyed 1 region
```

これで、`cxl create-region`を実施する前の段階まで戻ってきました。
次にこのメモリデバイスをOSから切り離します[^4]。
[^4]: 内部的にはunbindという操作を行うようです。

```console
# cxl disable-memdev mem0
cxl memdev: cmd_disable_memdev: disabled 1 mem
```
これでOS側の処理としては終わりです。
ただし、まだQEMU側としてはデバイスが残ったままになっているように見えています。
QEMUからもこのデバイスを取り外しましょう。以下のコマンドを投入すると、PCIeのhotremoveのノッチ操作を行った時と同じメッセージが出力されることが分かります。

```console
(qemu) device_del cxl-vmem0
(qemu) [ 2466.720143] pcieport 0000:0c:00.0: pciehp: Slot(2): Button press: will power off in 5 sec
```

この処理が完了すると、QEMUでdevice_addのコマンドを投入する前の状態に戻るというわけです。これで、HotRemoveの操作はおしまいです。

:::note alert
OS側のHotRemoveの処理が完了する前にQEMUのdevice_delコマンドを投入するのはやめましょう。システムが利用中に強引にデバイスを取り外す行為をCXL仕様では __Surprise Hot-Remove__ と呼んでおり、通常のHotRemoveの処理とは明確に記述を分けています。それを行った際の動作の結果は少なくともOSとして保証できるものではなく、システムパニックなど、何が起きてもおかしくない状態となります。
:::


## 1.3 余談 (Memory HotRemoveのrace condition bug)
実は、このCXLのメモリデバイスのHotplugの機能をうちのチームでテストしていた際、kernelのmemory hotplugの[race condition bug](https://lore.kernel.org/lkml/dcf9958e-a5ae-4219-87ba-b9833d2c1cf2@fujitsu.com/T/#m0eedea2f63939bd8a803cd2439d59d79f3f8b1eb)を見つけました。中々興味深いバグなので少し横道にそれますが、せっかくなので紹介しましょう。

Linux kernelのメモリ管理機構にはper cpu pages(pcp)と呼ばれる機能があります。これは、端的に言ってしまえば、「各CPUごとに確保される、free pageのキャッシュ」といってよいでしょう。
freeされたばかりのページはbuddy allocatorに返却される前に、いったんこのpcpに接続され、次にメモリ確保依頼があった場合、buddy allocatorよりも、キャッシュであるこのpcpから利用するという動作をします。

freeされたページがすぐに利用された場合、そのページに対応するCPUキャッシュがまだ残っていて有効に働くことで、その分動作が早くなる可能性があるためにこのような仕組みが作られています。
このpcpページが多数使われて無くなってしまった場合は、新たにbuddy allocatorに接続されている本当のfree pageから、いくつかのページがpcpに補充されます。

このpcpの補充処理がmemory offlineの実行時に並列して行われると、以下のようにofflineの処理が阻害されるというバグがあったのです。

ここからは以下の修正パッチの引用から、その時の時系列の説明しましょう。
offlineする側（以下の図ではCPU0)はpcpとして使われているpageが無いことを確認した[^drain]のに、並列してpcpを補充して、そのカウンタをUPしている(以下の図のCPU1)動作が起きると、結果としてこのmemory blockが空にならず、offlineに失敗するというものです。
[^drain]: pcpとして使われているpageが残っている場合は、buddy allocatorにページを返却しようとします。

```
         CPU0                              CPU1
    ----------------                    ---------------
                                      spin_lock(&pcp->lock);
                                      __rmqueue_pcplist() {
zone_pcp_disable() {
                                        /* list is empty */
                                        if (list_empty(list)) {
                                          /* add pages to pcp_list */
                                          alloced = rmqueue_bulk()
  mutex_lock(&pcp_batch_high_lock)
  ...
  __drain_all_pages() {
    drain_pages_zone() {
      /* read pcp->count, it's 0 here */
      count = READ_ONCE(pcp->count)
      /* 0 means nothing to drain */
                                          /* update pcp->count */
                                          pcp->count += alloced << order;
      ...
                                      ...
                                      spin_unlock(&pcp->lock);
```

この修正は`commit 66eca1021a42856d6af2a9802c99e160278aed91`としてLinux Kernelに取り込まれています。

# 2. 現在のエミュレーションの制約について
OSSJの発表でも述べましたが、当然ながら現在のQEMUのCXLエミュレーションは完璧というわけではありません。私も全部を知っているわけではありませんが、気が付いた点のみ紹介します。

## EFIのメモリマップについて
現在のQEMU CXLエミュレーションでは、システム起動時の物理メモリのアドレスマップ情報であるEFIメモリマップに、CXLメモリの領域を含めていません。Linux KernelのCXL関連のgit logを読む限りは、CXLメモリの物理アドレスは、システム起動時に`EFI_MEMORY_SP`という領域として表現されることが期待されています。
これは、Intelが公開している[CXL* Type 3 Memory Device Software Guide](https://cdrdv2-public.intel.com/643805/643805_CXL_Memory_Device_SW_Guide_Rev1_1.pdf)にもいくつか記載が見られます。

しかし、このQEMU CXLエミュレーション環境では、EFIのメモリマップにそのような領域は見当たりません。
以下は、一番最初に提示した、最もシンプルなCXLメモリの定義で起動した環境における、EFIメモリマップの出力です。HotAdd設定ではなく、システム起動時からCXLメモリを接続しているはずなので、この中に該当する物理アドレスがあってしかるべきですが、この出力の中にはCXLメモリに該当する物理アドレスも、`EFI_MEMORY_SP`に該当する表示もありません。

```
[    0.000000] BIOS-provided physical RAM map:
[    0.000000] BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable
[    0.000000] BIOS-e820: [mem 0x000000000009fc00-0x000000000009ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000000f0000-0x00000000000fffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000000100000-0x000000007ffdefff] usable
[    0.000000] BIOS-e820: [mem 0x000000007ffdf000-0x000000007fffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000b0000000-0x00000000bfffffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fed1c000-0x00000000fed1ffff] reserved
[    0.000000] BIOS-e820: [mem 0x00000000fffc0000-0x00000000ffffffff] reserved
[    0.000000] BIOS-e820: [mem 0x0000000100000000-0x000000047fffffff] usable
[    0.000000] BIOS-e820: [mem 0x000000fd00000000-0x000000ffffffffff] reserved
```

システム起動後に、`cxl create-region`でCXLメモリ上にregionを作成してシステムに認識させると、以下のように上記のEFIのメモリマップ内では使われていない`0xa90000000`から1GByteのサイズを持つ物理アドレスが見えてきます。
```console
# cxl list -Ru
{
  "region":"region0",
  "resource":"0xa90000000",
  "size":"1024.00 MiB (1073.74 MB)",
  "type":"ram",
  "interleave_ways":1,
  "interleave_granularity":256,
  "decode_state":"commit",
  "qos_class_mismatch":true
}
   
```
このように、現段階では、`cxl create-region`を実行するまでは、OSからCXLメモリが使用できず、システム起動時には利用することができないという制約があります。
筆者もCXL2.0をサポートした実ハードを持っているわけではないので、実際にLinuxのシステム起動時にCXLメモリがどのように認識されるのかはわかりません。機会が訪れたらこのあたりがどうなるのか、真っ先に試したいと思っています。もし違いがあったらQEMUの今後に期待したいところです。

#### (補足)/proc/iomemの出力について

:::note info
以下の捕捉は、読み飛ばしていただいて結構です。
:::

以下は/proc/iomemの出力について、`cxl create-region`を行う前と後の比較です。
システム起動時はCXLメモリ用の領域の予約はされているようです。

```console
# cat /proc/iomem
 :
 (中略）
 :
a90000000-128fffffff : CXL Window 0
12c0000000-bfffffffff : PCI Bus 0000:00
 :
```

この予約領域は、QEMUのオプションの`-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=32G`の設定から予約領域が設定されるようで、OSにはQEMUのファームウェアからCFMWSというテーブル経由で値が渡されています。[^6]
[^6]: ACPIのテーブルを参照するには[acpitools](https://github.com/acpica/acpica)というツールを使います。
```
$ sudo acpidump -b
$ iasl -d cedt.dat

Intel ACPI Component Architecture
ASL+ Optimizing Compiler/Disassembler version 20241212
Copyright (c) 2000 - 2023 Intel Corporation

File appears to be binary: found 78 non-ASCII characters, disassembling
Binary file appears to be a valid ACPI table, disassembling
Input file cedt.dat, Length 0x6C (108) bytes
ACPI: CEDT 0x0000000000000000 00006C (v01 BOCHS  BXPC     00000001 BXPC 00000001)
Acpi Data Table [CEDT] decoded
Formatted output:  cedt.dsl - 2620 bytes

$ less cedt.dsl
:
(中略)
:
[044h 0068 001h]               Subtable Type : 01 [CXL Fixed Memory Window Structure]
[045h 0069 001h]                    Reserved : 00
[046h 0070 002h]                      Length : 0028
[048h 0072 004h]                    Reserved : 00000000
[04Ch 0076 008h]         Window base address : 0000000A90000000
[054h 0084 008h]                 Window size : 0000000800000000
[05Ch 0092 001h]          Interleave Members : 00
[05Dh 0093 001h]       Interleave Arithmetic : 00
[05Eh 0094 002h]                    Reserved : 0000
[060h 0096 004h]                 Granularity : 00000000
[064h 0100 002h]                Restrictions : 000F
[066h 0102 002h]                       QtgId : 0000
[068h 0104 004h]                First Target : 0000000C
```

`cxl create-region`を行うと、予約領域の中からCXLメモリの物理アドレスがようやく割り当てられます。
```console
# cat /proc/iomem
 :
 (中略）
 :
a90000000-128fffffff : CXL Window 0
  a90000000-acfffffff : region0   <---- new!
    a90000000-acfffffff : dax0.0    <---- new!
      a90000000-acfffffff : System RAM (kmem)  <---- new!
12c0000000-bfffffffff : PCI Bus 0000:00
 : 
```
# 3. region作成の制限について

QEMUのエミュレーション環境化で行った、`cxl create-region`で行ったregionの設定は、そのregionが有効なままゲストを再起動した場合、システム起動時にその設定を再現して欲しいというのは、ユーザーとして自然な感情だと思います。特にinterleaveした時のregionの設定については、OSSJでの発表でも述べた通りメモリデバイスの順序とかを気を付けないといけないので、一度行った設定はシステムの側で覚えておいて欲しいものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101351/daeb3b97-2d03-45ee-a3d2-770eca62fa48.png)

しかし、残念ながらこのregion設定は再起動後に引き継ぐことができません。システム起動後に毎回region作成を実施しなければなりません。

実はこれは、QEMUのCXLエミュレーションの問題というよりも、「CXLの仕様上」やむなくこのようになっているようです。このため、CXLの実マシンでも(特にHotAddされたメモリに対してOS上からregion作成をした場合では)同じ問題が起きる可能性が高いです。

CXL仕様書には以下のように述べられています。(強調筆者）

> __9.13.2 CXL Memory Device Label Storage Area__
> CXL memory devices that provide volatile memory, such as DRAM, may be exposed with different interleave geometries each time the system is booted. This can happen due to the addition or removal of other devices or changes to the platform’s default interleave policies. __For volatile memory, these changes to the interleave usually do not impact host software since there’s generally no expectation that volatile memory contents are preserved across reboots.__ However, with persistent memory, the exact preservation of the interleave geometry is critical so that the persistent memory contents are presented to host software the same way each time the system is booted.
 Similar to the interleaving configuration, persistent memory devices may be partitioned into namespaces, which define volumes of persistent memory. These namespaces must also be reassembled the same way each time the system is booted to prevent data
loss.

ざっくりとした説明をすると、不揮発メモリの場合、永続データとしてメモリデバイス上のデータを復元する必要があるため、デバイス中にLabel Storage Area(LSA)という領域を設けて、その中にregionやnamespaceの情報を記録するようになっていて、それをもとにそれらを復元できるようにしています。[^7] 一方揮発メモリの場合、そのメモリ領域内のデータは(従来のDRAMと同じく)再起動すると無くなってしまうのが前提だから、regionの設定が無くなっても問題ないとしているわけです。

[^7]: この管理の仕方は、NVDIMMすなわちIntel Optane Pmemの時と同じです。

とはいえ、ユーザーとしてはシステム起動時などに毎回`cxl create-region`コマンドを投入するのは鬱陶しいですし、前回どのような設定をしたかをユーザー側としても覚えていないかもしれません。

「これはさすがにちょっと使いにくいのではないか？せめて再起動時にCFMWSといったファームからの情報でシステム起動時に再現できるようにできないのか？」と考え、[以前CXLの開発コミュニティで議論したことがあります。](https://lore.kernel.org/linux-cxl/TYWPR01MB10082738078CF843AF514F7E790F22@TYWPR01MB10082.jpnprd01.prod.outlook.com/)
しかし、結論としては、デバイスの交換やHotplugなど様々な要因も含めると、OS側で設定した情報を誰がどのような形で保存・管理するのかが困難であるため、やむなくこのような仕様になっているようです。
このため、少なくとも当面は`cxl create-region`でどのように設定したかを、シェルスクリプトなど何らかの形でユーザー側が記録しておかなければならない点については注意が必要です。

なお、ファームウェアやFabric MangerなどOSの外のコンポーネントによっても、CXLメモリのinterleave設定ができるはずなのですが、その場合にOSがシステム起動時にどのような動作をするのか？やはり`cxl create-region`を実施しないといけないのか？については筆者もまだわかっていません。来年はそのあたりが分かっているとありがたいのですが…。

# 4. まとめ
というわけで、今回はOSSJで話したCXLのエミュレーションの続きの話を中心に解説しましたが、いかがだったでしょうか？来年もCXLの仕事を続ける予定なので、しばらくはそのフォローなどが続くとおもいますそれでは良いお年を。