ESP32 で WebAssembly Micro Runtime (WAMR) を動かす

WebAssembly Advent Calendar 2023 11 日目の記事です…!

WebAssembly Micro Runtime (WAMR) は C言語で実装された WebAssembly ランタイムのひとつで、Bytecode Alliance が開発しています。

https://github.com/bytecodealliance/wasm-micro-runtime

WebAssembly Micro Runtime (WAMR) is a lightweight standalone WebAssembly (Wasm) runtime with small footprint, high performance and highly configurable features for applications cross from embedded, IoT, edge to Trusted Execution Environment (TEE), smart contract, cloud native and so on. It includes a few parts as below:

引用にあるとおり、小さなフットプリントで動作することからマイコンでも WebAssembly を動作させることができ、おなじみ M5Stack などで採用されている ESP32 SoC もターゲットのひとつとなっています。

本記事では M5 が販売している M5Stamp C3 (ESP32C3/RISC-V) で WARM を動作させた様子を紹介したいと思います。なお、M5Stack (ESP32/Xtensa) と M5Stamp C3 は esp-idf ツールチェインが共通ですので、LCD 制御部分に僅かな修正を入れれば M5Stack でも簡単に動作するはずです。

ESP32C3 のスペック: クロック周波数 160MHz、SRAM: 400KB

AssemblyScript で 3D Cube デモを作成

マイコン上で動作させるプログラムとして AssemblyScript を使った 3D Cube のデモを作成しました。

AssemblyScript は WebAssembly 向けに開発されている TypeScript-like な言語で、コンパイラとして動作し .wasm バイナリーを直接出力します。

https://www.assemblyscript.org/

AssemblyScript targets WebAssembly’s feature set specifically, giving developers low-level control over their code.

バイナリサイズが非常にコンパクトであること(3D Cube で 8KByte弱)からマイコンのようなリソースの限られたコンピュータにも向いており、また GC のタイミングを Embedder が制御できるような export を出力することができます。

AssemblyScript には Math ライブラリも備わりますので、3D Cube のプログラムのメイン部分は次のようなイメージになります。(立方体 8点の行列演算です)

