シリーズ目次(Qua Station を mainline Linux で動かす連載・全 7 本)
- (1) 6.18クロスビルド・Ubuntu 26.04起動・シリアルレス診断手法 ← 本記事
- (2) SATA・PCIe・Wi-Fi・LEDのハードウェア復活
- (3) rootfsをSSDへ移行
- (4) ext4破損インシデントとUSB2.0/PCIe診断ログ洪水の解決
- (5) 番外編: 作ったのに使えなかったBluetooth SPP非常口コンソールの話
- (6) 配布イメージを作って GitHub で公開する
- (7) Linux 7.2→7.3 移植編 — Wi-Fi の無言死は上流の 1 行、翌日出た 7.3 は 2 行で一発起動
概要
au/KDDIの家庭用NAS「Qua Station」(SoC: Realtek RTD1295)を、ベンダー製の古い独自カーネル(4.9.119)からmainline Linux(6.18 LTS)へ移植しました。カーネルはx86_64の開発用PCで自前にクロスビルドし、ユーザーランドはUbuntu 26.04へ載せ替えています。実機は現在ADS-B(航空機追跡)関連の13コンテナが本番稼働中のため、書き込み・再起動を伴う操作はすべて確認を取りながら慎重に進めています。このシリーズは全6編で、最終的にSSDへのrootfs移行・緊急アクセス経路の整備・実際の本番障害からの復旧、そして配布イメージの公開までを扱います。
なぜ今のカーネルのまま使い続けなかったのか
正直に言うと、着手した時点では「同じSoCの移植例が少なく面白そうだから」という趣味的な動機が半分でした。ただ、実際に運用しながら4.9.119ベンダーカーネルの限界にも何度もぶつかっており、それが「多少手間でも今やる」判断の後押しになっています。
まず前提として、Linux 4.9系そのものが既に開発終了(EOL)しています。4.9系は2016年12月にリリースされ、上流の長期サポート(LTS)としても2023年1月の4.9.337を最後に打ち切られました。Qua Stationのベンダーカーネルはそのさらに早い時点の4.9.119で止まっており、EOL前に存在した分の修正すら取り込まれていません。当然ながらauやKDDIからの継続的なセキュリティパッチ提供も期待できず、9年分のカーネル脆弱性が積み上がったまま動いている、というのが実態です。
なお、この機種向けにはコミュニティによるUbuntu 20.04 LTSのrootfs構築プロジェクトが既に存在します。ただしこれはユーザーランド(userspace)だけをUbuntu 20.04に更新するもので、カーネル自体は同じ4.9.119のままです。ディストリのパッケージは新しくなっても、土台のカーネルが抱える上記の問題(デバッグ不能・脆弱性・ハードウェア未対応)はそのまま残ります。今回mainline化に踏み切ったのは、この「土台ごと」の古さを解消したかったためです。
具体的に運用上ぶつかっていた限界は次の通りです。
-
カーネルのデバッグ手段が無い:
pstore/netconsoleが入っておらず、warm reboot後に3回中2回の頻度で起きていた原因不明の無通告リセットを、4.9のままでは一切追跡できませんでした - 内蔵ハードウェアが一部死んでいる: 内蔵GMAC(Ethernet)は4.9では一度もリンクアップしたことがなく、5GHz Wi-Fi(RTL8812AE)もRF-killのハード認識が通らないまま放置されていました
-
ネットワークスタックの制約: カーネル4.9.119ではbridgeからwlanへのNAT/MASQUERADEが動作せず、ADS-B関連コンテナは
BEASTHOST=127.0.0.1固定などの回避策に頼っていました - pinctrlドライバが無い: GPIOのピン多重化(pinmux)を制御するドライバが存在せず、UART/I2Cまわりの一部機能が使えないままでした
つまり「動いてはいるが、セキュリティ上は9年分の負債を抱え、詰まった時に何も見えず、一部の内蔵ハードウェアも死んだまま」という状態で、mainline化はロマンであると同時に実務上の必要性でもありました。
🚩 この移植作業は、実装のほぼ全工程をClaude Code(Anthropic製のCLIコーディングエージェント)にドライブさせて進めています。 私(人間側)は方針決定・実機の確認・実行の号令を担当し、クロックゲートドライバの新規実装からPCIeホストドライバの移植、カーネルパニックの原因追跡まで、実際のコード変更・ログ解析はClaude Codeが行っています。要所ではアドバイザー役の上位モデルに技術検証だけを依頼するマルチエージェント構成も使っています(詳細は(2))。
安定稼働までの流れ
各段階が「どの記事のどこに書いてあるか」に直接飛べるようにしておきます。
- シリアルコンソール無しで起動すらしない問題を切り分ける(本記事)
- 土台を乗り換え、rootfs到達後の疎通問題を切り分ける(本記事)
- SATA・PCIe・Wi-Fi・LEDを1つずつ復活させる — (2)
- rootfsを内蔵SSDへ移す(最終ゴール到達) — (3)
- 本番障害(ext4破損)から復旧する — (4)
- 番外編: 作ったのに使えなかったBluetooth非常口の話 — (5・番外編)
- 配布イメージを作って GitHub で公開する — (6)
- Linux 7.2→7.3 移植編 — Wi-Fi の無言死は上流の 1 行、翌日出た 7.3 は 2 行で一発起動 — (7)
参考文献(シリーズ共通)
今回の移植は先人の資産の上に成り立っています。ベースにしたもの・参照したものを、実機(Qua Station)に近い国内発の情報源、同じSoCの海外コミュニティ情報源、および各種公式ドキュメントに分けて挙げます。
国内(Qua Station実機ベース)
- tsukumijima/QuaStation-Kernel-BPi — Qua Station向けLinuxカーネル(RTD1295, Kernel 4.9.119)。今回移植元にした「元のベンダーカーネル」そのもの
- tsukumijima/QuaStation-Ubuntu — Qua Station向けUbuntuビルド自動化スクリプト
- はるろいどの冒険日記「Qua Stationを汎用サーバーにしてみる」
- AsamaNews「Qua stationでLinuxを動かす(eMMCに導入)」
- noriokun4649「Qua Station で起動できる Ubuntu Base 22.04 LTS の rootfs を作る」 — Ubuntu Base の rootfs を chroot で構築し U-Boot から起動する手順。着手前のベンダー4.9系カーネルでの Ubuntu 導入時に参考にした
- 「【Qua station】シリアル接続して、u-bootのコンソールに入る」(お部屋でモバイル) — シリアルコンソール用ピンの位置・配線を紹介
海外(同じRTD1295系SoCのコミュニティ資産)
- symops/monarch-6.18 — WD My Cloud Home(RTD1295)向けLinux 6.18.52。クロック・SATA・GMAC・SMP起動の実装が先行しており、今回の主な参照ツリー
- symops/pelican-6.18 — 同じくRTD1296向け6.18.52
- afaerber/linux (rtd1295-next branch) — RTD1295のmainline化パッチ群。PCIeホストドライバを新規実装する際のベースにした
- jjm2473/rtd1295-next — RTD1295/RTD1296向けLinux 5.9系(eMMC対応)
- Lötzimmer「Debian on WD My Cloud Home single-bay (MCH) - part 1 / part 2」
Wi-Fiファームウェアのライセンス
- linux-firmware WHENCE — RTL8192EE/RTL8812AEファームウェアの配布元・ライセンス種別の記載元
- LICENCE.rtlwifi_firmware.txt — Realtek Semiconductor Corporation, 2010。無改変バイナリの再配布許諾条件
Bluetooth / D-Bus
-
BlueZ Profile API (profile-api.txt) —
org.bluez.ProfileManager1.RegisterProfileの正式な仕様
ファイルシステム復旧(e2fsprogs)
-
e2fsck(8) 公式マニュアル —
-z(undoファイル)・-b(バックアップスーパーブロック)オプションの仕様 - e2undo(8) — undoファイルからのロールバック手順
シリーズ目次
- (1): 6.18クロスビルド・Ubuntu 26.04起動・シリアルレス診断手法
- (2): SATA・PCIe・Wi-Fi・LEDのハードウェア復活
- (3): rootfsをSSDへ移行
- (4): ext4破損インシデントとUSB2.0/PCIe診断ログ洪水の解決
- (5・番外編): 作ったのに使えなかったBluetooth SPP非常口コンソールの話
- (6): 動いている環境を配布イメージにして GitHub で公開する
- (7): Linux 7.2→7.3 移植編 — Wi-Fi の無言死は上流の 1 行、翌日出た 7.3 は 2 行で一発起動
前提・環境
| 項目 | 内容 |
|---|---|
| 実機 | au/KDDI Qua Station。※着手前の段階でシリアル経由のU-Boot設定変更(USB起動有効化)とベンダー4.9系カーネルによるUbuntu 24.04導入は完了済み(詳細は後述) |
| SoC | Realtek RTD1295(Cortex-A53 ×4) |
| 元カーネル | ベンダー独自 4.9.119(tsukumijima/QuaStation-Kernel-BPiベース) |
| 移植先 | Linux 6.18 LTS(6.12からリベース、symops/monarch-6.18を土台に採用) |
| ディストリ | Ubuntu 26.04(第2候補: Debian 13) |
| 起動方法(検証段階) | USB起動(本番SSDは無傷のまま。BOOTパーティションはFAT32・99MB) |
| 診断手段 | シリアルコンソール無し(本編の主題) |
| ビルド環境(開発用PC) | x86_64 Ubuntu 26.04 LTS。ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-でクロスコンパイル(QuaStation実機はarm64のため) |
シリアルケーブル無しでどう診断したか
組み込みLinuxのブートデバッグは本来シリアルコンソールを繋ぐのが定石です。この機種はハンダ付けは不要で、ケースを開けてピンにコンソールケーブルを挿すだけで使える構成でした(SDカードスロット側のUSB端子上部にある端子が該当し、左からRX/TX/GND、CH340などのUSBシリアル変換ケーブル+Tera Term(115200bps)で接続できることが「【Qua station】シリアル接続して、u-bootのコンソールに入る」で紹介されています)。ただ、今回は短いサイクルでビルド→再起動→確認を繰り返す試行錯誤フェーズだったため、毎回ケースを開けて挿し直す手間を避け、シリアル接続は別途まとまった時間を取って行う方針にしていました。同じ悩みを持つ人の参考になればと、実際にやった手順をそのまま書きます。
⚠️ 一点、前提を明記しておきます。この機種は元々USBメモリから起動できる状態ではありません。今回のシリーズに着手する以前の段階で、コミュニティの手順(参考文献参照)に沿ってシリアルコンソール経由でU-Bootに一度だけ接続し、bootcmdをUSB起動対応に書き換え済みでした(その上でベンダー4.9系カーネルによるUbuntu 24.04が既に動いている状態がスタート地点です)。つまり「一度もシリアルを使っていない」わけではなく、その土台の上に立って、今回の6.18移植作業そのものはシリアル無しで進めた、という話です。いきなりUSBメモリに書き込んで動いたわけではない点にご注意ください。
診断は大きく2段階に分かれました。まずそもそも起動すらしない問題をeMMCの生データ解析で切り分け、ベンダーの独自DTS+v6.18ベースを諦めて土台を乗り換えた後、rootfsまでは来ているのに疎通しない問題をUSBメモリへのログ書き出し(レベル1〜3)で切り分けています。
第一の壁: 起動すらしない — eMMCの生データからU-Bootのメモリレイアウトを読む
最初の実装は、自作のDTS(rtd1295-qua-station.dts)をv6.18ベースのカーネルに載せる方式でした。ビルドしたImageをUSB起動させても、LED(gpio-leds)が一切点かず、rootfsのマウント記録も残らない。カーネルが起動しているのかすら分からない状態が3回続きました(run4〜run6)。
シリアルが無い状態でここを切り分けるため、稼働中の4.9側から読み取り専用でeMMCの生データを調べる方針に切り替えました。
dd if=/dev/mmcblk2 bs=1M count=8 | strings
eMMC先頭8MBの生領域に、U-Bootの環境変数がそのまま平文で残っていました。
bootcmd=usb start;fatload usb 0 0x01f00000 /QuaStation.dtb;fatload usb 0 0x03000000 /uImage;fatload usb 0 0x01b00000 /bluecore.audio;env set bootargs ...;env set bootcmd booti 0x03000000 - 0x01f00000;b2ndbc
kernel_loadaddr=0x03000000 fdt_loadaddr=0x01F00000 rootfs_loadaddr=0x02200000
これで、U-Bootがカーネル・DTB・bluecore.audio(音声用コプロセッサのファーム)を固定アドレスに読み込んでいることが分かりました。さらに稼働中4.9の/proc/iomemを見ると、bootiはロードしたImageを0x03000000から実際の実行アドレス(RAM先頭+text_offset=0x280000)へメモリ上でコピーし直してから起動するという、U-Boot 2015.07時代の挙動も判明しました。
ここでImageのヘッダ(先頭64バイト)を直接読み、サイズを計算しました。
4.9のImage: image_size=0x016ab000(23.7MB) → 終端0x192b000 < 0x1b00000(bluecoreの開始アドレス) ✅ 収まっている
6.18の初回ビルド: image_size=0x02640000(40MB) → 終端0x28c0000 = bluecore(0x1b00000)・DTB(0x1f00000)・initrd領域(0x2200000)を全部踏み潰す
つまり原因は単純で、カーネルImageが大きすぎて、後ろに置かれているDTBや音声コプロセッサ用ファームの領域まで上書きしていたのでした。壊れたDTBを受け取ったカーネルはearlyconより前で停止するため、LED不点灯・rootfs未マウントという「何も起きていないように見える」症状と完全に一致します。ここからimage_size ≤ 0x1b00000 - 0x280000 = 0x1880000(25.7MB)という制約を逆算し、安全側を見て24MB未満を目標にしました。
ACPI/EFI/他社ドライバ等を.configから落として20.8MBまで圧縮したImage(run5)、さらにCPU起動コード(100-nopのタイミング調整パッチ・spin-table対応)まで追加したImage(run6)を試しましたが、いずれも無言のままでした。ここで「自作DTSのどこかがまだ壊れているのか、それとも別の初期段階で止まっているのか、シリアル無しではこれ以上は切り分けられない」と判断し、土台をゼロから作るのをやめ、同じSoCで既に起動実績のあるコミュニティの資産(symops/monarch-6.18)に乗り換えるという方針転換をしました。ここから先が(2)以降でも触れる「参照ツリー」の話につながります。
第二の壁: rootfsまでは来ているのに疎通しない
土台をsymops/monarch-6.18に乗り換えてからは、Image自体は起動するようになりました。しかし今度は「ネットワークに出てこない=疎通確認できない」という、また別の壁にぶつかりました。ここから、シリアルの代わりにUSBメモリへログを書き出す方式で、段階的に診断レベルを上げていきます。
レベル1: initにシェルスクリプトを割り込ませる
カーネルの起動オプションinit=を使い、systemdの代わりに自作の/boot-dump.shを最初に実行させます。
CMDLINE: ... init=/boot-dump.sh
#!/bin/sh
# /boot-dump.sh
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mkdir -p /var/log/boot-dump
dmesg > /var/log/boot-dump/dmesg.txt
cat /proc/cmdline > /var/log/boot-dump/cmdline.txt
cat /proc/cpuinfo > /var/log/boot-dump/cpuinfo.txt
ip a > /var/log/boot-dump/ip.txt 2>&1
lsblk > /var/log/boot-dump/lsblk.txt 2>&1
sync
exec /sbin/init
結果: 何も書き出されませんでした。1バイトも、です。
レベル2: 静的リンクのbusyboxに切り替える
「dashやsystemdなどの動的リンクバイナリが何らかの理由で実行できていないのでは」という仮説を立て、静的リンクのbusybox(1.9MB)を使ったinitに切り替えました。
結果: これも0バイト。dashでもbusyboxでも同じ死に方をする=userspaceの種類には依存しない、と判定しました。
正直に書くと、この時点でレベル1・2が0バイトになる正確な原因は特定できませんでした(後述する「rootfsの/lib破壊」は動的リンクバイナリにしか影響しないため、静的リンクのbusyboxがそれと同じ原因で失敗したとは説明が付きません)。当時の作業仮説は「initバイナリ自体をUSB越しに読み込む(page-in)処理が、まだ十分に初期化されていないUSBスタック上で失敗しているのでは」というものでしたが、これは検証しないまま次のレベルへ進んでいます。
レベル3: カーネル内蔵initramfsで診断する
レベル1・2は「initの実行ファイル自体をUSB上のrootfsから読み込む」形だったため、この読み込み自体を避けるべく、CONFIG_INITRAMFS_SOURCEでinitをカーネルImageに直接焼き込む方式に切り替えました。カーネルImageに埋め込まれたinitramfsはメモリ上に展開されるため、実行時にUSB経由の追加読み込みが発生しません。
# .config
CONFIG_INITRAMFS_SOURCE="<initramfsの元ディレクトリへのパス。各自の環境に合わせて変更>"
initramfs側の/initは、blkidでrootパーティションを探して見つかるまで最大30秒待ち、mountできたら診断情報一式(dmesg・cmdline・cpuinfo・uptime・LED/ネットワークデバイス一覧・pstore(ramoops)、さらにUSB読み込みテストとしてbusyboxおよび20MBのダミーファイルのdd結果)をFATパーティション(作業用PCがMacの場合/Volumes/BOOTとして自動マウントされる場所)に書き出し、最後にchrootでrootfs側の/bin/sh実行テストを行ってからswitch_rootする、という設計にしました。
結果: ついにdmesgが取得できました。CPU・タイマー・メモリ認識は正常。このレベル3のinit自体はカーネルImage内蔵のためUSB読み込み問題を踏まず、さらに内部で行ったUSB読み込みテスト(USB/ブロック層が初期化された後のタイミングでのdd)も成功(rc=0)しました。つまりレベル1・2の「USB page-in失敗」という仮説はここでは検証できず(そもそもレベル3は違う経路を通っているため)、むしろ本当の原因は別のところにあったと後で判明します。ただしchroot /mnt /bin/shが
can't execute '/bin/sh': No such file or directory (rc=127)
で失敗。ここで初めて「initramfsまでは動くが、rootfs内のシェル実行が壊れている」と切り分けられました。
原因確定: tarがusrmergeのシンボリックリンクを破壊していた
Mac側からdebugfs(macOSではbrew install e2fsprogsで導入可、macFUSE不要)でrootfsのext4パーティションを直接読み、原因を特定しました。
sudo debugfs -R "ls -l /lib" /dev/rdisk4s3
sudo debugfs -R "stat /lib" /dev/rdisk4s3
/binはシンボリックリンクのままでしたが、/libが実ディレクトリに化けており、中身が空でした。原因は、以前rootfsへtar -xzf modules.tar.gzでモジュールを展開した際、GNU tarがUbuntuのusrmerge構成(/lib→/usr/libのシンボリックリンク)を認識せず、リンクを実ディレクトリで上書きしてしまったことでした。以後ld-linux-aarch64.so.1が辿れなくなり、動的リンクの実行ファイルは何もかもENOENTで即死し、カーネルパニック(panic=10)→ウォームリセット→U-Bootという無限ループに陥っていたのです。
教訓: rootfsへtarで展開する時は必ず--keep-directory-symlinkを付け、展開後にls -ld lib bin sbinでシンボリックリンクが残っているか確認する。これ以降、この手順を恒久ルール化しました。
対処: initramfsの/initに自己修復ロジック(/libをシンボリックリンクへ張り直す処理)を仕込み、修復してからswitch_rootする形に変更したところ、Ubuntu 26.04がmulti-user.targetまで到達し、sshdが起動しました。
この方法の使い所
- シリアルコンソールが用意できない/毎回繋ぐのが手間な組み込みLinux機で
- 「起動しているように見えるが疎通しない」を切り分けたい時に
- レベル1→2→3と段階を上げることで、「userspace側の問題」か「カーネル/root周りの問題」かを二分探索的に絞り込める
細かいけど地味に手間取った話
-
pgrepで自分のシェルを巻き込んで強制終了: ビルドが終わったかを確認しようとpgrep -f "make ARCH=arm64 …"を打ったところ、このコマンド自体の引数文字列にも部分一致してしまい、実行中のビルドプロセスもろとも巻き込んでkillしてしまいました(実害は再ビルドのみで済みました)。makeを狙い撃ちで止めたいときはpgrep -x makeのように完全一致にすべきでした -
eMMCの生データ解析は読み取り専用を徹底: 本番機のeMMCを直接調べる作業だったため、
ddはすべて読み出し方向のみで実行し、書き込み系コマンドは一切実行していません。念のためdd if=/dev/mmcblk2でeMMC全体(boot0/boot1含む、認証専用のrpmb領域は仕様上I/Oエラーで取得不可)を事前にバックアップしてから解析を始めています。壊れたら後戻りできない領域なので、慎重さを優先しました -
あえてeMMC単体起動化はしなかった: このとき読み取ったeMMC先頭領域の
bootcmdを書き換えれば、外付けUSBメモリ無しでQuaStation単体からmainlineカーネルを起動させることも技術的には可能でした。ただし今回は意図的にこれをやっていません。USBメモリ側にmainlineの起動一式を置いたままにしておくことで、何か重大な問題が起きた時にUSBメモリを抜くだけで元の4.9系ベンダーカーネルへ即座に戻せる、という切り戻し手段を残す狙いです。eMMC側のbootcmdを直接書き換えてしまうと、この一番手軽な保険を失うことになります
次回
USB起動でrootfsまで到達したところで、次はSATA・PCIe・Wi-Fiといった実ハードウェアを1つずつ有効化していく作業に入ります。クロックゲート未開放によるポイズン値0xdeadbeef、RTD1295特有のPCIeページ切り替えアクセス、MSI割り込みでのカーネルパニックなど、それぞれ症状の癖が強く、(2)で詳しく扱います。