そういえば、ALIXボードでNetBSDを動かしているときに不可解なハングアップに陥る状況があった。カーネルを触っている時期だったので、ずいぶん「自分のコードがわるい」んだとおもって問題点を探すために時間をつかってしまった。
ハングアップするのは、「CS5536のMFGPTのIO空間にアクセスしてなにかを書き込んだとき」であったが、どう考えてもそれだけで落ちるとは考えられない、と思えるようになるまでデバグを続けて最後に確認したのが、BIOSの設定。
ALIXのTinybiosには [MFGPT workaround] というメニューがある。これはCS5536のアクセスでマシンがハングアップ*しないように*ある機能らしいので、役に立つことはあっても害にはならないだろうと思っていたのが、この機能をdisableすると問題なくMFGPTの空間への書き込みができるようになった。
もしかしてはまる人がいるかもしれないので、メモとして残しておく。
2007年12月22日土曜日
ALIXとNetBSD(補足)
NetBSDでWatchdog timerを使う(for ALIX) (4)
Watchdogタイマとして動かすためにsysmonフレームワークから呼び出される関数は、
- int glxpcib_wdog_setmode(struct sysmon_wdog *smw);
- int glxpcib_wdog_tickle(struct sysmon_wdog *smw);
/*
* sysmon_wdog_ktickle:
*
* Kernel watchdog tickle routine.
*/
void
sysmon_wdog_ktickle(void *arg)
{
[中略]
callout_reset(&sysmon_wdog_callout,
WDOG_PERIOD_TO_TICKS(smw->smw_period) / 2,
sysmon_wdog_ktickle, NULL);
}
mutex_exit(&sysmon_wdog_mtx);
}
と書いてあって、基本的には(カーネルモードのwatchdogの場合は)設定した時間の半分の周期でタイマを更新することになっていた。
今回はこれらの2つの関数を書くにあたって、共通的な要素を行う基本関数として
- glxpcib_wdog_disable()
- glxpcib_wdog_enable()
- glxpcib_wdog_reset()
- glxpcib_wdog_setmode()はdisable(), enable(),reset()の組み合わせとタイマ値の設定
- glxpcib_wdog_tickle()はreset()を呼び出す
int
glxpcib_wdog_setmode(struct sysmon_wdog *smw)
{
struct glxpcib_softc *sc = smw->smw_cookie;
if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
mutex_enter(&sc->sc_mtx);
glxpcib_wdog_disable(sc);
mutex_exit(&sc->sc_mtx);
return 0;
}
if (smw->smw_period == WDOG_PERIOD_DEFAULT)
smw->smw_period = 32;
else if (smw->smw_period > 0xffff) /* too big */
return EINVAL;
mutex_enter(&sc->sc_mtx);
glxpcib_wdog_enable(sc);
glxpcib_wdog_reset(sc);
mutex_exit(&sc->sc_mtx);
return 0;
}
ここまでで、デバイスドライバ部分は全部できあがったのだが、ここではたと気がついたことがある。「これってどうやって正しく動いていることを検証すればいいんだろうか?」と。
Watchdogタイマはシステムが動かなかったときにリセットするために使われるわけだが、任意にシステムを止めたりする方法はあまりしらない(別のHWのデバイスドライバをごにょごにょすればできないこともないがめんどくさい)。しょうがないから、
- printfをいれて関数の呼び出し状態をみてみる
- ユーザランドで指定した値と独立してある特定の時間(10秒)までカウントが進めばリセットするカーネルを作ってみる
というわけで、CS5536のMFGPTをつかってWatchdogタイマを動かすデバイスドライバは書けて、正しく動いているっぽいことが分かった。最後に、NetBSDでWatchdogタイマを使うように設定する方法を書いておく。
使うコマンドは、
- wdogctl
# wdogctl
Available watchdog timers:
cs5536wdt, 32 second period
# wdogctl -k -p 10 cs5536wdt
# wdogctl
Available watchdog timers:
cs5536wdt, 10 second period [armed, kernel tickle]
# wdogctl -d
# wdogctl
Available watchdog timers:
cs5536wdt, 10 second period
のように使う。引数なしでwdogctlを呼ぶと使えるタイマの名前とwatchdogが起動する長さが表示される。2行目は10秒でwatchdogがかかるようにkernelがタイマを更新するモードでcs5536wdt(今回つくったタイマ)を用いてwatchdogを実行するように指示する。もう一度引数なしwdogctlを呼ぶと、設定が反映されて動いていることがわかる。
途中でwatchdogタイマを止めたいときは-dオプションをつけてwdogctlを実行すればよい。
もし、起動時に設定したい場合は/etc/rc.confにwdogctl=YES wdogctl_flags="-k -p 10 cs5536wdt"などとと書いておけば動く。wdogctl_flagsの中身はwdogctlに渡す引数と同じでよい。
というわけで、PC EnginesのALIXボードが届いてから、2日ほど楽しくカーネル遊びをする時間をとれた。いままでGPIOとかWatchdogとかを使ったことがなかったが、動くようにするために結構深い理解ができたような気がする。でも、次にこの知識が役にたつのはいつだろう(とちょっとだけ不安)。
さて、次は一番ほしい機能であるI2Cバスのサポートに手をつけたいなーと思っている。
が、まだ何もやっていないので本当にできるのか?いつになるかは?は不明。とりあえずこの3連休は手元にI2Cデバイスもロジックアナライザもないので難しそうだ。
NetBSDでWatchdog timerを使う(for ALIX) (3)
なんだかデバイスドライバ作成講座みたいになってきて、UNIX系雑誌の原稿を書いているみたいな気分になってきた。まあいいか。もし変なところとか、質問があったらコメントしてもらえれば対応できることもあるかもしれない、ところが雑誌とは違うと納得することにした。あと、タイマのデバイスドライバは操作する場所もすくないし、滅多なことで変なこともおこらないので入門用としてはちょうどいいネタかもしれないので、一度書いておくとあとあと便利かも。
とりあえず、今回の話は手元にCS5536のデータブックを置いておくと少しはわかるかもしれない。650ページもあるドキュメントであんまり画面上で参照したい大きさじゃない。昔みたいに印刷物でくばってくれないかなあ、とこのデバイスを触っている最中よく思った。MFGPTの仕様はあちこちに分散されているけど、今回一番必要になるのは制御用のレジスタマップと書き込むべき内容が書いてある「6.17 Multi-Function General Purpose Timer Register Descriptions」(p527)あたりだ。タイマにしてはできることが多いのでたくさん設定できることがあるけれど、今回つかうのは
- 6.17.2.4 MFGPT[x] Setup (MFGPT[x]_SETUP) (p522)
- 6.17.2.3 MFGPT[x] Up Counter (MFGPT[x]_CNT) (p521)
- 6.17.2.2 MFGPT[x] Comparator 2 (MFGPT[x]_CMP2) (p520)
6.17.2.4をじーっと眺めると、MFGPT0_SETUPレジスタのビット構造の図がある。
このレジスタは16bitで各ビットの値を設定するとタイマのカウンタの動作を定義できる。注意しないといけないのは、いくつかのビットは最初の一回しか書き込みできない点で、それ以降はそのビットへの変更は反映されない。たとえば、bit0-3のMFGPT_SCALEはカウンタのプリスケーラーの値を設定するビットフィールドだが一度設定すると途中でプリスケーラーの値を変更できない(らしい)。MFGPT_CLKSELはカウンタのクロックを32KHzか14.328MHzか選択するために使うがこれも同様に最初に設定したら変更できない。Watchdogタイマはms単位ではあまり使わないだろうということと、比較的長い周期が設定できたほうがいいだろう、という判断でCLKSELでは32KHzを、プリスケーラは32K分周を選択することにした。つまり32KHzを32K分周するためカウンタは1Hzで駆動される。プログラムも簡単になるし、16ビットカウンタの最大値の65535秒=約18.2時間までの長周期を設定できる、ようになる。
初期設定は、glxpcib_attach()の中で行う。レジスタの値を変更するためには、PCIのアドレス空間をアクセスできるようにしておかないといけない、GPIOの時と同様にbus_space_map()を使ってio handleを取得しておく。該当部分のコードはこんな感じ。
/* Attach the watchdog timer */
wdtbase = rdmsr(MSR_LBAR_MFGPT) & 0xffff;
if (bus_space_map(sc->sc_iot, wdtbase, 64, 0, &sc->sc_ioh)) {
aprint_error("%s: can't map memory space for WDT",
sc->sc_dev.dv_xname);
} else {
mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_HIGH);
bus_space_write_2(sc->sc_iot, sc->sc_ioh,
AMD5536_MFGPT0_SETUP,
AMD5536_MFGPT_CNT_EN | AMD5536_MFGPT_CMP2EV |
AMD5536_MFGPT_CMP2 | AMD5536_MFGPT_DIV_MASK);
ここでは、IO空間をマップしてから、MFGPT0_SETUPレジスタに「カウンタを動かす+フルカウンタになったらリセットをする+入力クロック=32KHz+プリスケーラー=32K」を設定するところまでが含まれている。
その後は、
sc->sc_smw.smw_name = "cs5536wdt";
sc->sc_smw.smw_cookie = sc;
sc->sc_smw.smw_setmode = glxpcib_wdog_setmode;
sc->sc_smw.smw_tickle = glxpcib_wdog_tickle;
sc->sc_smw.smw_period = 32;
aprint_normal("%s: watchdog", sc->sc_dev.dv_xname);
wdt = 1;
}
/* Register Watchdog timer to SMW */
if (wdt) {
if (sysmon_wdog_register(&sc->sc_smw) != 0)
aprint_error("%s: can not register wdog\n",
sc->sc_dev.dv_xname);
}
sysmonのフレームワークに必要な設定(smw構造体にいろいろと書く)をして、sysmon_wdog_register()でMFGPT0タイマをsysmonのwatchdogタイマとして登録すれば、attach処理は完了する。
あとは、sysmonのwdogフレームワークがMFGPT0タイマを制御するための制御関数を記述すれば完了である。記述していないうちは、関数ポインタとしてNULLをいれておけば、カーネルの作成して連携部分のチェックまではできるはず。実際に開発しているときは、周りだけ作って中身がない状態から、すこしづつ中身を埋めていってつくっていた。
次は、watchdogタイマとしてMFGPTを動かすため操作関数について書いてみる予定。
2007年12月21日金曜日
NetBSDでWatchdog timerを使う(for ALIX) (2)
今回扱うWatchdogタイマはハードウェアサポートを前提としたものだ。ハードウェアサポートのWatchdogタイマは、CPUの演算やプログラムとは独立して動いているタイマと、そのタイマが一定の値になるとCPUを強制的にリセットできる機構が組み合わさって実現される。
つまり、今回CS5536でWatchdogタイマを実装するためには、CS5536のMFGPTがある一定の値になるとシステムをリセットするように設定し、かつカーネルやユーザランドが正しく動いている限り一定間隔でタイマの更新を続けるソフトウェアを書くこと、と等価である。
NetBSDのWatchdogタイマサポートは、
- デバイスドライバ部分(glxpcib.c)はタイマーの設定と更新のためのデバイス依存の部分
- カーネルのsysmonフレームワークがその他残りのデバイス非依存の部分
今回つかうデバイス非依存のサービスはsysmonフレームワークによって提供されている。これはsrc/sys/dev/sysmon/以下で実装されているフレームワークでコンピュータのハードウェアの電源やセンサなどを扱うための一般的な機能を提供している。Watchdogタイマもsysmon/sysmon_wdog.cをつかえば、他の実装のwatchdogと同じユーザランドプログラムと協調できるようになっている。
と、ここまでで全体の大雑把に構造を理解し(たつもりになっ)てから、デバイス依存の部分をやっつけてしまうことにする。glxpcib.cの中には、デバイス依存つまりCS5536特有な操作を扱う
- MFGPTの初期設定(周波数やプリスケーラーの値)する関数
- MFGPTの動作を制御するための関数
- sysmon_wdogとインターフェイスするための関数群
struct sysmon_wdog {
const char *smw_name; /* watchdog device name */
LIST_ENTRY(sysmon_wdog) smw_list;
void *smw_cookie; /* for watchdog back-end */
int (*smw_setmode)(struct sysmon_wdog *);
int (*smw_tickle)(struct sysmon_wdog *);
u_int smw_period; /* timer period (in seconds) */
int smw_mode; /* timer mode */
u_int smw_refcnt; /* references */
pid_t smw_tickler; /* last process to tickle */
};
関数ポインタであるsmw_setmode()とsmw_tickle()を実装してやれば、sysmon-wdogで扱えるwatchdogタイマになるだろう、という見通しを立てて進めていく。
次のエントリーでデバイス操作について説明してみる予定。
NetBSDでWatchdog timerを使う(for ALIX) (1)
Watchdogタイマはシステムがハングアップしたときに自動的に復帰するための監視機構だ。原理は簡単で、独立したタイマが動いていて、そのタイマが設定した値に到達するとシステムリセットがかかるようにしておく。システムが正しく動いているときはそのタイマの値を定期的に0にもどし、リセットがかからないように管理する、というものだ。
ALIXで動かすNetBSDでGPIOをサポートするコードを書くついでにwatchdogタイマも生かしてみることにした。最近は組込機器っぽい用途でNetBSDを使うこともあっていつか機会があればwatchdogタイマを使ってみようと思っていたので、ちょうどよい。
AMDのCS5536には、MFGPT(Multi Function General Purpose Timer)というすごい名前のタイマ群が実装されている。そのうちの一つMFGPT0をWatchdogタイマとして使う、というのが元々のOpenBSDのglxpcib.cでの実装だ。問題はNetBSDとOpenBSDではwatchdogタイマを使うためのフレームワークが異なっている点で、そのままでは動かない。そこで、NetBSDが使っているsysmonフレームワークに合わせて書き直すことにした。というわけで、やるべきことは、
- AMD CS5536のMFGPTの仕組みを理解
- NetBSD sysmonフレームワークを理解&sysmon wdogtimerの使い方を理解
- コードを書く
具体的な話は次のエントリーで。
NetBSDでGPIOを使う(for ALIX) (4)
実はNetBSDでgpioを使おうと思ったのは今回が初めてだったので、どうやればアクセスできるのか?というあたりから調べないといけなかった。Unixということで、とりあえず man -k gpioなどと打ち込んでみると、
- gpio (4) - General Porpose Input/Output
- gpioctl (8) - control GPIO device
- gpioow (4) - 1-Wire bus bit-banging through GPIO pin

