Jun Sun の

Linux MIPS 移植指南


あなたからの情報提供,誤り,スペルミス,デッドリンクの指摘,コメント,そして賞賛:-)などのフィードバックを待っています.


オリジナル文章はここにあります.
[フレーム版]

前書き

この文書には,私がいくつかのMIPSマシンへの移植や他のLinux関連の作業において学んだことが反映されています.初心者が何から始め,どのように経験を積んでいくかという手助けになれば幸いです.

この文書はMIPSマシンへのLinux移植における主要な工程をすべて含んでいます.中でも「MIPSマシン抽象化層」(すなわちマシン固有コードとほとんどのMIPS共通コード間のインタフェース)と呼ばれる部分に注目することになるでしょう.他にも「Linuxハードウェア抽象化層」(すなわちLinux共通コードとアーキテクチャ固有コード間のインタフェース)について注目した有用な文書には,C Hanish Menon (www.hanishkvc.com)によって書かれたこの文書が挙げられます.

この文書では次のような注釈を使います.

TODO
未完成部分に対する注意
HELP
本当に手助けが必要
DEBATE
これは私の意見です.あなたはどう考えますか?
THANKS
指摘してくれた方への感謝

第1章: 概要

前堤条件: Ralf Bachle(ralf@gnu.org)によるLinux MIPS HOWTOに目を通すことも強くお薦めします.ところで,前提条件の一つとして,Ralfの名前を忘れないようにもすべきでしょう :-)

カーネルソースツリー

共通のMIPSツリーはlinux-mips.orgにあるCVSツリーです.Linux/MIPS HOWTOの"Anonymous CVS servers"の章にある解説を見てください.2004年1月26日現在のカレントカーネルは2.6.1です."linux_2_4"もしくは"linux_2_2"ブランチタグを利用することで,以前の安定版リビジョンを常にチェックアウトできます.

カーネルパッチ

さまざまな理由から,適切な修正がチェックインされずに,ずいぶんと長い間カーネルツリーにバグが残されたままになっているかもしれません.さまざまな場所からパッチを取得できます.よく知られたものを次に挙げます.

Jun Sun patches
Linux/MIPS FTP archive
Maciej W. Rozyki patches
Brad LaRonde's patches

クロスコンパイル環境

おそらくあなたのMIPS boxではまだLinuxが動いてないことでしょう(そうでなれけばなぜ悩むのでしょうか?).そこでカーネルイメージを作るために別のマシンが必要になります.イメージを作ったならばすぐに,このイメージをMIPS boxにダウンロードし,MIPSカーネルを動かします.これはクロス開発と呼ばれています.MIPS boxはたいていターゲットマシンと呼ばれ,カーネルイメージを作るために使われるマシンはホストマシンと呼ばれます.

通常,組込み機器はネイティブ開発するための十分な性能や周辺機器を持っていないので,その開発においてはクロス開発が一般的です.

もっとも一般的なホストマシンはおそらくi386/PCで動作するLinuxでしょう.

移植を始める前に,ホストマシンにクロス開発ツールをセットアップする必要があります.クロスコンパイルツールを作るために,Linux/MIPS HOWTOのような解説を見つけることができるでしょうが,おそらく既存のツールを取得するのが確実な方法でしょう.

MontaVistaはフル機能のツールチェインを含んだ無償のJorneymanエディションを提供していましたが,残念なことにもはや提供されていません.その代わり,「スリム」バージョンのツールチェインを含んだプレビューキットがhttp://www.mvista.com/previewkit/index.htmlからダウンロードできます.

次のリンクはpre-buildツールチェインや自分でツールチェインを構築するための解説,そして最後に示すのはMIPSボート用のpre-compiledディストリビューションです.

Brad LaRonde's cross toolchain for Linux
Steve Hill's toolchains for glibc and uClibc
MIPS free toolchains
Distributions for MIPS

移植ステップの全体像:

あなた固有の状況に応じて,次に示すステップのいくつかはスキップできるでしょう.
  1. Hello World! - ボードをセットアップ,シリアルポートを有効にし,シリアルポート経由で"Hello, world!"を出力する.
  2. あなた自身のコードを追加する.
  3. 初期段階のprintkを動かす - 最初のMIPSイメージを作り,カーネルからのprintk出力を見る.
  4. シリアルドライバとシリアルコンソール - シリアルコンソールで動作する本物のprintkを動かす.
  5. KGDB - KGDBは開発において非常に大きな助けになるでしょう.強く推薦しますし,セットアップも難しくありません.
  6. CPUサポート - あなたが使うMIPS CPUが現在サポートされていなければ,新しいコードを追加する必要があります.
  7. ボード固有サポート - ボード固有のディレクトリを作成し,割込みルーチン/処理やカーネルタイマサービスをセットアップします.
  8. PCIサブシステム - マシンがPCIを持っているならば,PCIデバイスを使う前にPCIサブシステムを動かす必要があります.
  9. イーサネットデバイス - これを試す前にシリアルポートを動作させるべきです.普通はその次に望むドライバがイーサネットドライバでしょう.イーサネットドライバが動けば,Linuxユーザランドが完全に動作するNFSルートファイルシステムをセットアップすることができます.
  10. ROMFSルートファイルシステム - もう一つの方法として,RAMディスクに格納したROMFSイメージとしてユーザランドファイルシステムを作ることもできます.

