M5 UnitV2にGentoo Linuxを入れてみた

# 目的
デフォルトのLinux環境ではパッケージマネージャが無かったりbusyboxが苦手だったり、
面倒くさそうだったので、挑戦しました。  
メインマシンはKaliを使っていますが、インストールするのはGentooなのは単に趣味です。  
カメラとWIFIはWANT要件で、出来ればいい程度です。

::: note alert
本手順を実施し失敗すると、UnitV2が起動できなくなり、
公式のレスキューイメージを使用しても復元できない場合があります。
手順の実施は自己責任でお願いします。
:::
# 結論
- 内蔵Flashのバックアップと復元が出来た。
- U-Bootにしろkernelにしろバニラではなく、それ用にカスタマイズされたものが要る（気がする）。
- 参考元にあった通り、WiFiを認識してないっぽい。

# やりたいこと
- UnitV2のフラッシュをバックアップする。
- UnitV2のカーネルを最新のLinuxKernelにする。
- MicroSDにはいっているGentooLinuxを起動できるようにする。

# 前提など

## 対象
- M5 UnitV2 M12版
https://www.switch-science.com/products/7536?_pos=1&_sid=63f78118f&_ss=r

## 使ったもの
- LinuxKernelビルドに用いるPC （ビルド用PC）
  M1 Mac のParallels環境でやりました。Kali Linux(arm64) 
- USB-UART 
  https://www.switch-science.com/products/8665?_pos=13&_sid=c0181386b&_ss=r
  たまたま上記を持っていたので使いましたが、RaspberryPiにもUARTあるので必須ではないです。
- I2Cが使えるLinuxPC （フラッシュ用PC）
  私はRaspberry Pi4を用意しました。こちらにはKali Linuxを入れています。  
  ないと失敗したときのリカバリが出来ないのでほぼ必須です。
- ジャンパワイヤ
  ブレッドボードに差したりするようなジャンパワイヤです。  
  フラッシュ用PCとM5UnitV2のデバッグ端子に接続するために使います。  
  RaspberryPiを使用する場合は、片方オスもう片方メスの線が3本程度あればいいです。
  デバッグ端子のスルーホールがそこそこ小さいので、太すぎる端子だと刺さらないかも。  
- MicroSD 
  私はUnitV2付属の16GBのを使いました。何でもいいと思いますが、ないと目的を達成できません。
# やったこと

## 先頭領域以外のフラッシュデータの退避（UnitV2側作業)
UnitV2のFlashメモリには以下の通りのパーティションになっています。
|No|アドレス範囲|名前|デバイスファイル名|
|----|----|----|----|
|?|0x000000000000-0x000000140000 | (先頭領域) |-|
|0| 0x000000140000-0x0000001a0000 | "IPL0"|/dev/mtdblock0|
|1| 0x0000001a0000-0x000000200000 | "IPL1"|/dev/mtdblock1|
|2| 0x000000200000-0x000000260000 | "IPL_CUST0"|/dev/mtdblock2|
|3| 0x000000260000-0x0000002c0000 | "IPL_CUST1"|/dev/mtdblock3|
|4| 0x0000002c0000-0x000000380000 | "UBOOT0"|/dev/mtdblock4|
|5| 0x000000380000-0x000000440000 | "UBOOT1"|/dev/mtdblock5|
|6| 0x000000440000-0x0000004a0000 | "ENV0"|/dev/mtdblock6|
|7| 0x0000004a0000-0x0000004c0000 | "KEY_CUST"|/dev/mtdblock7|
|8| 0x0000004c0000-0x0000009c0000 | "KERNEL"|/dev/mtdblock8|
|9| 0x0000009c0000-0x000000ec0000 | "RECOVERY"|/dev/mtdblock9|
|10| 0x000000ec0000-0x000020000000 | "UBI"|/dev/mtdblock10|

UBIがUnitV2で普通にファイルシステムとして見える領域になります。  
内蔵フラッシュは起動関連を除き、以後使わないのですが約500MBがもったいないので、適当に/rootとかにマウントするようにします。

