HiFive Unmatched の NVMe SSD から Gentoo Linux を起動させる

## はじめに

先日 RISC-V の開発ボード [HiFive Unmatched](https://www.sifive.com/boards/hifive-unmatched) を購入しました。
このボードは基本的に本体付属の SD カードから U-Boot を起動し、そこから Linux を起動して使うような構成となっていますが、NVMe SSD を搭載することができ、そこから Lnnux を起動することもできます。
その他、ボード形状が Mini-ITX で電源も ATX 電源を利用する仕様になっていたり、PCIe のグラフィックボードを積めたり、M.2 の Wi-Fi/Bluetooch デバイスを積めたりとなかなか扱いやすい製品になっています。

さて、Gentoo Linux は RISC-V にも対応していますが、残念ながらハンドブックは用意されていません。というより、製品ごとに対応デバイスや起動方法がマチマチで汎用的なハンドブックの書きようがないというのが正確なところだと思います。
そんなわけで、NVMe SSD に stage3 を展開し、そこから最低限起動できるようにするまでの手順を書いていこうと思います。

### 予防線

今回利用した SD カードに書き込まれた U-Boot と Linux のバージョンは以下のようになっています。
これ以外のバージョンではうまくいかないかもしれませんがご容赦のほどを。

```:起動ログより
U-Boot 2021.01 (Apr 07 2021 - 17:59:15 +0000)
```

```conf:/etc/os-release
ID=nodistro
NAME="FreedomUSDK"
VERSION="2021.03.01 (2021March)"
VERSION_ID=2021.03.01
PRETTY_NAME="FreedomUSDK 2021.03.01 (2021March)"
```

### この記事で行うこと

 - NVMe SSD に Gentoo Linux の stage3 を展開して、そこから起動できるようにする 

### この記事で行わないこと

 - ハンドブック相当の各種セットアップのうち最低限の起動に関連しないもの (portage のセットアップ・カーネルビルド等)
 - SD カードレス起動 (マニュアルを読んだ感じそもそも無理そうな気がしている)

## 必要なもの

 - HiFive Unmatched と付属の SD カード
 - ATX または SFX 電源 (ATX 24 ピンコネクタの電源供給ができるもの)
 - NVMe SSD
 - micro B USB ケーブル (シリアルコンソール接続用)
 - HiFive Unmatched にシリアル接続するための PC (以降、単に PC と呼ぶ)
 - HiFive Unmatched と PC からインターネットに接続できる環境  
   また、本記事では DHCP が動いている前提で IP アドレスの明示的な割り振りは行っていません

## 大雑把な手順

 1. カーネルや起動設定を置くファイルシステムとルートファイルシステム用ファイルシステムを NVMe 上に作成
 1. stage3 を展開する
 1. SD カードからカーネル関連一式と起動設定をコピー
 1. 最低限の設定ファイルを編集
 1. 再起動 


## 手順詳細

### 1. 初期セットアップと起動
[HiFive Unmatched のサイト](https://www.sifive.com/boards/hifive-unmatched) に置かれてある Getting Started Guide に従ってセットアップ・起動を行います。日本語マニュアルもあるのでご安心を。
日本語マニュアルバージョン v1p4 に沿った形では、以下がすべきこととなります。

 - 「4 ボードのセットアップ」全般
 - 「5 立ち上げ＆作動」の「5.1コンソール(USB-to-シリアル) ターミナルウィンドウの設定」〜「5.3 ソフトウェアの起動」(root でログインするところまで)

ちなみに、SD カード上で動作しているためか動作は全体的にもっさりしており、ログインユーザー名と enter を入力した後、パスワードプロンプトが表示されるまで数秒かかることがあります。焦らずに待ちましょう。

以降、シリアルコンソール経由で操作を行います。

### 2. パーティション作成

#### 作成する内容

下記のようなパーティションを作成します。
今回はブート用パーティションを 256 MiB、 / 用パーティションを残り全てとしました。

 - /dev/nvme0n1p1 - ブート用パーティション (カーネル、起動設定)
 - /dev/nvme0n1p2 - ルートファイルシステム用パーティション (stage3 展開先)

#### パーティション作成手順

今回は `gdisk` で編集します (筆者が慣れているので。他のものが好みならそちらを使ってもかまいません)。

```sh
gdisk /dev/nvme0n1
```

ひとつめのパーティション作成。サイズ指定とタイプコード以外はデフォルト (何も入力せず enter) です。

```
Command (? for help): n
Partition number (1-128, default 1): 
First sector (34-468862094, default = 2048) or {+-}size{KMGTP}: 
Last sector (2048-468862094, default = 468862094) or {+-}size{KMGTP}: +256M
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): 0700
Changed type of partition to 'Microsoft basic data'
```

ふたつめのパーティション作成。こちらはすべてデフォルトです。

```
Command (? for help): n
Partition number (2-128, default 2): 
First sector (34-468862094, default = 526336) or {+-}size{KMGTP}: 
Last sector (526336-468862094, default = 468862094) or {+-}size{KMGTP}: 
Current type is 8300 (Linux filesystem)
Hex code or GUID (L to show codes, Enter = 8300): 
Changed type of partition to 'Linux filesystem'
```

念の為確認して……

```
Command (? for help): p
Disk /dev/nvme0n1: 468862128 sectors, 223.6 GiB
Model: WDC WDS240G2G0C-00AJM0                  
Sector size (logical/physical): 512/512 bytes
Disk identifier (GUID): 6C8CAE2C-76ED-4939-B4C4-69EE4B3846C4
Partition table holds up to 128 entries
Main partition table begins at sector 2 and ends at sector 33
First usable sector is 34, last usable sector is 468862094
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)

Number  Start (sector)    End (sector)  Size       Code  Name
   1            2048          526335   256.0 MiB   0700  Microsoft basic data
   2          526336       468862094   223.3 GiB   8300  Linux filesystem
```

問題なければ確定します。

```
Command (? for help): w 

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): y
OK; writing new GUID partition table (GP[  561.541158]  nvme0n1: p1 p2
T) to /dev/nvme0n1.
[  562.542638]  nvme0n1: p1 p2
The operation has completed successfully.
```

### 3. ファイルシステム作成

先程作成したパーティションにファイルシステムを作成します。
ひとつめは vfat 、ふたつめは ext4 にします。[^ext4]

[^ext4]: btrfs が好みなんですが、付属の SD カードではサポートしてないようなので妥協しました。

```sh
mkfs -t vfat -F 32 -n NVMEBOOT /dev/nvme0n1p1 
mkfs -t ext4 -L / /dev/nvme0n1p2 
```

### 4. ルートファイルシステムのマウントと stage3 の取得

```sh
mount -L / /mnt/
mkdir /mnt/root
```

SD カードには links や w3m といったテキストブラウザが見当たらなかったので、 stage3 のダウンロード URL を取得するため、ここまで操作したら一旦 PC に戻ります。

ダウンロードは [Gentoo Linux のダウンロードページ](https://www.gentoo.org/downloads/) へ、と言いたいところですが、このページからのダウンロードは遅い傾向にあるので [ミラー一覧](https://www.gentoo.org/downloads/mirrors/) から近いところを選びましょう。

ミラーページから

releases > riscv > autobuilds > *最新の日付* (`20210917T170644Z` など)
と辿り、 `stage3-rv64_lp64d-systemd-(日付).tar.xz` または `stage3-rv64_lp64d-openrc-(日付).tar.xz` の URL をコピーします。
**`..._lp64d-...` ではなく `..._lp64-...` となったものを選ばないよう注意。** 動かないわけではありませんが浮動小数点数計算がソフトウェア処理になってちょっと悲しくなります。なお、`..._lp64d-...` ではなく `..._multilib-...` のものを選んでも構いません。

URL をコピーしたら、シリアルコンソールに戻ってダウンロードをします。`wget` の引数にコピーした URL を指定します。

```sh
cd /mnt/root
wget 'http://ftp.iij.ad.jp/pub/linux/gentoo/releases/riscv/autobuilds/20210917T170644Z/stage3-rv64_lp64d-systemd-20210917T170644Z.tar.xz'
```

### 5. stage3 の展開

ダウンロード完了したファイルを展開します。[^digest]

[^digest]: ハンドブックにあるダイジェスト確認の手順を飛ばしてるって？ ごめんなさい手抜きです。真面目にやるならきっちり確認しましょう。

```sh
tar xvpf stage3-rv64_lp64d-systemd-20210917T170644Z.tar.xz -C /mnt --xattrs-include='*.*' --numeric-owner
```

### 6. カーネル関連ファイル・起動設定のコピーと編集

まずは一式コピー。
`/lib/modules/(カーネルバージョン)/build` にはカーネルソース一式が入っており、このコピーまですると時間と空き領域を消費するので省くようにします。この後のセットアップ (記事外) で別途カーネルのビルドをするでしょうし。

```sh
mount LABEL=NVMEBOOT /mnt/boot
cp -av /boot/* /mnt/boot
rsync -av --exclude='build' /lib/modules /mnt/lib
cp -av /lib/firmware /mnt/lib
```

続いて `/mnt/boot/extlinux.conf` を編集。 最後の行の `root=...` を書き換えます。
エディタは `vim` や `nano` あたりが使えます。

```:/mnt/boot/extlinux.conf
default OpenEmbedded
label OpenEmbedded-SiFive-HiFive-Unmatched
   kernel /Image.gz
   fdt /hifive-unmatched-a00.dtb
   append root=/dev/nvme0n1p2 rootfstype=ext4 rootwait console=ttySIF0,115200 earlycon
```

### 7. その他のファイルの設定
stage3 から正常に起動やログインができるようにするため、ファイルをいくつか編集します。

まず `/mnt/etc/fstab` 。

```:/mnt/etc/fstab
LABEL=NVMEBOOT          /boot           vfat            noauto,noatime  1 2
LABEL=/                 /               ext4            noatime         0 1
```

続いて `/mnt/etc/shadow`。1 行目の `root` 設定にパスワード設定します。具体的には `*` となっているところの書き換え。
`/etc/shadow` 一行目の `root` の対応する部分の内容をコピペして下さい[^mkpasswd]。

[^mkpasswd]: PC 側に whois 同梱の `mkpasswd` その他 `shadow` で使えるハッシュ生成できるものがあればそれを使って生成したものを設定してもよいです。SD カードにも `mkpasswd` という名前のコマンドが含まれていますが、これは挙動が違い、生パスワードのランダム生成を行うもので、残念ながら `shadow` に使えるハッシュの生成が行えません。

```:/mnt/etc/shadow
root:$6$O74p4bGZHXouJnn$AlV2dbg1pBi/sfPPP.qzQfPQx2RltwdXB/b1c/wPTKez8MhI0KZx5VccAzyV0fmzBZaqYxH3tWjSUvolFJwNM1:10770:0:::::
halt:*:9797:0:::::
operator:*:9797:0:::::
shutdown:*:9797:0:::::
sync:*:9797:0:::::
bin:*:9797:0:::::
daemon:*:9797:0:::::
...
```

(以下は OpenRC 利用時のみ)
シリアルコンソールを使えるようにするため、 `/mnt/etc/inittab` に以下の一行を加えます。ファイル中に `# SERIAL CONSOLES` とコメントが書かれている部分があるので、その付近に加えるとよいでしょう。

```
s0:12345:respawn:/sbin/agetty -L 115200 ttySIF0 vt100

```

### 8. 後始末〜再起動

マウント解除します。

```sh
cd
umount -l /mnt
```

その後再起動を行います。`reboot` で再起動することはできないようなので一端電源を落とします。

```sh
poweroff
```

この後電源を入れ直すと展開した stage3 をベースに起動します。

```
...
Hit any key to stop autoboot:  0 
PCIe Link up, Gen1

Device 0: Vendor: 0x15b7 Rev: 231800WD Prod: 212226805451        
            Type: Hard Disk
            Capacity: 228936.5 MB = 223.5 GB (468862128 x 512)
... is now current device
Scanning nvme 0:1...
Found /extlinux/extlinux.conf
Retrieving file: /extlinux/extlinux.conf
204 bytes read in 1 ms (199.2 KiB/s)
1:      OpenEmbedded-SiFive-HiFive-Unmatched
Retrieving file: /Image.gz
7247925 bytes read in 10 ms (691.2 MiB/s)
append: root=/dev/nvme0n1p2 rootfstype=ext4 rootwait console=ttySIF0,115200 earlycon
Retrieving file: /hifive-unmatched-a00.dtb
10473 bytes read in 1 ms (10 MiB/s)
   Uncompressing Kernel Image
Moving Image from 0x84000000 to 0x80200000, end=810b2000
## Flattened Device Tree blob at 88000000
   Booting using the fdt blob at 0x88000000
   Using Device Tree in place at 0000000088000000, end 00000000880058e8

Starting kernel ...

[    0.000000] Linux version 5.11.10 (oe-user@oe-host) (riscv64-oe-linux-gcc (GCC) 10.2.0, GNU ld (GNU Binutils) 2.36.1.20210209) #1 SMP Wed Apr 7 17:37:34 UTC 2021
[    0.000000] OF: fdt: Ignoring memory range 0x80000000 - 0x80200000
[    0.000000] earlycon: sifive0 at MMIO 0x0000000010010000 (options '')
[    0.000000] printk: bootconsole [sifive0] enabled
[    0.000000] efi: UEFI not found.
[    0.000000] Zone ranges:
...
This is localhost.unknown_domain (Linux riscv64 5.11.10) 11:04:16

localhost login:
```

ユーザー: `root`、 パスワード: SD カードのものと同様 (または自前で shadow の内容を設定していた場合はそれに沿ったもの) でログインできるはずです。

ログインして `cat /etc/os-release` してみると Gentoo を表す記述が見えることで無事 stage3 から起動できたことが分かります。

```conf:/etc/os-release
NAME=Gentoo
ID=gentoo
PRETTY_NAME="Gentoo/Linux"
ANSI_COLOR="1;32"
HOME_URL="https://www.gentoo.org/"
SUPPORT_URL="https://www.gentoo.org/support/"
BUG_REPORT_URL="https://bugs.gentoo.org/"
```

## この後行うこと

本記事での作業はここまでですが、ひとまず起動できるようになったというだけで、 Gentoo Linux のセットアップが完了したと言える状況ではありません。
セットアップを完了させるには以下を行う必要があります。

 - ハンドブックの残りの内容の消化  
   AMD64 用など、他のアーキテクチャのハンドブックを参考にしながら、 portage のセットアップや各種システム設定を行っていきます。ブートローダーのインストールは不要です。
 - カーネルビルド  
   これもハンドブック内の作業のひとつですが特に言及することがいくつかあるので別枠にて。  
   カーネルをビルドしたら、カーネルソース内に `arch/riscv/boot/Image.gz` ができるので、これを `/boot` (マウントしておくこと！) にコピーすることでカーネル更新できます。  
   しかし、カーネルのビルドをネイティブで行おうとすると非常に時間がかかりますのでクロスビルドをお勧めします[^kernel-build-time][^kernel-build-article]。

[^kernel-build-time]: 好みの設定を行ったものを clang でネイティブビルドしたところ 5 時間かかりました。gcc でも 3 時間。なお、Rysen 9 5950X 機でクロス gcc を用いて同一設定のビルドを行ったら 3 分。圧倒的な差です。
[^kernel-build-article]: 気が向いたら記事を書くかも。でも一般的なカーネルクロスビルドと大して手順が変わらないんですよね。

## おまけ
### NVMe SSD から起動できるようになった後、 SD カード側から起動する方法

1. 一端電源を落とす
1. シリアルコンソールを繋いでおく
1. 電源を入れる
1. シリアルコンソールで `Hit any key to stop autoboot` のカウントが 0 になる前に enter を入力する
1. `run bootcmd_mmc0` を実行する[^bootcmd]

[^bootcmd]: U-Boot にて `env print` を実行してずらずら表示される環境変数から、 `boot` から始まるものを探して追い掛けてみると何をやっているのか透けて見えて面白いのでお勧めです (面白さの感じ方には個人差があります)。

なお、NVMe に有効なファイルシステムがある場合、 `/run/media/*` に自動的にマウントされます。ファイルシステムのフォーマットをしたりパーティションを編集したりする場合はアンマウントをお忘れなく。