第2章: "Hello, world!"

クロス開発において,通常シリアルポートはもっとも重要なインタフェースで,起きていることは何でも見ることができる場所です.Linuxに取り組み始める前に,シリアルポートが動作するか確認することは有益でしょう.printfを実行できるスタンドアローンプログラムのsample codeサンプルコード,またはgzipしたtar ballを見つけることができるでしょう.このようなプログラムは後のデバッグ段階においても,ハードウェアレジスタ値を出力するなどに有益です.

'make'と急いでタイプする前に,次に示すコンフィグレーションをチェックし,修正しましょう.

  1. サンプルコートはR4KスタイルのCP0レジスタを前堤にしています.これは4000番以上の名前を持つ大半のCPUや最近のMIPS32/MIPS64にあてはまるはずです.
  2. 1MB以上のRAMを持っているかどうかチェックしましょう(Linuxを動かすのであれば,とにかく1MBはなければいけません).RAMを8MB以上持っていることを勧めます.
  3. シリアルポートは標準のUARTタイプですか? そうならば,コードやパラメータを修正してください.違うのであれば,あなた自身で関数を供給する必要があります.
  4. クロスツールの名前やパスは何ですか? それにしたがってMakefileを修正してください.

さぁ,"make"コマンドを実行しましょう.

あなたのMIPS boxのダウンローダに応じて,ELFイメージ,バイナリイメージ,またはSRECイメージを生成する必要があります.

bareboneイメージをターゲットにダウンロードして,実行してみましょう! ホストマシンとシリアルポートでつなぎ,minicomを実行すれば,おそらく"Hello, world!"メッセージが見えるはずです.

トラブルシューティング:

第3章 : あなた自身のコードの追加

Linuxツリーにコードを追加して,Linuxイメージを作ってみましょう.説明のために,AlohaというMIPSボードにLinuxを移植しようとしていることにします.

ボード用に正しいディレクトリを作る

新しいボード用のコードはボードサポートコード(もしくはボード固有コード)とドライバに分類できます.ドライバコードはdrivers/ディレクトリの下に配置すべきで,ボード固有コードはarch/mipsディレクトリの下に配置すべきです.

もっとも簡単な選択はarch/mips/alohaというディレクトリを作ることです.

しかし,2,3の検討事項がことを少々複雑にします.

以前は"mips-boards"のように,ボード製造メーカ名を基にディレクトリを作ってきました.これは一般的によい考えではありません.これらのボードのいくつかは共通部分がまったくないことがほとんど確実です.単純なグルーピングは強制的な結婚のようなものなのです.

ときどき異なる会社によって作られたボードが同じチップセットやSOCを使うことが,事情を悪くしています.今,あなたは何をしようとしていますか? 共通コードを複製しようとしていませんか? ある会社のボードを別の会社の名前の下に張りつけようとしてませんか?

通常,ヘッダファイルは同じディレクトリかinclude/asm-mipsの下に作ります.[DEBATE]ボード固有ヘッダファイルに関して,可能であれば対応するarch/mipsディレクトリの下に配置することを勧めます.

この例では,arch/mips/alohaディレクトリを作ることにしましょう.

最小限のAlohaコードを書く

Alohaボード用に,シンボルが見つからないと文句を言われない完全なLinuxイメージを生成できるコードを書いて行きましょう.

このディレクトリに行き,arch/mips/aloha ディレクトリを見てください.もしくはこのディレクトリをgzipしたファイルがダウンロードできます.

このコードは明らかにまだ未完全ですが,次から示すステップにしたがってすべてを修正していくことで,あなただけのLinux/MIPSカーネルイメージを生成できるようになるはずです.

Linuxツリーにあなたのコードを組み込もう

このステップの大半は極めて単純です.

すぐに試してみたいという要求に答えるために,2001年9月28日のOSS CVSツリーに対してAlohaボートサポートを追加するための完全なパッチを見つけることができます.

カーネルイメージのコンフィグレーションと生成

これでお気に入りのコンフィグレーションツールを実行する準備ができました.まだそれほど多くのコードを追加していないので,貪欲になりすぎないでください.シリアルやシリアルコンソールなどの2,3の単純なオプションを選択するだけです.

Alohaボードに対する最小限のコンフィグサンプルです.

'make'とタイプする前に'arch/mips/Makefile'を念入りにチェックし,クロスツールチェインプログラムが正しく,PATH環境変数などで実行パスに存在することを確認してください.

