みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

YoctoProject DEBIX Model Aを動かす

はじめに

DEBIX Model Aを動かすではbitbakeで吐き出したイメージを公式の手順を少しアレンジしてDEBIX Model Aを動かした。

今回はbitbake一発でイメージを出力できるようにする。

公式のカーネル、u-bootのリポジトリをそのまま使用するレイヤを作成するため、YoctoProjectのバージョンはhardknottとする。

ピンヘッダ

デバッグシリアル用のUARTは40ピンのピンヘッダに引き出されているが、このピンヘッダは2mmピッチなので注意が必要(ラズパイなどは2.54mm)。 筆者は2mmピッチから線を引き出せるジャンパワイヤを使用している。

環境構築

作業ディレクト

$ mkdir -p ~/yocto/debix-hardknott
$ cd ~/yocto/debix-hardknott

ダウンロード

repoツールで環境をダウンロードする。

$ repo init -u https://github.com/nxp-imx/imx-manifest -b imx-linux-hardknott -m imx-5.10.72-2.2.0.xml
$ repo sync

初期セットアップ

imx-yocto-bsp特有の初期セットアップ

$ DISTRO=fsl-imx-wayland MACHINE=imx8mp-lpddr4-evk source imx-setup-release.sh -b build

EULAが表示されるので最後まで読んでyを入力する。

2回目以降

別の端末を開いて、すでに作成されてビルドディレクトリを使用する場合は下記を実行する。

$ source setup-environment build

これらはimx-yocto-bspの独特なお作法となる。

meta-debixの作成

レイヤの作成

下記のコマンドを実行してmeta-debixのレイヤを作成すrう

$ bitbake-layers create-layer meta-debix -p 10
$ rm -rf ./meta-debix/recipes-example
$ mv ./meta-debix ../sources
$ bitbake-layers add-layer ../sources/meta-debix

MACHINEの定義

今回はimx8mp-lpddr4-evkの定義をベースにdebix-aというマシンを作成する。

$ mkdir ../sources/meta-debix/conf/machine

meta-debix/conf/machine/debix-a.confを下記の内容で作成する。

MACHINEOVERRIDES =. "imx8mp-lpddr4-evk:"
require conf/machine/imx8mp-lpddr4-evk.conf

local.confの修正

MACHINE定義を追加したので、local.confに下記を追加する。

MACHINE = "debix-a"

u-bootのレシピ作成

u-bootのリポジトリを差し替えるためにbbappendを作成する。

$ recipetool newappend -e ../sources/meta-debix virtual/bootloader

下記の内容を記述する。

UBOOT_SRC_debix-a ?= "git://github.com/debix-tech/uboot.git;protocol=https"
SRCREV_debix-a = "1a87b972fac74699482e2dce2023b66358d8c4f5"

カーネルのレシピ作成

カーネルリポジトリを差し替えるためにbbappendを作成する。

$ recipetool newappend -e ../sources/meta-debix virtual/kernel

bbappendに下記の内容を記載する。

KERNEL_SRC_debix-a ?= "git://github.com/debix-tech/linux;protocol=https"
SRCBRANCH_debix-a = "debix"
SRCREV_debix-a = "f924d76e63c153f58206425d31f88c5771caeb80"

dtbでエラー

下記のようなエラーが発生する。

| ERROR: Input tree has errors, aborting (use -f to force output)
| make[2]: *** [scripts/Makefile.lib:326: arch/arm64/boot/dts/freescale/imx8mp-evk-usdhc1-m2.dtb] Error 2

debixのカーネルでは関係の無いデバイスツリーはビルドしないように修正されている。 リファレンスボード向けのデバイスツリーの設定はKERNEL_DEVICETREEから削除しておく必要がある。

meta-debix/conf/machine/debix-a.confに次の内容を追記する。

KERNEL_DEVICETREE_remove_debix-a = " \
  freescale/imx8mp-evk-dsp.dtb \
  freescale/imx8mp-evk-ecspi-slave.dtb \
  freescale/imx8mp-evk-ndm.dtb \
  freescale/imx8mp-evk-usdhc1-m2.dtb \
  freescale/imx8mp-evk-rm67199.dtb \
  freescale/imx8mp-ab2.dtb \
  freescale/imx8mp-evk-basler.dtb \
  freescale/imx8mp-evk-basler-ov2775.dtb \
  freescale/imx8mp-evk-basler-ov5640.dtb \
  freescale/imx8mp-evk-dual-basler.dtb \
  freescale/imx8mp-evk-dual-ov2775.dtb \
  freescale/imx8mp-evk-ecspi-slave.dtb \
  freescale/imx8mp-evk-flexcan2.dtb \
  freescale/imx8mp-evk-hifiberry-dacplus.dtb \
  freescale/imx8mp-evk-inmate.dtb \
  freescale/imx8mp-evk-iqaudio-dacplus.dtb \
  freescale/imx8mp-evk-iqaudio-dacpro.dtb \
  freescale/imx8mp-evk-it6263-lvds-dual-channel.dtb \
  freescale/imx8mp-evk-jdi-wuxga-lvds-panel.dtb \
  freescale/imx8mp-evk-ndm.dtb \
  freescale/imx8mp-evk-ov2775.dtb \
  freescale/imx8mp-evk-ov2775-ov5640.dtb \
  freescale/imx8mp-evk-pcie-ep.dtb \
  freescale/imx8mp-evk-rm67191.dtb \
  freescale/imx8mp-evk-rm67199.dtb \
  freescale/imx8mp-evk-root.dtb \
  freescale/imx8mp-evk-rpmsg.dtb \
  freescale/imx8mp-evk-sof-wm8960.dtb \
  freescale/imx8mp-evk-spdif-lb.dtb \
  freescale/imx8mp-evk-usdhc1-m2.dtb \
"

modules_installで謎のエラー

下記のようなエラーが発生する。

nux/linux-imx/5.10.72+gitAUTOINC+f924d76e63-r0/image/lib/firmware modules_install
| tar: .extra.tar: Cannot open: No such file or directory
| tar: Error is not recoverable: exiting now

ログを見る限りmodule_installを実行した際になにかしらが発生しているらしい。 .extra.tarが見つからないというエラーのようだ。.extra.tarなんて聞いたことがないファイル名なので、 Google先生に聞いてみたが何も引っかからない。

最終的には、debixのカーネルメンテナが仕込んだ罠であることがわかった。

# Target to install modules
PHONY += modules_install
modules_install: _modinst_ _modinst_post

PHONY += _modinst_
_modinst_:
   @rm -rf $(MODLIB)/kernel
   @rm -f $(MODLIB)/source
   @mkdir -p $(MODLIB)/kernel
   @ln -s $(abspath $(srctree)) $(MODLIB)/source
   @if [ ! $(objtree) -ef  $(MODLIB)/build ]; then \
      rm -f $(MODLIB)/build ; \
      ln -s $(CURDIR) $(MODLIB)/build ; \
  fi
   @sed 's:^:kernel/:' modules.order > $(MODLIB)/modules.order
   @cp -f modules.builtin $(MODLIB)/
   @cp -f $(objtree)/modules.builtin.modinfo $(MODLIB)/
   @tar xpf .extra.tar       //★コレ
   @cp -rf extra $(MODLIB)/
  $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst

