みつきんのメモ

組み込みエンジニアです。Interface誌で「My オリジナルLinuxの作り方」連載中

Yocto Project 3.3(Hardknott)でラズベリーパイ4を動かす

はじめに

Yocto Project 3.3がリリースされていた。

リリースされた事自体は知っていたが、meta-raspberrypiをビルドする際に、bitbake-layers layerindex-fetchがうまく動かなかったので、しばらく様子を見ていた。

具体的には下記のようなこと。

  • OpenEmbedded Layer Indexとの接続に使用される証明書がエラーになった
  • OpenEmbedded Layer Indexのhardknottブランチにmeta-raspberrypiが登録されていなかった

5/26時点で、これらの問題が解消されたことを確認した。

構築手順

ソース取得

下記のコマンドでソースを取得する。

$ mkdir -p rpi-hardknott
$ cd rpi-hardknott
$ git clone git://git.yoctoproject.org/poky.git -b hardknott

環境変数設定

$ source poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

レイヤ追加

下記のコマンドでビルド対象にmeta-raspberrypiを追加する。

$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confの修正

MACHINEをraspberrypi4-64に設定し、UARTを有効化する。

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

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# connman
IMAGE_INSTALL_append = " connman \
                 connman-client \
"

ビルド

core-image-baseをビルドする。

$ bitbake core-image-base

書き込み

bmaptoolで書き込む。

$ sudo bmaptool copy core-image-base-raspberrypi4.wic.bz2 /dev/sdX

/dev/sdXは環境に応じて適宜読み替える。

起動

OSを書き込んだマイクロSDカードをラズベリーパイ4に挿入し起動する。

rootでログインしたあと下記のコマンドで環境を確認する。

# cat /etc/os-release
ID=poky
NAME="Poky (Yocto Project Reference Distro)"
VERSION="3.3.1 (hardknott)"
VERSION_ID=3.3.1
PRETTY_NAME="Poky (Yocto Project Reference Distro) 3.3.1 (hardknott)"
# uname -a
Linux raspberrypi4-64 5.10.31-v8 #1 SMP PREEMPT Fri Apr 23 15:16:49 UTC 2021 aarch64 GNU/Linux

hardknottが起動したことが確認できた。

まとめ

3.3がリリースされていた。

リリース直後はbitbake-layers layerindex-fetchがうまく動作しないことがあるということがわかった。

ユーザーが使用する機能としては便利になった反面、正しく動作する環境を提供するコストは高くなったのかもしれない。

開発者の皆さんに感謝。

Yocto Chromiumをビルドする時に何回やってもリンカでSegmentation faultする時の対応

はじめに

YoctoでChromiumをビルドする時に下記のようにエラーで終了する。

... (snip) ...

nterface.o
| ninja: build stopped: subcommand failed.
| WARNING: exit code 1 from a shell command.
| 
ERROR: Task (/home/mickey/work/yocto/rpi-gatesgarth/poky/meta-browser/meta-chromium/recipes-browser/chromium/chromium-x11_89.0.4389.90.bb:do_compile) failed with exit code '1'
NOTE: Tasks Summary: Attempted 3513 tasks of which 3512 didn't need to be rerun and 1 failed.

Summary: 1 task failed:
  /home/mickey/work/yocto/rpi-gatesgarth/poky/meta-browser/meta-chromium/recipes-browser/chromium/chromium-x11_89.0.4389.90.bb:do_compile
Summary: There was 1 ERROR message shown, returning a non-zero exit code

試しにリトライしても、meta-clangのブランチを変えても、キャッシュを消しても同じところでエラーになる。

エラーの詳細を調べる

ログファイルの特定

こういうケースではどのログファイルを参照するかをまず、調べる必要がある。そのためにtmp/log/cooker/raspberrypi4-64/console-latest.logを参照する。

このファイルの末尾の方に下記のようにエラーが起きているタスクのログファイルのフルパスが表示されている。

ERROR: Logfile of failure stored in: /home/mickey/work/yocto/rpi-gatesgarth/build_kiosk/tmp/work/cortexa72-poky-linux/chromium-x11/89.0.4389.90-r0/temp/log.do_compile.3513135
NOTE: recipe chromium-x11-89.0.4389.90-r0: task do_compile: Failed
ERROR: Task (/home/mickey/work/yocto/rpi-gatesgarth/poky/meta-browser/meta-chromium/recipes-browser/chromium/chromium-x11_89.0.4389.90.bb:do_compile) failed with exit code '1'
NOTE: Tasks Summary: Attempted 3513 tasks of which 3512 didn't need to be rerun and 1 failed.

log.do_compile.3513135である。最後の数字は実行時のプロセスIDとなっている。

ログファイルの調査

errorの文字列でログファイルを検索していくと下記のようなエラーがメッセージが見つかる。

これはmeta-clangがgatesgarthブランチ時のログ

clang-11: error: unable to execute command: Segmentation fault (core dumped)
clang-11: error: linker command failed due to signal (use -v to see invocation)

つぎのmeta-clangがmasterブランチの時のログ。

clang-12: error: unable to execute command: Segmentation fault (core dumped)
clang-12: error: linker command failed due to signal (use -v to see invocation)

つまり同じところ、それもリンカ実行時にエラーが起きている。