さぁ,'make dep',そして'make'をタイプしてください.奇跡が起きるのを待ちましょう!

第4章 : 初期段階のprintk

あなたが幸運で,実際に前章のようにイメージが生成できたとしても,何も出力されないので,わざわざそれを実行する必要はありません.ボード固有コードはすべてが空で,LinuxカーネルはまだシリアルポートやいかなるIOデバイスともやり取りしていないので,不思議なことではありません.

Linuxカーネルが生きているかのサインはprintkの出力からわかります.その出力は最初のコンソールに送られます.前章でシリアルコンソールを有効にしたので,設定が正しければ,シリアル接続により何かを見ることができるはずです.

不運なことに,シリアルコンソールの設定はカーネル起動プロセスのずいぶん後ろの方で行われます(カーネル起動シーケンスのチャートは付録Aを参照してください).あなたの新しいカーネルはおそらくそこにたどり着く前に死ぬでしょう.初期段階のprintkパッチが役に立つ場面です.これによってCコードのできるだけ先頭の行で,printkできるようになります.

ところで,Linux/MIPSにおけるCコードの最初の行はarch/mips/setup.cファイル中のinit_arch()関数の先頭です.

カーネルバージョンが2.4.10よりも前の場合は,標準UARTシリアルポートを持つボード用の初期段階のprintkパッチはここで見けることができます.2.4.10以降の場合は,新しいprintkパッチが必要になります.もしすでにスタンドアローンの"hello, world!"プログラムが動作しているのであれば,初期段階のprintkは簡単に動作するはずで,すぐにLinuxからprintkできるようになるでしょう.

第5章 : シリアルドライバとコンソール

初期段階のprintkはなかなか便利ですが,まだ真のシリアルドライバを動かす必要があります.

標準的なシリアルポートを持っているならば,シリアルサポートを追加するには静的定義,実行時セットアップの二つのスタイルが考えられます.

静的定義は,include/asm/serial.hファイルを修正します.そのファイルを見てください.あなたのボード用にサポートを追加する方法を説明するのは難しくありません.

Linux/MIPSにボードを追加するにつれ,必ずしもserial.hが混みあってくるわけではありません.もう一つのソリューションは実行時シリアルセットアップです.実行時シリアルセットアップはパラメータのいくつかは実行時にのみ検出されるので必要になることがあります.

実行時セットアップには次の二つの要素があります.

シリアルパラメータ

パラメータの設定の大半はほとんど明確です.あまり明確ではないものを次のリストに示します.

line
実行時シリアル設定でのみ使われます.これはrs_table[]配列のインデックスです.
io_type
io_typeはシリアルレジスタにアクセスする方法を決定するのに使われます.SERIAL_IO_PORTSERIAL_IO_MEMの二つが一般的なタイプです.SERIAL_IO_PORTドライバはレジスタにアクセスするためにinb/outbマクロを使います.このときportがレジスタのベースアドレスになります.つまりio_typeとしてSERIAL_IO_PORTを指定した場合も,portパラメータを指定すべきです.
SERIAL_IO_MEMの場合は,ドライバはレジスタにアクセスするためにreadb/writebマクロを使います.このときiomem_baseにiomem_reg_shiftの指定分をシフトしたオフセットを加えた値がレジスタのアドレスになります.例えば,すべてのシリアルレジスタは4バイト境界に配置されるので,iomem_reg_shiftは2になります.
一般的にSERIAL_IO_PORTはISAバス用であり,SERIAL_IO_MEMはメモリマップトシリアルポート用です.
他にはSERIAL_IO_HUB6SERIAL_IO_GSCがあります.[HELP: これらは何用?]

非標準的シリアルポート

非標準的なシリアルポートを持っているのであれば,あなた自身でシリアルドライバを書かねばならないでしょう.

何人かは標準シリアルドライバからコードを派生させました.serial.cファイルは6000行を越えており,残念ながら,これは非常に手ごわい作業です.

幸運なことに別の手段が存在します[THANKS: Fillod Stephane].それは汎用シリアルドライバ(drivers/char/generic_serial.c)です.このファイルはハードウェア独立なシリアル用ルーチンの集合を提供しています.あなたの作業は割込みハンドラルーチンのようなハードウェア依存のルーチンを提供するだけです.たくさんの例があります.drivers/char/Makefileの中身をのぞいて,generic_serial.oファイルをリンクしているドライバを見てみましょう.

[HELP: プロプライエタリなシリアルドライバコードを書いたり,generic_serial.cファイルを使った経験を共有できればうれしく思います.]

第6章 : KGDB

多くのLinuxカーネル開発者にとって,kgdbは人命救助具です.kgdbを使えば,カーネルを動かしながらデバッグできます! ブレークポイントの設定やソースコードレベルでのシングルステップ実行が可能です.