では、UnitV2にrootでログインし、以下のコマンドで先頭領域以外のバックアップをmicroSDに保存します。
```
dd if=/dev/mtdblock0 of=/media/microsd/0ipl0.img bs=256
dd if=/dev/mtdblock1 of=/media/microsd/1ipl1.img bs=256
dd if=/dev/mtdblock2 of=/media/microsd/2ipl_cust0.img bs=256
dd if=/dev/mtdblock3 of=/media/microsd/3ipl_cust1.img bs=256
dd if=/dev/mtdblock4 of=/media/microsd/4uboot0.img bs=256
dd if=/dev/mtdblock5 of=/media/microsd/5uboot1.img bs=256
dd if=/dev/mtdblock6 of=/media/microsd/6env0.img bs=256
dd if=/dev/mtdblock7 of=/media/microsd/7key_cust.img bs=256
dd if=/dev/mtdblock8 of=/media/microsd/8kernel.img bs=256
dd if=/dev/mtdblock9 of=/media/microsd/9recovery.img bs=256
dd if=/dev/mtdblock10 of=/media/microsd/10ubi.img bs=1M
sync
```
:::note warn
バックアップはなくさないよう大事に保管してください。カーネル入れ替え後、再度の入手は出来ません。
:::

## フラッシュデータの先頭領域の退避
ddだけでは先頭領域の待避が出来ませんので、i2cを使ってフラッシュから読み出します。
i2cなら全部の領域を読み書きできるので最初からそうしたいとも考えますが、恐ろしく遅いのでddと併用です。  
### 配線
UnitV2のケースを開けます。  
ファン側にある2本の長いツメは容易に外れますが、ボタン側のツメが固いので壊さないよう注意してください。  
ファンの横に4個並んだ金色のスルーホールがあります。  
四角いランドが1つに丸いランドが3つです。  
四角いランドを1番として、以下のピン配置になります。

|No|名前|
|----|----|
|1|+5V|
|2|RXD/SCL|
|3|TXD/SDA|
|4|GND|

このデバッグピンですが、UARTとI2Cが被っています。自動で変わるようです。  
フラッシュの時にはI2Cとして使い、その後の設定とかはUARTで行います。
スルーホールが小さく、間隔が狭いのでショートしないよう気をつけてください。

フラッシュ用PCとしてRaspberryPiを使用する場合はこちらのピン配置を参考にしながら結線してください。
https://pinout.xyz  

:::note warn
電源はUSB-Cから供給されるため、+5Vの結線は不要です。 2,3,4の3本のみ繋いでください。
ただし、+5Vをデバッグピンから供給したい場合はその限りではありません。
:::

### 下準備
必須ではないかもしれませんが、念のため作業中にLinuxが起動しないよう、動作を止めます  
microSDカードを抜いた状態でUnitV2の上側にあるボタンを押しつつUSBケーブルを挿します。  
電源が入りますので、赤LEDが点灯するまで待ちます。  
しばらくすると、白LEDが点滅した状態が継続します。これは、microSDカードがないため、リカバリーが止まっている状態です。
要はmicroSDカードなしで通常のリカバリー手順を実施するとこうなるのですが、フラッシュ時の作業はこの状態にしてまま行います。
#### 必要なものをインストール(フラッシュ用PC側作業)
I2C関連とかビルドに必要なのを入れます
```
sudo apt-get update && sudo apt-get -y install automake  git i2c-tools libusb-dev libudev-dev libusb-1.0-0-dev
```
SNANderをクローンします。ビルドが通るよう微修正するためフォークしています。
```
git clone https://github.com/ebisuke/SNANDer.git
```