リンカについての調査

リンカに問題がありそうということでコンパイル時にどのリンカが使用されているか、ログファイルを検索する。

python "../../build/toolchain/gcc_solink_wrapper.py" --readelf="aarch64-poky-linux-readelf" --nm="aarch64-poky-linux-llvm-nm"  --sofile="./libGLESv2.so" --tocfile="./libGLESv2.so.TOC" --output="./libGLESv2.so" -- aarch64-poky-linux-clang++ -target aarch64-poky-linux  -mcpu=cortex-a72 -march=armv8-a+crc+crypto   -mlittle-endian -Qunused-arguments -fstack-protector-strong  -D_FORTIFY_SOURCE=2 -Wformat -Wformat-security -Werror=format-security --sysroot=/home/mickey/work/yocto/rpi-gatesgarth/build_kiosk/tmp/work/cortexa72-poky-linux/chromium-x11/89.0.4389.90-r0/recipe-sysroot -shared -Wl,-soname="libGLESv2.so" -Wl,--fatal-warnings -Wl,--build-id=sha1 -fPIC -Wl,-z,noexecstack -Wl,-z,relro -Wl,-z,now -Wl,-z,defs -Wl,--as-needed -fuse-ld=gold -Wl,--threads -Wl,--thread-count=4 -Wl,--icf=all -Wl,-O2 -Wl,--gc-sections -rdynamic -Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-z,relro,-z,now -o "./libGLESv2.so" @"./libGLESv2.so.rsp"

-fuse-ld=goldということでgoldが使用されているようだ。

meta-chromiumgrepすると下記のような行が見つかる。

# by using the ld-is-lld distro feature otherwise use gold linker
GN_ARGS += "${@bb.utils.contains('DISTRO_FEATURES', 'ld-is-lld', 'use_lld=true use_gold=false', 'use_lld=false use_gold=true', d)}

これによるとDISTRO_FEATURESls-is-lldを追加する音でgoldの代わりにlldを使用するようになるらしい。

local.confの修正

conf/local.confに下記のように追加する。

IMAGE_INSTALL_append = " chromium-x11"
BBMASK += "meta-clang/recipes-core/busybox/busybox_1.33%.bbappend"
DISTRO_FEATURES_append = " ld-is-lld"

BBMASKはmeta-clangをmasterブランチで使用する時に発生するエラーを回避している。gatesgarthでは不要。

最終的に使用したレイヤの情報

こういう情報もcosole-latest.logに残っている。

Build Configuration:
BB_VERSION           = "1.48.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "universal"
TARGET_SYS           = "aarch64-poky-linux"
MACHINE              = "raspberrypi4-64"
DISTRO               = "poky"
DISTRO_VERSION       = "3.2.2"
TUNE_FEATURES        = "aarch64 armv8a crc crypto cortexa72"
TARGET_FPU           = ""
meta                 
meta-poky            
meta-yocto-bsp       = "gatesgarth:6ed895d2b2d0dd5e5c85851c082954f5bb0294a0"
meta-oe              = "gatesgarth:945f062ff098dc9c8ba8d22c5eef88adec60730d"
meta-clang           = "master:404e8129c26ea5387a51248eebff1820db6f5015"
meta-python2         = "gatesgarth:56288c2599d7a6df6e34e8b07b7dc3d4d2d71e30"
meta-chromium        = "master:d6b1385daf2fd9178ba7bed5ba1eb909e47e8347"
meta-raspberrypi     = "gatesgarth:3ae135e590e375c8da26b003bda41c18fb977ae1"

おそらくmeta-clangはgatesgarthでも良い気がする。

meta-browserについて

もともとchromiumのレシピはmeta-browserのは以下にあったが、つい最近meta-firefoxとmeta-chromiumに分離されたようだ。 どちらも巨大だし依存関係も異なっているので、別々に管理するのは賛成。

その都合上2021/03/25前後ではbitbake-layers layerindex-fetchが使えない。

まとめ

Yoctoでchromiumをビルドする際にはDISTRO_FEATURES_append = " ld-is-lld"は必須。

最近meta-chromiumとmeta-firefoxに分離した。

chromiumのビルドだけで数時間はかかるのでエラーが出るとしんどい。

エラー時の解析にはconsole-latest.logを最初に参照すると便利。

Yocto リードオンリーなルートファイルシステムを構築する

はじめに

組み込みシステムの場合、製品として使用するようなケースではルートファイルシステムをリードオンリーとして、 本体が持つデータを書き換えられないようにすることがある。

Yoctoでは簡単に実現することができる。

実機でやったほうが楽しいのでラズベリーパイ4で試す。

使用するブランチはgatesgarth。

環境構築

下記のコマンドを実行し、環境を構築する。

ソース取得

$ mkdir rpi-gatesgarth && cd rpi-gatesgarth
$ git clone git://git.yoctoproject.org/poky.git -b gatesgarth
$ source layers/poky/oe-init-build-env build
$ bitbake-layers layerindex-fetch meta-raspberrypi

local.confの修正

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

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

# enable uart
ENABLE_UART = "1"

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# connman
IMAGE_INSTALL_append = " connman \
                 connman-client \
"

EXTRA_IMAGE_FEATURES += "read-only-rootfs"