kgdbを使うには,ターゲット上に専用のシリアルポートが必要となり,開発ホストとクロスケーブルを使って接続します.シリアルコンソールとしても使うのであれば,ターゲットに二つのシリアルポートが必要であることを暗に意味します.一つのシリアルポート経由でカーネルデバッグとシリアルコンソールの両方を行なうことも可能ですが,これに関しては本章の後半でふれることにします.

カーネルをコンフィグするとき,"kernel hacking"の下にリストされたCONFIG_REMOTE_DEBUGオプションを有効にし,新しいイメージを作りましょう.最後のリンク段階で次の二つのシンボルが見つからないことにすぐ気づくことになるでしょう.

int putDebugChar(uint8 byte)
uint8 getDebugChar(void)

あなたのボート用にこれら二つの関数を提供する必要があります.例として,ここにDDB5476ボード用のdbg_io.cがあります.DDB5476はこれら二つの関数を実装するために,標準的なUARTシリアルポートを使います.

これら二つの関数を供給すれば,カーネルをデバッグする準備が整います.新しいカーネルイメージを実行しましょう.初期段階のprintkパッチも使っているのであれば,コンソールに次のような何かを見ることができるはずです.

Wait for gdb client connection ...

すでにターゲット上のシリアルポートとホストのシリアルポート(COM0とか)間をシリアルケーブルで接続しているのであれば,次のように適切なボーレートを設定し,ホスト上のクロスgdbを実行できます.

stty -F /dev/ttyS0 38400
mips_fp_le-gdb vmlinux
gdbプロンプトが表示されたら,次のようにタイプします.
target remote /dev/ttyS0
すると,kgdbを介してカーネルと対話できるようになるはずです.あなたが十分幸運であれば!

kgdbを使う上でのいくつかのコツを次に挙げます.

万が一,ボートに一つしかシリアルポートがない場合はどうしますか?

一つのシリアルポートしか持っていないボードもあります.シリアルポートをシリアルコンソールとして使うのであれば,いくつかのトリックを使わない限り本当はkgdb用に使えません.

二つのソリューションがあります.一つはkgdbコンソールで,もう一つはkgdb demuxingスクリプトを使う方法です.

kgdbコンソールを使うのは簡単です."Kernel hacking"サブメニュ下の"Remote GDB kernel debugging"を選択したとき,"Console output to GDB"が見えるようになります.単にそれを選択するだけです.実際,このオプションは二つ目のシリアルポートを持っていたとしても,使うことができるくらい簡単に使えます.

しかしながら,このオプションには制限があります.カーネルからユーザランドに遷移したとき,コンソールは動作を止めてしまいます[HELP: これを確認してください].私はkgdbコンソールドライバが割込みなどのような他の要求を処理するように完全に作られていないことが理由だと疑っています.[HELP: ボランティアはいませんか?]

二つ目のオプションはBrian Moyleによって書かれたkgdb_demuxと呼ばれるスクリプトを使うことです.それは通常,ttya0とttya1の二つの仮想ポートを作ります.そして(ttyS0のような)本物のシリアルポートを監視し,コンソールへのトラヒックをttya0に,kgdbへのトラヒックをttya1に送ります.あとは/dev/ttya0でminicomを起動し(ポートの設定は気にする必要はありません),/dev/ttya1でkgdbを動かせばよいだけです.

ここからtar ballをダウンロードできます.使い方を次に示します.

第7章 : CPUサポート

[TODO]

第8章 : ボードサポート - 割込み

ここまでしたがってきたのであれば,BogusMIPSキャリブレーションの段階でカーネルがハングしていることでしょう.その理由は単純で,割込みコードがなく,jiffiesが更新されていないからです.したがってキャリブレーションはけっして実行されません.

割込みコードを書きはじめる前に,まずハードウェアは本当に学びがいがあることをこころしてください.特にすべての割込みソースとそれらに関連するコントローラ,ルーティングを識別することに注意しましょう.

そして一般的には次のようなものを含む戦略を立てましょう.

割込みコード概要

割込みサービスを実現するために,四つのコード部が協調動作します.

IRQ検出/ディスパッチ
これは一般的にint_handler.Sファイル中に書かれたアセンブリコードです.ときどき複雑なIRQ検出のためにCで書かれたセカンドレベルのディスパッチコードも存在します.単一のIRQソースを識別,選択した結果を整数値で表現し,そして関数do_IRQ()に渡します.
do_IRQ()
do_IRQ()はarch/mips/kernel/irq.cファイル内で提供されます.これはIRQ処理のための共通フレームワークを提供し,個々のIRQコントローラコードに対して,特定の割込みを有効/無効するように要求できます.実際の処理は,ドライバが供給する割込み処理ルーチンに存在し,これを呼び出します.
hw_irq_handler
各IRQソースに関連付けられた構造体で,特定のIRQをどのように扱うべきなのかdo_IRQ()にたずねるための関数ポインタの集合です.
ドライバ割込み処理コード
実際の仕事を行なうコードです.

