ZRouter入門


はじめに

ZRouter.orgはFreeBSDを利用して無線ルータ用のファームを作成するビルド環境です。LinuxベースのOpenWRTのFreeBSD版といったところです。mips系のSOCを主にターゲットになっています。FreeBSD 9から10だった2010くらいに開発が始まりました。

現在ZRouterはgithubにもリポジトリを作っています。私がメンテナンスしていたコードもマージされて現在は12CURRENTがメインのターゲットですが、11Rでもビルド通るようです。ZRouterを作られたウクライナのrayさんはsys/mips/rt3050を作り込んでいたのですが、このSOCのサポートコードでheadにマージされないままの物があり、またrt3050のコードはsys/mips/mediatekでFDT化して作り替えられたので、sys/mips/rt3050は使わない方が良いです。(後日追記:sys/mips/rt3050は削除されました)

昔はFreeBSDのソースはいくつかのファイルの追加や修正がおこなわれていてhead本家とは違うレポジトリとしてmercurial(hg)で管理していました。

はじめて試した時に非常にスムーズにビルドができとても感動しました。昔FreeBSD上でOpenWrtをビルドする方法をLuigi Rizzoさんが書かれていて試してみたのですが、うまくいかずあきらめた事があって、ZRouterがあまりにすんなりビルドできて吃驚しました。

同じようなビルドツールにfreebsd-wifi-buildというものもありますが、各種ブートの対応やイメージの対応などZRouterの方が優秀なところが多いです。

freebsd-wifi-buildはFreeBSDのソースツリーのsys/mips/confのファイルを元にビルドしていますが、ZRouterはconf相当のファイルを独自に生成してビルドを実行しています。

ZRouterでのビルドの実行はFreeBSD上で行います。現在は私は10.3Rで実行しています。FreeBSDのamd64/i386の環境を用意ししてビルドしてください。以前はFreeBSD9でビルドしていたのですが、CURRENTと遠くなってしまうとshare/mkとかで不整合を起こす事があり、ある程度近いリリースが良いようです。

最近流行のQEMUでのビルドに比べると普通のクロスコンパイルですが、処理時間はこちらの方が短いのではないでしょうか。ZRouterのビルドは特殊な事はおこなっておらず、カーネルは普通にビルドしてrootfsは必要なファイルのみにするというようなことしかしてません。

clangのmipsサポートは良くなってきているようですが、ZRouterではgccを使っています。mipsにはmips16という縮小命令セットがあり、これによりバイナリのサイズを小さくできるのですがgcc 4.2+binutilでは使えないようで、また最近はFlashも随分大きい物があるので、あきらめました。

ZRouterが実行している内容は以下になります。

  • クロス用のgccなどのビルド
  • ターゲット用のconf作成
  • make world
  • make kernel
  • kernelを圧縮してU-Bootヘッダー付加
  • rootfsの整理
  • rootfsをiso9660化して圧縮
  • 圧縮kernelと圧縮rootfsを結合

WIFI関係の機能に関しては、法律上の制限があるためこの投稿では触れない事にします。

ターゲットの用意

無線ルータはそれぞれの国の規制のために、アメリカ・ヨーロッパ・日本では同じハードウエアを入手する事はできません。しかし内部で使われているSOCは同じ物だったり、そもそもOEMで基板自体は同じ物が別々のブランドから発売されていたりする事があります。

以下のような無線ルータがお勧めです。

  • Flashが8M以上
  • Memoryが32M以上
  • ブートプログラムがU-Boot
  • シリアルパターンがあるもの
  • はめ込みではなくネジで組み立てられていて分解しやすいもの
  • 2008年以降の製品
  • USBがあるもの

OpenWrtなどのデータベースに情報があるので、ネットで検索してみるとよいでしょう。

ネジはトルクス(ヘクスローブ)のT-6,T-8が多く、たまにT-10が使われています。ネジ穴が深くて軸が長くないと使えない事もあります。私は兼古製作所のこんなツールを使っています。

FreeBSDのsys/mipsではatherosの開発が一番活発なのでこのメーカのSOCが入った物が良いかもしれません。私はRalinkのRT3050がシンプルで好みです。

追記:sys/mips/rt3050はFDT対応で作り替えられて、sys/mips/mediatekになりました。こちらは十分安定しています。broadcomのチップ対応の開発も続けられているのですが、どきどき試しているのですが、安定していません。(2017/4)

私は主にハードオフのジャンクでターゲットを入手しています。四半世紀前には数百万円だったMIPSマシンが、わずか数百円で購入できるので、いろいろ買って試しています。

無線ルータの全ての機能が使える事はほとんどありません。必要な機能はDIYしましょう。

ビルドしてみよう

githubにあるfreebsdのmasterブランチのFreeBSD本体(SVNでも可)とZRouterのmasterブランチをチェックアウトしてください。