肝となるのはEXTRA_IMAGE_FEATURES += "read-only-rootfs"の部分。

ビルドおよび動作確認

下記のコマンドでビルドする。bitbakeは時間がかかる。

$ bitbake core-image-base

ビルドが完了したら、下記のコマンドでマイクロSDにイメージを書き込む。

$ cd tmp/deploy/images/raspberrypi4-64/
$ sudo bmaptool copy core-image-base-raspberrypi4-64.wic.bz2 /dev/sdX

/dev/sdXは環境に応じて適宜読み替える。

イメージを書き込んだマイクロSDでラズベリーパイ4を起動する。

試しに何かファイルを作成する。

root@raspberrypi4-64:~# echo hello > hello.txt
-sh: can't create hello.txt: Read-only file system

正しくエラーになる。これでルートファイルシステムがリードオンリーであることがわかる。

書き込み可能な領域

mega-manualを参照すると下記のようになっている。

With the "read-only-rootfs" feature enabled, any attempt by the target to write to the root filesystem at runtime fails. Consequently, you must make sure that you configure processes and applications that attempt these types of writes do so to directories with write access (e.g. /tmp or /var/run).

書き込みを伴うアプリケーションは/tmp/var/runのような書き込み可能な領域に配置する必要がある。

そのようなアプリケーションが/tmp/var/runでしか実行できないのは不便すぎる。

通常このような場合はoverlayfsやunionfsなどを使用して、書き込み可能な領域をオーバーレイすることによって問題を回避する。

オーバーレイする方法

単純に考えてオーバーレイする方法は下記のような感じになる。

  1. meta-readonly-rootfs-overlay
  2. aufs-utilやfuse-overlayfsを使って自前でなんとかする。

meta-readonly-rootfs-overlayは古すぎてメンテされていない。自前でなんとかするのは必要でなければできる限りやりたくない。

製品でYoctoを使用してリードオンリーなルートファイルシステムを使用したい場合、意外とこのへんで苦労すると思う。

VOLATILE_BINDSを使う

変更が揮発して良ければ、つまり、電源を切ったあとに保存される必要がなければ実のところ既にオーバーレイの機能は用意されいる。

local.confに下記のように追加することで、任意のディレクトリを書き込み可能にすることができる。

VOLATILE_BINDS_append = " \
    /var/volatile/root /home/root\n\
"

ここではrootユーザーのホームディレクトリを書き込み可能にしている。

これは"read-only-rootfs"を有効化すると組み込まれるvolatile-bindsというレシピが提供している機能で、upperdir lowerdirの順で記述すると、そのようにoverlayしてくれる。

本来はリードオンリーである/home/root(lowerdir)を揮発するが書き込み可能である/var/volatile/root(upperdir)でオーバレイすることにより、/home/rootを書き込み可能としている。

注意点としては、local.confでVOLATILE_BINDSを書き換えただけではvolatile-bindsパッケージは再ビルドされないので、下記のように明示的にビルドし直す必要がある。

$ bitbake voletile-binds -c clean
$ bitbake core-image-base

動作を確認する

先ほど書き込みできなかった/home/rootでファイルを作成してみる。

root@raspberrypi4-64:~# echo hello > hello.txt
root@raspberrypi4-64:~# cat hello.txt
hello

再起動したあとにlsを実行すると、ファイルが存在しないことがわかる。

まとめ

リードオンリーなルートファイルシステムIMAGE_FEATURES += "read-only-rootfs"で簡単に作れる。

任意のディレクトリを書き込み可能にしたい場合はvolatile-bindsVOLATILE_BINDS変数で可能。

電源を切ったあとも保存して置きたいデータは書き込み可能なパーティションを用意するか、USBメモリなどの外部メディアを用意する必要がある。

組み込み機器としてルートファイルシステムがリードオンリーであることの利点としては下記が考えられる。

  1. ファイル改ざんによる不具合の防止
  2. ストレージへの書き込み寿命による故障の防止
  3. セキュアアップデートなどでの環境の同一性の検証がしやすい

ファイルに書いたデータを保存するためには別途準備が必要となるが、リードオンリーなルートファイルシステムを持つ組み込み機器の用途を考えると、Yoctoが標準で用意する機能としては十分だと考える。

Raspberry Pi Pico タイマーでLチカ

はじめに

Raspberry Pi Pico(Pico)は4つの64ビットタイマーを持っている。それぞれ割り込みを発生させることができる。

ここまででSysTickやその割り込みを使ってLチカをしてきたが、すべてビジーループでタイミングを測っていた。

今回はWFIを使ってスリープしてみる。使用するタイマーはTIMER0。

タイマー

リセット

タイマーを使用するにはまず最初にタイマーをリセットする必要がある。リセットにはRESETSレジスタを使用する。RESETSレジスタは下記のようになっている。

f:id:mickey_happygolucky:20210311062823p:plain

f:id:mickey_happygolucky:20210311062848p:plain

割り込み

SysTickとは違いタイマーの割り込みはペリフェラルの割り込みとなる。

NVIC、IRQ

ペリフェラルの割り込みはNested Vectored Interrupt Controller(NVIC)に接続され、それぞれの機能がIRQ(Interrupt ReQuest)に割り当てられている。

f:id:mickey_happygolucky:20210311062900p:plain