.extra.tarには、ビルド済みのカーネルモジュールが入っていて、modules_installのタイミングで他のカーネルモジュールと一緒に 忍び込ませてしまおうというものであるようだ。

とりあえず、カーネルのレシピとしてはこの謎ファイルは入れないようにする。

./tmp/work-shared/debix-a/kernel-source/Makefileを下記のように修正する。

diff --git a/Makefile b/Makefile
index 5f90097f7..48211c850 100755
--- a/Makefile
+++ b/Makefile
@@ -1445,8 +1445,6 @@ _modinst_:
    @sed 's:^:kernel/:' modules.order > $(MODLIB)/modules.order
    @cp -f modules.builtin $(MODLIB)/
    @cp -f $(objtree)/modules.builtin.modinfo $(MODLIB)/
-  @tar xpf .extra.tar
-  @cp -rf extra $(MODLIB)/
    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modinst
 
 # This depmod is only for convenience to give the initial
-- 
2.25.1

パッチを下記のように作成する。

$ pushd ./tmp/work-shared/debix-a/kernel-source
$ git add -u
$ git commit -m 'linux-imx: fix tar option'
$ git format-patch -o /tmp HEAD^
$ popd

/tmp/0001-linux-imx-fix-tar-option.patchが生成されるので、レシピに取り込む。

$ recipetool appendsrcfile ../sources/meta-debix virtual/kernel /tmp/0001-linux-imx-fix-tar-option.patch

ツールで修正されたレシピを一部手動で修正し下記のようにする。

KERNEL_SRC_debix-a ?= "git://github.com/debix-tech/linux;protocol=https"
SRCBRANCH_debix-a = "debix"
SRCREV_debix-a = "f924d76e63c153f58206425d31f88c5771caeb80"

SRC_URI_append_debix-a = " file://0001-linux-imx-fix-tar-option.patch"
FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

カーネルモジュールのレシピを作成

先程罠として発動した.extra.tarの内容をインストールするためのレシピを作成する。

meta-debix/recipes-kernel/linux/linux-debix-modules_5.10.bbを下記の内容で作成する。

SUMMARY = "Extra kernel modules"
DESCRIPTION = "Extra kernel modules for DEBIX Model A"
LICENSE = "CLOSED"

SRC_URI = "git://github.com/debix-tech/linux;protocol=https;branch=debix"
SRCREV = "f924d76e63c153f58206425d31f88c5771caeb80"

S = "${WORKDIR}"

do_unpack:append() {
    bb.build.exec_func('shell_do_unpack_extra', d)
}

shell_do_unpack_extra() {
    tar xf ${WORKDIR}/git/.extra.tar -C ${WORKDIR}
}

inherit module-base

do_compile[noexec] = "1"

do_install() {
    cd ${S}/extra

    install -d ${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}
    tar --no-same-owner --exclude='./patches' --exclude='./.pc' -cpf - . \
        | tar --no-same-owner -xpf - -C ${D}${nonarch_base_libdir}/modules/${KERNEL_VERSION}
}

FILES_${PN} = "/lib/modules"

ライセンスはGPLv2あたりが妥当だと思うのだが、バイナリ提供であるため手元にソースコードがない。 仕方なくCLOSEDで凌ぐことにする。

最終的なlocal.conf

最終的なlocal.confは下記のようになる。

MACHINE = "debix-a"
IMAGE_INSTALL_append = " linux-debix-modules"

ビルド

$ bitbake core-image-minimal

動作確認

NXP i.MX Release Distro 5.10-hardknott debix-a ttymxc1

debix-a login: root
root@debix-a:~# uname -a
Linux debix-a 5.10.72-lts-5.10.y+g6ffd523c7 #1 SMP PREEMPT Tue Jan 10 10:28:10 UTC 2023 aarch64 GNU/Linux
root@debix-a:~# lsmod
Module                  Size  Used by
vvcam_dwe              28672  0
vvcam_isp              65536  0
imx219                 28672  0

追加したドライバも組み込まれている。

まとめ

DEBIX Model Aを動かすで行った手順をbitbake一発で行えるようにレイヤを作成した。

現時点では、DEBIXの公式BSPに合わせて下記のような構成になっている。

  • LinuxはDEBIXのもの
  • u-bootはDEBIXのもの
  • imx-yocto-bspはhardknott(imx-5.10.72-2.2.0)

1年前くらいにこのレイヤを作っていればありがたみもあったかもしれない。 とはいえ、imx-yocto-bspで提供する他のイメージなども簡単に作れるようになっているので便利ではあるはず。 KERNEL_DEVICETREEの設定を調整すれば、オプションボード用の環境も比較的容易に作れるはず。

一応GitHubにおいた。

DEBIX Model Aを動かす

はじめに

DEBIX Model Aを購入した。

対応OSにYocto-L5.10.72_2.2.0とあるので買ってみたのだが、ビルド手順が見つからない。

カーネルとu-bootはgithubにある。

u-bootのREADMEに下記の表記が。

これは既存のimx-yocto-bspのレシピを直接編集して、無理やり動くようにするやり方。

カーネルについては独自にビルドして一度イメージを書き込んだストレージ(ここではSDカード)をマウントして、 内容を書き換えるという手順になっている。

できればwicイメージを書き込んだSDカードを後からどうにかするのではなく、 wicイメージをどうにかして、ちゃんと動く環境を作りたい。

ピンヘッダ

デバッグシリアル用のUARTは40ピンのピンヘッダに引き出されているが、このピンヘッダは2mmピッチなので注意が必要(ラズパイなどは2.54mm)。 筆者は2mmピッチから線を引き出せるジャンパワイヤを使用している。

公開されている手順

DebixのGitHubリポジトリに記載されている手順を簡単にまとめると下記のようになる。

  • レシピを直接修正してu-bootをビルドする
  • YoctoProjectとは別のツールチェーンでカーネルをビルドする

Debix用のOSを書き込んだSDカードをマウントして下記を実施する。

  • カーネルを差し替える
  • バイスツリーを差し替える
  • ストレージ上のルートFSにモジュールをインストールする

今回実施する手順

最終的にはu-bootとカーネルが差し替わったwicイメージを作成する。

u-boot

u-bootに関してはひとまず公式の手順を踏襲し、予めwicイメージに含まれるようにする。

カーネル

カーネルは下記の方針で作業する。

環境構築

作業ディレクト

$ mkdir -p ~/yocto/debix-hardknott
$ cd ~/yocto/debix-hardknott

ダウンロード

repoツールで環境をダウンロードする。

$ repo init -u https://github.com/nxp-imx/imx-manifest -b imx-linux-hardknott -m imx-5.10.72-2.2.0.xml
$ repo sync

初期セットアップ

imx-yocto-bsp特有の初期セットアップ

$ DISTRO=fsl-imx-wayland MACHINE=imx8mp-lpddr4-evk source imx-setup-release.sh -b build