ZRouterのディレクトリのmenu.shを起動してメニュー操作で以下を実行します。

  1. Deviceの選択
  2. BasicProfileを選択(xSMALL_しかメンテしてませんSMALL_でもビルド通ります)
  3. PathsでFreeBSDのソースディレクトリとオブジェクトファイル保存ディレクトリの設定
  4. Build実行

Deviceにターゲットがない場合は同じSOCを使った機種を変わりに選択してみてください。

ビルドに失敗するときはobjディレクトリのファイルを消すとうまくいく事があります。

menu.shはmakeをキックしているだけなので、makeにパラメータを渡してビルドする事もできます。

Buildが成功するとオブジェクトファイル保存ディレクトリの深いところいファイルが作成されます。ファイルはカーネルとrootfsの中間ファイルとイメージおよびこれらを結合してU-Boot形式にした最終バイナリになります。rootfsはiso9660形式にしています。

Planex_MZK-WNH.zimage
Planex_MZK-WNH_kernel
Planex_MZK-WNH_kernel.kbin
Planex_MZK-WNH_kernel.kbin.oldlzma
Planex_MZK-WNH_kernel.kbin.oldlzma.uboot
Planex_MZK-WNH_kernel.kbin.oldlzma.uboot.sync
Planex_MZK-WNH_rootfs
Planex_MZK-WNH_rootfs_clean
Planex_MZK-WNH_rootfs_clean.iso
Planex_MZK-WNH_rootfs_clean.iso.ulzma
Planex_MZK-WNH_rootfs_clean.mtree

FreeBSDにはU-Boot用のブートローダーが用意されていますが、ZRouterでは利用せずにlocore.Sを直接キックするようにカーネルをビルドしています。

メモリサイズがハードされている事があるので大きく設定しているとクラッシュするのでsys/mipsの該当のソースを確認してみてください。

ビルドはこれだけでできるのですが、大きな落とし穴があります。rootfsのflash上の位置はhintsに書いてあって、それをgeom_mapが読み取ってgeom_uzipで読めるようにします。ビルドしただけだとhintsのstartとendの位置が正しくないために、geom_uzip認識できず、rootfs.uzipが作られません。このため一度ビルドしてkernelとrootfsのサイズを確認してhintsを書き換え、その後もう一度ビルドを実行する必要があります。

mapのkernelのendとrootfsのstartはsearchで自動取得できました。rootfsのendはflashの利用領域最後までにしておけばビルドの度に修正する必要はありません。

rootfsのendは小さいとエラーになりますが、大きい分に問題ないようなので、実際rootfsのサイズではなくフラッシュの利用可能領域を指定しても大丈夫のようです。

fdtを使うarmではgeom_flashmapでdtsのpartitionを元にしますが、こちらもスタティックにdtsファイルをカーネルに入れた場合は、同じように一度ビルドした後にdtsファイルののregを修正して再ビルドが必要です。

私は以下のようなスクリプトで設定値を確認して設定しています。

#!/bin/sh

if [ $1 = "Fon_FON2305E" ]; then
UBOOTEND=131072
elif  [ $1 = "Buffalo_WZR2-G300N" ]; then
UBOOTEND=65536
elif  [ $1 = "Planex_MZK-WNH" ]; then
UBOOTEND=262144
else
echo "Please set image name [Fon_FON2305E|Buffalo_WZR2-G300N|Planex_MZK-WNH]"
exit 0
fi

KERNSIZE=`ls -l $1*.sync | awk '{print $5}'`
FSSIZE=`ls -l $1*.ulzma | awk '{print $5}'`

KERNEND=`expr ${UBOOTEND} + ${KERNSIZE}`
FSEND=`expr ${KERNEND} + ${FSSIZE}`

printf "=== hints(map) ===¥n"

printf "%08x %08x¥n" 0 ${UBOOTEND}
printf "%08x %08x¥n" ${UBOOTEND} ${KERNEND}
printf "%08x %08x¥n" ${KERNEND} ${FSEND}

printf "=== dts(partition) ===¥n"

printf "%08x %08x¥n" 0 ${UBOOTEND}
printf "%08x %08x¥n" ${UBOOTEND} ${KERNSIZE}
printf "%08x %08x¥n" ${KERNEND} ${FSSIZE}

BasicProfileのxSMALL_でzimageがだいたい5Mバイトくらいになります。

ZRouterの中身

menu.shで設定された内容をMakefileに渡してビルドするような流れです。

soc由来の設定はsocsの下に、ボード由来の設定はboards下に、プロファイル由来の設定はprofilesの下に置くようになっています。

profilesの下でアンダースコアーで終わるディレクトリがBasicProfileでそれ以外が一般のProfileになるようです。

