Hypervisor を使い ZCU104 で Dual Linux を動かす

# はじめに

ZCU104 上で Dual Linux を動作させる手順について記述します。Dual Linux の実現には自作の Hypervisor を使っています。本記事では Hypervisor が提供する機能概要と、構築手順について記述します。

Hypervisor の実装は４年ほど前ですが、公開にあたりセキュリティー面で問題のある機能の削除・改修を実施しました。

# Hypervisor 機能概要

## 公開場所

Hypervisor は GitHub にて公開しています。

<table>
  <tr>
    <td> URL </td>
    <td> <a href="https://github.com/hkato-sssl/Hypervisor-OG.git">https://github.com/hkato-sssl/Hypervisor-OG.git</a> </td>
  </tr>
</table>


ソース・ツリーの概要は以下の通りです。ビルドに関係無いファイルも多く存在します。

```
Hypervisor-OG/
|-- CMakeLists.txt
|-- LICENSE
|-- docs              - 作業時のメモ
|-- projects
|   |-- drivers       - aarch64 用 Bare-Metal driver 一式
|   |-- guest         - テスト用 Guest 環境作成
|   |-- hypervisor    - Hypervisor 共通機能ライブラリ
|   |-- libs          - aarch64 用 各種ライブラリ
|   `-- zcu104-linux  - Main Project
|-- scripts           - cmake, ld 用スクリプト
`-- work              - ソースの自動生成に利用した作業場所
```

ビルドに必要なのは `projects` 配下の `drivers`, `libs`, `hypervisor`, `zcu104-linux` の４つです。 各種ライブラリとドライバを使い `zcu104-linux` で Hypervisor 本体をビルドするイメージです。ハード固有設定や Hypervisor に関するコンフィグレーションは `zcu104-linux` に含まれています。

`guest` はテスト環境作成用プロジェクトです。公開環境ではビルド対象から外しています。

## 基本機能

Hypervisor は arm の Virtualization Extension（以下、VE と記述）を使い Type-1 型で実装しています。実装にはＣ言語とアセンブラを利用しています。

主な提供機能は以下の通りです。

* メモリ空間の仮想化
* 割り込み番号の仮想化
* 周辺デバイスの割り当て
* ユーザーによる機能拡張

公開環境では２つの Linux が動作し、rootfs として MMC と USB を使います。以降では Guest OS として動作する LInux を **Linux-MMC**, **Linux-USB** と記述します。

## メモリ空間の仮想化

Hypervisor は複数の VM(Virtual Machine) を起動する事が可能であり、VM 毎に独立したメモリ空間を提供します。この機能は Stage2 Translation と SMMU を使って実現しています。

Stage2 Translation はプロセッサに対するメモリ空間の仮想化に、SMMU はバス・マスター・デバイスに対するメモリ空間の仮想化に利用します。Zynq UltraScale+ は SMMU-500 を実装しているので、これを利用しています。

公開環境でのメモリ・マップは以下の通りです。

![MemoryMap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3332407/17361b34-47c2-b174-867e-7c1c56ab6eec.png)

Petalinux はデフォルトで 0-7FEF_FFFFh を RAM 領域として利用します。この領域を２つの領域に分割して各 Linux に割り当てます。これらは各 Linux にて 0 番地から始まる RAM 領域として認識されます。

Peripheral Devices には各 Linux が利用するデバイスのレジスタ領域のみがマッピングされています。割り当てられていないデバイスのレジスタ領域はアクセス不可能に設定されています。

## 割り込み番号の仮想化

これは割り込み番号を変更する機能です。デバイスが割り込みを通知した際、プロセッサに対してデバイスに割り当てられている番号とは異なる任意の番号で割り込みを通知します。これは VE で提供される機能の一つです。

## 周辺デバイスの割り当て

これは前述のメモリと割り込み番号の仮想化を合わせて実現する機能です。組み込み用途を想定した Hypervisor の為、周辺デバイスの完全仮想化や準仮想化は行わず、デバイス単位で VM に割り当てる（利用可能にする）形を取っています。VM に割り当てられたデバイスは、他の VM からはアクセス不可能となります。

今回公開する環境では以下の様にデバイスを割り当てています。