SNANDerをビルドします。
```
cd SNANDer/src
make
```
`SNANDer`という実行ファイルができあがるので、適当な場所にコピーします。
#### i2c-detect
i2c-detectでダンプしてみて、アドレス0x49が見つかることを確認します。
```
sudo i2cdetect 1
```
```
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-1.
I will probe address range 0x08-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- 49 -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- 59 -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```
#### 待避（フラッシュ用PC側作業)
先頭領域をファイルにバックアップします。   
この領域を何かの拍子に上書きしてしまうと、問題が発生する可能性がありますのでその復元用です。
```
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x000000 -l 0x140000 -r gci.bin
```
:::note warn
こちらのバックアップもなくさないよう大事に保管してください。
:::
## Gentooを用意
### 作業用ディレクトリ作成（ビルド用PC側作業）
説明を簡単にするために作業用ディレクトリをホームに作成します。
一連のビルドをおこなう諸々もクローンします。
openRCだと止まるのでsystemdにしました。やりようあると思うけど・・・わからん・・・
```
mkdir ~/work
cd ~/work
git clone https://github.com/ebisuke/buildroot_customconfig_unitv2.git custom
```
### U-bootとカーネルをビルド（ビルド用PC側作業）
クロスコンパイル用の環境を整えます。
```
sudo apt-get update && sudo apt-get install -y git gcc-arm-linux-gnueabihf build-essential flex bison libncurses5-dev fakeroot xz-utils bc
```
シェルスクリプトを実行します。
UnitV2用カーネルとU-Bootをクローンして自動でビルドと、GentooのStage3を自動でダウンロードしmicroSDに展開します。
```
cd ~/work/custom
sh build.sh
```
最初にmicroSDのパスを聞いてきますので入れてください。空欄にするとGentooのStage3を自動でダウンロードはせず、行います。
最終的に`output`というフォルダに`uImage`と`unitv2-ipl`が出来ますので、これをフラッシュ用PCにもって行きます。

## フラッシュする（フラッシュ用PC側作業）
### フラッシュにイメージを書き込む
"UBOOT0"以降を全部消して、書き込み先を綺麗にした後、書き込みます。  
消すときにエラーが出力されますが、スルーしています。
:::note warn
この手順実施後、UnitV2標準のリカバリ方法では復元できなくなります。後述の「復元する」手順を確認ください。
:::
```
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x200000 -l 0x0cc0000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x2c0000 -l 0x1FD40000 -e

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x200000 -w unitv2-ipl -v
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0xec0000 -w uImage -v
```

## 動作チェック(UnitV2、UARTをつなげるPC)
電源用USBケーブルを抜き、UnitV2に繋いでいたI2C用の接続を外し、代わりにUART用に繋ぎかえます。
U-Boot起動待ち時間が増えたので、起動にやや時間がかかります。

とりあえず起動。パスワードは`root`です。
```
[  OK  ] Started Rule-based Manager for Device Events and Files.
         Starting Network Configuration...
[  OK  ] Started Network Configuration.
[   15.529564] random: crng init done
[  OK  ] Finished Load/Save Random Seed.
[  OK  ] Found device /dev/ttyS0.
[  OK  ] Finished Create Volatile Files and Directories.
         Starting Network Name Resolution...
         Starting Network Time Synchronization...
         Starting Record System Boot/Shutdown in UTMP...
[  OK  ] Finished Record System Boot/Shutdown in UTMP.
[  OK  ] Started Network Time Synchronization.
[  OK  ] Reached target System Time Set.
[  OK  ] Started Network Name Resolution.
[  OK  ] Reached target Network.
[  OK  ] Reached target Host and Network Name Lookups.
[  OK  ] Reached target System Initialization.
[  OK  ] Started Daily Cleanup of Temporary Directories.
[  OK  ] Reached target Timer Units.
[  OK  ] Listening on D-Bus System Message Bus Socket.
[  OK  ] Reached target Socket Units.
[  OK  ] Reached target Basic System.
         Starting D-Bus System Message Bus...
         Starting User Login Management...
         Starting Permit User Sessions...
[  OK  ] Finished Permit User Sessions.
[  OK  ] Started Getty on tty1.
[  OK  ] Started Serial Getty on ttyS0.
[  OK  ] Reached target Login Prompts.
[  OK  ] Started D-Bus System Message Bus.
[  OK  ] Started User Login Management.
[  OK  ] Reached target Multi-User System.
[  OK  ] Reached target Graphical Interface.
         Starting Record Runlevel Change in UTMP...
[  OK  ] Finished Record Runlevel Change in UTMP.


This is unitv2.unknown_domain (Linux armv7l 5.18.0-rc7+) 14:02:31

unitv2 login: root
Password:
root@unitv2 ~ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether be:e7:57:62:c2:e0 brd ff:ff:ff:ff:ff:ff
root@unitv2 ~ #
```

## 復元する(フラッシュ用PC)
完璧にやらかした場合は、以下の手順で復元します。一部除くほぼ全パーティションをバックアップを用いてフラッシュします。
NANDフラッシュなので削除→書き込みの順です。  