EULAが表示されるので最後まで読んでyを入力する。

2回目以降

別の端末を開いて、すでに作成されてビルドディレクトリを使用する場合は下記を実行する。

$ source setup-environment build

local.confの修正

conf/local.confに下記を追加する。

IMAGE_FSTYPES_append = " wic"

ここまででbitbakeによってイメージを作成するための環境は作成できた。

Debix向けのイメージの作成

u-bootの修正

u-bootのREADMEに従って、sources/meta-imx/meta-bsp/recipes-bsp/u-boot/u-boot-imx_2021.04.bbを改ざんする。

UBOOT_SRC ?= "git://github.com/debix-tech/uboot.git;protocol=https"
SRCREV = "1a87b972fac74699482e2dce2023b66358d8c4f5"

ビルド

まず一度イメージをビルドしてみる。

$ bitbake core-image-minimal

カーネルの修正

ツールチェーンのビルド

カーネルをビルドするためにツールチェーンをビルドする。

$ bitbake meta-toolchain

ツールチェーンのインストール

別の端末で下記を実行。

$ cd ~/yocto/debix-hardknott/build/tmp/deploy/sdk/
$ ./fsl-imx-wayland-glibc-x86_64-meta-toolchain-cortexa53-crypto-imx8mp-lpddr4-evk-toolchain-5.10-hardknott.sh

カーネルビルド

ソースの取得

$ cd ~/yocto/debix-hardknott
$ git clone --depth=1 https://github.com/debix-tech/linux
$ cd linux

ビルド

SDKカーネルをビルドする。

$ source /opt/fsl-imx-wayland/5.10-hardknott/environment-setup-cortexa53-crypto-poky-linux
$ make imx_v8_defconfig
$ make -j 32

モジュールのインストール

カーネルをビルドした端末で下記のコマンドを実行する。

$ make INSTALL_MOD_PATH=../build/tmp/work/imx8mp_lpddr4_evk-poky-linux/core-image-minimal/1.0-r0/rootfs INSTALL_MOD_STRIP=1 modules_install
$ sudo chown -R root:root ../build/tmp/work/imx8mp_lpddr4_evk-poky-linux/core-image-minimal/1.0-r0/rootfs/lib/modules/5.10.72

bitbakeを実行した端末で下記のコマンドを実行する。

$ bitbake core-image-minimal -c do_image_wic -f
$ bitbake core-image-minimal -f

カーネルdtbの差し替え

bitbakeを実行した端末で下記のコマンドを実行し、生成済みのwicイメージのカーネルdtbを差し替える。

$ wic cp ../linux/arch/arm64/boot/Image ./tmp/deploy/images/imx8mp-lpddr4-evk/core-image-minimal-imx8mp-lpddr4-evk.wic:1
$ wic cp ../linux/arch/arm64/boot/dts/freescale/imx8mp-evk.dtb ./tmp/deploy/images/imx8mp-lpddr4-evk/core-image-minimal-imx8mp-lpddr4-evk.wic:1

wic cpは直接wicイメージのファイルを差し替えられて便利だが、ファイルが追加されるような変更をするとイメージが壊れることがあるので注意が必要。

差し替えを確認

bitbakeを実行した端末で下記のコマンドを実行し、カーネル

$ wic ls ./tmp/deploy/images/imx8mp-lpddr4-evk/core-image-minimal-imx8mp-lpddr4-evk.wic:1
Volume in drive : is boot       
 Volume Serial Number is BA80-4C7C
Directory for ::/

IMAGE         31474176 2023-01-29   8:47  Image
... (snip) ...

wicイメージ上のカーネルモジュールを確認する。 Imageとimx8mp-evk.dtbだけ他と日付が異なっていればOK。

$ wic ls ./tmp/deploy/images/imx8mp-lpddr4-evk/core-image-minimal-imx8mp-lpddr4-evk.wic:2/lib/modules
debugfs 1.45.5 (07-Jan-2020)
    616   40755 (2)      0      0    1024 11-Feb-2023 10:42 .
    548   40755 (2)      0      0    3072  9-Mar-2018 21:34 ..
    617   40775 (2)      0      0    1024 11-Feb-2023 10:42 5.10.72
   1487   40755 (2)      0      0    1024  9-Mar-2018 21:34 5.10.72-lts-5.10.y+ga68e31b63f86

5.10.72があればOK。

動作確認

修正したcore-image-minimal-imx8mp-lpddr4-evk.wicをマイクロSDに書き込んで動作確認する。

起動した。差し替えたカーネルではUSBのキーボードなどのペリフェラルもきちんと動いているようだ。

まとめ

i.MX8M Plus搭載のボードが2万円弱で手に入るのは嬉しい。 DEBIX Model Aで遊ぶのは楽しそうだ。

YoctoProject対応をうたっていて、実際にイメージも配布されているが、どうやらbitbakeしたイメージにいろいろ手を加えたものを配布しているようだ。

今回はwicコマンドなどを駆使して、無理やりイメージを作成した。 近いうちにbitbake一発でイメージを吐き出せるようにしたい。

meta-st-stm32mpでの「正しい」wicイメージの作り方

はじめに

YoctoProject STM32MP157F-DK2を試すで下記のようなエラーに遭遇した。

| wic.filemap.error: cannot open image file '/home/mickey/yocto/stm32mp15-kirkstone/build/tmp/deploy/images/stm32mp1/st-image-bootfs-poky-stm32mp1.ext4': [errno 2] no such file or directory: '/home/mickey/yocto/stm32mp15-kirkstone/build/tmp/deploy/images/stm32mp1/st-image-bootfs-poky-stm32mp1.ext4'
| warning: exit code 1 from a shell command.
error: task (/home/mickey/yocto/stm32mp15-kirkstone/poky/meta-st-stm32mp/recipes-st/images/st-image-userfs.bb:do_image_wic) failed with exit code '1'

その解決方法として、各パーティション向けのイメージで下記のフラグを指定した。

do_image_wic[noexec] = "1"

この方法でプルリクエストを作成したが下記のような返事があり、却下された。

Hello, The right way to add wic image is to use this variable: meta-st-stm32mp/conf/machine/include/st-machine-common-stm32mp.inc:WKS_IMAGE_FSTYPES ?= ""

By the way, we think ST implementation is not robust and we will add some additional checks to avoid this issue. So we don't think your patch is the right way to solve (but is doing the job). Anyway, thanks a lot to have pointed on this weakness.

正しい方法も教えてくれた。

それによると、WKS_IMAGE_FSTYPESを使用することが正解らしい。この変数はmeta-st-stm32mp/conf/machine/include/st-machine-common-stm32mp.incで定義されている。つまりmeta-st-stm32mp独自の変数となるため、YoctoProjectとしての一般的な方法では無い。

WKS_IMAGE_FSTYPEのメカニズムについて調査してみる。

実装の調査

WKS_IMAGE_FSTYPESの参照箇所

WKS_IMAGE_FSTYPESが使用されている箇所をgrepしてみる。