<table>
  <tr>
    <td> Linux-MMC </td>
    <td> DDR MC, UART0, MMC, Ethernet, RTC </td>
  </tr>
  <tr>
    <td> Linux-USB </td>
    <td> DDR MC, UART1, USB </td>
  </tr>
  <tr>
    <td> Hypervisor </td>
    <td> UART Lite </td>
  </tr>
</table>

UART1 は Linux-USB 上では UART0 として認識されます。これは割り込み番号とメモリ領域の仮想化により実現しています。Hypervisor に割り当てられている UART は PL 部で追加される UART Lite です。これはメッセージ出力専用のコンソールとして利用します。

各 Linux での未割り当てデバイスへのアクセスが発生した場合、Hypervisor がトラップしてアクセス情報をコンソールに出力します。この時、VM 上の仮想プロセッサはストール状態となります。

DDR MC(Memory Controller) だけ双方の Linux にマッピングされています。このマッピングを行わないと Linux が起動しなかった為この様な形になっています。DDR MC は Stage2 Translation で Read Only 属性でマッピングされており、レジスタ領域の参照のみ可能となっています。

## ユーザーによる機能拡張

VE では VM から Hypervisor に処理を要求する用途で HVC 命令が用意されています。この命令を使う事で、Hypervisor に新しいサービスを実装する事が可能となります。使用例として公開環境では OS 間通信を行う仮想デバイスを実装しています。

# ビルド手順

## 作業環境

本記事では下記環境でビルドと動作確認を行っています。

<dl>
    <dt>Bord</dt>
        <dd>AMD ZCU104 Rev.A</dd>
    <dt>Tool</dt>
        <dd>Vivado ML Edition 2024.1.2 (Windows11)</dd>
        <dd>Petalinux 2024.1 (Ubuntu 22.04.4)</dd>
</dl>

## Vivado でのビルド作業

最初に Vivado を使い、システムの実行環境を作成します。実行環境は PS 部と PL 部が存在し、PS 部は ZCU104 のデフォルト設定を使い、PL 部では UART Lite を１つ実装します。UART Lite は Hypervisor がメッセージ出力に利用します。

実行環境の作成には下記の Tcl スクリプトを使います。

<details><summary>動作環境作成スクリプト</summary>

```TCL:
# set project parameters
set PROJ_NAME zcu104-ps-uart
set PROJ_ROOT c:/vivado
set PROJ_DIR $PROJ_ROOT/$PROJ_NAME
set PROJ_BD_NAME design_1

# create a project
file mkdir $PROJ_ROOT
create_project $PROJ_NAME $PROJ_DIR -part xczu7ev-ffvc1156-2-e
set_property board_part xilinx.com:zcu104:part0:1.1 [current_project]

# create a Block Design
create_bd_design $PROJ_BD_NAME
create_bd_cell -type ip -vlnv xilinx.com:ip:zynq_ultra_ps_e:3.5 zynq_ultra_ps_e_0
create_bd_cell -type ip -vlnv xilinx.com:ip:axi_uartlite:2.0 axi_uartlite_0
set_property CONFIG.C_BAUDRATE {115200} [get_bd_cells axi_uartlite_0]
apply_board_connection -board_interface "uart2_pl" -ip_intf "axi_uartlite_0/UART" -diagram $PROJ_BD_NAME 
apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config { Clk_master {Auto} Clk_slave {Auto} Clk_xbar {Auto} Master {/zynq_ultra_ps_e_0/M_AXI_HPM0_LPD} Slave {/axi_uartlite_0/S_AXI} ddr_seg {Auto} intc_ip {New AXI Interconnect} master_apm {0}}  [get_bd_intf_pins axi_uartlite_0/S_AXI]
apply_bd_automation -rule xilinx.com:bd_rule:zynq_ultra_ps_e -config {apply_board_preset "1" }  [get_bd_cells zynq_ultra_ps_e_0]
apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config { Clk_master {Auto} Clk_slave {/zynq_ultra_ps_e_0/pl_clk0 (100 MHz)} Clk_xbar {/zynq_ultra_ps_e_0/pl_clk0 (100 MHz)} Master {/zynq_ultra_ps_e_0/M_AXI_HPM0_FPD} Slave {/axi_uartlite_0/S_AXI} ddr_seg {Auto} intc_ip {/ps8_0_axi_periph} master_apm {0}}  [get_bd_intf_pins zynq_ultra_ps_e_0/M_AXI_HPM0_FPD]
apply_bd_automation -rule xilinx.com:bd_rule:axi4 -config { Clk_master {Auto} Clk_slave {/zynq_ultra_ps_e_0/pl_clk0 (100 MHz)} Clk_xbar {/zynq_ultra_ps_e_0/pl_clk0 (100 MHz)} Master {/zynq_ultra_ps_e_0/M_AXI_HPM1_FPD} Slave {/axi_uartlite_0/S_AXI} ddr_seg {Auto} intc_ip {/ps8_0_axi_periph} master_apm {0}}  [get_bd_intf_pins zynq_ultra_ps_e_0/M_AXI_HPM1_FPD]
regenerate_bd_layout

make_wrapper -files [get_files $PROJ_DIR/${PROJ_NAME}.srcs/sources_1/bd/$PROJ_BD_NAME/${PROJ_BD_NAME}.bd] -top
add_files -norecurse $PROJ_DIR/${PROJ_NAME}.gen/sources_1/bd/$PROJ_BD_NAME/hdl/${PROJ_BD_NAME}_wrapper.v
update_compile_order -fileset sources_1

launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
write_hw_platform -fixed -include_bit -force -file $PROJ_DIR/$PROJ_NAME.xsa
```
</details>