i2cで書く都合、恐ろしく時間がかかりますのでUBI領域に関してはi2cでは行わずに、以下のURLの手順で普通に復元した方がいいと思われます。  
https://docs.m5stack.com/en/quick_start/unitv2/update

先頭領域(0x000000-0x140000)は上書きしてしまった場合にコメント解除して書き込みしてください。
```
#sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x000000 -l 0x140000 -e
#sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x000000 -w gci.bin -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x140000 -l 0x060000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x140000 -w 0ipl0.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x1a0000 -l 0x060000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x1a0000 -w 1ipl1.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x200000 -l 0x060000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x200000 -w 2ipl_cust0.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x260000 -l 0x060000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x260000 -w 3ipl_cust1.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x2c0000 -l 0x0c0000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x2c0000 -w 4uboot0.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x380000 -l 0x0c0000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x380000 -w 5uboot1.img  -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x440000 -l 0x060000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x440000 -w 6env0.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x4a0000 -l 0x020000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x4a0000 -w 7key_cust.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x4c0000 -l 0x500000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x4c0000 -w 8kernel.img -v 

sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x9c0000 -l 0x500000 -e
sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0x9c0000 -w 9recovery.img -v 

#sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0xec0000 -l 0x1F140000 -e
#sudo ./SNANDer -p mstarddc -c /dev/i2c-1:49 -a 0xec0000 -w 10ubi.img -v 
```

なお、今回のカスタムイメージを書き込んだあと、
UARTの入力受付待ちになりI2Cのコマンドを受け付けなくなる場合があります。
その場合はUARTに繋いだ状態で一旦電源を入れ直し、U-Bootのプロンプトで`loop 0x00 0x00`を入力すると動作が止まり、I2Cを受け付けるようになります。



# 感想
情報なさ過ぎだったのとLinuxカーネルのビルド経験も初めてだったので、当初数時間で終わるだろうと思っていたら4日もかかってしまいました…  
勉強不足を実感します。

# 参考
今回の手順は以下を参考にしました。
https://github.com/fifteenhex/buildroot_unitv2
https://wiki.gentoo.org/wiki/Handbook:AMD64/Full/Installation/ja
https://fowlrat.hatenablog.com/entry/2014/08/31/011056

# おまけ
参考になるかわからない文字列です。デフォルトのカーネルで起動時に出力されるものです。
https://gist.github.com/ebisuke/11df957da25fcbd1501af9bb7c3ecc5d

# おまけ2 シェルスクリプト作る前の解説
:::note warn
以下は不完全な記述です。ご了承ください。
:::

mstarカーネルをCloneします。  
UnitV2用のconfigも持ってきます。  

```
export ARCH="arm"
export CROSS_COMPILE="arm-linux-gnueabihf-"
cd ~/work/
git clone -b mstar_v6_1_rebase https://github.com/linux-chenxing/linux.git
cd linux
rm .config
curl -o .config https://raw.githubusercontent.com/fifteenhex/buildroot_unitv2/master/br2unitv2/board/m5stack/unitv2/linux.config 
make menuconfig
```
make menuconfigで設定画面が開きますので、`.config`をロードします。  
以下の項目を変えます。`init=/sbin/openrc-init`は適宜変更してください。
その他の設定も必要なら変更してみてください。  
- General Setup ->Default Init Path
    - `/sbin/openrc-init`
- Boot Options -> Default Kernel Command String
    - `console=ttyS0,115200 ubi.mtd=UBI,2048 root=mmcblk0p1:ext4 rw init=/linuxrc rootwait=1 LX_MEM=0x7f00000 mma_heap=mma_heap_name0,miu=0,sz=0x380000 mma_memblock_remove=1 highres=off mmap_reserved=fb,miu=0,sz=0x300000,max_start_off=0x7C00000,max_end_off=0x7F00000 mtdparts=nand0:384k@0x140000(IPL0),384k(IPL1),384k(IPL_CUST0),384k(DUMMY0),768k(DUMMY2),768k(DUMMY3),384k(DUMMY4),128k(DUMMY5),5m(DUMMY6),5m(DUMMY7),-(UBI)`