それぞれのmkをまとめてconfのファイルを作りhintsをまとめてkernelにつっこみます。mkにはrootfsにいれるファイルの一覧も含まれます。

vendorディレクトリにはboard直下のメーカー毎のディレクトリを作って、vendor.mkとfiles/を置く事ができます。機種毎にはなっていません。またhintsの反映機能もありません。hintsのサポート追加しました。

portsの下にはports形式のビルド設定ファイルを置きます。

ZRouterで新しいバイナリなどを追加する時にはFreeBSDのソースツリーに含まれる物であれば、mkファイルに追加だけでOKです。それ以外は新しいprofileとtargetを作ってmkとMakefileを書きます。実体のファイルはcontribeに置きます。portsで追加する事もできますが、portsはmips固定だったりしてちょっと難があります。

ブートローダーについて

ほとんどのルーターはブートローダーとOSで構成されています。ブートローダーにはU-BootやRedBootやCFEなどがあります。U-Bootが最近では一番良く使われていてAtherosのAR7以降やRalinkで標準的に使われています。RedBootはFON2201などの古いAtherosで使われています。CFEはBroadcomのMIPS SOCで使われていました。これ以外にも独自なブートローダーもあります。それぞれのブートローダー毎にロードできる形式は違います。ZRouterではU-Boot,RedBoot,CFEのサポートをおこなってます
。FreeBSDではU-Bootについてはセカンドブートローダーを用意していますがZRouterではそれを使わず、直接locore.Sをキックするカーネルをビルドしています。

U-Bootの操作

U-Bootはボード毎にコンフィグレーションが違い、機能差があります。大抵のボードではネットワークからTFTPでイメージをメモリにコピーしてFlashに焼く機能は持っています。この機能がない場合はシリアルでの転送などになりますが、かなり厄介です。

私はMacにFTDI社のUSBシリアル変換モジュールを使って無線ルータと接続してます。ほとんどの無線ルータのシリアルは3.3Vですが、ピンの並びはいろいろあります。ボーレートもいろいろです。FTDI社のモジュールは秋葉原の秋月電子やスイッチサイエンスで購入できます。

たまにシリアルコンソールがUSBシリアル変換モジュールと相性悪いことがあり、ターミナルソフトを立ち上げなおしたり、RXとGNDだけで確認してみるとか試すと良い時がありました。

上記はサンプルでピンの配置はそれぞれ違います。たいてい1番ピンがVCC(3.3V)になっていますが、GNDはいろいろあります。GNDを調べるのは近くのGNDと抵抗値がゼロなピンを探します。次はTXを探すために、文字化けしてもなんらか出力があるピン見つけてボーレートを試してちゃんと文字が受信されている事を確認します。のこりがRXとなります。

通信プログラムはJerminalを使っています。cuでフロー制御がオフにできなかったのでこちらを使うようになったような気がします。

Kernelを起動せずにU-Bootのコマンドモードに入るにはコントロール+Cやボタンを押しながらなどいくつかの種類があります。

U-Bootのコマンドモードに入れたらhelpコマンドでU-Bootの機能を確認してみるとよいでしょう。たまにhelpはあっても機能しないコマンドなどもありますが。

カーネルの確認

たいていのブートローダーにはFlashには焼かずにカーネルをメモリに貼付けて実行する機能があります。これにより、どの程度のデバイスが認識されているか起動ログにより確認できます。問題なく起動できれば、rootfsをマウントするところで止まります。

ZRouterのサーバでtftpdが使えるように設定して、ビルドしたファイルがあるディレクトリがルートになるように設定しておきます。

U-Bootの場合は以下のように操作します。

RT3052 # setenv ipaddr 10.10.10.190
RT3052 # setenv serverip 10.10.10.3
RT3052 # tftpboot 0x80800000 Planex_MZK-WNH_kernel.kbin.oldlzma.uboot
RT3052 # bootm

ipaddrは自分のアドレスになり、serveripはZRouterでビルドしたホストのアドレスになります。

tftpbootコマンドはTFTPでU-Boot形式の実行ファイルを取得してメモリに貼付けます。その後bootmコマンドを実行する事でU-Boot形式に入った圧縮された実行イメージをU-Bootのヘッダーのロードアドレスに解凍して実行を移します。ビルドしたカーネルのU-Bootイメージはビルドホストでfileコマンドで見ると以下のように表示されます。

Planex_MZK-WNH_kernel.kbin.oldlzma.uboot:                             u-boot leg
acy uImage, FreeBSD Kernel Image, Linux/MIPS, OS Kernel Image (lzma), 1054519 by
tes, Mon Dec 14 11:41:37 2015, Load Address: 0x80001000, Entry Point: 0x80001000

RedBootの場合は以下のように操作します。

RedBoot> ip_address -l 10.10.10.180 -h 10.10.10.3
RedBoot> load Fon_FON2201_kernel
RedBoot> exec