Vivado の Tcl Console か Tcl Shell からスクリプトを実行して下さい。プロジェクトは `C:\vivado\` 配下に作成されます。プロジェクトの作成場所を変更したい場合は、最初のパラメータ定義を修正して下さい。スクリプトの実行は Windows 11 上で確認しています。

スクリプトで作成される Block Design は以下の通りです。

![zcu104-ps-uart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3332407/480ceb48-1cb9-912c-2daa-4f19a606e5dc.png)

スクリプトの実行が成功するとプロジェクト配下に `zcu104-ps-uart.xsa` が作成されます。このファイルは Petalinux のビルドに利用するので、後ほど Petalinux のビルド環境へコピーします。

## Hypervisor のビルド手順

Hypervisor は Petalinux Tools でコンパイルします。事前に Petalinux Tools のインストール・ディレクトリに用意されている `setting.sh` を実行してツール群を利用可能にしておいて下さい。

Hypervisor は cmake を使ってビルドします。最初にソースを GitHub から取得します。

```bash:
HYP_DIR=$HOME
cd $HYP_DIR
git clone https://github.com/hkato-sssl/Hypervisor-OG.git -b v1.0.0
cd Hypervisor-OG
mkdir build
cd build
cmake ..
make
```

`HYP_DIR` 配下でビルドを実施します。これには任意のパスを設定可能です。

ビルドに成功すると `$HYP_DIR/Hypervisor-OG/build/projects/zcu104-linux/hypervisor.bin` が生成されます。

## Linux のビルド手順

### ビルド方針

Linux のビルドは少々面倒です。今回は Guest OS として２つの Linux を動作させる為、Linux 環境を２つビルドする必要があります。ビルドの手間を削減する為、今回は以下の方針を取ります。

* Linux Kernel Image は共通の物を使用する
* Linux 毎に固有の Devicetree を用意する

上記方針によりビルドは下記手順で行います。
1. BOOT.BIN, u-boot, Kernel Image のビルド
2. Linux-MMC 用 system.dtb のビルド
3. Linux-USB 用 system.dtb のビルド

### BOOT.BIN, u-boot, Kernel Image のビルド

最初に Vivado で作成した `zcu104-ps-uart.xsa` を使いプロジェクトの作成からビルドまでを行います。

最初に作業用ディレクトリの作成を行います。

```bash:
WORK_DIR=$HOME/petalinux
PROJ_NAME=zcu104-ps-uart
PROJ_DIR=$WORK_DIR/$PROJ_NAME
mkdir -p $WORK_DIR
```

`WORK_DIR` が作業ディレクトリになります。任意のディレクトリを指定して下さい。

次にプロジェクトの作成とハードウエア情報の取り込みを行います。`zcu104-ps-uart.xsa` はホーム・ディレクトリ直下にある事を前提としています。

```bash:
XSA_FILE=$HOME/zcu104-ps-uart.xsa
cd $WORK_DIR
petalinux-create project -n $PROJ_NAME --template zynqMP
cd $PROJ_DIR
petalinux-config --get-hw-description $XSA_FILE --silentconfig
```

次にカーネル・モジュールのレシピを追加します。このレシピでは OS 間通信ドライバをビルドします。

最初に仮想デバイス・ドライバ用のレシピを追加します。

```bash:
petalinux-create modules --name p128 --enable
```

**p128** がレシピの名前です。追加が成功すると `$PROJ_DIR/project-spec/meta-user/recipes-modules/p128` が作成されます。

レシピが追加されたら `p128.bb` を以下の様に修正して下さい。

```bash:$PROJ_DIR/project-spec/meta-user/recipes-modules/p128/p128.bb
SUMMARY = "Recipe for  build an external p128 Linux kernel module"
SECTION = "PETALINUX/modules"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=12f884d2ae1ff87c09e5b7ccc2c4ca7e"