移植において,IRQ検出/ディスパッチコード,システムで使われている新しいIRQコントローラ用のhw_irq_handlerコードを書く必要があることは明らかです.さらに,IRQセットアップや初期化コードもあります.

IRQコントローラとしてのCPU

たいていのR4K互換MIPS CPUは八つの割込みソースをサポートする能力を持っています.最初の二つはソフトウェア割込みに,最後の一つは典型的にCPUカウンタ割込みとして使われます.したがって五つまでの外部割込みソースを持つことができます.

幸運なことにシステム内でCPUだけがIRQコントローラであれば,やるべきことは終わっています! hw_irq_handlerコードはarch/mips/kernel/irq_cpu.cファイルに書かれています.あなたがマシンに対してすべきことは"CONFIG_IRQ_CPU"を定義するだけです.そしてirqセットアップルーチン内でmips_cpu_irq_init(0)を呼んでいることを確認してください.この呼出しは割込み記述子を初期化し,IRQ番号を0から7の範囲に設定します.

このファイルには,割込みをマッチングして,ディスパッチするコードが見つかるでしょう.

割込みカスケードの設定

CPUのIPピンに直接つながるより多くの割込みソースを持っているかもしれません.その場合は,割込みをカスケードすることによって,セカンダリ,またはサードレベルの割込みコントローラを一つかそれ以上のCPU IPピンにつなぐことができます.

割込みをカスケードする方法にはddb5477,korva,ospreyのようにたくさんの例があります.次に簡単にまとめましょう.

hw_irq_controller構造体

hw_irq_controller構造体はhw_interrupt_type構造体の別名としてinclude/linux/irq.hファイルにて定義されています.

struct hw_interrupt_type {
        const char * typename;
        unsigned int (*startup)(unsigned int irq);
        void (*shutdown)(unsigned int irq);
        void (*enable)(unsigned int irq);
        void (*disable)(unsigned int irq);
        void (*ack)(unsigned int irq);
        void (*end)(unsigned int irq);
        void (*set_affinity)(unsigned int irq, unsigned long mask);
};

irq_cpu.cはhw_irq_controllerメンバ関数を書くためによいサンプルコードになります.各関数に対するプログラミング上の注意点を次に挙げます.

const char * typename;
コントローラ名./proc/interrupts下に表示されます.
unsigned int (*startup)(unsigned int irq);
request_irq()setup_irqが呼ばれると実行されます.ここでこの割込みを有効にする必要があります.その他にIRQ固有の初期化を行ないたい場合は,ここで行ないます(おそらく割込みのための電力供給など).
void (*shutdown)(unsigned int irq);
free_irq()が呼ばれると実行されます.割込みを無効にし,必要であればIRQ固有のクリーンアップを行なう必要があります.
void (*enable)(unsigned int irq)void (*disable)(unsigned int irq)
ドライバコードによって交互に使われるenable_irq(), disable_irq(),そして disable_irq_nosync()を実装するために使われます.
void (*ack)(unsigned int irq)
割込みに対する確認応答をする際,do_IRQ()の先頭で,ack()が実行されます.同一の割込みソースに対して再帰的に割り込まないように,ここで割込みを無効にする必要があると思います.[HELP: 誰か確認できますか?]
void (*end)(unsigned int irq)
割込みを処理した後,do_IRQ()によって呼ばれます.ack()関数によって割込みが無効なっていれば,ここで有効にすべきです.[HELP: 一般的にそれ以外にやることはあるのか?]
void (*set_affinity)(unsigned int irq, unsigned long mask)
SMPマシンにおいて,特定のCPUに対する割込み処理アフィニティを設定するために使われます.[TODO] [HELP]

IRQ初期化コード

IRQの初期化はinit_IRQ()です.現在,各ボードごとに供給されていますが,将来はMIPS共通ルーチンになり,そこからさらにボート固有の関数board_irq_init()が実行されるようになると思われます.board_irq_initは関数ポインタであり,<my_board>_setup()関数によってポインタ値を割り当てる必要があるでしょう.

とにかく,次に示すコードは通常のinit_IRQルーチンに対するスケルトンコードです.

extern asmlinkage void vr4181_handle_irq(void);
extern void breakpoint(void);
extern int setup_irq(unsigned int irq, struct irqaction *irqaction);
extern void mips_cpu_irq_init(u32 irq_base);
extern void init_generic_irq(void);

static struct irqaction cascade =
        { no_action, SA_INTERRUPT, 0, "cascade", NULL, NULL };
static struct irqaction reserved =
        { no_action, SA_INTERRUPT, 0, "reserved", NULL, NULL };
static struct irqaction error_irq =
        { error_action, SA_INTERRUPT, 0, "error", NULL, NULL };