- File Systems -> Second Extended fs support
    - `[*]`  
  
※mtdpartsの定義はいろいろ試す前にコピペしていて、古いのでもしかしたら要らないかもしれない。

済んだらSaveし、設定画面を抜け、makeします。

```
make
```
### KernelのFITファイルを作成する
UnitV2はDas U-Bootを使用しています。  
それ用のU-Bootイメージを作成するためのツールを入れます。
```
sudo apt-get install -y u-boot-tools
```

### 書き込み用のイメージを作成する


自前ではよくわからなかったので、今回はfifteenhexさんのbuildroot_unitv2を使用しました。  
以下のようにクローンしてください。  
いろいろ入っていますが、U-Bootのみ使います。
```
cd ~/work
git clone --recursive https://github.com/fifteenhex/buildroot_unitv2
```
makeしてください。
u-boot以外もビルドするので時間がかかります。  
```
make
```
U-bootの設定を変えるので再度makeします。
```
cd ~/work/buildroot_unitv2/
rm uboot.config 
curl -o uboot.config https://gist.githubusercontent.com/ebisuke/7bb1a357d37df155e18547b491bd4ada/raw/788d2581483c853d7a0e6becb4f35430ac6e66f2/uboot.config
make
```
outputsにいろいろできますので、これを元にイメージを作ります。
buildコンフィグをgistから持ってきます。 ビルドしたカーネル(gentoo-kernel.fit)も同じフォルダにコピーしておきます。
env.imgも作ります。
```
cd ~/work/buildroot_unitv2/outputs
rm ubi.cfg uImage
wget https://gist.githubusercontent.com/ebisuke/bb2e5ec9ddbbf355c50b77b077bf1d7d/raw/f461763e44de046d02b344155313b538dd7cc91b/ubi.cfg
#cp -f ~/work/linux/arch/arm/boot/gentoo-kernel.fit ./
dd if=/dev/zero bs=1024 count=256 | tr '\000' '1' > env.img
ubinize -o uImage -p 128KiB -m 2048 -s 2048 ubi.cfg
```
uImageができあがりますので、scpか何かでフラッシュ用PCに持って行きます。

## Gentoo Linuxの準備（ビルド用PC作業）
### SDカードをext４でフォーマットする
MicroSDカードに展開する前に、先にMicroSDをext4でフォーマットしておきます。  
コマンドラインでは面倒だったので私はKDE Partition Managerを使いました。  

### Stage3をSDカードに展開する
こちらから、Gentoo stage3のアーカイブを入手します。私はarmv7a|HardFP|openrcを選びました。
https://www.gentoo.org/downloads/

Stage3を展開します。microsdは/mnt/sdにマウントされているものとします。
```
cd /mnt/sd/
sudo wget https://bouncer.gentoo.org/fetch/root/all/releases/arm/autobuilds/20230111T210213Z/stage3-armv7a_hardfp-openrc-20230111T210213Z.tar.xz
sudo tar xpvf stage3-*.tar.xz --xattrs-include='*.*' --numeric-owner
```

### /bootをコピー
stage3には/bootが空っぽなので、カーネルをビルドしたときに出来たbootをコピーします。

```
cd ~/work/linux/arch/arm/boot/
sudo cp -rvf ./* /mnt/sd/boot/
```

### /etc/fstabの編集
microSDをマウントできるようにします。
既存のUBIパーティションを再利用しますのでext2でマウントします。恐らくext4にも出来ると思いますが、よくわからないのでそのままです。  
"/"以外の設定もおおよそそのままです。
```
sudo nano /mnt/sd/etc/fstab
```
```
# <fs>                  <mountpoint>    <type>          <opts>          <dump/pass>
proc            /proc           proc    defaults        0       0
devpts          /dev/pts        devpts  defaults,gid=5,mode=620,ptmxmode=0666   0       0
tmpfs           /dev/shm        tmpfs   mode=0777       0       0
tmpfs           /tmp            tmpfs   mode=1777       0       0
tmpfs           /run            tmpfs   mode=0755,nosuid,nodev  0       0
sysfs           /sys            sysfs   defaults        0       0
/dev/mmcblk0p1  /               ext4    rw,nofail       0       1
```

これで、microSDの準備が出来ました。