inherit module

INHIBIT_PACKAGE_STRIP = "1"

SRC_URI = "git://github.com/hkato-sssl/Hypervisor-OG-p128.git;protocol=https;branch=main"
SRCREV = "506ac7835ed295c6894723957d9b2b636cf697ee"

S = "${WORKDIR}/git"

# The inherit of module.bbclass will automatically name module packages with
# "kernel-module-" prefix as required by the oe-core build environment.
```

`petalinux-create` 実行時に生成される `files` ディレクトリは使いません。

次に Kernel Configuration を変更します。`bsp.cfg` を下記の様に変更します。

```:$PROJ_DIR/project-spec/meta-user/recipes-kernel//linux/linux-xlnx/bsp.cfg
# CONFIG_CRYPTO_DEV_XILINX_RSA is not set
# CONFIG_CRYPTO_DEV_ZYNQMP_AES is not set
# CONFIG_CRYPTO_DEV_ZYNQMP_SHA3 is not set
```

これは双方の Linux から同時利用するのを避ける為に無効化しています。通常は Devicetree で対応するのですが、これらは Kernel Configuration の変更が必要でした。

最後にビルドを実行します。

```bash:
petalinux-build
```

ビルドが成功すると `$PROJ_DIR/images/linux/` 配下に成果物が生成されますので、これらを使い BOOT.BIN を作成します。

```bash:
cd images/linux/
petalinux-package --boot --fsbl zynqmp_fsbl.elf --u-boot u-boot.elf --pmufw pmufw.elf --fpga system.bit
cd -
```

:::note info
以降に記述するビルド作業では、ここで作成したビルド環境をそのまま使います。
:::

### Linux-MMC 用 sysytem.dtb のビルド

`$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files` 配下に `system-user-mmc.dtsi` を追加します。

<details><summary>system-user-mmc.dtsi</summary>

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files/system-user-mmc.dtsi
/ {
	chosen {
		bootargs = "earlycon console=ttyPS0,115200 root=/dev/mmcblk0p2 rootwait rw clk_ignore_unused";
	};

	memory@0 {
		reg = <0x0 0x0 0x0 0x40000000>;
	};

	pmu {
		status = "disabled";
	};

        p128@0 {
                compatible = "sssl,hvcs-p128";
                device-id = <0x50313238>;       /* "P128" */
                interrupt-parent = <&gic>;
        };
};

&phy0 {
	/delete-property/ reset-gpios;
};

&lpd_dma_chan1 {
	status = "disabled";
};
&lpd_dma_chan2 {
	status = "disabled";
};
&lpd_dma_chan3 {
	status = "disabled";
};
&lpd_dma_chan4 {
	status = "disabled";
};
&lpd_dma_chan5 {
	status = "disabled";
};
&lpd_dma_chan6 {
	status = "disabled";
};
&lpd_dma_chan7 {
	status = "disabled";
};
&lpd_dma_chan8 {
	status = "disabled";
};
&xilinx_ams {
	status = "disabled";
};
&can1 {
	status = "disabled";
};
&cci {
	status = "disabled";
};
&zynqmp_dpsub {
	status = "disabled";
};
&zynqmp_dpdma {
	status = "disabled";
};
&gem3 {
	status = "okay";
};
&fpd_dma_chan1 {
	status = "disabled";
};
&fpd_dma_chan2 {
	status = "disabled";
};
&fpd_dma_chan3 {
	status = "disabled";
};
&fpd_dma_chan4 {
	status = "disabled";
};
&fpd_dma_chan5 {
	status = "disabled";
};
&fpd_dma_chan6 {
	status = "disabled";
};
&fpd_dma_chan7 {
	status = "disabled";
};
&fpd_dma_chan8 {
	status = "disabled";
};
&gpio {
	status = "disabled";
};
&gpu {
	status = "disabled";
};
&i2c1 {
	status = "disabled";
};
&qspi {
	status = "disabled";
};
&rtc {
	status = "disabled";
};
&sata {
	status = "disabled";
};
&sdhci1 {
	status = "okay";
};
&psgtr {
	status = "disabled";
};
&ttc0 {
	status = "disabled";
};
&ttc1 {
	status = "disabled";
};
&ttc2 {
	status = "disabled";
};
&ttc3 {
	status = "disabled";
};
&uart0 {
	status = "okay";
};
&uart1 {
	status = "disabled";
};
&usb0 {
	status = "disabled";
};
&dwc3_0 {
	status = "disabled";
};
&lpd_watchdog {
	status = "disabled";
};
&watchdog0 {
	status = "disabled";
};
&ams_ps {
	status = "disabled";
};
&ams_pl {
	status = "disabled";
};
&dcc {
	status = "disabled";
};
&zynqmp_dp_snd_pcm0 {
	status = "disabled";
};
&zynqmp_dp_snd_pcm1 {
	status = "disabled";
};
&zynqmp_dp_snd_card0 {
	status = "disabled";
};
&zynqmp_dp_snd_codec0 {
	status = "disabled";
};

&zynqmp_ipi {
	status = "disabled";
};
&perf_monitor_ocm {
	status = "disabled";
};
&perf_monitor_ddr {
	status = "disabled";
};
&perf_monitor_cci {
	status = "disabled";
};
&perf_monitor_lpd {
	status = "disabled";
};
```
</details>

