2007年12月22日土曜日

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)
の3つだけ。上から、タイマのカウンターの動作設定、カウンターの値、カウンターの上限値をそれぞれ設定するレジスタになる。
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を動かすため操作関数について書いてみる予定。

0 件のコメント: