
本記事は "Kernel/VM 探検隊@東京 No18" で発表したプレゼンのブログ版です。
購入
2024 年のある日、私はある古い無線ルーターを購入した。大昔に発売された、最初期の Linux 搭載無線ルーター WRT54GS である。無線ルーターの機能を増強できる代替ファームウェアとして有名な OpenWrt は、この機種の兄弟である WRT54G から生まれた。
「青いルーターで有名な WRT54G シリーズを手に入れて、最新の OpenWrt を動かしてやりたいよなぁ…」
そう考えながらヤフオクを見ていたら、OpenWrt に適した機種である WRT54GS v1 のきれいな個体を発見した。
OpenWrt が動く最低要件: flash memory と DRAM
「OpenWrt に適した機種」とはどういうものだろうか。
OpenWrt の動作可否を分けるのは SoC や中身の CPU ではなく、ズバリ flash(フラッシュメモリ)と DRAM の容量である。Flash には Linux kernel と user space のソフトウェア(ルートファイルシステム)が全て入る。実行時に DRAM にそれらがコピーされ*1、CPU があれこれ作業する。計算や DRAM や I/O が遅い分には待てばいいが、記憶容量の不足はどうしようもない。よって、これら 2 つの十分な容量が動作の絶対条件となる。
限界ポーティングに基づく個人的な知見から言えば、本記事執筆時点での OpenWrt (master*2) は flash 8 MB と DRAM 32 MB があればギリギリ動かせる。Flash が 8 MB あれば、最低限の構成 (defconfig) にプラスで Web UI である LuCI を入れても案外収まるし*3、DRAM が 32 MB あれば起動直後の free コマンドで 10 MB ほどが available と表示される*4。

"8/32" を満たす機種を探すと、残念ながら WRT54"G" だといずれのリビジョンもこれを満たさない。時期も見た目も SoC も同じで、わずかに高いスペックを持つ WRT54"GS" の初期リビジョンは、ぴったり 8/32 を満たしている。

出典: https://en.wikipedia.org/wiki/Linksys_WRT54G_series

出典: 上に同じ
かくして私は、WRT54GS の初期リビジョンを狙って購入することにした。
入手成功
そもそも Linksys の製品を中古市場で見かける機会が少ないので、購入は困難だろうと予想していた。と思いきや、買うと決心した日に v1 の個体がヤフオクに転がっていたのを発見した。こんな古い製品のために争う相手もいないことから、出品価格そのままであっさり入手に成功した。


届いた品を確認すると、ビックカメラ有楽町店で平成 17 年(2005年)に購入した証明シールが付属していた。外箱こそひどく擦れていたが、本体は使用歴がほとんどないのか 20 年以上の経過を感じさせないほど状態が良かった。


予想以上の美品を手に入れて上々のスタートを切りつつ、OpenWrt のビルドへと駒を進めた。
ファームウェアはビルドするだけ、と思いきや
WRT54GS はとうに OpenWrt のサポート範囲を外れているため、公式のビルドは 22.03.7 を最後に途絶えているが、実はソースコードからは drop されていない。つまり、OpenWrt を Git で clone して機種の設定だけ行えば、すぐにファームウェアがビルドできる。私はまず手始めに OpenWrt の stable release である 24.10.1 をビルドした。

ビルドは難なく終わり、昔懐かしい雰囲気の漂う設定画面を眺めながらファームウェアをインストールした。


再起動してすぐ異変に気がついた。Ethernet がリンクアップせず、SSH で接続できない。シリアルコンソールで ifconfig や dmesg を確認したところ、どうやら Ethernet PHY*5 の初期化に失敗して Linux に認識されていないようだった。

以下は OpenWrt 24.10.1 のカーネルログの一部である。libphy: PHY fixed-0:00 not found から明らかにエラーっぽいメッセージが並んでいて、PHY が登録できなかった旨が読み取れる。
[ 3.729778] adm6996: adm6996_gpio: ADM6996L model PHY found. [ 3.763375] b44 ssb0:1: could not find PHY at 30, use fixed one [ 3.775831] libphy: PHY fixed-0:00 not found [ 3.780449] b44 ssb0:1: could not attach PHY at 0 [ 3.790291] b44 ssb0:1: Cannot register PHY, aborting
問題切り分けの第一歩として、OpenWrt 公式ビルドが提供されていた最後のバージョン 22.03.7 をインストールしたところ、問題なく eth0 が表示された。さらにバージョンを詰めていくと、23.05.5 では動くが直後のリリース 24.10.0 では動かないことがわかった。以下は 23.05.5 のカーネルログの一部で、fixed-0:00 なる PHY が mdio_bus で見つかり attach されたと表記されている。
[ 3.820714] adm6996: adm6996_gpio: ADM6996L model PHY found. [ 3.850127] b44 ssb0:1: could not find PHY at 30, use fixed one [ 3.862767] bus=mdio_bus name=fixed-0:00 driver=mdio_bus dev=(ptrval) [ 3.870028] Generic PHY fixed-0:00: attached PHY driver (mii_bus:phy_addr=fixed-0:00, irq=POLL) [ 3.879142] b44 ssb0:1 eth0: Broadcom 44xx/47xx 10/100 PCI ethernet driver 00:0f:66:ce:c0:e3
この時点で私は問題の原因が Linux kernel 内部にあると悟り、最終的な PR 提出も視野に入れつつ OpenWrt の master にパッチを追加するプランへと作戦を変更した。
問題が発生した箇所を Git で特定
原理からいえば、OpenWrt のリポジトリで 24.10.0 から 23.05.5 まで遡っていけば、いつかは問題のコミットにたどり着くだろう。しかし、何万ものコミットをひとつひとつ遡るのは時間がかかりすぎる。そこで、「問題を生じさせたコミットを高速に見つける機能」である git bisect の世話になることにした。Git bisect を使えば、あるコミット2つの間を二分探索で反復横跳びしながら動作可否をマークして、問題が生じた最初のコミットを素早く発見できる。
今回の場合、タグ v23.05.5 では動いたが、v24.10.0 では動かなかった。両バージョンの共通祖先から*6 v24.10.1 までの間で二分探索すれば原因が見つかると予測し、Git bisect を開始した。
それっぽく bisect できている(上から下へ) pic.twitter.com/VE4ZvqlYct
— Takumi Sueda (@puhitaku) 2025年6月5日
休憩しつつ2日かけて bisect した結果、Linux kernel が 5.15 から 6.1 に切り替わったところで PHY を認識しなくなることを突き止めた。
ようやく bisect 完了、Linux 5.15 から 6.1 に切り替えたとこで壊れたらしい pic.twitter.com/edxNcylDXK
— Takumi Sueda (@puhitaku) 2025年6月5日
まだ問題は解決していないが、真因に着実に迫れているのでここで少しガッツポーズをした。そして、何が悪いのかをバージョン間の差分から見つけていくステップに入った。
根本原因探しの旅
PHY 初期化周辺のログを眺めると、"could not find PHY at ..." や "could not attach PHY at ..." など特徴的な文字列が並んでいる。これらのログを出している行をヒントに問題の箇所を文字列検索で発見し、考察に入っていくのがカーネルデバッグの定石である。今回も、正常なバージョンと異常なバージョンで実行パスが分かれる箇所を文字列をヒントにして発見できた。
この時点で、原因について以下のようにいくつかの予想を立てた。
- 暗示的に操作されていた何らかの GPIO*7 が操作されなくなり PHY がリセットを抜けなくなった
- PHY を登録するカーネル内部の API について、シグネチャは変わっていないが呼ばれるコンテキストや意図が変化した
- もともと偶然合致していた初期化の順番が前後して、依存関係の根もとから順に初期化されなくなった*8
仮説ベースで修正を加えながら調査したため困難を極めたが、最終的に2と3が実際に原因であると判明した。修正は PR にまとめて GitHub で提出し、現在レビューを通過してマージを待っている。
追記: 上記 PR を見て反応していただいた LKML 上のメンテナーの方々によって、より簡潔な修正が示されているため、OpenWrt 側の参照する Linux のバージョンが更新された暁には上記のパッチは不要になると見ている。
修正 & 動作成功
これでついに、ようやく、令和最新版の OpenWrt を約 22 年前のルーターである WRT54GS で動かせた。問題解決を経て達成感もひとしおである。
本件再開、そして無事 Ethernet PHY を認識しない問題も解決!OpenWrt の master が 22 年前のルーターで動いてる! pic.twitter.com/M8Hbxz1KJC
— Takumi Sueda (@puhitaku) 2025年7月31日
Web UI があったほうが見た目にわかりやすいので、数 MB 残された flash に LuCI を追加して動かしたところで記念写真。Flash 残り 1.88 MB、DRAM 残り 9.0 MB という限界極まるフィニッシュとなった。

おまけ: MAP-E でネットに出よう
せっかくなので、このルーターに IPv6 の世界を見せてやりたい。発売当時のファームでは IPv6 を知らなかったこのルーターも、令和最新 OpenWrt を手にした今、MAP-E や DS-Lite が自由に扱える。やってやろうじゃないか。
複雑な日本のインターネットで OpenWrt に VNE へ接続させると設定が大変になりがちだが、Linksys の最新 Wi-Fi ルーターに MAP-E させた時の知見が頭にあったため難なく接続できた。この手順について興味があれば以下の記事を参照してほしい。
LuCI を見ると、wan6 インターフェースに有効な IPv6 アドレスが表示され、WANMAP インターフェースに有効な IPv4 アドレスが表示されている。実際に WRT54GS 経由で適当に YouTube を開いたところ、微かに遅れは感じるが思ったより高速にページが表示され、動画の閲覧も問題なくできた。

スピードテストも実行してみよう。日頃 7 Gbps 出る回線に WRT54G を接続したら、一体どれくらいのスピードが出るのか!?その結果は!?!?

P.S. 2026-01-05 改訂。
*1:XIP = eXecute In Place といって、バイト単位で読める NOR flash など条件を満たせば、バイナリを DRAM にコピーせず flash から直接実行できる SoC もある。その場合 DRAM 容量の下限が下がる。
*2:最も先端の開発バージョンのこと。main とも呼ばれる。
*3:Flash 4 MB の場合、ルートファイルシステムをほぼ無にしつつ、printk の無効化など明らかに消したらダメそうなオプションを消してまでカーネルをダイエットしなければ収まらない。
*4:DRAM 16 MB の場合、どう頑張っても init に達する前後で OOM Killer が発生する上、kill できるプロセスがない場合は kernel panic に陥り世にも珍しいエラーメッセージを目にすることになる。
*5:Ethernet の物理層を実装するパーツ。SoC とつながる MDIO というバスと、RJ45 端子にやってくる物理層の信号とを相互に変換する。
*6:メジャーリリースでバージョンが切られた時点で master から分岐しメンテナンス状態になるため、リリースタグと master は共通祖先以降異なる歴史を歩む。OpenWrt のバージョニングについて詳細はこちらを参照。
*7:電圧の高低だけを出力できる基本的なデジタル I/O。
*8:すべての初期化を手続きで書いている古式ゆかしいコードで、かつ現代では一般的な EPROBE_DEFER(依存先が初期化されていない等の理由で初期化を順延したいとカーネルに伝えるための errno)が見当たらないため、初期化の順番が入れ替わることを想定していないのではと推測した。