TIMER0の割り込みはIRQ0に割り当てられていることがわかる。

IRQ0の割り込みが発生するとベクターテーブルのIRQ0に飛んでくる。

NVIC割り込みの操作には下記のレジスタを使用する。

f:id:mickey_happygolucky:20210311063033p:plain

タイマー割り込み

NVICは接続されているペリフェラルの割り込みを処理するだけのものなので、IRQ0を有効化するだけではタイマーの割り込みは発生しない。

タイマーの割り込みを発生させるには、ペリフェラル側でも下記のような操作が必要になる。

  1. 割り込みの有効化
  2. 割り込み条件の設定

タイマーのレジスタ構成は下記のようになっている。

f:id:mickey_happygolucky:20210311062912p:plain

f:id:mickey_happygolucky:20210311062931p:plain

割り込みの有効化

タイマーの割り込みを有効化するにはINTEレジスタをを使用する。INTEレジスタは下記のようになっている。

f:id:mickey_happygolucky:20210311063020p:plain

割り込み条件の設定

割り込み条件の設定にはALARM0〜3のレジスタを使用する。

ALARM0〜3に書き込まれた値がタイマーのカウンタであるTIMELRと一致すると4つのタイマーのうちの対応する割り込みが発生する。

タイマー0で使用するALARM0は下記のようになっている。

f:id:mickey_happygolucky:20210311062945p:plain

割り込みのクリア

割り込みが発生したら、割り込みハンドラの中でその割り込みをクリアする必要がある。割り込みをクリアするにはINTRレジスタの対応するビットをクリアする。

f:id:mickey_happygolucky:20210311062955p:plain

f:id:mickey_happygolucky:20210311063009p:plain

実装

main.cの中でそれぞれ実装する。

タイマーの初期化

タイマーの初期化処理では下記のことを行う。

  1. タイマーのリセット
  2. リセット完了待ち
  3. タイマー割り込みの有効化
  4. IRQ0の有効化

init_timer()として下記のように実装する。

void init_timer() {
        /* reset TIMER */
        write_reg_op(RESETS_RESET, (1<<21), OP_CLR);
        /* wait for reset TIMER to be done */
        while (!read_reg(RESETS_RESET_DONE)&(1<<21)) ;

        /* Enable timer interrupt */
        write_reg_op(TIMER_INTE, 1, OP_SET);

        /* Enable IRQ */
        irq_set_enabled(0, true); /* TIMER_IRQ_0=0 */
}

割り込み条件の設定

sleep_us()として下記のように実装する。

void sleep_us(uint32_t us) {
        uint32_t wakeup = read_reg(TIMER_TIMERRAWL);
        wakeup += us;
        write_reg(TIMER_ALARM0, wakeup);

        /* Wait For Interupt */
        __asm__ __volatile__("wfi");
}

sleep_usでは下記の処理を行っている。

  1. 現在のタイマーの値を取得
  2. 待ちたい時間を加算した値をALARM0に書き込む
  3. WFIを使って割り込み待ち状態(sleep)にする

WFIはWait For Interrupt命令のことで、割り込みが入るまでCPUがスリープ状態になる。 割り込み(この場合はタイマー割り込み)が発生すると、スリープが解除されCPUが復帰する。

割り込みハンドラ

isr_timer0()として下記のように実装する。

void isr_timer0() {
        /* Clear the timer0 irq */
        write_reg_op(TIMER_INTR, 1, OP_CLR);
}

isr_timer0start.Sの割り込みベクタの定義のIRQ0の位置に登録しておく。

/* vector table */
    .section .vectors, "ax"
    .align 2
    .global __vectors
__vectors:
.word 0x20001000
.word reset
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word hang
.word isr_timer0

メイン処理

main関数は下記のようになっている。

int main(void) {

        /////////////////////
        // This initialise code is ported from https://github.com/dwelch67/raspberrypi-pico/blob/master/blinker00/notmain.c
        // release reset on IO_BANK0
        write_reg_op(RESETS_RESET, 1<<5, OP_CLR); //IO_BANK0
        //wait for reset to be done
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<5))!=0) break;
        }
        write_reg_op(RESETS_RESET, (1<<8), OP_CLR); //PADS_BANK0
        while(1) {
                if((read_reg(RESETS_RESET_DONE)&(1<<8))!=0) break;
        }
        /////////////////////

        // GPIO init
        write_reg(SIO_GPIO_OE_CLR, (1ul<<25));
        write_reg(SIO_GPIO_OUT_CLR, (1ul<<25));
        uint32_t ra = read_reg(PADS_GPIO25);
        write_reg_op(PADS_GPIO25, (ra^0x40)&0xC0, OP_XOR);

        write_reg(IO_GPIO25_CTRL, 0x5);

        // Timer0 init
        init_timer();

        // Blink
        write_reg(SIO_GPIO_OE_SET, (1ul<<25));
        while (1) {
                sleep_us(100*1000); //100ms
                write_reg(SIO_GPIO_OUT_XOR, (1ul<<25));
        }
        return 0;
}

sleep_us関数を使って100msごとにLEDを点滅させている。

波形

問題なくPluseが100msとなっている。

f:id:mickey_happygolucky:20210311063044p:plain

まとめ