void __init init_IRQ(void)
{
        int i;
        extern irq_desc_t irq_desc[];

	/* hardware initialization code */
	....

	/* this is the routine defined in int_handler.S file */
        set_except_vector(0, my_irq_dispatcher);

	/* setup the default irq descriptor */
	init_generic_irq();

        /* init all interrupt controllers */
        mips_cpu_irq_init(CPU_IRQ_BASE);
	....

        /* set up cascading IRQ */
        setup_irq(CPU_IRQ_IP3, &cascade);

	/* set up reserved IRQ so that others can not mistakingly request
	 * it later.
 	 */
        setup_irq(CPU_IRQ_IP4, &reserved);

#ifdef CONFIG_DEBUG
	/* setup debug IRQ so that if that interrupt happens, we can 
	 * capture it.
	 */
        setup_irq(CPU_IRQ_IP4, &error_irq);
#endif

#ifdef CONFIG_REMOTE_DEBUG
        printk("Setting debug traps - please connect the remote debugger.\n");
        set_debug_traps();
        breakpoint();
#endif
}

最後通達

本章の記述は現在,新しい割込み処理スタイルと呼ばれているものです.おそらく将来は割込み処理コードを書くための唯一有効な方法になると思われます.この方法を動作するにはCONFIG_NEW_IRQを定義する必要があります.

いくつかのボードではCONFIG_ROTTEN_IRQオプションを定義することで,arch/mips/kernel/old-irq.cファイルを使っています.old-irq.cは非常にPC中心的で,ひどく時代遅れです.どちらにするか悩まないでください.

多くの他のボードはどちらのIRQコンフィグオプションも定義していません.それらは独自のIRQルーチンを使っています.将来,新しいスタイルのIRQルーチンに移行すべきです.

第9章 : ボードサポート - システム時間とタイマ

Linuxは実際のカレンダ時刻と起動してからの時間を取得するためにRTCデバイスに頼っています.さらにjiffiesと呼ばれるティックカウントを進めるためのシステムタイムもそうです.適切な時間とタイマコードがないと,Linuxは動きません.実際,jiffiesが加算されないので,起動プロセス中のcalibrate_delay()で止まってしまったことでしょう.(Linux/MIPS起動シーケンスの詳細については付録Bを参照してください.)

Linux/Documentations/mips/time.README にすばらしい文書があります(少々はずかしいですが:-0) かならず読んでください.

次に時間とタイマサービスを実装する上でのコメントをいくつか挙げておきます.

第10章 : ボードサポート - PCIサブシステム

おそらくPCIサブシステムは移植過程で扱わなければならないもっとも複雑なコードでしょう.PCIサブシステムを動作させるにあたっての鍵は,PCIバス自体,そしてLinuxのコードレイアウトと実行フローを十分に理解することです.移植の他の部分同様,結局,実際にコードを書くのは本当に最小限であることがわかるでしょう.

リファレンス

Pete PopovがLinux/MIPS向けのすばらしいPCI READMEファイルを書いています.これはlinux/Documentation/mips/pci/pci.READMEにあります.強く推薦します.

PCIバス自体についてより知りたい方は,MindShare Inc.より出版されている書籍PCI System Architectureをお薦めします.

PCIバスの概要

PCIバスの実際を要約すると次のようになります.

LinuxにおけるBARの割当て

すべてのIBM PC互換機では,BARはBIOSによって割り当てられるので,Linuxは単にバスをスキャンし,BARの値を記録するだけです.

いくつかのMIPSボードはBARをファームウェアによって割り当てることで,これと同様なアプローチを採っています.しかし,ファームウェアによるBAR割当ての品質には非常に幅があります.いくつかのファームウェアはオンボードPCIデバイスにはBARを割り当てるだけで,すべてのアドオンカードは無視します.その場合は,Linuxは単にファームウェアによる割当てに頼ることはできません.

ファームウェアの割当てに頼ることは,ファームウェアによって設定されたアドレス範囲を変えてはいけないという別の危険もあります.つまり,もしファームウェアが0x10000000から0x14000000のPICメモリ空間に割り当てた場合,Linuxからそれを別の場所に動かすことは実際にできません.

Linuxにはこれを修正する三つの方法があります.

最初の方法はボード設定ルーチンにおいて手動でBARの割当てを修正する方法です.これはボードに任意のPCIカードが挿さったPCIスロットがない場合に限り動作します.重複したアドレス範囲に割り当てることはできないので,ファームウェアによって割り当てられたPCI資源の存在を注意深く調べる必要があります.

二番目の方法はLinuxがPCIバスのスキャンを始める「前」に完全にPCI資源を割り当てる方法です.つまり,ファームウェアによって割り当てられたいかなるPCI資源も放棄し,自力で新しい割当てを行ないます.このアプローチはアドレス範囲と資源割当てを完全に制御できます.CONFIG_PCI_AUTOオプションとarch/mips/kernel/pci_auto.cファイルによって,非常に簡単に実現できます.本章ではこのアプローチに焦点をあてます.

もう一つの方法はLinuxがPCIバスのスキャンを終えた「後」にpci_assign_unassigned_resources()関数を呼び出すことです.これは最近の2.4.xカーネルのdrivers/pci/setup-bus.cファイルで定義されています. この関数の以前のバージョンでは,LinuxはBARが適切に割り当てられていないPCIデバイスに資源を割り当てていました.最近のバージョンでは(サイズをベースにした「最適な」資源割当てを行ないます),どうもPCI資源を完全に再割当てしているようです.つまり,pci_auto.cファイルでやっていることとまったく同じだと言えます.[HELP: 誰か確認できますか?]

[DEBATE] pci_autoアプローチとassign_unassigned_resourceアプローチにはそれぞれ利点と欠点があります.今すぐにではなくても次に挙げる点を理解できるようになるには,理想的にはPCIサブシステムを完全に書き直すべきです.

PCI起動シーケンス

  1. do_basic_setup()はdrivers/pci/pci.cで定義されているpci_init()を呼びます.
  2. pci_init()は最初にpcibios_init()を呼びます.もしCONFIG_NEW_PCIが有効であれば,pcibios_init()はarch/mips/kernel/pci.cファイルで実装されています.無効であれば,ボード依存コードが提供する必要があります.
  3. さらに,pcibios_init()は完全なPCI資源割当てのためにpciauto_assign_resources()を呼ぶかもしれません.
  4. pcibios_init()内のどこかでpci_scan_buf()が呼ばれます.マシンが複数のホストPCIコントローラを持っているならば,pci_scan_bus()はそれぞれのトップレベルPCIバスに対して呼ばれるべきです.pci_scan_bus()が適切に実行できる前に,バス番号を設定しておくべきようです.
  5. pci_scan_bus()が呼ばれた後,pcibios_init()は完全なPCI資源割当てを行なうために,状況に応じてpci_assign_unassigned_resources()を呼びます.
  6. pcibios_init()はさらにいくつかの修正(資源,IRQなど)も行なうでしょう.
  7. pcibios_init()から戻ると,pci_init()デバイスベース修正の最終ラウンドを行ないます.

本章では,CONFIG_NEW_PCIとCONFIG_PCI_AUTOの両方が有効になっている場合のアプローチに焦点を当てます.

PCIドライバインタフェース

PCIにまつわるすべての騒動は,結局すべてのPCIデバイスドライバがなかよく動作できるように構造体を設定することにあります.どのようにPCIデバイスドライバがPCI資源にアクセスするかを知ることは,どのようにPCIの初期化と設定を行なうべきかを理解する助けになるでしょう.

ホストPCIコントローラの設定

ここまでの議論でわかるように,1)PCIメモリ空間とCPU物理アドレス空間を1対1にマッピングする,2)PCI IO空間の先頭部分を物理アドレス空間のアドレスブロックにマッピングするようにホストPCIコントローラの設定が必要になります.

通常,ホストPCIコントローラはPCIメモリ空間を物理アドレス空間のウィンドウにマッピングできます.ベースアドレスはba_mem,サイズはsz_memeになります.そして普通はそのアドレスを固定オフセットoffからの別アドレスに変換します(offは正値にも負値にもなることに注意してください).つまりドライバがba_mem + x (0 <= x < sz_mem)にアクセスした場合,ホストPCIコントローラはそのコマンドを捕らえ,アドレスba_mem+off+xに対するPCIメモリアクセスに変換します.

1対1マッピングを維持することは,offが0になるように設定しなければならないことを暗喩します.このように設定したとしても,[0x0, ba_mem]と[ba_mem + sz_mem, 0xffffffff]のPCIメモリ範囲にはアクセスできないことに注意してください.

さらに,PCIデバイスがDMA転送するために,システムRAMをPCIメモリバス上のアドレス0x0(そのアドレスが物理アドレス空間に存在することを仮定しています)に見えるようにしなければいけません.

通常,PCI IO空間の先頭部分は物理アドレス空間内の別ウィンドウ[ba_io, ba_io + sz_io]にマッピングされています.言い換えれば,PCI IO空間の範囲[0, sz_io]は[ba_io, ba_io + sz_io]と一致することになります.当然,mips_io_port_baseba_ioに設定しなければいけません.

上記の設定は典型的にはボード固有セットアップルーチン内(例えば,<board>_setup())で行なわれます.ioport_resourceiomem_resourceも次のように設定されます.

	ioport_resource.start = 0x0;
	ioport_resource.end = sz_io;
	iomem_resource.start = 0x0;
	iomem_resource.end = sz_mem;

これらの変数は資源のルートです.簡単のためにendが0xffffffffになるようにも設定できます.

ボード固有関数と変数

次に実装が必要なボード固有関数をリストアップします.くり返しになりますが,CONFIG_NEW_PCIとCONFIG_PCI_AUTOであることを仮定しています.

