BCM/mips mrubyのネットワークサポート


BCM/mips mrubyでEthernetのネットワークをサポートするためにいろいろやった事のメモです。

BCM/mipsでも蟹さんのときと同じようにドライバーコードはブートローダー(CFE)のネットワークコードを再利用する形にしました。

CFEはGPLではないので、公開の必要はなさそうなのですが、時々linuxのアーカイブに一緒に入っている事があります。

以前Broadcomのサイトでダウンロードした、CFE-1.4.0というコードがあったので、これを必要なところけ抜き出してコンパイルできるようにしました。必要なところは以下になります。

  • 割り込みと例外の処理
  • 割り込みハンドラ登録の処理
  • キャッシュの処理
  • MAC(Ether)の処理

ほとんどがCコードですが一部アセンブラの部分があります。

CFEは一番良く使われていた1.0.37というバージョンの後1.0台は40までで、その後は矢継ぎ早にバージョンアップして1.4.0になったようです。

抜き出しの作業は、必要なファイルを追加してコンパイルしてエラーを取りながらリンクして必要な関数を確認して、ファイルを追加しての繰り返しになります。

蟹さんのときは同じディレクトリに置きましたが、今回はcfeというディレクトリの下に置いて、.aを作るようにしました。おかげで結構すっきりして良かったです。そのうち蟹さんも同じように整理した方が良いかもしれません。

MIPSは6本のハードウエア割り込みがあって、エントリーポイントは一緒でCP0の値を参照してどの割り込みか判断して、それぞれの割り込み処理ルーチンに飛びます。6本目はタイマーに使用されていて、それ以外はそれぞれです。

MIPS系のSOCは自由に使える5本の内のどれかに独自のProgrammable Interrupt Controller(PIC)が付いているケースもあります。MediatekやAtherosはそうなってます。

MACの割り込みの前にまずtimerの割り込みを確認しようと思ったところ、CFEはtimer割り込みを使っていませんでした。CFEはTimer割り込みは使わず、独自のプロセススイッチ的な処理を持っていてsleepでコンテキストスイッチするようになっていました。この部分は必要ないので、使いませんでした。

タイマー割り込みを使わないという方法もちょっと考えたのですが、蟹さんがタイマー割り込みを使って実装されているので、がんばりました。

MIPS 4Kはカウンター(タイマー)もアーキテクチャの一部ですべて同じ物が入ってます。割り込みはハードウエア割り込みの最後の6番目に割り当てられています。CFEのコードは6番目の割り込みは処理しないコードになっていたので、使えるように修正しました。

CFEの割り込みコードは一つの割り込みに複数のハンドラを設定でき、割り込みが上がったときには、これらを順に呼び出します。上の図では3にBとCというハンドラが登録されていて、3の割り込みが上がったときに順に呼び出します。呼びだされたハンドラは自分の割り込みかレジスタを調べて、自分の場合には処理をおこないます。

MIPSのカウンターはリセットする事もできますが、リセットせずに使います。割り込みはCP0のレジスタに設定した値になると上がって、次の割り込みは現在のカウンター値にインターバルを足した値をレジスタに設定します。そのままにしておくと一周して同じカウンターになった時に上がるので割り込みを止めたい時はレジスタを0xffffffffに設定します。なぜかCFEにこのレジスタ設定関数は用意されていました。

レジスタ CP0 内容
Count register 9 CPUクロックでインクリメントされるカウンター
Compare register 11 Count registerがこの値になったら割り込みを上げる
Status register 12 0ビット目が全割り込みの許可で、10から15ビットがハード割り込みのマスク。0ビットが1で、15ビットが1であればCompare registerの値で割り込みが上がる。追記:ERLとEXLが0も必要
Cause register 13 割り込みが上がったときにその理由を表すレジスタ。10から15ビットがハード割り込み。

adm5120をいじっていて割り込みが上がらずいろいろ悩んだのですが、MIPS32® M4KTM Processor Core Software User’s ManualにERLとEXLの事が書いてありました。

タイマー周りを確認していて、CPUクロックが正しく拾えなかったのでFreeBSDのbhndのコードと比べてみたところ、コピーしたCFEのコードはケーパビリティレジスタの16ビット目からの2ビットでクロックの属性を拾っていたのですが、bhndは15ビット目から3ビットで拾ってました。おそらくCFEのコードが古かったのだと思います。

カウンターの周波数は定期的にuart出力をしてストップウオッチ計ったところCPUクロックの半分の100MHzでした。

出だしからコードが古い事が分かったのですが、せっかくコンパイルできるように下ので、もう他のコードを探す事はせずに、このコードが動く事を祈って作業しました。

BCM4712のMACはPCIなMACチップのBCM4401と同じでアドレスに見えるようになっています。CFEのMACのコードはPCIのコードとアドレス直のコードが一つのファイルに一緒に入っていて、PCIが先にあります。同じ関数名が二つあったりするので注意が必要です。

BCM4401はFreeBSDではbfeというドライバーになります。