$ grep -r 'WKS_IMAGE_FSTYPES' .
./meta-st-stm32mp/recipes-st/images/st-image-partitions.inc:IMAGE_FSTYPES:remove = "${WKS_IMAGE_FSTYPES}"
./meta-st-stm32mp/conf/machine/stm32mp13-disco.conf:#WKS_IMAGE_FSTYPES += "wic wic.bz2 wic.bmap"
./meta-st-stm32mp/conf/machine/stm32mp15-disco.conf:#WKS_IMAGE_FSTYPES += "wic wic.bz2 wic.bmap"
./meta-st-stm32mp/conf/machine/stm32mp15-eval.conf:#WKS_IMAGE_FSTYPES += "wic wic.bz2 wic.bmap"
./meta-st-stm32mp/conf/machine/include/st-machine-common-stm32mp.inc:WKS_IMAGE_FSTYPES ?= ""
./meta-st-stm32mp/conf/machine/include/st-machine-common-stm32mp.inc:IMAGE_FSTYPES ?= "${WKS_IMAGE_FSTYPES} tar.xz"

注目するべきは下記の2つのファイル

  • st-machine-common-stm32mp.inc
  • st-image-partitions.inc

st-machine-common-stm32mp.inc

st-machine-common-stm32mp.incの参照箇所は以下の通り。

$ grep -r 'st-machine-common-stm32mp.inc' .
./meta-st-stm32mp/conf/machine/stm32mp13-disco.conf:include conf/machine/include/st-machine-common-stm32mp.inc
./meta-st-stm32mp/conf/machine/stm32mp1.conf:include conf/machine/include/st-machine-common-stm32mp.inc
./meta-st-stm32mp/conf/machine/stm32mp15-disco.conf:include conf/machine/include/st-machine-common-stm32mp.inc
./meta-st-stm32mp/conf/machine/stm32mp15-eval.conf:include conf/machine/include/st-machine-common-stm32mp.inc

MACHINE定義の中から参照される。

その中でWKS_IMAGE_FSTYPESは次のように使用されている。

# Default FSTYPES requested
WKS_IMAGE_FSTYPES ?= ""
IMAGE_FSTYPES ?= "${WKS_IMAGE_FSTYPES} tar.xz"

WKS_IMAGE_FSTYPESに"wic"関連のタイプを追加することでIMAGE_FSTYPESにそれらが追加されwicイメージが出力されるようになる。

st-image-partitions.inc

YoctoProject STM32MP157F-DK2を試すでは、次の3つのレシピにbbappendを作成した。

  • st-image-userfs.bb
  • st-image-bootfs.bb
  • st-image-vendorfs.bb

これらはcore-imageをinheritしており、イメージファイルを出力するためのタスクが実装されている。当然IMAGE_FSTYPESも参照する。

st-image-partitions.incはこれら3つのレシピ全てからincludeされている。つまりこのファイルを修正するとこれら3つのレシピに影響が出る。

WKS_IMAGE_FSTYPESはst-image-partitions.incの中で次のように使用されている。

# Remove WIC image generation for the partition image
IMAGE_FSTYPES:remove = "${WKS_IMAGE_FSTYPES}"

上記3つのイメージレシピのIMAGE_FSTYPESからWKS_IMAGE_FSTYPESで定義したwic関連のタイプを削除している。 このようにすることで、循環参照を避けwicイメージを出力している。

WKS_IMAGE_FSTYPESの役割

ここまでで、WKS_IMAGE_FSTYPESは次の2つの役割を持っていることがわかる。

  1. wicイメージを出力させるためのトリガ
  2. 循環参照を避けるためのフィルタ

この実装について

メンテナのコメント

プルリクエストでメンテナであるBernardPuelさんは次のようにコメントしている。

The right way to add wic image is to use this variable: meta-st-stm32mp/conf/machine/include/st-machine-common-stm32mp.inc:WKS_IMAGE_FSTYPES ?= ""

By the way, we think ST implementation is not robust and we will add some additional checks to avoid this issue.

「現状の実装としてはWKS_IMAGE_FSTYPESを使用することが正しい解決方法だが、この実装もロバスト(堅牢)ではない。 今回のような問題を避けるためにチェックを追加するつもりだ」とのこと。

So we don't think your patch is the right way to solve (but is doing the job).

「あなたのパッチも正しい解決方法ではないと考えている」らしい。

現実装の問題点

WKS_IMAGE_FSTYPESを使った実装の問題点はいくつかある。

  1. WKS_IMAGE_FSTYPESが独自の変数であること
    1. マニュアルなどから見つけづらい
    2. YoctoProjectの一般的な方法ではない(IMAGE_FSTYPESだけで解決できない)
  2. WKS_IMAGE_FSTYPESの実装が煩雑であること
    1. 1つの変数に2つの役割(トリガ・フィルタ)を持たせている
    2. コードに対して機能が直感的に理解できない

自分の実装にこだわるつもりは無いが、WKS_IMAGE_FSTYPESはできれば撤廃してほしい。

まとめ

  • 現状はWKS_IMAGE_FSTYPESを使用することが正解
  • メンテナも現状のままでは問題があることは認識している
  • WKS_IMAGE_FSTYPESは撤廃してほしい(チェックを追加するって言ってるので撤廃されない可能性が高い気がする)

(おまけ)使用方法

作業環境

$ mkdir -p ~/yocto/stm32mp15-kirkstone
$ cd ~/yocto/stm32mp15-kirkstone

pokyの取得

$ git clone -b kirkstone git://git.yoctoproject.org/poky.git

環境変数の設定

$ source poky/oe-init-build-env

meta-openembeddedの取得

$ bitbake-layers layerindex-fetch meta-python

meta-st-stm32mpの取得

$ pushd ../poky
$ git clone https://github.com/STMicroelectronics/meta-st-stm32mp.git -b kirkstone
$ popd
$ bitbake-layers add-layer ../poky/meta-st-stm32mp

local.confの修正

MACHINE ?= "stm32mp1"

WKS_IMAGE_FSTYPES:append = " wic.bz2 wic.bmap"
WKS_FILE = "sdcard-stm32mp157f-dk2-optee-1GB.wks.in"

ビルド

$ bitbake core-image-minimal

YoctoProject systemdでシステム起動時間計測

はじめに

前回、systemdのサービスを追加して、システム起動時にストレージの最後のパーティションを目一杯まで拡張するようにした。

このサービスはパーティションを拡張するためのシステムの初回起動時だけ実行されればよいのだが、 2回目以降のために、サービスを無効化するようなことはしていない。

起動時間に悪影響を及ぼさないのか確認してみる。

systemd-analyze

パーティションの拡張にはsystemdを使用しているため、sysvinitのことは除外する。

systemdでの起動時間計測といえばsystemd-bootchartが定番だったが、initプロセスを差し替える必要があったり使用するハードルが高かった。 現在systemdではsystemd-analyzeというツールを提供しており、これを使用することで特にinitプロセスの差し替えなどせずに起動時間を計測することができる。

systemd-analyzeを使用するにはlocal.confに次の内容を追加する。

IMAGE_INSTALL:append = " systemd-analyze"

起動時間の計測

初回