次に同じディレクトリ配下にある `system-user.dtsi` を下記内容に書き換えます。

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
#include "zcu104-reva.dtsi"
/include/ "system-conf.dtsi"
/include/ "system-user-mmc.dtsi"
```

`device-tree.bbappend` を下記内容に書き換えます。

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/device-tree.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI:append = " file://system-user.dtsi \
                   file://system-user-mmc.dtsi \
                 "
```

全ての修正を終えたら Devicetree のビルドを実行します。ビルドが成功すると `$PROJ_DIR/images/linux/system.dtb` が更新されますので、これを `system-mmc.dtb` に変更します。

```bash:
petalinux-build -c device-tree
mv $PROJ_DIR/images/linux/system.dtb $PROJ_DIR/images/linux/system-mmc.dtb
```

以上で Linux-MMC 用のビルド作業は終了です。

### Linux-USB 用 sysytem.dtb のビルド

作業手順は Linux-MMC と同じです。

`$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files` 配下に `system-user-usb.dtsi` を追加します。

<details><summary>system-user-usb.dtsi</summary>

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files/system-user-usb.dtsi
/ {
	chosen {
		bootargs = "earlycon console=ttyPS0,115200 root=/dev/sda1 rootwait rw clk_ignore_unused";
	};

	memory@0 {
		reg = <0x0 0x0 0x0 0x3f000000>;
	};

	pmu {
		status = "disabled";
	};

        p128@0 {
                compatible = "sssl,hvcs-p128";
                device-id = <0x50313238>;	/* "P128" */
                interrupt-parent = <&gic>;
        };
};