LED2,3を操作して点灯すれば、GPIOはまあ動いていることにしても良いだろう、ということでやってみた。
# gpioctl
/dev/gpio0: 32pin
# gpioctl -d /dev/gpio0 25 0
pin 25: state 1 -> 0
# gpioctl -d /dev/gpio0 27 0
pin 27: state 1 -> 0無事に両方ついてくれた。あとは、必要に応じてgpio(4)のAPIをつかって制御すれば動くだろう、ということで、GPIOサポートは一通り終了。本当はLPCポートに配線されているgpioポートを利用できるか?というあたりを試してみないといけないのだが、実際にgpioを使う気になるまでは放置しておくことにしよう。
NetBSDでGPIOを使う(for ALIX) (3)
カーネル関係の移植作業は、カーネルの大きな構造を理解していれば、あとは単純な作業になる。が、「カーネルの大きな構造の理解」は短期間に取得することは比較的難しい気がする。なんか長い間小さい作業を進めているといつかわかっている、みたいな理解の仕方をする人が多いのではないだろうか。少なくとも私はそうだった。
細かい作業の履歴はとっていないので、ここではglxpcib.cの変更点の要点を列挙して注釈をつけてみることにする。もし、万が一カーネルに手を出そうとする人の参考になればうれしいな。ちなみにNetBSDにsend-prしたglxpcib.cとOpenBSDのglxpcib.cとの差分はdiff -uでみたところ393行だった。OpenBSDが383行、NetBSDが484行になったので、純増で101行。結構差分が大きかったことに調べてびっくり。
やらないといけないことは、大きく分けると
- PCI deviceとして検出してアタッチできるようにする
- カーネルの既存のフレームワークと協調できるようする
- デバイスとして動くようにする
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にマージされていることを期待)
使い方は次回のエントリーで。