PCIコードを書くためのコツ

第11章 : イーサドライバとネットワーキング

[TODO]

第12章 : Romfsルートファイルシステム

[TODO]

第13章 : デバッギング

最後章になりましたが,もっとも読まれる章になることでしょう.本章では,一般的に使われているデバッギングのコツをいくつか挙げていきます.

Kernel Oops

ksymoops は呼出しスタックとトレースに隠された数々を解読するプログラムです.しかし,私が知る限り,現在MIPS用に動作するバージョンはありません.[HELP: 私が間違っていたら訂正してください.]

私は普段Phil Hollenbackによって書かれたcall2symスクリプトを使っています.呼出しトレース部分をカットアンドペーストしてスクリプトに与えるだけで,クラッシュ時の可能性がある関数呼出しスタックを表示します.いくつかのシンボルは間違っているかもしれませんが,本物も表示されることに注意してください.あなた自身の判断が必要になります.

Appendix A: Linuxの起動シーケンス(2.4.x)

kernel_entry() - arch/mips/kernel/head.S
   set stack;
   prepare argc/argp/envp;
   jal init_arch - arch/mips/kernel/setup.c
        cpu_probe() -
        prom_init(...) - arch/mips/ddb5074/prom.c
        loadmmu()
        start_kernel()  - init/main.c
                setup_arch(&commaind_line); - arch/mips/kernel/setup.c
                        ddb_setup()         - arch/mips/ddb5074/setup.c
                parse_options(command_line);
                trap_init();
                init_IRQ();                 - arch/mips/kernel/irq.c
                sched_init();
                time_init();
                        if (board_time_init) board_time_init();
                        set xtime by calling rtc_get_time();
			pick appropriate do_gettimeoffset()
			board_timer_setup(&timer_irqaction);
                softirq_init();
                console_init();
                init_modules();
                kmem_cache_init();
                sti();			/* interrupt first open */
                calibrate_delay();
                mem_init();
                kmeme_cache_sizes_init();
                fork_init();
                filescahe_init();
                dcache_init();
                vma_init();
                buffer_init();
                page_cache_init()
                page_cache_init();
                kiobuf_setup();
                signals_init();
                bdev_init();
                inode_init();
                file_table_init();
                ipc_init();
                check_bugs();
                smp_init();
                kernel_thread(init, ...)
                cpu_idle();


init() - init/main.c
  - lock_kernel();
    do_basic_setup();
      - [MTRR] mtrr_init();
        [SYSCTL] sysctl_init();
        [PCI] pci_init();
        [SBUS] sbus_init();
        [PPC] ppc_init();
        [MCA] mca_init();
        [ARCH_ACORN] ecard_init();
        [ZORRO] zorro_init();
        [DIO] dio_init();
        [MAC] nubus_init();
        [ISAPNP] isapnp_init();
        [TC] tc_init();
        sock_init();
        [BLK_DEV_INITRD] <set up variables>
        do_initcalls();
        filesystem_setup();
        [CONFIG_IRDA] irda_device_init();
        [PCMCIA] init_pcmcia_ds();
        [HOTPLUG] net_notifier_init();
        mount_root();
        mount_devfs_fs ();

    free_initmem();
    unlock_kernel();

    if (open("/dev/console", O_RDWR, 0) < 0)
                printk("Warning: unable to open an initial console.\n");
    (void) dup(0);
    (void) dup(0);

    if (execute_command)
                execve(execute_command,argv_init,envp_init);
    execve("/sbin/init",argv_init,envp_init);
    execve("/etc/init",argv_init,envp_init);
    execve("/bin/init",argv_init,envp_init);
    execve("/bin/sh",argv_init,envp_init);
    panic("No init found.  Try passing init= option to kernel.");

Appendix B: 謝辞

次のリストに挙げた人々は気前よくフィードバックを与えてくれた.できる限り完全なリストを維持する努力にもかかわらず,残念ながら多くの人々がまだ抜けている.

	Dirk Behme <dirk.behme@de.bosch.com>
	Fillod Stephane <FillodS@thmulti.com>
	Geoffrey Espin <espin@idiom.com>
	Gerald Champagne <gerald.champagne@esstech.com>
	Henri Girard <khgirard@broadbandnetdevices.com>
	Neal Crook <ncrook@micron.com>
	Steven J. Hill <sjhill@realitydiluted.com>
	TAKANO Ryousei <takano@axe-inc.co.jp>

Appendix C: 更新履歴

2002/12/11
Neal Crookからのコメントを取り入れる.
2002/08/23
更新履歴を追加する.外部リンクを_topフレームにリンクする.ツールを更新する(Henri Girardに感謝).
2004/01/26
Steven Hillからの初期化バッチの変更と更新を含めた(多謝).外部リンクは別ウィンドウで表示されるようにした.