~# systemd-analyze
Startup finished in 3.320s (kernel) + 15.091s (userspace) = 18.411s
multi-user.target reached after 14.762s in userspace

~# systemd-analyze blame  | grep repart
 466ms repart.service

# systemd-analyze blame  | grep growfs
 1.913s systemd-growfs@data.service

2回目以降

# systemd-analyze
Startup finished in 3.560s (kernel) + 12.769s (userspace) = 16.330s
multi-user.target reached after 12.438s in userspace

# systemd-analyze blame  | grep repartw
 466ms repart.service

# systemd-analyze blame  | grep growfs
 164ms systemd-growfs@data.service

systemd-growfs@data.serviceは明らかに2回目以降の時間が短縮しているが、repart.serviceは初回と同じ時間かかっている。 500ミリ秒は無視できない数字だと思う。

repart.serviceの停止

$ systemctl disable repart.service

repart停止後の計測結果

# systemd-analyze
Startup finished in 3.321s (kernel) + 12.094s (userspace) = 15.415s
multi-user.target reached after 11.753s in userspace

root@raspberrypi4-64:~# systemd-analyze blame | grep repart
root@raspberrypi4-64:~# systemd-analyze blame | grep growfs
  59ms systemd-growfs@data.service

計測結果には多少の振れ幅があるが、起動時間が短縮されるのは確かなようだ。

repart.serviceを改造

初回起動時にパーティションを拡張したあと、自身のサービスをdisableするように改造してみる。

[Unit]
Description=Service to resizing parition
DefaultDependencies=no
Before=local-fs.target

[Service]
Type=oneshot
ExecStart=/usr/sbin/parted --script @repart-dev@ resizepart @repart-no@ 100%
ExecStart=systemctl disable repart.service

[Install]
WantedBy=local-fs.target

イメージを作り直し実機で実行してみる。

fdisk -l

改造後も意図通り、パーティションが拡張されていることが確認できる。

# fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 3768 MB, 3951034368 bytes, 7716864 sectors
120576 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1 *  64,0,1      1023,3,32         8192     152543     144352 70.4M  c Win95 FAT32 (LBA)
/dev/mmcblk0p2    1023,3,32   1023,3,32       155648     624229     468582  228M 83 Linux
/dev/mmcblk0p3    1023,3,32   1023,3,32       624640     890879     266240  130M 83 Linux
/dev/mmcblk0p4    1023,3,32   1023,7,32       892928    7716863    6823936 3332M 83 Linux

repart.serviceのログ

repart.servceのログを確認すると、パーティション拡張処理は成功した上にサービスがdisabledになっていることが確認できる。