&lpd_dma_chan1 {
	status = "disabled";
};
&lpd_dma_chan2 {
	status = "disabled";
};
&lpd_dma_chan3 {
	status = "disabled";
};
&lpd_dma_chan4 {
	status = "disabled";
};
&lpd_dma_chan5 {
	status = "disabled";
};
&lpd_dma_chan6 {
	status = "disabled";
};
&lpd_dma_chan7 {
	status = "disabled";
};
&lpd_dma_chan8 {
	status = "disabled";
};
&xilinx_ams {
	status = "disabled";
};
&can1 {
	status = "disabled";
};
&cci {
	status = "disabled";
};
&zynqmp_dpsub {
	status = "disabled";
};
&zynqmp_dpdma {
	status = "disabled";
};
&gem3 {
	status = "disabled";
};
&fpd_dma_chan1 {
	status = "disabled";
};
&fpd_dma_chan2 {
	status = "disabled";
};
&fpd_dma_chan3 {
	status = "disabled";
};
&fpd_dma_chan4 {
	status = "disabled";
};
&fpd_dma_chan5 {
	status = "disabled";
};
&fpd_dma_chan6 {
	status = "disabled";
};
&fpd_dma_chan7 {
	status = "disabled";
};
&fpd_dma_chan8 {
	status = "disabled";
};
&gpio {
	status = "disabled";
};
&gpu {
	status = "disabled";
};
&i2c1 {
	status = "disabled";
};
&qspi {
	status = "disabled";
};
&rtc {
	status = "disabled";
};
&sata {
	status = "disabled";
};
&sdhci1 {
	status = "disabled";
};
&psgtr {
	status = "okay";
};
&ttc0 {
	status = "disabled";
};
&ttc1 {
	status = "disabled";
};
&ttc2 {
	status = "disabled";
};
&ttc3 {
	status = "disabled";
};
&uart0 {
	status = "okay";
};
&uart1 {
	status = "disabled";
};
&usb0 {
	status = "okay";
};
&dwc3_0 {
	status = "okay";
};
&lpd_watchdog {
	status = "disabled";
};
&watchdog0 {
	status = "disabled";
};
&ams_ps {
	status = "disabled";
};
&ams_pl {
	status = "disabled";
};
&dcc {
	status = "disabled";
};
&zynqmp_dp_snd_pcm0 {
	status = "disabled";
};
&zynqmp_dp_snd_pcm1 {
	status = "disabled";
};
&zynqmp_dp_snd_card0 {
	status = "disabled";
};
&zynqmp_dp_snd_codec0 {
	status = "disabled";
};

&zynqmp_ipi {
	status = "disabled";
};
&perf_monitor_ocm {
	status = "disabled";
};
&perf_monitor_ddr {
	status = "disabled";
};
&perf_monitor_cci {
	status = "disabled";
};
&perf_monitor_lpd {
	status = "disabled";
};
```
</details>

次に同じディレクトリ配下にある `system-user.dtsi` を下記内容に書き換えます。

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
#include "zcu104-reva.dtsi"
/include/ "system-conf.dtsi"
/include/ "system-user-usb.dtsi"
```

`device-tree.bbappend` を下記内容に書き換えます。

```:$PROJ_DIR/project-spec/meta-user/recipes-bsp/device-tree/device-tree.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"

SRC_URI:append = " file://system-user.dtsi \
                   file://system-user-usb.dtsi \
                 "
```

全ての修正を終えたら Devicetree のビルドを実行します。ビルドが成功すると `$PROJ_DIR/images/linux/system.dtb` が更新されますので、これを `system-usb.dtb` に変更します。

```bash:
petalinux-build -c device-tree
mv $PROJ_DIR/images/linux/system.dtb $PROJ_DIR/images/linux/system-usb.dtb
```

以上で Linux-usb 用のビルド作業は終了です。

:::note alert
u-boot と system-mmc.dtb や system-usb.dtb をまとめてビルドしないで下さい。

各 DTB では RAM 領域のサイズを変更しており、まとめてビルドした場合この設定が u-boot に反映されてしまいます。

u-boot は DTB で設定された RAM 領域外にプログラムをロードする事は出来ず、結果としてHypervisor を含めたシステム起動が不可能になります。
:::

# 起動メディア作成

SD Memory Card と USB Memory に起動用ファイルと rootfs を書き込みます。

先ずは起動用 SD Memory Card を作成します。これはボード起動用の他、 Linux-MMC の rootfs として利用します。

SD Memory Card にパーティションを２つ作成して下さい。１つ目は FAT、２つ目は Linux で作成します。

各パーティションをフォーマットしたら、FAT パーティションに `$PROJ_DIR/images/linux/` 配下にある下記ファイルをコピーします。

* BOOT.BIN
* image.ub
* boot.scr
* Image
* system-mmc.dtb
* system-usb.dtb

次に Hypervisor のバイナリ・イメージをコピーします。コピーするファイルは `$HYP_DIR/build/projects/zcu104-linux/hypervisor.bin` です。

次は Linux パーティションを EXT4 でフォーマットした後、 `$PROJ_DIR/images/linux/rootfs.tar.gz` を展開します。これで SD Memory Card での作業は終了です。

続けて USB Memory の作成です。これは Linux-USB の rootfs として利用しますので、Linux パーティションを１つ作成して下さい。

パーティションを作成したら EXT4 でフォーマットを行い、 SD と同様に `$PROJ_DIR/images/linux/rootfs.tar.gz` を Linux パーティションに展開します。

