はじめに
NervesをM5Stack core MP135に移植しました。
移植をしてみて、Nervesの内部の構造や、知らない機能を知る機会となりました。
ファームウエアのアップデート機能は、Nervesの便利な機能の一つです。
利用者の立場では、特に動作の詳細は意識していなかったのですが、移植してファームウエアの書き換えをしてみると、ファームウエアのステータスが、「Not Validated」となり、それ以降、ファームエアのアップデートができない状況となってしまいました。
「Not Validated」と言われても・・・。
Firmwareのバリデーションとは?
nerves-systemでは、firmware-metadataが定義されています。
UBootEnvというモジュールがあり、ここにmetadataが保存されています。
次の例では、"nerves_fw_active" => "a"
となっているので、aパーティションがアクティブである事がわかります。
a.はAパーティション、b.はBパーテイションに関する値です。
iex(3)> UBootEnv.read()
{:ok,
%{
"a.nerves_fw_application_part0_devpath" => "/dev/mmcblk0p6",
"a.nerves_fw_application_part0_fstype" => "f2fs",
"a.nerves_fw_application_part0_target" => "/root",
"a.nerves_fw_architecture" => "arm",
"a.nerves_fw_author" => "The Nerves Team",
"a.nerves_fw_description" => "",
"a.nerves_fw_misc" => "",
"a.nerves_fw_platform" => "stm32mp135d",
"a.nerves_fw_product" => "test2_project",
"a.nerves_fw_uuid" => "8d326331-eb89-55b0-08b0-245f85194c70",
"a.nerves_fw_vcs_identifier" => "",
"a.nerves_fw_version" => "0.1.0",
"b.nerves_fw_application_part0_devpath" => "/dev/mmcblk0p6",
"b.nerves_fw_application_part0_fstype" => "f2fs",
"b.nerves_fw_application_part0_target" => "/root",
"b.nerves_fw_architecture" => "arm",
"b.nerves_fw_author" => "The Nerves Team",
"b.nerves_fw_description" => "",
"b.nerves_fw_misc" => "",
"b.nerves_fw_platform" => "stm32mp135d",
"b.nerves_fw_product" => "test2_project",
"b.nerves_fw_uuid" => "8d326331-eb89-55b0-08b0-245f85194c70",
"b.nerves_fw_vcs_identifier" => "",
"b.nerves_fw_version" => "0.1.0",
"nerves_fw_active" => "a",
"nerves_fw_autovalidate" => "0",
"nerves_fw_booted" => "0",
"nerves_fw_devpath" => "/dev/mmcblk0",
"nerves_fw_validated" => "0",
"nerves_serial_number" => ""
}}
この中に、"nerves_fw_validated" => "0"
があります。
この値が1の場合、validatedとなります。
ファームウエアアップデート時のvalidatedの扱い
nerves_fw_validated
の値を1にすれば、validatedとなる事はわかりました。
この値は本来どの様に使うものなのか、ドキュメントに記述がありました。
以下は、Nerves-runtimeの日本語訳です
- 新しいファームウェアが通常の方法でインストールされると、Nerves.Runtime.KV 変数の nerves_fw_validated が 0 に設定されます(fwup.conf がこれを行います)
- システムは通常通り再起動します
- デバイスは 5 分間の再起動タイマーを開始します(ハングアップや非常に遅い起動を検出したい場合は、コード内でこれを行う必要があります)
- アプリケーションはファームウェア更新サーバーへの接続を試みます
- 接続が成功した場合、アプリケーションは Nerves.Runtime.validate_firmware/0 を呼び出して nerves_fw_validated を 1 に設定し、再起動タイマーをキャンセルします
- エラーが発生した場合、再起動タイマーが失敗した場合、またはハードウェアウォッチドッグのタイムアウトが発生した場合、システムは再起動します。ブートローダーは前のファームウェアに戻ります
このようなフローにしておけば、確実にファームウエアアップデートができた場合だけ、新しいファームウエアに切り替わり、ファームウエアに問題があった場合には、元のファームウエアに戻る事ができます。
ファームウエアの更新サーバと接続ができる事をnerves_fw_validatedにする条件としておくのが最も安全です。更新サーバと接続ができれば、何か問題があったとしても、最悪、次のアップデートを行う事ができるからです。
Core MP135で行った対応
iexから次のコマンドを入力すれば、nerves_fw_validatedの値が1にかわります。
Nerves.Runtime.validate_firmware
こうなれば、validateとなる事は確認しました。
確実な事を考えると、ドキュメントにあるような、ファームウエアの更新サーバと接続ができる事を確認のような仕組みの作りこみは個別に行う必要があります。
汎用的な、ファームウエアの更新サーバがあるわけでもないのでちょっと難しいです。
Raspberry PiのTargetの実装を調べてみると、バリデートの機能は無効になっていました(具体的には、ファームウエアのアップデートを行った時に、nerves_fw_validated を 0 に書き換える処理を行わない) 。
Core MP135でもこれに習って、無効となっています。
ちなみに、BeagleBoneはubootと連携してなにか行う実装がされていましたが、詳細は追えていません。Core MP135でもできるようなものだったら導入してみたいと思います。
実際に製品のファームウエアを作成する場合には、ドキュメントにあるような再起動タイマーの設定やNerves.Runtime.validate_firmware
を適切に実行してバリデーションを実装するとファームウエアアップデート時の信頼性を高められるので、興味のある方は、お試しください。
関係資料