タイマーペリフェラルの割り込みでスリープを実装した。

  1. NVICで割り込みを有効化
  2. TIMERで割り込みを有効化
  3. TIMERで割り込みの発生条件を設定

これで使える。

ビルド可能なソースコード一式はここに置いてある。

参考

Yocto UEFIセキュアブート

はじめに

Yoctoでx86_64向けのボードを動かす。その際にUEFIセキュアブートも有効にしてみたいと考えた。そこで、meta-efi-secure-bootというレイヤを見つけた。

実機はROCK Pi X V1.4を使用する。

UEFIセキュアブート

UEFIセキュアブートは秘密鍵を使用して署名されたバイナリを、実行時に署名が正しいかどうかを検証して、正しい場合にのみ起動できるようになっている。検証に使用する公開鍵はNVRAMに保存されている。

NVRAMに保存される鍵は下記のようなものがある。

役割 主な所持者
Platform Kay (PK) KEKの変更に必要 HWベンダ
Key Exchange Key (KEK) db/dbxの変更に必要 OSベンダ
db/dbx 実際のバイナリの検証に使用

UEFIファームウェアから起動されるプログラムはdb/dbxによって検証され、正しいと確認が取れた場合にのみ実行可能となる。dbは署名データベースdbxは失効した署名データベースとなっている。dbにある署名と一致する場合は正しいバイナリ、dbxにある署名と一致する場合は不正なバイナリと判断される。

f:id:mickey_happygolucky:20210309091436p:plain
db/dbx

shimについて

UEFIセキュアブートでは実際のバイナリの署名を検証するのはdb/dbxだが、そのdb/dbxに鍵を登録するにはKEK秘密鍵が必要になる。

KEKはPKによって署名されているが、PKの秘密鍵はHWベンダしか持っておらず、UEFIを使用する場合は大概PCのHWとなり、HWベンダとしてはWindowsが起動できれば良いため、KEK秘密鍵マイクロソフトが持っているという状況が多い。

Linuxディストリビューションを起動するためにはカーネルを起動するためのブートローダであるgrubに対してdbの署名をしてもらう必要があるが、dbの署名をすることができるのはKEK秘密鍵を持っているマイクロソフトである。オープンソースであるgrubをビルドするたびにいちいちマイクロソフトに署名してもらうのは現実的ではない。

そこでshimという小さいブートローダ作成し、shimにしてマイクロソフトに署名してもらうようにした。shimからはdb/dbxの署名を検証し次のブートローダを起動するという機能しか持たせずビルドされる機会は少ないように作られている。 shimからgrubを起動し、従来のgrubからカーネルを起動するという流れにすることで、UEFIセキュアブートの環境でもLinuxが起動できるようになった。

shimから署名をチェックしないでなんでも起動できるようにしてしまうとセキュアブートの意味がなくなるので、shim以降はMachine Owner Key(MOK)という署名鍵でバイナリを検証する。 MOKの証明書の公開鍵はNVRAMではなくshimに埋め込まれている。

非PEのValidation

shimはPE形式のバイナリに対してのみ検証を行う。grub.cfgやカーネル、initrdなどの非PEのファイルに対して検証を行うために、 meta-efi-secure-bootではSELoaderを導入ししている。

SELoaderはshimに埋め込まれた証明書でバイナリを検証する。

ブートシーケンス

UEFIファームウェアはデフォルトの状態だと、EFI/BOOT/bootx64.efiを探して実行しようとする。そのため、最初に実行したいブートローダbootx64.efiにリネームしておく。 ここではshimbootx64.efiとして配置されている。

shimからgrubを直接実行しても問題はないが、shimは前述の通りPE形式のファイルしか認証できないため、shimの次にSELoaderを実行しSELoaderからgrubが実行されるようになっている。

まとめると次のような順序でプログラムが実行される。

  1. UEFIファームウェア
  2. shim
  3. SELoader
  4. grub
  5. Linux Kenrel

UEFIファームウェア(BIOS)の設定

最終的にカーネルが起動した時の設定を下記に示す。

鍵は全て、meta-efi-secure-bootが提供しているサンプルの鍵を使用している。

Aptio Setup Utility(American Megatrends, Inc.)

Securityのタブ

Key Value
System Mode User
Secure Boot Active
Vendor Keys Not Active
Secure Boot [Enabled]
Secure Boot Mode [Custom]

Key Management

Key Value
Provison Factory Default Keys [Disabled]
Secure Boot variable Size Key# Key source
Platform Key(PK) 843 1 Custom *
Key Exchange Keys 823 1 Custom *
Authorized Signatures 821 1 Custom *
Forbidden Signatures 813 1 Custom *
Authorized TimeStamps 0 0

meta-efi-secure-bootの挙動

boot-menuの変更

検証環境ではboot-menu.incの内容が実機とあっていないので、grubメニューでeを押してメニューの内容を変更する必要があった。

その場合はユーザーとパスワードが聞かれるので下記のように入力する必要がある。

User Password
root root

これはpassword.incによって設定される。

ブートパーティション

カーネルが起動した時のブートパーティションのファイル構成を下記に示す。

