2007年12月21日金曜日

NetBSDでGPIOを使う(for ALIX) (3)

カーネル関係の移植作業は、カーネルの大きな構造を理解していれば、あとは単純な作業になる。が、「カーネルの大きな構造の理解」は短期間に取得することは比較的難しい気がする。なんか長い間小さい作業を進めているといつかわかっている、みたいな理解の仕方をする人が多いのではないだろうか。少なくとも私はそうだった。
細かい作業の履歴はとっていないので、ここではglxpcib.cの変更点の要点を列挙して注釈をつけてみることにする。もし、万が一カーネルに手を出そうとする人の参考になればうれしいな。ちなみにNetBSDにsend-prしたglxpcib.cとOpenBSDのglxpcib.cとの差分はdiff -uでみたところ393行だった。OpenBSDが383行、NetBSDが484行になったので、純増で101行。結構差分が大きかったことに調べてびっくり。

やらないといけないことは、大きく分けると

  1. PCI deviceとして検出してアタッチできるようにする
  2. カーネルの既存のフレームワークと協調できるようする
  3. デバイスとして動くようにする
という感じ。1ができるようになるためには、コンパイルが通ってkernelを作成できるようになっていないといけないので、とりあえず中身は空でもいいからkernelを作成できるように外側から攻めていく方が楽だと思う。

PCIバスは昔のISAなどと比べて、デバイスの検出、リソース管理の部分がよくできているので、OS側で扱うのも非常に簡単だ。PCIデバイスにはPCIコンフィグレーションレジスタがかならず実装されていて、そこにグローバルユニークなベンダーID、プロダクトID、デバイスクラスなどが記録されているのでそれを読めば大体そのPCI空間にいるデバイスがなにかを判断できる。OSではその値を比較して具体的な処理を進めれば間違いは無い。NetBSDでは、hogehoge_match()とhogehoge_attach()という関数を使うのが普通なので、いろいろなPCIデバイスのドライバのコードをみればすぐに理解できると思う。CS5536のデバイス検出のコードは以下のようになる。

int
glxpcib_match(struct device *parent, struct cfdata *match, void *aux)
{
struct pci_attach_args *pa = aux;

if (PCI_CLASS(pa->pa_class) != PCI_CLASS_BRIDGE ||
PCI_SUBCLASS(pa->pa_class) != PCI_SUBCLASS_BRIDGE_ISA)
return (0);

if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD &&
PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_CS5536_PCIB)
return (2); /* supersede pcib(4) */

return 0;
}

PCI_VENDOR,PCI_PRODUCTなどをみて適切なデバイスを認識できれば処理が進む。NetBSDではベンダーコードなどはsrc/sys/dev/pci/pcidevsというファイルに列挙されている。新しいデバイスを登録するときはそこにエントリーを追加してmake -f Makefile.pcidevsでデータベースを生成しなければならない。

*_match, *_attachをカーネルのデバイスコンフィギュレーションフェーズに登録するためのマクロとして、

CFATTACH_DECL(glxpcib, sizeof(struct glxpcib_softc),
glxpcib_match, glxpcib_attach, NULL, NULL);

を使う。最初は、おまじないだとおもってコードの最初の方にかいておくと良いだろう。この辺はPCIデバイスなら全部同じなので特に悩むことはない。デバイス依存のコードは実際にデバイスをアタッチするところから始まる。デバイス毎の情報を保存するsoftc構造体(今回はstruce glxpcib_softc)を設定し、デバイスをアクセスするためのPCI空間の調整やメモリーへのマップを設定して、具体的なデバイスの初期化を行わなければならない。
glxpcib_attach()の中を全部説明すると大変なので、PCIデバイス一般な説明をしておく。OSがPCIのバスの管理をしてくれているので、デバイスドライバは自分が使うメモリ空間にだけ気をつけておけばそれなりに使える。bus_space_map()をつかってそのPCIデバイスのIO空間をメモリにマップし、bus_space_write/read_*()でその空間の読み書きを行うというイメージでデバイスにアクセスできる。
あとは仕様書をみて、必要な機能のレジスタを読んだり書いたりする方法を実装すればいい。

CS5536はたくさんの機能が1チップにまとまっている。これは物理的な1チップに複数のPCIデバイスが含まれている=複数のデバイスIDがある、ということを意味しているのだが、各PCIデバイスにも複数の機能が含まれてもいる。今扱っているPCI-ISAブリッジにもPCI-ISAブリッジ以外にGPIOやMFGPT(Multi function General Purpose Timer)などの機能がある。PCI-ISAブリッジとしてmatch/attachをする際にGPIOやMFGPTをつかったWatchdog timerも設定してあげなければ使えるようにならない。
たとえば、GPIOの空間は以下に示す用に、GPIOのIO空間のベースアドレスなどをつかってio handleを取得してそのio handoleをつかって読み書きをする、という感じ。うまく設定できればconfig_found_ioをつかって、gpioバスができたことをOSに通知してあげると、OSのGPIOフレームワークがそれ以降の処理を引き取ってくれる。

/* map GPIO I/O space */
sc->sc_gpio_iot = pa->pa_iot;
gpiobase = rdmsr(MSR_LBAR_GPIO) & 0xffff;
if (!bus_space_map(sc->sc_gpio_iot, gpiobase, 0xff, 0,
&sc->sc_gpio_ioh)) {
aprint_normal(", gpio\n");
[このへんでいろいろ設定]
gpio = 1;
}
/* Attach GPIO framework */
if (gpio)
config_found_ia(&sc->sc_dev, "gpiobus", &gba, gpiobus_print);


全部うまく動くと、NetBSDがブートするときに

glxpcib0 at pci0 dev 15 function 0
glxpcib0: Advanced Micro Devices CS5536 PCI-ISA Bridge (rev. 0x03)
timecounter: Timecounter "CS5536" frequency 3579545 Hz quality 1000
glxpcib0: watchdog, gpio
gpio0 at glxpcib0: 32 pins
gpioow0 at gpio0 pins 6: DATA[6] open-drain pull-up
onewire0 at gpioow0

という表示が拝めて、うれしい気分になる、というのがこの作業の最初の到達点だ。
パッチはすでにNetBSDにsend-prしてある。kern/37577に全ソースがついているので興味があったら参照してほしい。(ある程度時間がたっていればNetBSDにマージされていることを期待)

使い方は次回のエントリーで。

0 件のコメント: