産業用堅牢ハードでElixir：e-RT3にNervesポーティング

# はじめに

タイトルがやったことのすべてを語っていますが、今回Nervesのポーティングにトライし、無事動作させることができました！

そこで、これからNervesポーティングのトライをする人のために、ポーティング作業のポイントをまとめてみました。


# ターゲットマシン

ターゲットマシンは @myasu さんが記事を量産している
横河電機製の「[リアルタイムOSコントローラ（e-RT3）](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/)」の「[汎用OS対応CPUモジュール](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/rtos-cpu/rtos-osfree-cpu/)」です。

彼が

* 「Nervesのポーティングができるんじゃないか💡」
* 「**産業用堅牢ハード**にNervesが載ったら💡」

とアイデアを持ってきてくれたことが、このハードがターゲットマシンとなったきっかけでした。

**この「[汎用OS対応CPUモジュール](https://www.yokogawa.co.jp/solutions/products-platforms/control-system/real-time-os-controller/rtos-cpu/rtos-osfree-cpu/)」は、なかなか尖っていてユーザーが走らせたいOSを自由に選定することができる機種です。そのため、OSがブートする仕様のドキュメントや、Linuxカーネルソースが提供されています。**

**ユーザーが走らせたいOSを自由に選定できるように仕様/ソースが開かれているので、今回のポーティングを成功させることができました。**

※「汎用OS対応CPUモジュール」は長いので、本記事内では以降「e-RT3」とよびます。


# ポーティングのアウトライン

Nervesのポーティング手順は[nerves_system_brのREADME](https://github.com/nerves-project/nerves_system_br/blob/0229532e3843bc3b4e534807636c767c380ae4b4/README.md)に以下のようにまとめられています。

> 1. Create a minimal Buildroot `defconfig` that boots and runs on the board. This
   doesn't use Nerves at all.
> 2. If the `defconfig` requires a writable root filesystem, figure out how to
   make it read-only. This should be pretty easy unless you're using `systemd`.
   Since Nerves uses a custom init system, keep in mind for later that `systemd`
   may be helping initialize something on the board that will need to be done
   manually later.
> 3. Take a look at the Flash memory layout and compare that to the layouts used
   in one of the supported systems. We use
   [fwup](https://github.com/fhunleth/fwup) to create images. There's a lot of
   variety in how one can lay out Flash memory and deal with things like
   failbacks.  At this point, just see if you can get `fwup` to create an image.
> 4. Clone one of the official systems that seems close for your board. Update
   the `nerves_defconfig` based on the Buildroot `defconfig` that works.
> 5. Build the system using `mix` or manually by running the `create-build.sh`
   script.

私はこの順に作業を進めましたが、振り返ると、初めてLinuxのポーティングをする私がまずすべきは大枠の理解でした。

大枠というのは、`「ターゲットマシンのブートする仕組み」`です。
これが分かると、あとはBuildrootの使い方を勉強して、カーネルとdtbを作ることで、上記手順の2までを終わらすことができました。
手順3,4,5は、[nerves_system_rpi3](https://github.com/nerves-project/nerves_system_rpi3)をベースとし改造を加えました（何をしたかを文字に起こすのが難しいところです）。


# まずは、e-RT3のブートの仕組みを理解する

このモジュールは電源が投入されると内蔵ROM（？）にあるブートローダー、U-Bootを読み込み起動します。

そのU-BootはモジュールのSDスロット1, 2に挿さるSDにuEnv.txtが順にないか確認し、あればそれに従い動作します。※uEnv.txtはU-bootのスクリプトのようなものです。

提供されているUbuntuイメージにも当然uEnv.txtがあり、動作するリファレンスとしてとても役に立ちました。


## uEnv.txtのポイント

uEnv.txtを使う際のポイントは、

* カーネル、dtbがどこにあるか
  * これは作成するSDイメージの構成によって定まります
* それらをRAMのどこ（のアドレス）に読み込ませるべきか
  * これはターゲットマシンの仕様で、e-RT3ではドキュメントに記載されていました
* カーネルパラメータ（root=rootfsのデバイス等）をどうするか
  * これはUbuntuのuEnv.txtにあるパラメータを踏襲しつつ、改造します

です。

上記がわかると、uEnv.txtを使い、ターゲットマシンのRAMにカーネルとdtbを読み込ませて、カーネルパラメータを渡して、uBootのbootm/z コマンドでカーネルを起動させることができます。


# 次に、Buildrootを使って、カーネル, dtb, rootfsをつくる

ターゲットマシンの起動の仕組みが分かると、カーネルとdtb、ルートファイルシステムの作成が必要なことがわかります。これらはLinuxのカーネルソースをダウンロードして、コンフィギュレートしてコンパイルすることで作成可能ですが、Nervesでは[Buildroot](https://buildroot.org/)を使ってこれを行います。

Buildrootは組み込み向けのLinuxシステム（カーネル、dtb、rootfs）を構成するためのツールです。

Linuxのカーネルソースで使う`make menuconfig`が、Buildrootにもあり（Buildroot用にラップされています）、コンフィギュレートすることができます。

Buildrootはコンフィギュレーションに応じ、必要なものをダウンロードしホストマシンでクロスコンパイルを行います。必要なものというのは、例えば、

* 指定したバージョンのLinuxカーネルソース
* カーネルのパッチ（リアルタイムパッチ等）
* クロスコンパイラ（ターゲットマシン用バイナリを作成するコンパイラ）
* イメージ作成のための[fwup](https://github.com/fwup-home/fwup)

などです。これにより、[Buildrootに登録されているboard](https://github.com/buildroot/buildroot/tree/master/board)であれば、コンフィギュレーションのみでlinuxシステムを構成できます。

**e-RT3はBuildrootのboardには登録されていないので、該当する設定を作成してやる必要があります。**


## e-RT3 Linuxカーネルソースの調査とカーネル類の生成

提供されているカーネルソースを調査すると、それが以下のように構成されているだろうとわかりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/79cf83dd-7c38-9df9-1207-26b187c75411.png)

※e-RT3はザイリンクスのZynq-7000を採用しており、ザイリンクスの[linux-xlnx](https://github.com/Xilinx/linux-xlnx)のコードが部分的に適用されていそうでした。

下段2つはBuildrootをコンフィギュレートすることで、ダウンロードさせることができます。
なので、カバーできない上段2つをパッチとして用意できれば、e-RT3の機能をカバーできるカーネルを作れます。
※[aufs](https://github.com/sfjro/aufs4-standalone/tree/aufs4.14.73+)パッチはNervesには不要のため、無視しました。

そこで、上図の下段3つで構成されるカーネルソースを作り、それと提供されているカーネルソースの差分から上段2段の合成パッチを作ることで、最終的なカーネルソースが下図となるようにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/4917da66-d1fb-960f-e910-4e31fd454d67.png)

合成パッチを作成したら、Buildrootのリファレンスに従いboardディレクトリに必要な設定ファイルを作成し、makeするとカーネル類が生成できます。（詳細は割愛
※BR2_ROOTFS_POST_IMAGE_SCRIPTを使い、SDに焼けるimg, fwを作成できるようにすると大変楽ができます。

リファレンスは、 https://buildroot.org/docs.html からアクセスできる以下の２つを参考しました。
どちらも分かりやすい素晴らしいドキュメントです。

* [The Buildroot user manual](https://buildroot.org/downloads/manual/manual.html)
  * Chapter 9をよく読みました。
* [BootlinのBuildroot Trainingのスライド](https://bootlin.com/doc/training/buildroot/buildroot-slides.pdf)
  * 約350ページありますが、すべてを読む必要はなく、知りたいキーワードを絞って検索をかけると高確率でそれの答えが載っていた気がします。

このとき作成したe-RT3向けの[br2-external](https://github.com/pojiro/br2-external-f3rp70)はここに公開しています。


## 作成したカーネルの起動確認

作成したイメージ(buildroot-dir/output/images/f3rp70.img)をSDに焼いて、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/35e922d2-e44a-2dd4-c870-9c56489727c7.png)

シリアルをつないで、電源を投入すればBuildrootで作成したミニマルなLinuxが起動できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/a9a0e3c5-f03f-d9e0-f55a-4dd64237c60a.png)



# 最後に、Nervesになるように合わせ込む

何をしたか記載するのが難しいですが、
今回作成した[nerves_system_f3rp70](https://github.com/pojiro/nerves_system_f3rp70)は[nerves_system_rpi3](https://github.com/nerves-project/nerves_system_rpi3)をベースに作成しました。
両者のdiffを見てもらうことで何を変更したか確認いただけると思います。

※あわせこみでは、Buildroot用のdefconfigをnerves_system_f3rp70のnerves_defconfigにリネームしています。


## Nervesの起動確認

プロジェクトを作って動作させれば、yey!!

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/369713/553b229d-9eae-fb75-93e2-23fd8009af66.png)


# Nervesがポーティングできたら

Nervesになってしまえば、

* Elixirの強みである、並行処理、プロセス監視復旧機能がフルに
* Elixirで作られているライブラリを存分に

に使えます。例えば、

* 通常のLinux環境でsystemdやshellスクリプトで行っていたデーモンの監視復旧はElixirだけで作成する
* Nginx, ApacheはElixirのPhoenixで代用する

ことができます。そして、設定ファイルが/etcに散ることもないです。

**これらは純粋にNervesの強みです。**

※私がNervesを一押しする理由は「[なぜ僕はNervesに期待するのか](https://qiita.com/pojiro/items/99a60dc8a4427b79ddc7)」に書いています。

※追記：hex.pmに公開しました→ https://hex.pm/packages/nerves_system_f3rp70

## e-RT3固有の対応が必要なところ

e-RT3のIOはメーカー固有のlibm3.soを介して叩く必要が有ります。

たとえば、DIOはGPIOでないので、[circuits_gpio](https://github.com/elixir-circuits/circuits_gpio)は使えません。なので、@myasu さんが[リアルタイムOSコントローラ e-RT3 （Elixirから制御編）](https://qiita.com/myasu/items/0dbf308947b878423112)でやられているようにNIFsを介する必要が有ります。

circuits_gpioのようなライブラリを作成するのがよさそうです。


# まとめ

* **横河電機製の産業用堅牢ハード, e-RT3(F3RP70)** に
* **組み込みLinuxの開発体験ゲームチェンジャーであるNerves** をポーティングできました🎉

今回の経験により、以下が押さえられれば、単純なポーティングならばできるだろうという自信が持てました。

* ブートの仕様が分かる
* 動作するカーネルソース（dtsを含む）がある


※本取り組みの動機などは[FA設備技術勉強会 in ROBOMECH](https://fa-study.connpass.com/event/206844/)で、[ラダーを使わないコントローラ](https://speakerdeck.com/pojiro/radawoshi-wanaikontorora)というタイトルで発表を行っています。こちらもぜひご覧いただけると嬉しいです。

## 謝辞

* ポーティングのきっかけを作った **@myasu さん**
* 開かれたハードを提供されている **横河電機 さん**
* 僕らの味方 **Nerves**

に感謝します。Thank you!!