.
├── EFI
│   └── BOOT
│       ├── Hash2DxeCrypto.efi
│       ├── LockDown.efi
│       ├── Pkcs7VerifyDxe.efi
│       ├── SELoaderx64.efi
│       ├── boot-menu.inc
│       ├── boot-menu.inc.p7b
│       ├── bootx64.efi
│       ├── efi-secure-boot.inc
│       ├── efi-secure-boot.inc.p7b
│       ├── grub.cfg
│       ├── grub.cfg.p7b
│       ├── grubenv
│       ├── grubx64.efi
│       ├── mmx64.efi
│       ├── password.inc
│       └── password.inc.p7b
├── bzImage
└── bzImage.p7b

カスタムキーのProvision

カスタムキーをNVRAMに一括に登録する機能としてLockDown.efiがある。このアプリケーションは通常Setupモードで実行されることになる。

カスタムキー非登録時には非セキュアな状態で実行されるgrubによって、自動的にLockDown.efiが実行されるようになっている。

これが自動的に実行されない場合はUEFI設定画面からUEFIシェルを起動し、手動で実行する。

UEFIシェルでのコマンドの例を下記に示す。

> fs0:
> cd EFI\BOOT
> LockDown.efi

meta-efi-secure-bootのビルド

meta-efi-secure-bootを使用してROCK Pi Xを起動する環境を作成する手順を示す。いくつか修正する必要があったため独自のレイヤを作成している。

gatesgarthブランチを使用する。

pokyの取得

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

環境変数を読み込む。

$ source poky/oe-init-build-env build

この時点でbuildディレクトリに移動される。

必要なレイヤをダウンロードする。

$ bitbake-layers layerindex-fetch meta-oe
$ bitbake-layers layerindex-fetch meta-efi-secure-boot -b master

meta-efi-secure-boot-helperの取得

ワークアラウンドのために作成したレイヤをダウンロードする。

$ cd ../poky
$ git clone https://github.com/mickey-happygolucky/meta-efi-secure-boot-helper.git
$ cd -
$ bitbake-layers add-layer ../poky/meta-efi-secure-boot-helper

local.confの修正

conf/local.confの下の方に下記の内容を追加する。

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

# systemd
DISTRO_FEATURES_append = " systemd pam"
VIRTUAL-RUNTIME_init_manager = "systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED = "sysvinit"
VIRTUAL-RUNTIME_initscripts = ""

# secure boot
#SIGNING_MODEL = "user"

INITRAMFS_IMAGE = "secure-core-image-initramfs"
DISTRO_FEATURES_append = " efi-secure-boot"

IMAGE_EFI_BOOT_FILES_append = " \
        ${KERNEL_IMAGETYPE}.p7b \
        Hash2DxeCrypto.efi;EFI/BOOT/Hash2DxeCrypto.efi \
        LockDown.efi;EFI/BOOT/LockDown.efi \
        Pkcs7VerifyDxe.efi;EFI/BOOT/Pkcs7VerifyDxe.efi \
        SELoaderx64.efi;EFI/BOOT/SELoaderx64.efi \
        boot-menu.inc;EFI/BOOT/boot-menu.inc \
        boot-menu.inc.p7b;EFI/BOOT/boot-menu.inc.p7b \
        bootx64.efi;EFI/BOOT/bootx64.efi \
        efi-secure-boot.inc;EFI/BOOT/efi-secure-boot.inc \
        efi-secure-boot.inc.p7b;EFI/BOOT/efi-secure-boot.inc.p7b \
        grub.cfg;EFI/BOOT/grub.cfg \
        grub.cfg.p7b;EFI/BOOT/grub.cfg.p7b \
        grubenv;EFI/BOOT/grubenv \
        grubx64.efi;EFI/BOOT/grubx64.efi \
        mmx64.efi;EFI/BOOT/mmx64.efi \
        password.inc;EFI/BOOT/password.inc \
        password.inc.p7b;EFI/BOOT/password.inc.p7b \
"

ビルド

secure-core-minimal-imageをビルドする。

$ bitbake secure-core-minimal-image

書き込み

bmaptoolで書き込む。メディアはUSBメモリスティックか、SDカードを使用する。

SDカードを使用する場合はオンボードのカードスロットではなく、USB接続のカードリーダを使用する必要がある。

メディアが自動でマウントされている場合はアンマウントする。