RedBootではELFファイルをtftpで取得して実行できます。

CFEの場合は以下のように操作します。

CFE> ifconfig eth0 -addr=10.10.10.200
CFE> boot -elf 10.10.10.3:Buffalo_WHR-HP-G54_kernel

この方法がなかなかうまく行かなくて、疑問に思っていたのですがCFEではELF形式に細工が必要でそのためのldscriptがldscript.mips.cfeとして用意されていて、これで作ったカーネルは上記の操作で実行できます。

cfid(cfi)やmx25l(spi)が見えないとrootfsがマウントできないのでまずこのログが起動時に出力されるようになるまで確認します。

Flashへ書き込み

注意 Flashに書き込みに失敗すると文鎮化する可能性があります。

Flashに書き込み用のモードを持っているU-Bootもありますが、無い場合はU-Bootのコマンドで書き込みをおこないます。

  1. tftpbootコマンドでzimageをサーバから読み込み
  2. eraceコマンドで古いファームを消去
  3. メモリに読み込んであるzimageをcp.bコマンドでFlashにコピー

設定の保存

/dev/map/configがあると起動時にそこから/etc/のunifsの差分のバックアップを拾いだして展開します。保存するには/etc/rc.save_configを実行します。

ネットワーク越しのupgrade

ZRouterにはupgradeというコマンドが用意されていたのですが、rootfsをマウントしたまま書き込みを行っていて、あまり安全ではないものだったので、考えてみました。

RedBootのtelnetでのアップデート

失敗した時にあきらめてシリアルコンソールから焼き直しましょう。

ファイルシステムなど

FreeBSDではrootfsはリードオンリーで大丈夫ですが、/tmpは書き込みできる必要がありmd(4)で用意するようにrcで設定しています。また/etcのファイルもアップデートが必要なケースもあるので、/etcは/tmpにunionfsを作って更新できるようにしてあります。rootのパスワードはrootなので、このままでインターネットにつながれた環境に出すような事はしないでください。

# df
Filesystem           1K-blocks  Used Avail Capacity  Mounted on
/dev/map/rootfs.uzip     19528 19528     0   100%    /
devfs                        1     1     0   100%    /dev
/dev/md0                  7543   786  6154    11%    /tmp
<above>:/tmp/etc         27071 20314  6154    77%    /etc

プログラムの追加

ZRouterではpkgシステムには対応していません。このためもし新しいプログラムをイメージに焼き込みたい時は以下の方法をとります。

一つはcontribの下にソースディレクトリを追加して、packagesにそれを追加する方法です。

もう一つはportsに追加する方法です。portsは純正のportsとはちょっと違っていて、あくまでビルドシステムとして使っていて、pkgの処理はしません。このためdo-installで必要なファイルをターゲットとのrootfsにコピーするようにします。設定などはportsと同じです。開発当初は古いpkgシステムでpkgの利用もおこなえていた可能性があるのですが、作りかけのままpkgのシステムが入れ替わったりしてうまく使えなくなっているのでファイルのコピーをするようにしています。

私はmrubyのビルドのためのlibuvはcontribに追加してmruby自体はportsに入れてコンパイルできるようにしました。

トラブルシュート

ドライバがprobeに失敗している場合などはとりあえずprintfを入れてみてどういう状態か確認します。既存コードにdebugログの機能がある場合はそれを使ってみます。

CFIなFlashはU-Bootのmdコマンドでも中身が見れるので、この機能でrootfsのオフセットの確認などできます。

ネットでログを検索してみると情報になる事があります。

ZRouterでビルドした時に、なにかのはずみで前のファイルが残っているとエラーになる事があります。その時はオブジェクトディレクトリのターゲットのファイルを消すとエラーが無くなります。ただしtmpディレクトリを消すとフルビルドになるので、消さない方が良いです。

FreeBSDのsys/mipsは非常にシンプルに作られているので、おそらく実績があるSOCであればブートでひっかかる事はないような気がします。起動は以下のような流れになりますが、一番の山はrootfsマウントでしょう。

rootfsのマウントポイントはZRouterの設定ファイルに入っています。

rootfsがマウントできればrd.dに含まれるスクリプトでmdなデバイスを作って、/etcをunionfsとしてその中に置いています。

新しいバイナリを追加した時や、HEADの構成変更でsoが足りなくなる時があります。その時はmkファイルに該当のライブラリを追加します。

課題

  • 各種デバイスドライバ
  • 各種SOC対応
  • 自動でmap/flashmapの値の設定 mapはスキャンで設定できました。
  • headの追っかけ
  • ZRouterの独自Hackのheadへのポート(siba,Switch関係)
  • FDT対応dtsを用意すればビルドできます。

あなたもLinuxクリーンな環境を実現してみませんか?