# systemctl status repart
* repart.service - Service to resizing parition
     Loaded: loaded (8;;file://raspberrypi4-64/lib/systemd/system/repart.service/lib/systemd/system/repart.service8;;; disabled; vendor preset: enabled)
     Active: inactive (dead)

Apr 28 17:42:27 raspberrypi4-64 systemctl[142]: Removed /etc/systemd/system/local-fs.target.wants/repart.service.
Apr 28 17:42:27 raspberrypi4-64 systemd[1]: repart.service: Deactivated successfully.
Apr 28 17:42:27 raspberrypi4-64 systemd[1]: Finished Service to resizing parition.

初回起動時のsystemd-anlyze

repart.serviceをdisableしてしまうので、systemd-analyze blameでは表示されなくなっているようだ。

# systemd-analyze
Startup finished in 3.321s (kernel) + 15.900s (userspace) = 19.222s
multi-user.target reached after 15.563s in userspace

# systemd-analyze blame  | grep repart
root@raspberrypi4-64:~#

# systemd-analyze blame  | grep growfs
2.465s systemd-growfs@data.service

2回目以降のsystemd-analyze

狙い通り起動時間が短縮されているようだ。

# systemd-analyze
Startup finished in 3.310s (kernel) + 12.155s (userspace) = 15.466s
multi-user.target reached after 11.823s in userspace

まとめ

initプロセスにsystemdを使用している場合、systemd-analyzeでシステムの起動時間を計測することができる。 起動後にコマンドを実行するだけなのでbootchartより簡単となっている。

前回、作成したrepart.serviceの時間を計測し、 本来実行が不要な2回目以降のシステム起動時間に及ぼす影響を調査したところ、 毎回パーティション拡張を実行した場合と同じ時間がかかることが判明した。

そのため、2回目以降はrepart.serviceが実行されないように改造し、 システム起動時間のパフォーマンスを向上した。

YoctoProject 最後のパーティションを目一杯まで広げる

はじめに

YoctoProjectではwicツールを使用することで、SDなどのストレージに直接書き込めるイメージを作成することができる。 wicイメージを作成する時点では書き込むストレージのサイズがわからないため、基本的には必要最低限のサイズでイメージは作成される様になっている。

データ領域などを多めに取りたい場合は、IMAGE_ROOTFS_EXTRA_SPACEなどの変数でファイルシステムの領域を確保することができるが、 その場合でも、実際に書き込むストレージのサイズはわからないため目一杯というのは難しい。

systemd-growfsを使用すると、初回システム起動時に指定したデバイスファイルシステムパーティションサイズいっぱいに拡張することができるが、パーティションサイズ自体は拡張されない。

そのため、ストレージ全体を使うためには少し工夫が必要となる。 meta-rauc-communityにその実装のヒントがあるため参考にしてみる。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone git://git.yoctoproject.org/poky.git -b kirkstone 

環境変数の設定

$ source poky/oe-init-build-env

meta-raspberrypiの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confを編集

MACHINE = "raspberrypi4-64"
DL_DIR = "${TOPDIR}/../downloads"

# systemd
DISTRO_FEATURES:append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# enable uart
ENABLE_UART = "1"

レイヤを作成

meta-workを作成

$ bitbake-layers create-layer meta-work
$ rm -rf ./meta-work/recipes-example
$ mkdir -p ./meta-work/recipes-support/repart/files ./meta-work/wic
$ bitbake-layers add-layer ./meta-work

レシピ作成

サービスファイル

systemdのサービスを作成するが、一部はbitbakeするまで内容が未確定なので、 変数にしておき、レシピを処理する過程で内容を確定させる。

そのためには次のようにsystemdのユニットそのものではなくテンプレートを作成する。

[Unit]
Description=Service to resizing parition
DefaultDependencies=no
Before=local-fs.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/sbin/parted --script @repart-dev@ resizepart @repart-no@ 100%

[Install]
WantedBy=local-fs.target

[2023/1/28 追記]システムの2回目以降の起動時間を改良したバージョンをYoctoProject systemdでシステム起動時間計測で紹介している.

この内容をmeta-work/recipes-support/repart/files/repart.service.inとして保存する。 最後の.inは慣例的にそのファイルがテンプレートであるということを示している。 つまり、.inがついているファイルに何らかの加工を加えることで、.inのないファイル(ここでは「repart.service」)が出来上がることを示している。

これの本体は下記の部分で、指定のパーティションストレージ目一杯に拡張する。

ExecStart=/usr/sbin/parted --script @repart-dev@ resizepart @repart-no@ 100%

repart.bb

meta-work/recipes-support/repart/repart.bbを以下の内容で作成する。

SUMMARY = "resize partition for data"
DESCRIPTION = "Recipe for resizin the partition and filesystem"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

REPART_DEV ??= ""
REPART_NO ??= ""
REPART_MOUNTPOINT ??= ""

ALLOW_EMPTY:${PN} = "1"

SRC_URI = "file://repart.service.in"

RDEPENDS:${PN} = "parted"

inherit systemd features_check
REQUIRED_DISTRO_FEATURES = "systemd"

S = "${WORKDIR}"

do_compile() {
    if [ -z "${REPART_DEV}" ]; then
        bbfatal "REPART_DEV should be set."
    fi
    if [ -z "${REPART_NO}" ]; then
        bbfatal "REPART_NO should be set."
    fi
    sed -e "s#@repart-dev@#/dev/${REPART_DEV}#g; s#@repart-no@#${REPART_NO}#g" \
    repart.service.in > repart.service
}

do_install() {
    if [ -n "${REPART_MOUNTPOINT}" ]; then
        install -d ${D}${REPART_MOUNTPOINT}
    fi

    install -d ${D}${systemd_unitdir}/system/
    install -m 0644 ${S}/repart.service ${D}${systemd_system_unitdir}
}

SYSTEMD_SERVICE:${PN} = "repart.service"

FILES:${PN} = "\
  ${systemd_system_unitdir} \
  ${REPART_MOUNTPOINT} \
"

下記の3つの変数は、必要に応じてlocal.confなどで設定する。

REPART_DEV ??= ""
REPART_NO ??= ""
REPART_MOUNTPOINT ??= ""

do_compileタスクでは、先程のrepart.service.inを処理し、repart.serviceを作成する。

wksファイルを作成

meta-work/wic/sdimage-raspberrypi-growfs.wks.inを下記の内容で作成し、/homeにx-systemd.growfsを指定することにより、システム起動時にパーティションジいっぱいに拡張されるようにする。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096
part /home --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/home --ondisk mmcblk0 --fstype=ext4 --label homefs --align 1024 --size 500 --fsoptions "x-systemd.growfs"

拡張子をwksではなくwks.inとすることで、wksファイルの中でbitbakeの変数を使用することができる。

local.confの修正

local.confに下記を追加する。

IMAGE_INSTALL:append = " repart"
REPART_DEV ?= "mmcblk0"
REPART_NO ?= "3"
#REPART_MOUNTPOINT ?= ""
WKS_FILE = "sdimage-raspberrypi-growfs.wks.in"

今回は既存の/homeを拡張するためREPART_MOUNTPOINTは指定しない。

動作確認

ビルド

$ bitbake core-image-base

動作確認

出来上がったイメージでラスベリーパイ4を起動する。

root@raspberrypi4-64:~# fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 3768 MB, 3951034368 bytes, 7716864 sectors
120576 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1 *  64,0,1      1023,3,32         8192     152543     144352 70.4M  c Win95 FAT32 (LBA)
/dev/mmcblk0p2    1023,3,32   1023,3,32       155648     624229     468582  228M 83 Linux
/dev/mmcblk0p3    1023,3,32   1023,7,32       624640    7716863    7092224 3463M 83 Linux

/dev/mmcblk0p3が3463Mになっていることがわかる。

下記mountの結果

root@raspberrypi4-64:~# mount | grep home
/dev/mmcblk0p3 on /home type ext4 (rw,relatime,x-systemd.growfs)

次にdfの結果

root@raspberrypi4-64:~#  df -h | grep home
/dev/mmcblk0p3            3.4G     89.5M      3.2G   3% /home

/dev/mmcblk0p3がext4でフォーマットされ、3.4Gとして認識されている。また、/homeにマウントされていることが確認できる。

パーティションを追加

wksとlocal.confの修正

第4パーティションとして/dataを追加してみる。sdimage-raspberrypi-growfs.wks.inを下記のように修正する。

part /boot --source bootimg-partition --ondisk mmcblk0 --fstype=vfat --label boot --active --align 4096 --size 20
part / --source rootfs --ondisk mmcblk0 --fstype=ext4 --label root --align 4096
part /home --source rootfs --rootfs-dir=${IMAGE_ROOTFS}/home --ondisk mmcblk0 --fstype=ext4 --label homefs --align 1024 --size=100M
part /data --ondisk ${REPART_DEV} --fstype=ext4 --label data --align 4096 --size=1024 --fsoptions "x-systemd.growfs"

/dataは既存のルートファイルシステムには存在しないのでlocal.confでREPART_MOUNTPOINTを設定する。

IMAGE_INSTALL:append = " repart"
REPART_DEV ?= "mmcblk0"
REPART_NO ?= "4"
REPART_MOUNTPOINT ?= "/data"

動作確認

fdiskの結果

root@raspberrypi4-64:~# fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 3768 MB, 3951034368 bytes, 7716864 sectors
120576 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1 *  64,0,1      1023,3,32         8192     152543     144352 70.4M  c Win95 FAT32 (LBA)
/dev/mmcblk0p2    1023,3,32   1023,3,32       155648     624229     468582  228M 83 Linux
/dev/mmcblk0p3    1023,3,32   1023,3,32       624640     890879     266240  130M 83 Linux
/dev/mmcblk0p4    1023,3,32   1023,7,32       892928    7716863    6823936 3332M 83 Linux

mountの結果

root@raspberrypi4-64:~# mount | grep mmc
/dev/mmcblk0p2 on / type ext4 (rw,relatime)
/dev/mmcblk0p3 on /home type ext4 (rw,relatime)
/dev/mmcblk0p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,errors=remount-ro)
/dev/mmcblk0p4 on /data type ext4 (rw,relatime,x-systemd.growfs)

まとめ

meta-rauc-communityの実装にヒントを得てrepart.bbを実装してみた。

指定したデバイスの最後のパーティションを指定することで、そのパーティションおよびファイルシステムがストレージの残り目一杯まで拡張されることを確認した。

もちろん途中のパーティションを指定しても拡張することはできない。

YoctoProject RAUCと実機でOTAアップデート

はじめに

IoTではよく見かける、遠隔から機器のファームウェア(ここではLinux)をアップデートする仕組みがある。 これらの仕組みはOver-The-Air Update(OTAアップデート)と呼ばれる。

組み込みLinux向けのOTA Updateのためのソフトウェアはいくつかある。YoctoProjectをサポートしているもののうち、 レシピの内容や使い方などをみて、一番とっつきやすそうだったRAUCを試してみる。

ターゲットはラズベリーパイ4。

OTAアップデートの実装

OTAアップデートは、ストレージ上にOSをどの様にレイアウトするか?というところから設計する必要がある。

運用するOSを1面だけ持って、それをOTAアップデート対象とする場合は、基本的にリカバリ用のOSをストレージ上のどこかに配置する必要がある。 このような運用はMain/aux imagesとか、Single imageとか呼ばれる。日本では「1面持ち」みたいに表現される。(※)