MACアドレスはCFEのNVRAMの値を使うようにしてあります。

  macptr = nvram_get("et0macaddr");

adm5120のサポートを作っていて見直したのですが、BCM4401は結構変わった仕様でした。ディクリプタは二つのフィールドで8バイトでリングではなくて全てのエントリーをレジスタに入れる必要があるようです。またディスクリプタのデータへのポインターの先は生のパケットではなくて、構造を持っていてその最後に生のデータが入っています。このため32バイトのオフセットを付けて生のデータを拾っていました。

BCM4712はSonicという会社のSiloconBackplaneという仕組みをつかっていて、割り込み管理などが出来るようです。当時このまま規模が大きくなると大変なので、PCIに変わるようなSOCバスを目指したのかもしれないのですが、結局SOCはそれほど発展は無く、今となっては無用の長物にも思えてしまいます。

MACは自分のMACアドレス宛のパケットとブロードキャストのパケットをホストに上げます。同じネットワーク内で使われてないIPにpingを打つとarpが連続して投げられて、arpはブロードキャストなので、ホストに上がってくるか確認します。

arpを打って自分の割り込みルーチンに届くようになったら、lwipとの接続コードを入れます。これはほぼ蟹さんの時のものを流用しました。

pingはlwip内で処理されるので、lwipを初期化すれば受信と送信の処理がちゃんと出来れば動き出します。

受信データのlwIPへのデータの引き渡しは、受信の割り込みではqueueにつんでおこなわず、10msのタイマー割り込みでqueueをlwIPに渡しています。受信割り込みが重くならないようにする事と、メインのループはmrubyがにぎっているのでそうしています。どれくらい効果があるのかはわからないのですが、とりあえずは動いています。

蟹さんのときにはether部分とnet部分が奇麗に分かれてなかったので整理して、蟹さんにも反映しました。

次はUDPの送信と受信を確認します。蟹さんのコードをそのまま使いました。蟹さんはビッグエンディアンで、BCM/mipsはリトルエンディアンなので、反転のコードを入れないと正常に動作しない事が分かりました。ちなみにTCPやIPはビッグエンディアンです。

UDPができたらTCPのhttpを確認します。とりあえず自宅内のサーバとの通信を確認して、出来たらインターネットのサイトにもアクセスしてみました。そうすると一部アクセスできないサイトがあり調べてみたところ、IPアドレスをmruby内で数値として持っていたため、上位バイトが128以上のネットワークは-1となりアクセスできなくなっていました。以前は大丈夫だった気もするのですが、とりえあずmrubyの世界ではIPアドレスは文字列とするようにしました。mrbgemにiptostrとstrtoipというメソッドを用意して内部で変換しています。

あとはBearSSLをひっつけてhttpsの確認しました。BearSSLは乱数ルーチンと時間が必要なのを忘れていました。

mrbgemは蟹さんの時の流用で、ローレベルのmruby-yabm(mruby-rtlbm-rtl8196cから名前変えました)と既存のmruby-simpehttpにパッチをあてたもので構成してあります。

というような流れてほぼ一週間かかりました。

BCM4712はMACだけのSOCですがBCM5350-4はEthernet Switch(BCM5325E相当と思われる)が入った、SOCになります。MACは同じはずなので同じコードが使えるはずなのですが何故かBCM5351はダメでBCM5352は大丈夫でした最初は使えてますがちょっとすると調子が悪くなるみたいなので、いじらないとダメそうです。BCM5354はPMCが入っていて、今使っているCFEのコードには処理がないので、どっかから持ってこないといけません。

BCM5352の問題は当初switchの問題と思ってroboswitchのコードを入れてみたのですが、解決しませんでした。動作を見ているとどうもMACの受信の割り込みが上がらず、送信の割り込みだけしか上がらないような感じです。このため送信している間は一緒に処理されますが、送信がないと受信が止まってしまうようです。

今回利用したにdev_sb_mac.cというBCM4401系のCFEのコードはおそらく2003年くらいにPCIベースのコードをSiloconBackplane(SIBA)用に書き換えた物と思われます。その後2004年くらいにこのコードは全面的に書き換えられet_cfe.cというコードになり、linuxのドライバーと共通コードになったようです。dev_sb_mac.cは1.4.0に入っていたのですが、et_cfe.cは1.0.36に既に入っていました。何らかの理由で1.4.0には古いコードが入ってしまったようです。et_cfe.cと一緒にroboswitchのコードがetc_robo.cとして作られていたようです。このrobowitchのコードも2010年くらいに書き換えられ、bcmrobo.cとなったようです。BCM5356以降はネットワークインターフェースはGIGAなものに移行しているのですが、それに対応するためにネットワークインターフェースのコードも作り変えられたようです。BroadcomのGIGAなチップはBCM5702などになるのですが、どうもBCM5356のGIGAのIPはBCM5702ではなくBCM4401とも違っているようです。