tick(): void {
    const angle: f32 = this.angle;

    const cos: f32 = Mathf.cos(angle);
    const sin: f32 = Mathf.sin(angle);

    const rotX: f32[][] = [
        [    1,    0,     0],
        [    0,  cos,  -sin],
        [    0,  sin,   cos]
    ];
    const rotY: f32[][] = [
        [  cos,    0,  -sin],
        [    0,    1,     0],
        [  sin,    0,   cos]
    ];
    const rotZ: f32[][] = [
        [  cos, -sin,     0],
        [  sin,  cos,     0],
        [    0,    0,     1]
    ];

画面描画部分では次のように Embedder 側の関数を呼ぶようにします。

export declare function draw_line(
    x0: i32, y0: i32, x1: i32, y1: i32, color: i32): void;

connect(i: u32, j: u32, k: u32[][], color: c3dev.COLOR): void {
    const a = k[i];
    const b = k[j];
    c3dev.draw_line(a[0], a[1], b[0], b[1], color);
}

完成した .wasm の export/import は以下のようになります。

  (import "c3dev" "draw_line" (func (;0;) (type 8)))
...snip...
  (export "init" (func 12))
  (export "tick" (func 19))
  (export "__new" (func 6))
  (export "__pin" (func 20))
  (export "__unpin" (func 21))
  (export "__collect" (func 22))

export で __ 付きになっている関数は AssemblyScript ランタイムの関数で、GC に関する制御を行えるものです。これは --exportRuntime コンパイルオプションで指定すると export されます。

asc src/main.ts
  -o ../../resources/wasm/3dcube.wasm
  -t build/3dcube.wat
  -b build/3dcube.d.ts
  --runtime minimal
  --exportRuntime
  --lowMemoryLimit 32767
  --use Math=NativeMathf
  --use abort=
  -O3
  --optimize

また --runtime オプションでは GC の動作を指定できます。ここでは任意のタイミングで GC を行いたかったため minimal を指定しています。

--runtime Specifies the runtime variant to include in the program.
incremental  TLSF + incremental GC (default)
minimal      TLSF + lightweight GC invoked externally
stub         Minimal runtime stub (never frees)

AssemblyScript 3D Cube のソースコードは次のリポジトリに置きました。draw_line を Web API の Canvas に繋いでいますので、npm install && npm run start にてウェブブラウザで動作確認できます。

https://github.com/h1romas4/m5stamp-c3wamr/tree/main/wasm/3dcube

WebAssembly Micro Runtime を esp-idf でビルド

WebAssembly Micro Runtime は ESP32 のツールチェインである esp-idf 向けのビルドスクリプトを持っています。

このことからビルドは簡単で、 esp-idf のディレクトリストラクチャで新規プロジェクトを作成し、components ディレクトリに WAMR を submodule add するなどした後、CMakeLists.txt で次のようにすればセットアップ完了です。

# wasm-micro-runtime settings
list(APPEND EXTRA_COMPONENT_DIRS "components/wasm/wasm-micro-runtime/build-scripts/esp-idf")
set(WAMR_BUILD_INTERP 1)
set(WAMR_BUILD_FAST_INTERP 1)
set(WAMR_BUILD_AOT 0)
set(WAMR_BUILD_LIBC_BUILTIN 1)
set(WAMR_BUILD_APP_FRAMEWORK 0)
idf_component_register(
    INCLUDE_DIRS ${INCLUDEDIRS}
    SRCS ${SRCS}
    REQUIRES arduino-esp32 lcd wamr #←追加
)

update (2024-07-21):

WARM 2.1 系より esp-idf 向けの kconfig が加わり、上記の設定は CMakeLists.txt ではなく menuconfig から行えるようになりました。

idf.py menuconfig
-> Component config -> WASM Micro Runtime

WebAssembly C and C++ API

WAMR は WebAssembly ランタイムを C/C++ からロードするために標準化が進められている WebAssembly C and C++ API に対応しています。

https://github.com/WebAssembly/wasm-c-api

Provide a “black box” API for embedding a Wasm engine in other C/C++ applications.

この仕様は、様々な WebAssembly ランタイムごとに異なっているランタイムインスタンスの作成や export/import のバインドの仕方を WebAssembly C and C++ API で統一化しようという動きです。

ということで、WebAssembly C and C++ API な #include “wasm_c_api.h” を使った場合、ランタイムのロードや設定は次のようになります。(参考:https://github.com/WebAssembly/wasm-c-api/blob/main/example/hello.c

なお、WAMR は mutex を pthread API で取得して動作するため、esp-idf 上で動作させる場合は pthread をひとつ立てた中で実行してください。

ランタイムイニシャライズ:

// Initialize.
ESP_LOGI(TAG, "WAMR Initialize...");
wasm_engine_t* engine = wasm_engine_new();
wasm_store_t* store = wasm_store_new(engine);

.wasm ロード:

// Load binary.
ESP_LOGI(TAG, "Load Wasm binary...");
SPIFFS_WASM.begin(false, "/wasm", 4, "wasm");
File wasm_file = SPIFFS_WASM.open("/3dcube.wasm", "rb");
size_t file_size = wasm_file.size();
ESP_LOGI(TAG, ".wasm size: %d", file_size);
// load wasm binary
wasm_byte_vec_t binary;
wasm_byte_vec_new_uninitialized(&binary, file_size);
if(wasm_file.read((uint8_t *)binary.data, file_size) != file_size) {
    ESP_LOGE(TAG, "Error loading module!");
    return NULL;
}
// close file
wasm_file.close();
SPIFFS_WASM.end();

.wasm バリデート・コンパイル

// Validate.
ESP_LOGI(TAG, "Validating module...");
if (!wasm_module_validate(store, &binary)) {
    ESP_LOGE(TAG, "Error validating module!");
    return NULL;
}

// Compile.
ESP_LOGI(TAG, "Compiling module...");
own wasm_module_t* module = wasm_module_new(store, &binary);
if (!module) {
    ESP_LOGE(TAG, "Error compiling module!");
    return NULL;
}

.wasm import のコールバック作成(ここで draw_line 5引数関数からマイコン LCD 描画を呼び出します)

// Create external functions.
ESP_LOGI(TAG, "Creating callback...");
// (import "c3dev" "draw_line" (func (;1;) (type 8)))   draw_line_type
own wasm_functype_t* draw_line_type = wasm_functype_new_5_0(
    wasm_valtype_new_i32(),
    wasm_valtype_new_i32(),
    wasm_valtype_new_i32(),
    wasm_valtype_new_i32(),
    wasm_valtype_new_i32()
);
own wasm_func_t* draw_line_func = wasm_func_new(store, draw_line_type, draw_line_callback);
wasm_functype_delete(draw_line_type);
/**
 * draw_line_callback (A function to be called from Wasm code)
 */
own wasm_trap_t* draw_line_callback(
    const wasm_val_vec_t* args, wasm_val_vec_t* results
) {
    tft.drawLine(
        args->data[0].of.i32,
        args->data[1].of.i32,
        args->data[2].of.i32,
        args->data[3].of.i32,
        args->data[4].of.i32
    );
    return NULL;
}

.wasm export のバインド(ここで .wasm 側の init、tick__collect を取得して Embedder から呼び出せるようにします)

// (export "init" (func 14))        init_func
// (export "tick" (func 21))        tick_func
// (export "__new" (func 8))        -
// (export "__pin" (func 22))       -
// (export "__unpin" (func 23))     -
// (export "__collect" (func 24))   collect_func
const wasm_func_t* init_func = wasm_extern_as_func(exports.data[0]);
if(init_func == NULL) {
    ESP_LOGE(TAG, "Error accessing export!");
    return NULL;
}
const wasm_func_t* tick_func = wasm_extern_as_func(exports.data[1]);
if(tick_func == NULL) {
    ESP_LOGE(TAG, "Error accessing export!");
    return NULL;
}
const wasm_func_t* collect_func = wasm_extern_as_func(exports.data[5]);
if(collect_func == NULL) {
    ESP_LOGE(TAG, "Error accessing export!");
    return NULL;
}

.wasm アプリケーション開始。 init 関数(LCD解像度 2引数)を呼び出し:

ESP_LOGI(TAG, "Calling export...");
wasm_val_t as[2] = { WASM_I32_VAL(160), WASM_I32_VAL(128) };
wasm_val_vec_t args = WASM_ARRAY_VEC(as);
wasm_val_vec_t results = WASM_EMPTY_VEC;
// init
if (wasm_func_call(init_func, &args, &results)) {
    ESP_LOGE(TAG, "Error calling function!");
    return NULL;
}

アプリケーションループ。 wasm 側の tick と __collect (GC) 関数の呼び出し(Wasm tick 関数が 3D Cube の座標計算を行い draw_line することで実際にマイコン側に描画されます)

// loop 10000 frame
wasm_val_vec_t args_empty = WASM_EMPTY_VEC;
wasm_val_vec_t results_empty = WASM_EMPTY_VEC;
for(size_t i = 0 ; i < 10000; i++) {
    // tick
    if(wasm_func_call(tick_func, &args_empty, &results_empty)) {
        ESP_LOGE(TAG, "Error calling function!");
        return NULL;
    }
    // GC for AssemblyScript
    if(wasm_func_call(collect_func, &args_empty, &results_empty)) {
        ESP_LOGE(TAG, "Error calling function!");
        return NULL;
    }
    delay(1); // for watch dog timer
}

デモンストレーション

WebAssembly Micro Runtime で 3D Cube を動作させた様子です。動作クロック 160MHz のインタープリタ Wasm として十分実用可能な速度と感じました。

実行中の残ヒープメモリは 176KB とこちらもまだまだ余裕がありそうです。

I (307) main.cpp: heap_caps_get_free_size: 320940
I (1670) main.cpp: heap_caps_get_free_size: 176924

マイコン WebAssembly は import/export のインターフェースさえ実装できれば、ウェブブラウザー上でさくさくつくって、時折数キロの .wasm をマイコンに転送して動作確認するというイテレーションで開発できるようになるので便利です。(この記事のプロジェクトでも設定していますが、ESP32 のフラッシュパーティションを .wasm 専用にコンパクトに切ると PC からの転送が数秒で終わります

WARM は簡単に動作させられますので、何かマイコンを見つけたら動作させてみると面白いかもしれません。

組み込み向け WebAssembly としては、WASI において Digital I/O(GPIO)、I2C、SPI のインターフェースが Phase 1 に上がっていてこちらの進展も楽しみです。

https://github.com/WebAssembly/WASI/blob/main/Proposals.md#phase-1—feature-proposal-cg

この記事のソースコードリポジトリ

https://github.com/h1romas4/m5stamp-c3wamr

This repository demonstrates the use of wasm-micro-runtime on M5Stamp C3 to run WebAssembly applications.

M5Stack Core2 (Xtensa 240MHz)バージョン

https://github.com/h1romas4/m5stamp-c3wamr/tree/target-m5stack

関連

Roland SH-4d 向けのパターンチェイン(ソングモード)シーケンサーの製作

Roland SH-4d 向けにパターンを束ねてソングとして扱うウェブアプリケーションをつくってみました。

SH-4d は TR-REC スタイルにてスタンドアローンで打ち込みができるデスクトップシンセサイザーです。 最大 64 step の長さで 4ch シンセ + 1ch リズムの各パートを入力して 1パターンとして扱うタイプで、簡単に打ち込み・再生できるため、自分は楽器の練習用としてとても便利に使っています。(適当に思いつきのリズムとベース・コードトラックを打ち込んで、合わせて KORG opsix を演奏するプレイ(?)です)

サウンドもグッド。

Roland – SH-4d | Synthesizer

Desktop Synthesizer

SH-4d の「パターン」は 1/16 スケールで 64 step 打つと 4小節になりますが、練習していると欲がでてきまして「…もう 4小節欲しい」となり、SH-4d のマニュアルを確認すると、

https://www.roland.com/jp/support/by_product/sh-4d/owners_manuals/8e612acf-31f2-4528-bb01-32cad0afd135/

Program Change MSB: 85, LSB: 0, PC: 0-127 (*4)

(*4) PATTERN画面でのみ送受信します。プログラム・チェンジは(読み込みたいパターンのバンク番号)×8+(パターン番号)-1となります。

と PATTERN 画面でプログラムチェンジを受け付けてパターンチェンジできそうでしたので、手軽に使えるようウェブブラウザでパターンシーケンスを打てるアプリケーションを製作しました。

ソースコードメモ: https://github.com/h1romas4/sh4d-chain/blob/main/src/components/Sequencer.vue#L162-L172

/**
 * sendSH4dPc
 *
 * @param {*} bank
 * @param {*} no
 */
function sendSH4dPc(bank, no) {
  midiOutputChannel.sendControlChange(
    0, pcMSB
  ) // CC0 bank select MSB 0x55
  midiOutputChannel.sendControlChange(
    0x20, pcLSB
  ) // CC32 bank select LSB 0x0
  midiOutputChannel.sendProgramChange(
    (bank - 1) * 16 + (no - 1)
  ) // Program change
}

ちなみに上記の MIDI ドキュメント上は「(読み込みたいパターンのバンク番号) x 8」となっていますが x 16 の誤記かと思います。

Pattern Chainer for Roland SH-4d の使い方

最初に、PC や Mac の USB に SH-4d を接続して電源を入れ MIDI デバイスとして認識させます。macOS ではドライバのインストールが必要で、Windows/Ubuntu は不要です。

次に SH-4d の設定を次のようにします。

  • SYSTEM SETTINGS – TEMPO/SYNC – Sync Out – USB
  • SYSTEM SETTINGS – MIDI – Ctrl Ch – 16
  • PATTERN 画面にしておく(CURRENT と NEXT パターンがでている画面)

最後に、 Microsoft Edge などの Chromium 系のウェブブラウザで以下のサイトにアクセスします。初回 MIDI デバイスへのアクセス権限の問い合わせがあると思いますので「許可」してあげてください。(Firefox はセキュリティ権限の関係で動作せずで仕様を確認中です。 Firefox でもうまく動作することを確認しました。Safari は2023/11 現在 Web MIDI API が動作しないため非対応です)

https://h1romas4.github.io/sh4d-chain/

Pattern Chainer for Roland SH-4d – like song mode

うまく MIDI が接続されていると Sync にカレントパターンの BPM が表示されるはずです。(BPM が 0 の場合は MIDI デバイスと Sync の関係を MIDI Setting から確認ください)

画面からいい感じにシーケンスを打った後「SH-4d 側の START」を押すと、シーケンスが同期されパターンチェンジ(プログラムチェンジ)が発行されパターン同士が接続されたように振る舞います。

例えばプリセットパターンの 1-1, 1-2, 1-3 をチェーンする場合は次のようになります。

各シーケンスにはパターンの最大長を入力すると良いです。

また、ソングの設定でデフォルトオンになっている Send program change in the last step. の設定はプログラムチェンジを送信するタイミングです。

SH-4d のパターンチェンジはパート内の最短長をもって切り替えられる仕様のため、それよりパターン全体の長さが長い場合はこの設定をオンにしてプログラムチェンジの発行をパターン演奏の最後まで遅延させます。もしパターン中全てのパート長が 1/16 64 step などで揃っている場合は、この設定をオフにすると SH-4d 画面上の NEXT パターンが見れるので、演奏中に安心できるかもしれません。

追加の機能としては、簡単ではありますが冒険の書的にソングの保存機能も付けています。Save | Load ボタンからウェブブラウザのローカルストレージに保存されます。

アプリケーションのソースコードは次の GitHub リポジトリに。

https://github.com/h1romas4/sh4d-chain

This is a simple sequencer to chain play patterns on the Roland SH-4d. Connect the pattern and treat it as a song.

It works based on the MIDI clock of SH-4d on a web browser, and It can work with other rhythm boxes by setting Program Change MSB/LSB.

Vue.js 3 と WEBMIDI.js を活用して 1000 行に満たない簡単なプログラムになっていますので自分好みに改造して使うと良いかもです。ライセンスは MIT License です。

設定などを SH-4d スペシャルでつくってしまっていますが、おそらく他のリズムボックス系のシンセも MIDI Settings 画面の Program Change MSB/LSB 値をセットすれば同様に動作するのではないかと思います。

また、タイミングの考慮が必要ですが、2窓してギターのマルチエフェクタのパッチ切り替えもできるかもしれないとふと製作中に思いました。(そこまでするなら普通のシーケンサー使えという話ではありますがw

最後に、製作中のバージョンですが動作デモです。てなわけで Enjoy!


以下は開発メモ。

Firefox の Web MIDI API

Windows 版の Firefox から Web MIDI API をリクエストすると次のように権限を追加する拡張が導入され正しく動作します。おそらく macOS 版でも同様です。

Ubuntu 標準の Snap 版 Firefox ではこの動作がうまくできないようで、本アプリケーションは MIDI に接続できず機能しません。「サイト権限」の設定自体がでてこないので、何かしらの判定で disable になっているようですが調査中です。

$ snap connections firefox
dom/midi/MIDIPermissionRequest.cpp

UPDATE 2023-12-15 Snap 版 Firefox に MIDI サポートのパッチが入りました。 Ubuntu Snap Firefox では 121.0-1 から動作すると思います。

https://github.com/canonical/firefox-snap/commit/6790ed70ce53944d24d5b059314f697d8b42610e

Merge pull request #39 from seb128/stable-midi-support Use alsa plug to interact with MIDI devices

UPDATE 2023-12-23 alsa は接続されましたが、まだ動作しないようです。

UPDATE 2024-02-10 Linux 版の Firefox において、Mozilla の debian package 版を使うことで、Web MIDI API が正しく動作することを確認しました。

https://support.mozilla.org/en-US/kb/install-firefox-linux#w_install-firefox-deb-package-for-debian-based-distributions

Install Firefox .deb package for Debian-based distributions

古の ASUS EeeBook X205TA に Ubuntu 22.04 LTS を導入

2015 年に 2万円台で購入した ASUS EeeBook X205TA を引張り出してきまして、プリインストールの Windows 10 を削除、Ubuntu 22.04 LTS の導入をしてみました。

CPU は Intel Atom の 1.33GHz、メモリー 2GB、 内蔵ストレージ eMMC 64GB といった当時としてもロースペックの格安機です。2014 年末発売とのこと。

10.1 インチ、重さ 980g 程度と持ち運びが容易で、バッテリーも 8 時間程度もち、またキーボードも癖がなかったため、かばんに忍ばせ比較的重宝していました。その後、ストレージの少なさから Windows 10 のアップデートができない状態となりお蔵入りとなった経緯です。

本日ふと存在を思い出し、OSC などの展示ブースでサイネイジ的に画面を表示してそっと置いておくのにまだ使えるかもとレストア。なお、しばらく電源を入れていなかったのでバッテリーは充電されずでした。

この機械の購入時は Linux のデバイスサポートが良くない状況だったため Windows で利用していたのですが、今回 Ubuntu 22.04 の導入を試行してみたところ、懸案だった無線 LAN はそのまま動作。Bluetooth は動作未確認ですが OS からは認識し問題なさそうです。

残念ながらサウンドデバイスは認識しているものの、発音から 1分程度で必ず同一周波数の謎のビープ音(矩形波)が鳴り響くという状況でした(この部分は解決できず)(修正できたので記事に対応方法を追加しました)。技術的になぜなのか気になりますが、音声が必要な場合は USB DAC や Bluetooth スピーカなどを接続して逃げるといいのかもしれません。

起動時の dmesg:

eeebook-dmesg-ubuntu-6.2.0-63-generic.log

そんなこんなで Ubuntu 導入をして復活できましたので、以下に手順をメモしておきます。

32bit EFI GRUB 起動

この機械は起動時の EFI が 64bit ではなく 32bit モードで起動されるとのことで、通常の Ubuntu 22.04 LTS の ISO イメージがブートできません。てなわけで、Ubuntu 22.04 の ISO イメージを USB メモリーに焼いた後、 boot/efi 配下に以下の 32bit 用の実行ファイルを配置します。

https://github.com/jfwells/linux-asus-t100ta/blob/master/boot/bootia32.efi

この操作で Ubuntu 22.04 インストレーション用の起動 USB メモリーができたら、おもむろに X205TA の USB ポートに差し込み、電源を入れ即座に ESC キーを連打して起動メニューを立ち上げます。

最初に「Enter Setup」で BIOS 設定を表示し Secure Boot を disable に設定保存後、もう一度同じ操作で ESC 連打で次は「UEFI: SanDisk」(USBの名前)を選択して Ubuntu 22.04 のインストーラを起動します。

あとは道なりに進めばインストールの最終段階まで進めるはずです。かなり遅いのでがんばります。ストレージが 64GB と小さいのでインストールオプションでは「最小インストール」を選択すると良いと思います。

ちなみに自分は初回 eMMC にパーティションテーブルを書き込むところ(Windows 10 を消すところ)でマウスカーソルごとフリーズしましたが、再起動後に最初からリトライしたところ通過しました。原因は不明です。

インストールはしばらくかかりますが、最終段階でおそらく「GRUB を /dev/mmcblk2 にインストールできません。 grub-install /dev/mmcblk2 の実行に失敗しました。これは致命的なエラーです」が表示されると思います。

対応するためいったん OK で無視して、USB メモリーを挿したまま X205TA の再起動をかけます。

eMMC 側にも 32bit EFI 向けのブートバイナリを配置

ESC 連打で USB メモリーから再びブートし、今度は GRUB の起動メニューで「c」を押下して GRUB のプロンプトを出し、次のようにコマンドを打って eMMC 側の Ubuntu 22.04 を起動してあげます。(いったん eMMC のブートローダを使わずに USB メモリー内のブートローダから eMMC 内の Ubuntu を起動する形になります)

入力する GRUB コマンド:

> set root=(hd1,gpt2)
> linux /boot/vmlinuz-6.2.0.26-generic root=/dev/mmcblk2p2
> initrd /boot/initrd.img-6.2.0-26-generic
> boot

コマンド内のファイル名中のバージョン識別は、Ubuntu 22.04 を導入時期によってバージョンが変わると思いますので、適宜コマンドを修正してください。ファイルシステムのファイル名はタブキーで補完(表示)できます。

boot コマンド投入後 eMMC に導入された Ubuntu 22.04 LTS が起動するはずです。

Ubuntu 22.04 GUI 起動後、言語などの初期設定を経てから、ターミナルエミュレータから次のようにして eMMC 内に 32bit EFI 向けの GRUB ブートを構成します。

$ sudo apt update
$ sudo apt upgrade
$ sudo apt install grub-efi-ia32-bin
$ sudo grub-install --efi-directory=/boot/efi
$ update-grub

以下、実行ログです。

$ sudo apt install grub-efi-ia32-bin
$ sudo ls -laF /boot/efi/EFI/BOOT # BOOTIA32.EFI があることを確認
合計 1984
drwx------ 2 root root   4096 11月  3 20:27 ./
drwx------ 4 root root   4096 11月  2 23:09 ../
-rwx------ 1 root root 106496 11月  3 10:37 BOOTIA32.EFI*
-rwx------ 1 root root 960472 11月  4 10:19 BOOTX64.EFI*
-rwx------ 1 root root  88296 11月  4 10:19 fbx64.efi*
-rwx------ 1 root root 860824 11月  4 10:19 mmx64.efi*
$ sudo grub-install --efi-directory=/boot/efi/
Installing for i386-efi platform.
Installation finished. No error reported.
$ sudo update-grub
Sourcing file `/etc/default/grub'
Sourcing file `/etc/default/grub.d/init-select.cfg'
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.2.0-36-generic
Found initrd image: /boot/initrd.img-6.2.0-36-generic
Found linux image: /boot/vmlinuz-6.2.0-26-generic
Found initrd image: /boot/initrd.img-6.2.0-26-generic
Memtest86+ needs a 16-bit boot, that is not available on EFI, exiting
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done

実行できたら USB メモリーを抜いて eMMC から通常ブートしてみます。うまくいけば eMMC 上の Ubuntu 22.04 が起動すると思います。

SD カードスロット

Ubuntu デフォルト導入では SD カードスロットが認識しないため、次のように操作して認識させます。

$ echo "options sdhci debug_quirks=0x8000" | sudo tee /etc/modprobe.d/sdhci.conf
$ sudo update-initramfs -u -k all

update-initramfs コマンドは 10 分程度かかります。コマンドが戻ってきたら OS を再起動し、メディアを挿すと SD カードがオートマウントされるはずです。

参考 URL:

https://github.com/RobotGhost/ubuntu-x205ta/blob/master/debian-fixes-x205ta.md#microsd-card-reader

サスペンド後のトラックパッドの動作

サスペンド復帰後、トラックパッドが動作しなくなりマウスカーソルの移動やクリックができなくなります。

elan_i2c の dmesg エラーログ:

[ 1309.121342] elan_i2c i2c-ELAN0100:00: invalid report id data (1)

elan_i2c モジュールかバス初期化の不具合と思いますが、次のようにリロードしてあげると動作を再開します。

$ sudo modprobe -r elan_i2c && sudo modprobe elan_i2c

ターミナルにカーソルを移せない場合は、CTRL + ALT + F3 でコンソールを開くと良いかもです。(GUI に戻すのは CTRL + ALT + F2)また、上記のコマンドを systemd の suspend/resume 契機にうまいこと入れると自動復帰できるようになるかもしれません(未実施)。

サウンド設定

この記事の最初で書いた通りサウンドについてはアプリケーションから発音させると数十秒は正しく動作しますが、その後ビープ音(必ず同じ周波数の矩形波)が鳴り響きだすという謎の事象が発生します。

これを解消するために次のようにサウンドデバイスを構成します。

# 念の為必要のない HDMI サウンドデバイスを見えないように
$ echo "blacklist snd_hdmi_lpe_audio" | sudo tee /etc/modprobe.d/50-block-hdmi-audio.conf
# dsp_driver を SOF にすると不具合でビープ音となるようなので alsa で SST を使うように設定(dsp_driver:Force the DSP driver for Intel DSP (0=auto, 1=legacy, 2=SST, 3=SOF)
$ echo "options snd-intel-dspcfg dsp_driver=2" | sudo tee -a /etc/modprobe.d/alsa-base.conf

OS 再起動後の aplay -l ログ:

$ aplay -l
**** ハードウェアデバイス PLAYBACK のリスト ****
カード 0: chtrt5645 [chtrt5645], デバイス 0: 1 []
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0
カード 0: chtrt5645 [chtrt5645], デバイス 1: Deep-Buffer Audio (*) []
  サブデバイス: 1/1
  サブデバイス #0: subdevice #0

Ubuntu (PulseAudio) 的には次のように見え、ウェブブラウザーなどのアプリケーションから正しく音声が発音するようになっていると思います。

参考 URL:

[BUG] sof-audio-acpi-intel-byt stops and give a screaming high sound #4662

alsamixer から見たサウンドカードの認識:

動画再生支援

CPU の動画再生支援は設定無しでうまく動作するようです。フルHD 動画が CPU 4コア 25% 程度でフレームドロップなく再生できました。

$ sudo apt install mpv
$ mpv --hwdec=auto hegohego.mpv
 (+) Video --vid=1 (*) (h264 1920x1080 29.970fps)
 (+) Audio --aid=1 --alang=eng (*) (opus 2ch 48000Hz)
[vo/gpu/wayland] GNOME's wayland compositor lacks support for the idle inhibit protocol. This means the screen can bla
nk during playback.
[vaapi] libva: /usr/lib/x86_64-linux-gnu/dri/iHD_drv_video.so init failed
Cannot load libcuda.so.1
Using hardware decoding (vaapi).
AO: [pulse] 48000Hz stereo 2ch float
VO: [gpu] 1920x1080 vaapi[nv12]
AV: 00:02:17 / 00:02:50 (80%) A-V:  0.000 Dropped: 3
[input] No key binding found for key 'MBTN_MID'.
AV: 00:02:19 / 00:02:50 (82%) A-V:  0.000 Dropped: 3
Exiting... (Quit

intel_gpu_top で VA-API が使われている様子をみた図:

$ sudo apt install intel-gpu-tools vainfo
$ export LIBVA_DRIVER_NAME=i965 # .bashrc に入れても良いかもです。
$ vainfo
libva info: VA-API version 1.14.0
libva info: User environment variable requested driver 'i965'
libva info: Trying to open /usr/lib/x86_64-linux-gnu/dri/i965_drv_video.so
libva info: Found init function __vaDriverInit_1_10
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.14 (libva 2.12.0)
vainfo: Driver version: Intel i965 driver for Intel(R) Bay Trail - 2.4.1
vainfo: Supported profile and entrypoints
      VAProfileMPEG2Simple            : VAEntrypointVLD
      VAProfileMPEG2Simple            : VAEntrypointEncSlice
      VAProfileMPEG2Main              : VAEntrypointVLD
      VAProfileMPEG2Main              : VAEntrypointEncSlice
      VAProfileH264ConstrainedBaseline: VAEntrypointVLD
      VAProfileH264ConstrainedBaseline: VAEntrypointEncSlice
      VAProfileH264Main               : VAEntrypointVLD
      VAProfileH264Main               : VAEntrypointEncSlice
      VAProfileH264High               : VAEntrypointVLD
      VAProfileH264High               : VAEntrypointEncSlice
      VAProfileH264StereoHigh         : VAEntrypointVLD
      VAProfileVC1Simple              : VAEntrypointVLD
      VAProfileVC1Main                : VAEntrypointVLD
      VAProfileVC1Advanced            : VAEntrypointVLD
      VAProfileNone                   : VAEntrypointVideoProc
      VAProfileJPEGBaseline           : VAEntrypointVLD
$ sudo intel_gpu_top

ウェブブラウザの YouTube 再生は残念ながら標準 codec が H264 ではなくなっているため、動画再生支援は使われず。ウェブサイト自体が重たいためこの機械で YouTube を使うことはないだろうと今回はやっていませんが、ブラウザに YouTube H264 化系の拡張を入れると URL 変換でなんとかなるのかもしれません。

おわりに

最後に動作試験的に、Alacritty、gcc や Rust の開発系、Helix Editor や Zellij ターミナルマルチプレクサなどを入れ、CUI 環境でコードリーディングくらいできるところまで環境設定してみました。Rust 系のアプリケーションは cargo install でビルドして導入しています。ストレージは残り半分(32GB)といった感じになりました。

検証でまる一日程度使ってみた感じですが、この機械はストレージの遅さが使用感を損ねている場合が多いようですので、Ubuntu Frame などで GDM ログインや GNOME Shell を介さずに Kodi など特定用途のアプリケーションを kiosk 起動するように構成するのもいいかもしれません。(アプリがメモリーに載ってしまえば結構動きます)

というわけで、Ubuntu 22.04 LTS のサポート期間は 2027-04 で、Ubuntu Pro を enable にするとさらに 5年だと記憶していますので、ハードウェアが壊れるまでこのままいけそうです。w

関連