# 起動手順

作成した SD Memory Card と USB Memory を ZCU104 に装着して下さい。USB Memory はボード上の USB ポートに挿入しても問題ありませんが、**直挿しだとメディアを認識しない事がありました。** 所有しているボードの問題かも知れませんが USB 3.0 対応の USB Memory を利用した時のみ発生しています。この様な症状が出た時は USB Hub 経由での接続を試してみて下さい。尚、ボード上の Ethernet Port は Linux-MMC に割り当てられていますので、Linux-USB でネットワーク利用する場合は、USB Hub 経由で USB Memory と USB Ethernet を接続して下さい。

ボードの電源を投入すると u-boot が立ち上がります。カウント・ダウンが始まったら Enter Key を押します。今回利用する UART0, UART1, UART Lite の Baud Rate は全て 115200 に設定して下さい。

プロンプトが表示されたら下記コマンドを投入してシステムを起動します。

```:起動手順
fatload mmc 0:1 280000 Image
fatload mmc 0:1 100000 system-mmc.dtb
fatload mmc 0:1 40280000 Image
fatload mmc 0:1 40100000 system-usb.dtb
fatload mmc 0:1 7f000000 hypervisor.bin
booti 7f000000 - 100000
```

最後の `booti` で Hypervisor を起動します。第３引数で `system-mmc.dtb` のアドレスを指定していますが、これはダミーです。`booti` コマンドには必ず DTB ファイルを渡す必要があるのでダミーを指定しています。

Hypervisor は arm の EL2 で実行します。EL2 で実行するには **bootX** コマンドを使う必要がある為、この様な形となっています。

起動が成功すれば UART0/UART1 に Linux の起動メッセージが、UART Lite に Hypervisor の起動メッセージが出力されます。

Hypervisor の起動メッセージは以下の通りです。

```:
Start HypervisorOG ver.1.0.0
Start a thread for Linux#1: PhysicalProcessor=1 VirtualProcessor=1
Start a thread for Linux#1: PhysicalProcessor=3 VirtualProcessor=0
Start a thread for Linux#0: PhysicalProcessor=2 VirtualProcessor=1
Start a thread for Linux#0: PhysicalProcessor=0 VirtualProcessor=0
```

上記は物理コアと仮想コアの割り当てを出力しています。`Linux#0` と `Linux#1` は VM を指しています。本記事との対応は以下の通りです。

<table>
  <tr>
    <td> Linux#0 </td>
    <td> Linux-MMC </td>
  </tr>
  <tr>
    <td> Linux#1 </td>
    <td> Linux-USB </td>
  </tr>
</table>

# Dual Linux の動作状態確認

ここでは Linux-MMC と Linux-USB が別々に動作している事を確認します。

メモリ・マップと割り込みの利用状況の２つを見れば、各 Linux が異なる環境で動作している事が解るかと思います。

## メモリ・マップ確認

各 Linux 上で `/proc/iomem` を表示してみて下さい。異なるメモリ・マップが以下の様に表示されます。

Linux-USB では UART1 が UART0 と同じアドレスにマッピングされています。

```bash:Linux-MMC の /proc/iomem
zcu104-ps-uart:/home/petalinux# cat /proc/iomem 
00000000-3fffffff : System RAM
  00100000-0010bfff : reserved
  00290000-015cffff : Kernel code
  015d0000-0188ffff : reserved
  01890000-01a0ffff : Kernel data
  2aac0000-3fbfffff : reserved
  3fc64000-3fd65fff : reserved
  3fd66000-3fdb1fff : reserved
  3fdb3000-3fdb6fff : reserved
  3fdb7000-3fdc7fff : reserved
  3fdc8000-3fffffff : reserved
ff000000-ff000fff : xuartps
ff0e0000-ff0e0fff : ff0e0000.ethernet ethernet@ff0e0000
ff170000-ff170fff : ff170000.mmc mmc@ff170000
```