dev_sb_mac.cにはdefineで割り込みを使わない処理にもできます。割り込みを使わない理由は二つ考えられて、一つはパフォーマンスの問題からと、もう一つはなんからの不具合のためです。ブートローダーはあまりパフォーマンスは気にしなくても良いはずなので、後者の可能性が高いのではないかと思われます。特定の型番のチップやレビジョンになんらか問題があったのではないかと想像されます。et_cfe.cは完全に割り込みを使わない実装になっています。

古いコードはレジスタのアドレスをdefineしていたのですが、新しいコードは構造体でレジスタを設定しています。構造体だとオフセットが分かりにくくdefineの方が良かった気がします。

後から思えば古いコードを使ったのは正解だった気がします。

httpbin.orgでテストしていたのだが、このサイトはFINを送ってこなくて、tcp_pollでタイムアウトするまで待つ事になる。Content-Lengthで自分からcloseを試してみたのだがおかしな事になるので、そのままにした。またpollの60を短くしてもおかしな事になっていた。FINちゃんと送ってくるサイトもあるし、SSLも考慮する必要もあるし、またいつか調べたい。

Broadcomは後期のMIPS SOCはBCMAというバスアーキテクチャを採用してCFEやLinuxのドライバを書き直してます。これをベースにFreeBSDのbhndというドライバとsys/mips/broadcomは作られているようです。BCMAにはSIBA互換なレジスタセットがあるようです。

MACアドレスは開発当初はハードコードしていましたが、nvramに入っているので、パースのコードをCFEからコピーして使えるようにして、MACアドレスを拾って設定するようにしてあります。

蟹さんを見直してみたら蟹さんのMAC(NIC)のコードは自分が書き直していました。Switchのコードはブートからのコピーでした。

「スタンドアローンの...ナンセンスだからな」

追記:BCM5352動きました。

いろいろデバッグライトを入れて調べたところ割り込みが上がっていない事が確認できました。BCM5352ではBCM4712とENETのアドレスが違っていて割り込みの場所が違うみたいです。

いろいろ調べたところ、下記のコードが原因と分かりました。Timer割り込みでちょっといじっているので、これはオリジナルのままのコードです。

static inline unsigned int
bcmcore_map_irq(unsigned int irq)
{
#if !defined(_BCM96345_) && !defined(_BCM9635x_) /* XXX cleanup */
    return sb_map_irq(irq) + 2;
#else
    return (irq % 4) + 3;    /* XXX Works for external interrupts only? */
#endif
}

おそらく後半の部分はBCM5352などで動くようにするためのワークアラウンドで、後から見た人がなんの意味があるか分からずコメントを入れたぽいです。BCM4712のSIBAの割り込みマッピング処理がBCM535xでは動かなくなったようです。

BCM5351とパッケージにあるターゲットは中身のIDは5350だった。こちらはなぜかいくつかのレジスタアクセスが落ちていたので、スキップするようにした。あまりに不自然なので、仕様とは考えづらく、なんらかのおまじないがあるのかもしれないのですが、分からないのでアクセスしないようにしたところ、とりあえず動いています。

BCM5354はPMCがはいってクロック周りが変わっているのと、roboswの初期化が出来ない事と、MIPS16サポートでブートが小さくなっているのでflashの配置が変わっていました。

roboswのコードは新しいものを使わないとダメなのかもしれません。

JTAGで確認できるProcessor Identificationは以下のようになっています。

CPU ID
BCM4704 00029006
BCM4712 00029007
BCM5350 00029008
BCM5352 00029008
BCM5354 00029029

BCM5352からBCM5354がずいぶん差がある事やリリースも1年以上あいていて、当時Broadcom社内で混乱があったのではないかと推測されます。この当時のMIPSのプロダクトラインは他にケーブルモデム用とDSL用があって、そっちに開発の主力を移していたのかもしれません。BCM5354以降のチップは国内ではほとんど見あたりません。

BCM5354はMIPS16がサポートされてブートが従来の256Kから128Kに小さくなっています。このためcfeのapiのFE_GETINFOでブートのサイズを拾ってみたところ、256Kのままでした。なんだかなー。。。

BCM5356以降はアーキテクチャが変わっているのでサポートしません。

AtherosやADMTekがMIPS SOCにARM由来のAHBを採用したのは、Memory ControllerなどのIPが良かったからなのかもしれません。

BCM5356に対応したCFEのソースは以下のような構成になっていました。このコードはLinuxのドライバと共通化をしているようです。

パス 内容
src/cfe/cfe cfe本体
src/include 共通化ヘッダー
src/share 共通化ソース
src/et/sys Ehternetドライバソース

src/et/sys/et_cfe.cが大元のコードで、src/et/sys/etc47xx.cが100用のコードでsrc/et/sys/etcgmac.cがGIGA用のコードになります。DMAのコードはこれらには入ってなくてshared/hnddma.cにあります。

ipv6のサポートのためdev_sb_mac.cでマルチキャスト受信のためM_RCFG_AMを設定する必要がありました。