または運用する面を2面もって、片方をアクティブ、もう片方をインアクティブとして、OTAアップデートはインアクティブの方に対して行うという方法がある。こちらはA/B UpdateとかDouble imagesとか呼ばれる。日本では「2面持ち」みたいに表現される。(※)

※ 筆者の近所だけかもしれない

このあたりは次の記事が詳しいと思う。

RAUCではどちらにも対応できるが、サンプル実装であるmeta-rauc-raspberrypiではA/B Updateを実装している。

環境構築

作業環境

$ mkdir -p ~/yocto/rpi-kirkstone
$ cd ~/yocto/rpi-kirkstone

pokyの取得

$ git clone git://git.yoctoproject.org/poky.git -b kirkstone 

環境変数の設定

$ source poky/oe-init-build-env

meta-raspberrypiの取得

$ bitbake-layers layerindex-fetch meta-raspberrypi

meta-rauc関連レイヤの取得

$ bitbake-layers layerindex-fetch meta-rauc
$ pushd ../poky
$ git clone https://github.com/rauc/meta-rauc-community.git -b kirkstone 
$ popd
$ bitbake-layers add-layer ../poky/meta-rauc-community/meta-rauc-raspberrypi

証明書と鍵の作成

$ ../poky/meta-rauc-community/create-example-keys.sh

このスクリプトで鍵、証明書とsite.confが生成される。

local.confを編集

MACHINE ?= "raspberrypi4"
DL_DIR ?= "${TOPDIR}/../downloads"

# Generic raspberrypi settings
ENABLE_UART = "1"
RPI_USE_U_BOOT = "1"

# systemd
DISTRO_FEATURES:append = " systemd"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# Settings for meta-rauc-raspberry-pi
IMAGE_INSTALL:append = " rauc"
IMAGE_FSTYPES:append = " ext4"
WKS_FILE = "sdimage-dual-raspberrypi.wks.in"

DISTRO_FEATURES:append = " rauc"

ビルド

$ bitbake core-image-minimal

動作確認

core-image-minimal-raspberrypi4.wicをSDに書き込み、ラズベリーパイを起動する。

ターゲット上で、下記のようにしてブート状態を確認する。

Poky (Yocto Project Reference Distro) 4.0.6 raspberrypi4 ttyS0

raspberrypi4 login: root
Last login: Sun Jan  8 14:35:19 +0000 2023 on /dev/ttyS0.
root@raspberrypi4:~# rauc status
=== System Info ===
Compatible:  RaspberryPi4
Variant:     
Booted from: rootfs.0 (A)

=== Bootloader ===
Activated: rootfs.0 (A)

=== Slot States ===
o [rootfs.1] (/dev/mmcblk0p3, ext4, inactive)
        bootname: B
        boot status: good

x [rootfs.0] (/dev/mmcblk0p2, ext4, booted)
        bootname: A
        mounted: /
        boot status: good

A面とB面があり、A面がアクティブ(booted)であることが確認できた。

バンドル

RAUCではOTAアップデートで配信されるイメージファイルをbundleと呼ぶらしい。 meta-raucではbundleを生成するためにbundle.bbclassを提供している。

meta-rauc-raspberrypiではupdate-bundle.bbで生成することができる。

この内容を見てみると、bundle.bbclassを継承していることがわかる。 bundleの元になるイメージはcore-image-minimalで有ることも読み取れる。

DESCRIPTION = "RAUC bundle generator"

inherit bundle

RAUC_BUNDLE_COMPATIBLE = "RaspberryPi4"
RAUC_BUNDLE_VERSION = "v20200703"
RAUC_BUNDLE_DESCRIPTION = "RAUC Demo Bundle"
RAUC_BUNDLE_SLOTS = "rootfs" 
RAUC_SLOT_rootfs = "core-image-minimal"
RAUC_SLOT_rootfs[fstype] = "ext4"

RAUC_KEY_FILE = "${THISDIR}/files/development-1.key.pem"
RAUC_CERT_FILE = "${THISDIR}/files/development-1.cert.pem"

鍵について

update-bundle.bbにもRAUC_KEY_FILERAUC_CERT_FILEがあり、こちらは=で代入されているため上書きができない。 このままビルドしたバンドルでOTAアップデートしようとすると、create-example-keys.shで生成された鍵と異なるため、 署名検証で失敗する。

OTAアップデートの更新イメージはきちんと検証されていることが確認できる。

create-example-keys.shで生成した鍵で署名されるようにするため、このレシピのRAUC_KEY_FILEとRAUC_CERT_FILEの行はコメントアウトしておく。

バンドルのビルド

update-bundleをビルドする。

$ bitbake update-bundle

tmp/deploy/images/raspberrypi4/update-bundle-raspberrypi4.raucbが生成される。

hawkBit

OTAアップデートするOSイメージを配信するための仕組みとして、hawkBitというものがある。

RAUCはhawkBitとの連携に対応している。

hawkBitクライアントの追加

meta-raucでは2つのhawkBitクライアントを提供している。

  • rauc-hawkbit (python実装)
  • rauc-hawkbit-updater (C実装)

今回は、C実装のrauc-hawkbit-updaterを使用する。そのためにはlocal.confに下記を追加する。

IMAGE_INSTALL:append = " rauc-hawkbit-service"

hawkBitの起動

$ docker run --rm -p 8080:8080 hawkbit/hawkbit-update-server:latest

ログイン

下記でログイン

username admin
password admin

最初にターゲットからのトークンによるアクセスを許可する。 [System Config] -> [Authentication Configuration] とクリックして、[Allow targets to authenticate directly with their target security token]にチェックをつける。

ターゲットトークンによるアクセスの許可

ターゲットの登録

次にデバイスを登録する。この時ターゲット上の/etc/rauc-hawkbit-updater/config.confの「target_name」と「Controller Id」を一致させておくこと。 ここでは、サンプルの設定が「test-target」となっているためそれに合わせる。

バイスの追加

バイスを追加するとトークンが発行される。これをconfig.confのauth_tokenに設定する。

トークンの発行

ターゲットの設定

アクセス設定

初期状態の設定ではターゲット(ラズベリーパイ)はhawkBitと通信できないため、/etc/rauc-hawkbit-updater/config.confにhawkBitサーバのIPアドレスと先ほど発行したターゲットトークンを設定する。

下記の内容で~/config.shを作成

#!/bin/sh

readonly CONFIG_FILE=/etc/rauc-hawkbit-updater/config.conf
readonly SERVER="$1"
readonly TOKEN="$2"

sed -i "s/hawkbit_server.*=.*/hawkbit_server            = ${SERVER}:8080/" ${CONFIG_FILE}
sed -i "s/auth_token.*=.*/auth_token                 = ${TOKEN}/" ${CONFIG_FILE}

systemctl restart rauc-hawkbit-updater.service

下記のようにして実行する。

# chmod +x ~/config.sh
# ~/config.sh <hawkBitのIPアドレス> <発行したSecurity token>

これで一定時間毎に更新イメージがないかサーバにチェックするようになる。