```bash:Linux-USB の /proc/iomem
zcu104-ps-uart:/home/petalinux# cat /proc/iomem 
00000000-3effffff : System RAM
  00100000-0010bfff : reserved
  00290000-015cffff : Kernel code
  015d0000-0188ffff : reserved
  01890000-01a0ffff : Kernel data
  29ac0000-3ebfffff : reserved
  3ec6c000-3ed6cfff : reserved
  3ed6d000-3edb8fff : reserved
  3edbb000-3edbbfff : reserved
  3edbc000-3edbefff : reserved
  3edbf000-3edcffff : reserved
  3edd0000-3effffff : reserved
fd3d0000-fd3d0fff : fd400000.phy siou
fd400000-fd43ffff : fd400000.phy serdes
fe200000-fe207fff : usb@fe200000
  fe200000-fe207fff : xhci-hcd.0.auto usb@fe200000
fe20c100-fe23ffff : fe200000.usb usb@fe200000
ff000000-ff000fff : xuartps
ff9d0000-ff9d00ff : ff9d0000.usb usb@ff9d0000
```

## 割り込み番号確認

これは `/proc/interrupts` を表示します。公開環境では以下の様に表示されます。

メモリ・マップと同様に Linux-USB では UART1 の割り込み番号が UART0 の割り込み番号になっています。

```bash:Linux-MMC の /proc/interrupts
zcu104-ps-uart:/home/petalinux# cat /proc/interrupts 
           CPU0       CPU1       
 12:       5585       5280 GIC-0  27 Level     arch_timer
 14:        483          0 GIC-0  53 Level     xuartps
 15:          0          0 GIC-0  95 Level     eth0, eth0
 16:       2576          0 GIC-0  81 Level     mmc0
 17:          0          0 GIC-0 168 Level     p128
IPI0:        89        141       Rescheduling interrupts
IPI1:      1342       2207       Function call interrupts
IPI2:         0          0       CPU stop interrupts
IPI3:         0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0       Timer broadcast interrupts
IPI5:         0          0       IRQ work interrupts
IPI6:         0          0       CPU wake-up interrupts
Err:          0
```

```bash:Linux-USB の /proc/interrupts
zcu104-ps-uart:/home/petalinux# cat /proc/interrupts 
           CPU0       CPU1       
 12:       3674       5993 GIC-0  27 Level     arch_timer
 14:        463          0 GIC-0  53 Level     xuartps
 15:       3553          0 GIC-0  97 Level     xhci-hcd:usb1
 16:          0          0 GIC-0 170 Level     p128
IPI0:        57        143       Rescheduling interrupts
IPI1:      2283       5016       Function call interrupts
IPI2:         0          0       CPU stop interrupts
IPI3:         0          0       CPU stop (for crash dump) interrupts
IPI4:         0          0       Timer broadcast interrupts
IPI5:         0          0       IRQ work interrupts
IPI6:         0          0       CPU wake-up interrupts
Err:          0
```

`p128` は OS 間通信を担う仮想デバイスです。これらは割り込み番号として PL 用に割り当てられている SPI を利用しています。

## OS 間通信の確認

各 Linux 上に `/dev/p128-0` というデバイスが存在しますが、これが OS 間通信を実現する仮想デバイスです。このデバイスに書き込んだデータが対向する Linux で読み取り可能となります。

p128 という名称は **Packet** と **128-byte** の組み合わせです。この仮想デバイスは 128-byte 固定長パケットによる Peer-to-Peer の双方向通信を提供します。

仮想デバイスが持つ通信用バッファはパケット１つ分しかありませんので、送信したパケットを対向が読み取るまで次のパケットは送信不可能になります。対向が先に送信したパケットを読み取る前に次のパケットを送信した場合、送信プロセスはブロックされます。

以下は簡単な確認手順です。デバイスへのアクセスは root 権限で行う必要があります。

```bash:Linux-MMC
echo Hello > /dev/p128-0
```

```bash:Linux-USB
cat /dev/p128-0
```

上記は Linux-MMC で実行した `echo` コマンドの出力を Linux-USB で表示します。実行コマンドを逆にする事も可能です。

Linux-USB で `cat` コマンドを実行する前に再度 Linux-MMC で `echo` コマンドを実行してみて下さい。Linux-USB が `cat` を実行するまで処理がブロックされます。

p128 は双方向通信なので実行コマンドを逆にしても問題ありません。

`echo` の代わりに `cat` で適当なファイルを出力すれば、対向の Linux で表示されます。

```bash:
cat /etc/netconfig > /dev/p128-0
```

おわりに

各 VM に割り当てるデバイスが必要最小現に抑えていますので大した事は出来ませんが、Dual Linux の動作環境を試すには必要最低限の環境が作れたと思います。