$ sudo umount /media/${USER}/*

下記のコマンドで書き込む。

$ cd tmp/deploy/images/genericx86-64/
$ sudo bmaptool copy secure-core-minimal-image-genericx86-64.wic /dev/sda

UEFIファームウェア(BIOS)の設定

ボードにはUSBでキーボード、HDMIでモニタを接続しておく。

UEFIファームウェア(BIOS)の設定画面で下記のことを実行する。

  1. セキュアブートを有効化
  2. 設定されている鍵を全て削除
  3. LockDown.efiの実行

次回からセキュアな環境でgrubが起動されるようになる。

meta-efi-secure-boot-helperの内容

構成

設定ファイルを上書きしたいのでプライオリティは20と高めに設定している。

.
├── COPYING.MIT
├── README.md
├── conf
│   └── layer.conf
├── recipes-bsp
│   └── grub
│       ├── grub-efi
│       │   └── boot-menu.inc
│       └── grub-efi_2.04.bbappend
└── recipes-devtools
    └── sbsigntool
        ├── sbsigntool
        │   └── resolve_efi_dir.patch
        └── sbsigntool_git.bbappend

追加したレシピは下記の2つ。

  1. grub-efi
  2. sbsigntool

grub-efi

recipes-bsp/grub/grub-efi_2.04.bbappendで署名されていないgrubのバイナリがインストールされるのを抑制した。

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"

# avoid the installing grubx64.efi which does not certified.
do_deploy() {
}

recipes-bsp/grub/grub-efi/boot-menu.incgrubメニューの内容を修正した。

menuentry "Sample EFI boot" --unrestricted {
    savedefault
    set fallback=1
    linux /bzImage root=/dev/sda2 ro rootwait
}

sbsigntool

そのままではビルドに失敗するため、recipes-devtools/sbsigntool/sbsigntool_git.bbappendで依存関係とパッチを追加した。

SRC_URI += "file://resolve_efi_dir.patch;subdir=git"

FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
DEPENDS += "binutils-native"

recipes-devtools/sbsigntool/sbsigntool/resolve_efi_dir.patchはクロスコンパイルに対応している。

diff --git a/configure.ac b/configure.ac
index 4ffb68f..a77f94d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -71,7 +71,7 @@ AM_CONDITIONAL(TEST_BINARY_FORMAT, [ test "$EFI_ARCH" = "arm" -o "$EFI_ARCH" = "
 # no consistent view of where gnu-efi should dump the efi stuff, so find it
 ##
 for path in /lib /lib64 /usr/lib /usr/lib64 /usr/lib32 /lib/efi /lib64/efi /usr/lib/efi /usr/lib64/efi /usr/lib/gnuefi /usr/lib64/gnuefi ; do
-    if test -e $path/crt0-efi-$EFI_ARCH.o; then
+    if test -e $base_prefix$path/crt0-efi-$EFI_ARCH.o; then
        CRTPATH=$path
     fi
 done
@@ -79,7 +79,7 @@ if test -z "$CRTPATH"; then
    AC_MSG_ERROR([cannot find the gnu-efi crt path])
 fi
 
-EFI_CPPFLAGS="-I/usr/include/efi -I/usr/include/efi/$EFI_ARCH \
+EFI_CPPFLAGS="-I$base_prefix/usr/include/efi -I$base_prefix/usr/include/efi/$EFI_ARCH \
  -DEFI_FUNCTION_WRAPPER"
 CPPFLAGS_save="$CPPFLAGS"
 CPPFLAGS="$CPPFLAGS $EFI_CPPFLAGS"

まとめ

Yoctoでx86のボードをちゃんと動かしたのは初めて。せっかくなのでUEFIセキュアブートを有効化してみた。

起動しない時に何が原因かを探るのが難しいが意外とドキュメントは見つかるように感じた。

参照

Yocto bitbake-layers layerindex-fetch

はじめに

bitbake-layersコマンドの便利なサブコマンドを見つけた(認識した)。

レイヤ間の依存関係

bitbakeで取り扱うレイヤではlayer.confに下記のようにLAYERDEPENDSを記述することで、依存関係を定義することができる。

LAYERDEPENDS_gnome-layer = "core openembedded-layer networking-layer"

しかし、bitbake-layers add-layerをする場合、依存関係の順番を間違えると面倒なことになる。

$ bitbake-layers add-layer ../poky/meta-openembedded/meta-gnome

一見問題なく追加できるが、次回以降bitbake-layersのコマンドを実行すると下記のようにエラーが発生する。

$ bitbake-layers show-layers
NOTE: Starting bitbake server...
Traceback (most recent call last):
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/bin/bitbake-layers", line 95, in <module>
    ret = main()
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/bin/bitbake-layers", line 63, in main
    tinfoil.prepare(True)
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/tinfoil.py", line 414, in prepare
    self.run_command('parseConfiguration')
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/tinfoil.py", line 470, in run_command
    raise TinfoilCommandFailed(result[1])
bb.tinfoil.TinfoilCommandFailed: Traceback (most recent call last):
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/command.py", line 74, in runCommand
    result = command_method(self, commandline)
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/command.py", line 275, in parseConfiguration
    command.cooker.parseConfiguration()
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/cooker.py", line 433, in parseConfiguration
    self.handleCollections(self.data.getVar("BBFILE_COLLECTIONS"))
  File "/home/mickey/work/yocto/x86-dunfell/layers/poky/bitbake/lib/bb/cooker.py", line 1225, in handleCollections
    raise CollectionError("Errors during parsing layer configuration")
bb.cooker.CollectionError: Errors during parsing layer configuration

これはmeta-gnomeが依存しているレイヤがないため発生するのだが、この状態になるとテキストエディタなどでbblayers.confを修正する必要がある。

meta-gnomeに記載されている依存関係は下記。

  1. core
  2. openembedded-layer
  3. networking-layer

しかしここで厄介なのが、networking-layerがmeta-pythonに依存しているためそちらも記述しなければならない。 このようなネストした依存関係がある場合には、1つずつ追加していくのが面倒となってくる。

bitbake-layers layerindex-fetch

レイヤ間の依存関係をプログラム的にチェックできるのであれば、追加する前にエラーにするか、自動的に追加するなどしてほしいと思っていた。 しかし、layerindex-fetchサブコマンドを使用すると自動的に依存関係を解決して、必要であればレイヤのダウンロードまでおこなってくれる。

$ bitbake-layers layerindex-fetch meta-gnome
NOTE: Starting bitbake server...
Loading https://layers.openembedded.org/layerindex/api/;branch=dunfell...
Layer                                              Git repository (branch)                                 Subdirectory
=============================================================================================================================
local:HEAD:openembedded-core                       git://git.yoctoproject.org/poky.git (dunfell)           meta
  required by: meta-gnome meta-networking meta-python meta-oe
layers.openembedded.org:dunfell:meta-oe            git://git.openembedded.org/meta-openembedded (dunfell)  meta-oe
  required by: meta-python meta-networking meta-gnome
layers.openembedded.org:dunfell:meta-python        git://git.openembedded.org/meta-openembedded (dunfell)  meta-python
  required by: meta-networking
layers.openembedded.org:dunfell:meta-networking    git://git.openembedded.org/meta-openembedded (dunfell)  meta-networking
  required by: meta-gnome
layers.openembedded.org:dunfell:meta-gnome         git://git.openembedded.org/meta-openembedded (dunfell)  meta-gnome
Cloning into '/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded'...
remote: Counting objects: 148262, done.
remote: Compressing objects: 100% (49118/49118), done.
remote: Total 148262 (delta 92673), reused 147741 (delta 92348)
Receiving objects: 100% (148262/148262), 39.27 MiB | 3.67 MiB/s, done.
Resolving deltas: 100% (92673/92673), done.
Adding layer "meta-oe" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-oe) to conf/bblayers.conf
Adding layer "meta-python" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-python) to conf/bblayers.conf
Adding layer "meta-networking" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-networking) to conf/bblayers.conf
Adding layer "meta-gnome" (/home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-gnome) to conf/bblayers.conf

レイヤの設定状況を確認する。

$ bitbake-layers show-layers
NOTE: Starting bitbake server...
layer                 path                                      priority
==========================================================================
meta                  /home/mickey/work/yocto/x86-dunfell/layers/poky/meta  5
meta-poky             /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-poky  5
meta-yocto-bsp        /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-yocto-bsp  5
meta-oe               /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-oe  6
meta-python           /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-python  7
meta-networking       /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-networking  5
meta-gnome            /home/mickey/work/yocto/x86-dunfell/layers/poky/meta-openembedded/meta-gnome  7

meta-gnomeが依存する全てのレイヤが追加されている。

自動的にダウンロードできるレイヤについて

レイヤのダウロードにはhttps://layers.openembedded.org/layerindex/api/というWeb APIを使用している。

そのため、OpenEmbedded Layer Indexに登録されているもののみが対象となっていると推測される。

既存の設定について

layerindex-fetchでは既に追加してあるレイヤに関しては削除されないため、ローカルで開発中のレイヤなどがBBLAYERSに既にある場合でも問題なく使用することができる。

ブランチ

下記のようにすることで、ダウンロードする際に使用するブランチを指定することができる。

$ bitbake-layers layerindex-fetch meta-gnome -b dunfell

ブランチを指定しない場合は、現在チェックアウトしているpokyのブランチがデフォルトとなる。

例えば、pokyがgatesgarthである時にブランチを指定せずにlayerindex-fetchを実行するとgatesgarthブランチが選択される。

また、レイヤ側がmasterブランチしか持っていない場合などに、pokyがdunfellだった場合ではデフォルト指定ではエラーになる。

$ bitbake-layers layerindex-fetch meta-efi-secure-boot
NOTE: Starting bitbake server...
Loading https://layers.openembedded.org/layerindex/api/;branch=dunfell...
ERROR: Layer "meta-efi-secure-boot" not found in layer index

そのような場合にはブランチを明示する必要がある。

$ bitbake-layers layerindex-fetch meta-efi-secure-boot -b master

まとめ

bitbake-layers layerindex-fetchは便利。

Yocto meta-gnomeでpolkitのエラー

はじめに

デフォルトの設定でmeta-gnomeをビルドするとエラーが発生する。

エラーの内容

gnome-control-centerとsystmedがそれぞれで/usr/share/polkit-1/rules.dをインストールしようとしてコンフリクトしている。

Error: Transaction test error:
  file /usr/share/polkit-1/rules.d conflicts between attempted installs of gnome-control-center-3.36.4-r0.cortexa7t2hf_neon_vfpv4 and systemd-1:246.9-r0.cortexa7t2hf_neon_vfpv4

設定ファイル自体がかぶってエラーになるならともかく、格納場所であるディレクトリがかぶってエラーになってしまうのはどうかと思う。

原因

meta-gnomeはmeta-openembeddedの持ち物なので、こんな基本的なところがメンテナンスされないことはないだろうと思いつつ調べていくと、ここで似たようなエラーメッセージが引っかかった。

これはPC(サーバ)用のLinux上でrpmの処理中に似たような状況になっている。

これはbitbakeのエラーではなくRPMの制限なのかと推測した。

回避策

local.confに下記を追加し、パッケージシステムをrpmからdebにする。

PACKAGE_CLASSES = "package_deb"

まとめ

meta-gnomeを使う場合はパッケージシステムはRPM以外にする。