root@raspberrypi4:~# systemctl status rauc-hawkbit-updater
* rauc-hawkbit-updater.service - HawkBit client for Rauc
     Loaded: loaded (8;;file://raspberrypi4/lib/systemd/system/rauc-hawkbit-updater.service/lib/systemd/system/rauc-hawkbit-updater.service8;;; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2023-01-08 17:16:50 UTC; 51s ago
   Main PID: 273 (rauc-hawkbit-up)
     Status: "Init completed, start polling HawkBit for new software."
      Tasks: 1 (limit: 4915)
     CGroup: /system.slice/rauc-hawkbit-updater.service
             `- 273 /usr/bin/rauc-hawkbit-updater -s -c /etc/rauc-hawkbit-updater/config.conf

Jan 08 17:16:50 raspberrypi4 systemd[1]: Started HawkBit client for Rauc.
Jan 08 17:16:51 raspberrypi4 rauc-hawkbit-updater[273]: Checking for new software...
Jan 08 17:16:51 raspberrypi4 rauc-hawkbit-updater[273]: No new software.

更新イメージの配信

hawkBitのWeb UIを使用してバンドルを配信し、ターゲットのOSをOTAアップデートする。

全体の流れは次のようになる。

今回は、配信するための最小限の手順のため、Target Filtersの作成やRolloutの設定などは行わない。

Upload画面

画面左のメニューの「Upload」をクリックし、Upload画面に遷移する。

Software Moduleを作成

「Software Module」の枠の右上にある「+」マークをクリックし作成する。

Software Moduleの作成

Typeは「OS」か「Application」からどちらかを選択。 Name、Versionは適当な文字列。バージョンは任意の文字列、この組み合わせが完全に一致するものがすでにある場合はエラーとなる。

バンドルをアップロード

画面右下の「Drop Files to upload」の領域にバンドルをドラッグアンドドロップする。

Distributions画面

画面左のメニューの「Distribution」をクリックし、Distribution画面に遷移する。

この時点で画面右側の「Software Module」にはUpload画面で作成した、Software Moduleが1つ登録されている。

Distributionを作成

「Distribution」の枠の右上にある「+」マークをクリックし作成する。

Distributionの作成

Typeは「App(s) only」「OS only」、「OS with app(s)」の中から選択。 Name、Versionは適当な文字列。バージョンは任意の文字列、この組み合わせが完全に一致するものがすでにある場合はエラーとなる。

Software ModuleをDistributionに関連付け

画面右側の「Software Module」のリストにあるアイテムを、作成したDistributionにドラッグアンドドロップする。

これでDistributionが配信可能な状態となる。

Deployment画面

画面左のメニューの「Deployment」をクリックし、Deployment画面に遷移する。

DistributionをTargetに配信

画面真ん中くらいにある「Distributions」のリストから配信するアイテムを、「Targets」にあるターゲットにドラッグアンドドロップする。

イメージの配信

「Forced」にすると即時配信、インストールが実行される。 即時配信といっても、実際にはターゲットからの次回の更新確認で処理が実行されるため少しタイムラグがある。

配信が完了すると次のように履歴が追加される

配信完了

ターゲットの再起動

配信およびインストールが完了したら、ターゲット(ラズベリーパイ)をリブートし、状態を確認する。

root@raspberrypi4:~# rauc status
=== System Info ===
Compatible:  RaspberryPi4
Variant:
Booted from: rootfs.1 (B)

=== Bootloader ===
Activated: rootfs.1 (B)

=== Slot States ===
x [rootfs.1] (/dev/mmcblk0p3, ext4, booted)
        bootname: B
        mounted: /
        boot status: good

o [rootfs.0] (/dev/mmcblk0p2, ext4, inactive)
        bootname: A
        boot status: good

B面がboottedになっていることがわかる。

まとめ

YoctoProjectを使ってOTAアップデートを試した。OTAアップデートにはRAUCを使用した。 配信サーバとしてhawkBitを使い、遠隔からのOS更新の手順も確認した。

RAUCとhawkBitは連携はするが、別々のプロジェクトで開発されているため、必ずセットで使用する必要はない。 hawkBitはSWUpdateなどの他のOTAアップデートフレームワークとも連携可能となっている。

RAUCとしてはA/Bアップデートの仕組みはGRUBやu-bootなどの環境変数を使用して、 それなりに仕組みを合わせ込む作業が必要となる。ラズベリーパイ4の場合はmeta-rauc-raspberpiをベースにカスタムすれば、 割と簡単に環境が構築できると思う。

hawkBitとしては今回はターゲットトークンを使用して、各ボードそれぞれにトークンを発行してアクセス認証をしたが、 実際に製品で使うようなことを考えると、「gateway security token」などを使って複数のボードで共通のトークンを使うなども検討したほうが良いかもしれない。

RAUCもhawkBitも良くできているので、OTAアップデートに必要な機能は十分揃っているように見えた。

LXDコンテナをホストのネットワークにぶら下げる

はじめに

実験的にコンテナ上にサーバを立てて潰すという作業はよく発生する。 このサーバが、ホストのネットワーク上に単一のマシンとして見えているのが望ましいが 毎度、このあたりの作業が面倒くさい。

ホストにブリッジインターフェースを作成し、それを参照するようなprofileを作成することで、LXDコンテナをホストネットワークにぶら下げることができるのでまとめる。

手順

ホストのNICがenp3s0の場合とする。

  1. ホストPCにブリッジインターフェース(br0)を作成
  2. ブリッジインターフェースに、ホストのNIC(enp3s0)を追加
  3. LXDにブリッジ用プロファイル(bridge)を作成
  4. bridgeプロファイルで、br0にLXDコンテナのNIC(eth0)を追加
  5. LXDコンテナ作成時にbridgeプロファイルを適用

ブリッジインターフェースの作成

下記手順に相当。

  • ホストPCにブリッジインターフェース(br0)を作成
  • ブリッジインターフェースに、ホストのNIC(enp3s0)を追加

/etc/netplan/10-bridge.yamlを下記の内容で作成する。

network:
  version: 2
  renderer: NetworkManager
  ethernets:
    enp3s0:
      dhcp4: no
  bridges:
    br0:
      interfaces:
        - enp3s0
      dhcp4: yes
      parameters:
        forward-delay: 0
        stp: no
      optional: true

設定を反映する。

$ sudo netplan apply

LXDコンテナプロファイルの作成

下記手順に相当。

  • LXDにブリッジ用プロファイル(bridge)を作成
  • bridgeプロファイルで、br0にLXDコンテナのNIC(eth0)を追加
$ lxc profile create bridge
$ lxc network attach-profile br0 bridge eth0
$ lxc profile device add bridge root disk path=/ pool=default

コンテナの作成

下記手順に相当。

  • LXDコンテナ作成時にbridgeプロファイルを適用

以降は、bridgeプロファイルを指定してLXDコンテナを作成すると、そのコンテナはホストネットワークにぶら下がることになる。

$ lxc launch ubuntu:20.04 ${CONTAINER} --profile=bridge

export CONTAINER=hawkBit lxc launch ubuntu:20.04 ${CONTAINER} --profile=bridge

まとめ

一度設定するとそれっきりになり、忘れてしまうため備忘録。

参考