SWUpdateを用いたA/Bアップデート

組み込み機器のソフトウェア更新は、セキュリティ脆弱性の修正やシステムの安定性向上、新機能追加のために必要な非常に重要なプロセスです。しかし、更新中の不具合や予期せぬ電源断が発生すると、システム全体が起動不能になる可能性があります。これを防ぐために、A/Bアップデートという手法が広く採用されています。

本記事では、Linux®ベースの組み込み機器におけるA/Bアップデートを、SWUpdate[^1]を使って実現する方法を解説します。SWUpdateは、柔軟で効率的なソフトウェア更新をサポートする強力なツールであり、多くのプロジェクトで採用されています[^3]。

今回は、Civil Infrastructure Platform™ Project(以下、CIP™)[^2]が提供するリポジトリを活用し、QEMU™[^4]を利用してデモンストレーションを行います。

[^1]: [SWUpdate - Your OTA for Embedded Linux and IOT - swupdate.org](https://swupdate.org/)
[^2]: The Linux Foundationのプロジェクトの1つ。[Civil Infrastructure Platform™ - cip-project.org](https://www.cip-project.org/)
[^3]: [Testimonials - SWUpdate - swupdate.org](https://swupdate.org/testimonials)
[^4]: [qemu.org](https://www.qemu.org/)

# 1 A/Bアップデートについて

A/Bアップデートは、システムが安全かつ確実にソフトウェア更新を行えるように、2つのパーティション(AとB)を活用します。

図をもとに、仕組みを簡単に解説します。

## 1.1 パーティション切り替えの流れ

以下の図では、バージョンを1.0.0から2.0.0へ更新する流れを3ステップで示しています。

![パーティション切り替え図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633486/984441e2-31da-4d0a-3fc8-27a3d45bcd09.png)
図1 パーティション切り替え図

　

ここ(①)では、Aパーティションが現在稼働しているActiveな状態、Bパーティションが未使用であり、Inactiveな状態であることを示しています。

新しいイメージ(2.0.0)は、Inactive状態であるBパーティションにインストールされます。インストールが完了した時点(②)では、システムはまだ、Aパーティションを使用して起動しています。

インストール後にシステムを再起動すると、BパーティションがActive状態になります。Bパーティションでの正常動作が確認されると、この状態(③)が確定されます。
**問題があった場合は**、再起動し、Aパーティションを使用して起動します。

この仕組みにより、更新失敗時にも、正常に起動していた元のパーティションから起動できるため、システム停止のリスクを最小化できます。

## 1.2 ステータス遷移

A/Bアップデートでは、各パーティションがステータスを持つことで、更新の進行状況と安全性を管理します。以下の4つのステータスを活用し、システムがどの段階にいるかを正確に把握します。これらのステータスは、Ustateと呼ばれる状態変数として管理されます。

| ステータス    | ustate | 説明                                                                                     |
|------------|:----:|------------------------------------------------------------------------------------------|
| OK         | 0    | 通常状態。正常に動作することを示します。                                     |
| INSTALLED  | 1    | 新しいイメージがインストールされたが、テストされていない状態。まだ、Inactiveです。 |
| TESTING    | 2    | INSTALLEDのパーティションを初めて利用した状態であり、Activeです。問題が発生した場合は元のパーティションへロールバックします。 |
| FAILED     | 3    | TESTINGの結果、失敗/異常となった状態です。または、何かしら問題が発生した状態。                                             |

これらのステータス(Ustate)は図に示すように遷移しながら管理され、安全性を確保しつつ更新プロセスを進めます。

![ステータス遷移図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633486/19985832-8422-7eb4-e876-525a8765897f.png)
図2 ステータス遷移図

## 1.3 全体フロー

A/Bアップデートの全体フローでは、パーティション切り替えとステータス遷移が連動して動作します。このフローでは、各パーティションにリビジョン番号が割り当てられ、起動時には「リビジョン番号が大きいパーティション」が優先されます。新しいイメージをインストールすると、リビジョン番号が更新(+1)されます。FAILEDのパーティションには、自動でリビジョン番号が0に設定され、起動対象から除外されます。

パーティション切り替えとステータス遷移を統合的に示したフロー図を示します。

![A/Bアップデートの全体フロー](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633486/c86a0b97-59cc-f150-1a8f-9674d90b6a45.png)
図3 A/Bアップデートの全体フロー
　

A/Bアップデートは、このようにリビジョン番号とステータス管理を組み合わせ、パーティションを切り替えることで、安全性と確実性を担保しながら更新プロセスを実現しています。

### 1.3.1 watchdogでのロールバック

上記のA/Bアップデートの全体フローでは、テスト(TESTING)を行うことで、新しいパーティションが正常に動作しているかを確認しています。
しかし、パーティションの切り替えやテスト中にシステムが異常停止(ハングアップ)してしまう可能性もあります。

こうした場合、Watchdogが機能します。
システムが応答しなくなった際、Watchdogが自動的に再起動を促します。
そして、TESTINGのステータスの状態で再起動された場合、そのパーティションはFAILEDとみなされ、元のパーティションにロールバックされて起動します。

# 2 SWUpdateについて

SWUpdateは、組み込みシステム向けに設計された信頼性の高いソフトウェア更新ツールです。セキュリティ対策、拡張性、そしてロールバック機能を備え、更新の安全性と柔軟性を両立しています。

## 2.1 ブートローダーとの連携

SWUpdateは、上記で述べたA/Bアップデートを基盤とし、システムの状態に応じたロールバック機能を提供します。更新プロセスで重要な役割を果たすのが状態変数であるUstateです。そして、これを適切に管理するためには、ブートローダーでの対応が必須です。組み込みシステムでは、CPUアーキテクチャやボードによって、異なるブートローダが使用されます。SWUpdateは複数のブートローダ(U-Boot, GRUB, EFI Boot Guard)をサポートするインターフェースを提供しており、汎用性の高い設計となっています。

## 2.2 sw-description

SWUpdateでは、cpio形式でアーカイブされた更新イメージを利用します。
この中には、更新対象等を定義するsw-descriptionという構成ファイルが含まれています。

以下は、sw-descriptionの簡単な例です。

```plaintext
software = {
    version = "2.0.0";
    hardware-compatibility: [ "1.0", "1.2"];
    images: (
        {
            filename = "rootfs.img.gz";
            device = "/dev/mmcblk0p2";
            sha256 = "f2f69c5d3ec9883bc36474947bf6fe4749f1f6ab5ff5288360b2f50ed03b89e4";
            compressed = "zlib";
        }
    );
}
```

このファイルでは、まず、更新のバージョンを表す`version`が記述されています。また、`hardware-compatibility`には、対象ハードウェアを識別するための情報が含まれています。さらに、imagesセクションには、更新するイメージファイルの名前や配置先デバイス、ハッシュ値、圧縮に関してなどが定義されています。

これらの情報により、更新内容が正確かつ安全に適用されます。

## 2.3 セキュリティ対策

SWUpdateは、更新プロセス全体の安全性を確保するために、以下のようなセキュリティ機能を備えています。

- ハッシュ値の検証
  - 各イメージファイルのハッシュ値を記述し、更新時に改ざんされていないことを確認します
- 署名付きsw-description
  - sw-descriptionを署名することで、インストール時に、改ざんされていないことを確認します。これにより、イメージファイルのサイズやハッシュ値また、バージョンなどの情報が正しいことを保証します
- 暗号化対応
  - イメージファイルを共通鍵で暗号化し、配信中や保存時のデータ保護を強化します

# 3 ブートローダー EFI Boot Gurardについて

EFI Boot Guard[^ebg] (以下、EBG)はUEFIベースのブートローダーです。
以下の機能を提供します:

- OSをロードする前にハードウェアウォッチドッグを起動
- UEFI や、EFI アプリケーションの実行をサポートします
- その他のブートローダ(U-Bootなど) から実行させる事も可能です
- Ustate の状態に基づく、Linux® カーネルの選択と起動を行います
- A/B管理されたパーティションごとに、以下の環境情報を保持・制御します
  - revision: ソフトウェアのバージョン情報。新しいものを優先して起動
  - kernel: 起動対象のカーネルのファイルパス
  - kernelargs: カーネルコマンドライン
  - ustate: 現在のUstate
- 上記の環境情報をユーザプログラム(SWUpdate 含む)が制御するためのAPIの提供

ステータス遷移(Ustateの変更) は、EBG自身、インストーラ(＝今回はSWUpdate)、ユーザプログラムのうちいずれかのソフトウェアによって実行されます。ユーザーは`bg_printenv`と`bg_setenv`[^ebg_tool]を利用することで、参照また編集することができます。

[^ebg]: [siemens/efibootguard - GitHub](https://github.com/siemens/efibootguard)
[^ebg_tool]: [efibootguard/docs/TOOLS.md master · siemens/efibootguard - GitHub](https://github.com/siemens/efibootguard/blob/master/docs/TOOLS.md)

## 3.1 役割

前述の全体のフロー図において、EBGは以下の役割を担います。

1. 初期状態において、`revision=2(>1)`と`ustate=OK`であることから、A 面を起動
2. インストール(a)後に、B面のustateを`INSTALLED`に変更
3. 再起動(b)後に、`revision=3(>2)`と`ustate=INSTALLED`であることからB面を起動すべきと判断し、B面のustateを`TESTING`に設定した上で起動
4. テストを実行(c)
5. テストが成功した時、テストソフトウェアがB面のustateを変更
6. 失敗したら再起動(d)する。B面が`revision=3(>2)`だが、`ustate=TESTING`のままであることから、テストに失敗したと判断し、B面を`ustate=FALIED`にセットした上で、A面を起動

EBGの動作を含めたフローは以下のようになります。

![全体フロー w/ EBG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/633486/38f3d1eb-ff29-0194-9563-5c616cdffaf9.png)
図4 全体フロー w/ EBG

# 4 SWUpdate round-robin handler

A/Bアップデートにおける各パーティションの状態管理や、起動先の切り替えなどは、前述のとおりEBGが実現しています。一方、**SWUpdateによる更新データのインストール時にも、A/Bアップデートに関して以下の制御を行う必要があります。**

- active/inactive パーティションの識別
- sw-description に記載されたデータごとの、インストール先デバイスの決定
  - 例:
    - rootfs(パーティションイメージ): A=/dev/sda4, B=/dev/sda5
    - カーネルファイル: A=/dev/sda2, B=/dev/sda3

CIP™では、上記のA/B 切り替えを効率的に設定するための仕組みとして、SWUpdate向けの拡張ハンドラであるround-robin handler[^rrh]をツールとして開発・提供しています。このハンドラを利用することで、更新イメージを作成する際には、イメージがA/Bどちらのパーティションにインストールされるかを考える必要がなくなります。

[^rrh]: [cip-project / cip-sw-updates / swupdate-handler-roundrobin · GitLab](https://gitlab.com/cip-project/cip-sw-updates/swupdate-handler-roundrobin)

# 5 デモ(試行手順と解説)

本章では、SWUpdateを使用したソフトウェア更新のデモンストレーションを行った際の手順と結果について述べます。

分かりやすいように、パーティションAでは通常のカーネルを利用し、パーティションBへRTカーネルを含むイメージをインストールする例を検討します。また、成功時と失敗時(ロールバック発生)の２パターンを確認します。

## 5.1 リポジトリ・環境

CIP™のリポジトリである`isar-cip-core`[^cip-core]を利用します。こちらのリポジトリは、CIP™ Core Generic ProfileおよびCIP™ SLTSカーネルのdebian®パッケージセットを使用し、仮想および物理ターゲット用のブータブルイメージを生成するためのプロジェクトです。

今回は、オープンソースのCPUエミュレータであるQEMU™を利用するため、QEMU™向けのイメージを作成し、デモンストレーションを行います。

以下のようにリポジトリをクローンし、`tag/v1.5`を利用します。

```bash
git clone https://gitlab.com/cip-project/cip-core/isar-cip-core.git
cd isar-cip-core
git checkout tags/v1.5
```

今回使用するQEMU™のバージョンを以下に示します。

```bash
qemu-system-x86_64 --version
# QEMU emulator version 6.2.0 (debian 1:6.2+dfsg-2Ubuntu6.6)
# Copyright (c) 2003-2021 Fabrice Bellard and the QEMU Project developers
```

[^cip-core]: [cip-project / cip-core / isar-cip-core · GitLab](https://gitlab.com/cip-project/cip-core/isar-cip-core)

## 5.2 パーティション構成など

以下に今回利用するパーティション構成を示します。

| 名称       | NAME | SIZE | コメント |
|------------|------|------|-------------|
| BOOTLOADER | sda1 | 16M  | - |
| BOOTA      | sda2 | 64M  | カーネル(UKI), 環境変数(A) |
| BOOTB      | sda3 | 64M  | カーネル(UKI), 環境変数(B) |
| ROOTFSA    | sda4 | 1G   | ROOTFS(A) |
| ROOTFSB    | sda5 | 1G   | ROOTFS(B) |
| HOME       | sda6 | 1.3G | /home       |
| VAR        | sda7 | 2G   | /var        |

BOOTA(sda2)とROOTFSA(sda4)がパーティションA、BOOTB(sda3)とROOTFSB(sda5)がパーティションBとして管理する領域になります。ROOTFSA/Bはそのままの意味ですが、BOOTA/Bではユニファイドカーネルイメージ(UKI)[^uki]と環境変数(Ustate)を管理します。

[^uki]: [Unified Kernel Image | UAPI Group Specifications - uapi-group.org](https://uapi-group.org/specifications/specs/unified_kernel_image/)

## 5.3 イメージのビルド

このリポジトリでは、`isar`[^isar]/`kas-container`[^kas]を利用してイメージをビルドします。ツールについては解説を省略します。

以下のコマンドのように、イメージをビルドします。ビルドされたイメージなどは`build/`以下に配置されます。

```bash
# 通常のカーネルを含むQEMU向けのイメージの作成
./kas-container build kas-cip.yml:kas/board/qemu-amd64.yml:kas/opt/ebg-swu.yml

# パーティションBへインストールされる予定のRTカーネルを含むQEMU向けのイメージの作成
KAS_BUILD_DIR=build/2.0.0 ./kas-container build kas-cip.yml:kas/board/qemu-amd64.yml:kas/opt/ebg-swu.yml:kas/opt/rt.yml
```

[^isar]: [ilbers/isar - GitHub](https://github.com/ilbers/isar)
[^kas]: [isar/kas at master · ilbers/isar - GitHub](https://github.com/ilbers/isar/tree/master/kas)

## 5.4 QEMU™での起動

以下のように、QEMU™を起動します。

```bash
DISTRO_RELEASE=bookworm SWUPDATE_BOOT=y ./start-qemu.sh amd64
```

起動したQEMU™の中で、現在の情報をチェックしていきます。
ステータスに関しては`bg_printenv`コマンドで確認することができます。

```bash
# (🦃 QEMU™の中)

uname -a
# Linux demo 6.1.112-cip30 #1 SMP PREEMPT_DYNAMIC Thu, 01 Jan 1970 01:00:00 +0000 x86_64 GNU/Linux

ls /lib/modules
# 6.1.112-cip30

bg_printenv
# ----------------------------
#  Config Partition #0 Values:
# in_progress:      no
# revision:         2
# kernel:           C:BOOT0:Linux.efi
# kernelargs:       
# watchdog timeout: 60 seconds
# ustate:           0 (OK)

# user variables:

# ----------------------------
#  Config Partition #1 Values:
# in_progress:      no
# revision:         1
# kernel:           C:BOOT1:Linux.efi
# kernelargs:       
# watchdog timeout: 60 seconds
# ustate:           0 (OK)

# user variables:

```

EBGではパーティションの表記にA/Bではなく数字の0/1を利用しています。

パーティションB(#1)では、`revision=1`、`ustate=0`であることが確認できます。

## 5.5 更新イメージの送信

更新イメージをQEMU™へ共有します。SWUpdateでは、OTAにも対応していますが、今回は直接ファイルを置きます。

```bash
scp -P 22222 build/2.0.0/tmp/deploy/images/qemu-amd64/cip-core-image-cip-core-bookworm-qemu-amd64.swu root@localhost:update.swu
```

ちなみに`*.swu`は以下のようになっています。

```bash
cpio -t < build/2.0.0/tmp/deploy/images/qemu-amd64/cip-core-image-cip-core-bookworm-qemu-amd64.swu
# sw-description
# sw-description.sig
# cip-core-image-cip-core-bookworm-qemu-amd64.squashfs
# Linux.efi
# 294943 blocks
```

## 5.6 インストール

`swupdate -i <filename>`で更新イメージを利用してインストールを開始できます[^NOTE_1]。

```bash
# (🦃 QEMUの中)

ls
# update.swu

swupdate -v -i update.swu
# ...
# [INFO ] : SWUPDATE started :  Software Update started !
# ...
# [INFO ] : SWUPDATE successful ! SWUPDATE successful !
```

パーティションBへのインストール完了したので、ステータスを確認します。

```bash
# (🦃 QEMUの中)

bg_printenv -p 1 -o ustate,revision
# Using config partition #1
# Values:
# revision:         3
# ustate:           1 (INSTALLED)
```

`revision=3`かつ`ustate=1`がであることを確認できました。

手動で再起動します。(こちらは設定で、インストール後に自動で再起動させることも可能です。)

```bash
# (🦃 QEMUの中)

reboot
```

[^NOTE_1]: 今回は`swupdaet`コマンドを直接利用していますが、`swupdate-client`コマンドも利用可能です。こちらのコマンドだとインストール完了後自動で再起動されます。

## 5.7 更新完了

再起動するとパーティションBで起動します。

まず、パーティションと内容について確認します。

```bash
# (🦃 QEMUの中)

lsblk
# NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# sda      8:0    0  5.4G  0 disk 
# ├─sda1   8:1    0   16M  0 part 
# ├─sda2   8:2    0   64M  0 part 
# ├─sda3   8:3    0   64M  0 part 
# ├─sda4   8:4    0    1G  0 part 
# ├─sda5   8:5    0    1G  0 part /
# ├─sda6   8:6    0  1.3G  0 part /home
# └─sda7   8:7    0    2G  0 part /var

uname -a
# Linux demo 6.1.111-cip29-rt15 #1 SMP PREEMPT_DYNAMIC Thu, 01 Jan 1970 01:00:00 +0000 x86_64 GNU/Linux

ls /lib/modules
# 6.1.111-cip29-rt15
```

sda5から起動し、カーネル、ROOTFSがRTになっていることを確認できました。

次に、ステータスを確認します。

```bash
bg_printenv -p 1 -o revision,ustate
# Using config partition #1
# Values:
# revision:         3
# ustate:           2 (TESTING)
```

ステータスは現在`TESTING`になっています。

実際の環境では、起動後にテスト等を行い、自動でステータスを更新します。今回は手動でステータスを`OK`にし、アップデート完了となります。

```bash
# (🦃 QEMUの中)

bg_setenv -c
# Environment update was successful.

bg_printenv -p 1 -o revision,ustate
# Using config partition #1
# Values:
# revision:         3
# ustate:           0 (OK)
```

## 5.8 更新失敗時(カーネルパニック)

前節から継続して動作確認を行います。

起動後にカーネルパニックが発生する更新イメージをビルドして、QEMU™へ共有します。

```bash
# カーネルパニックが発生するQEMU向けのイメージの作成
KAS_BUILD_DIR=build/panic ./kas-container build kas-cip.yml:kas/board/qemu-amd64.yml:kas/opt/ebg-swu.yml:kas/opt/kernel-panic.yml

# QEMUへ共有
scp -P 22222 build/panic/tmp/deploy/images/qemu-amd64/cip-core-image-cip-core-bookworm-qemu-amd64.swu root@localhost:panic.swu
```

前節同様に、更新イメージをインストールして、再起動します。

```bash
# (🦃 QEMUの中)

swupdate -v -i panic.swu
# ...
# [INFO ] : SWUPDATE running :  [endupdate] : SWUpdate was successful !

reboot
# ...
#          Starting sysrq-panic.service - sysrq panic...
# [    2.744300] sysrq: Trigger a crash
# [    2.744348] process '/usr/bin/swupdate-progress' started with executable stack
# [    2.744576] Kernel panic - not syncing: sysrq triggered crash
# ...
# [    2.764162] Rebooting in 10 seconds..
```

カーネルパニックが発生し、Watchdogにより自動で再起動します。

起動後のパーティションを確認します。

```bash
lsblk
# NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
# sda      8:0    0  5.4G  0 disk 
# ├─sda1   8:1    0   16M  0 part 
# ├─sda2   8:2    0   64M  0 part 
# ├─sda3   8:3    0   64M  0 part 
# ├─sda4   8:4    0    1G  0 part 
# ├─sda5   8:5    0    1G  0 part /
# ├─sda6   8:6    0  1.3G  0 part /home
# └─sda7   8:7    0    2G  0 part /var
```

パーティションBで起動していることを確認できました。

この時のステータスをチェックします。

```bash
bg_printenv -o revision,ustate
# ----------------------------
#  Config Partition #0 Values:
# revision:         0
# ustate:           3 (FAILED)

# ----------------------------
#  Config Partition #1 Values:
# revision:         3
# ustate:           0 (OK)
```

`revision=0`で`ustate=3`となっていることが確認できました。

インストール中に問題が発生した場合も、A/Bアップデート＋Watchdogにより、ロールバックすることができることを確認できました。

# 6 おわりに

SWUpdateを用いたA/Bアップデートについて紹介しました。基本的なA/Bアップデートについての解説と、QEMU™での通常/ロールバックについてデモを行いました。

今回は更新イメージをscpコマンドで直接用意しました。これは、実機だとUSB等でイメージを準備するのと同じになるかと思います。SWUpdateはOTA更新に対応しているので、また機会があったら紹介したいと思います。

加えて、SWUpdateのOTA更新にThe Update Frawork(TUF™)を適応する発表とかもしたりしているので、紹介させていただきます。

- [Secure Software Update for Embedded Devices with SWUpdate and TUF™ - YouTube](https://www.youtube.com/watch?v=8pzURi-oZFY)
- [Device Management and Delta Update for Embedded Devices with SWUpdate and TUF™ - YouTube](https://www.youtube.com/watch?v=Hrk5WpA7jBA)

　
最後までお読みいただきありがとうございました。

---

Linuxは、Linus Torvalds 氏の米国およびその他の国における登録商標です。
Debianは、Software in the Public Interest, Inc.が所有する登録商標です。
Ubuntuは、Canonical Ltd.の登録商標です。
Civil Infrastructure Platform(CIP)およびTUFは、米国およびその他の国におけるThe Linux Foundationの商標です。
QEMUは、Fabrice Bellardの商標です。