APC8750にNetBSD/evbarmを移植するための記録 4/26
2014/04/26(土) 17:51 NetBSD はてブ情報 はてブに登録 はてブ数

前回が昨年の10/23ということで、実に半年の月日が流れました。

なんとかそれっぽく見えるになったのですが、/sbin/initに手を入れて無理やり動かしていたり、まだまだちゃんとしていません。

なにより、タイマードライバが手抜きというか、コンパイル通るだけの代物なので、なんとかちゃんとしたものにしたいと。そうすれば、delayも呼べるようになるし。

ということで、wmt_tmr.cの原型となったbcm2835_tmr.cを読んでみます。

実は、最初はRTC (real-time clock) のドライバを書くのかと思っていたのです。タイマー = 時計、つまりRTCだろうと。

ところがbcm2835_tmr.cを読んでもRTCという文字列は出てきません。あらためてWikipediaでRTCを調べてみると、RTCは年月日時分秒という意味での時計であって、CPUクロックやシステムクロックを供給するものではない、ということがわかりました。それどころか、RTCへの書き込みは遅いので、いったんRTCから現在時刻を取得した後は、CPUの高精度なタイマーを使って、それ以降の時間を管理しているということがわかりました。

ということで、ここでいうタイマーはCPUが持っているタイマーである、つまり、いままでタイマーの割り込み番号をRTCに合わせていたのですが、まったくの間違いだということです。

それであ、真のタイマーはどこか、正しい割り込み番号はどこか、ということでLinuxのソースをあさります。

https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/mach-wmt/include/mach/irqs.h

をみると
#define IRQ_OST0 36 /* OS Timer match 0 */
#define IRQ_OST1 37 /* OS Timer match 1 */
#define IRQ_OST2 38 /* OS Timer match 2 */
#define IRQ_OST3 39 /* OS Timer match 3 */
と書かれています。きっとこれでしょうね。しかし、四つもあるのはなんでしょうか。

さて、NetBSDに話を戻して、あらためてbcm2835_tmr.cを眺めてみましょう。

matchとattachは、大したことはしていなさそうで、ちょっとした置換だけでそのまま使えそうです。

あとはcpu_initclocks, delay, clockhandler, bcm2835_get_timecountという四つの関数で構成されています。

名前から想像するに、cpu_initclocksはinitなので初期化ルーチン、delayはdelay(9)の本体、clockhandlerはhandlerなので割り込みが起こった時に呼ばれるルーチン、bcm2835_get_timecountはタイマーの値を取得する、って感じかなぁ。

まずは、初期化ルーチンであるcpu_initclocksを見てみます。
    129 void
    130 cpu_initclocks(void)
    131 {
    132 	struct bcm2835tmr_softc *sc = bcm2835tmr_sc;
    133 	void *clock_ih;
    134 	uint32_t stcl;
    135 
    136 	KASSERT(sc != NULL);
    137 
    138 	bcm2835tmr_timecounter.tc_priv = sc;
    139 
    140 	counts_per_hz = BCM2835_STIMER_HZ / hz;
    141 
    142 	stcl = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_CLO);
    143 	stcl += counts_per_hz;
    144 
    145 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_C3, stcl);
    146 	clock_ih = bcm2835_intr_establish(BCM2835_INT_TIMER3, IPL_CLOCK,
    147 	    clockhandler, NULL);
    148 	if (clock_ih == NULL)
    149 		panic("%s: unable to register timer interrupt", __func__);
    150 
    151 	tc_init(&bcm2835tmr_timecounter);
    152 }
うーん。bus_space_read_4でレジスタから値を読んで、bcm2835_intr_establishで割り込みコントローラとお話しして、tc_initというのを読んでるということぐらいしかわかりません。

initつまり初期化処理がこの関数の目的で、きっと初期化はデバイス固有でしょうからLinuxのソースを見てから考えることにします。

初期化処理を探すにあたっては、OS Timerのレジスタを触っているところだろう、ということでhttps://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/mach-wmt/include/mach/wmt_pmc.h#L423に定義されているOS Timer周りのレジスタ名から検索で絞り込んでみました。
#define OSM0_VAL (REG32_VAL(OSM0_ADDR))
#define OSM1_VAL (REG32_VAL(OSM1_ADDR))
#define OSM2_VAL (REG32_VAL(OSM2_ADDR))
#define OSM3_VAL (REG32_VAL(OSM3_ADDR))
#define OSCR_VAL (REG32_VAL(OSCR_ADDR))
#define OSTS_VAL (REG32_VAL(OSTS_ADDR))
#define OSTW_VAL (REG32_VAL(OSTW_ADDR))
#define OSTI_VAL (REG32_VAL(OSTI_ADDR))
#define OSTC_VAL (REG32_VAL(OSTC_ADDR))
#define OSTA_VAL (REG32_VAL(OSTA_ADDR))
Linuxでの初期化処理に該当するのは、おそらく、https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/kernel/time.c#L804にあるtime_init関数でしょう。
void __init time_init(void)
{
	unsigned int i = 0;
	struct timespec tv;

	if (system_timer->offset == NULL)
		system_timer->offset = dummy_gettimeoffset;

	/* system_timer->init(); */
	/* Stop ostimer. */
	OSTC_VAL = 0;

	if (wmt_rtc_on) {
		rtc_init();
		gettimeoffset = wmt_gettimeoffset;
		set_rtc       = wmt_set_rtc;     /* draft, need to verify */
		tv.tv_nsec    = 0;
		tv.tv_sec     = wmt_get_rtc_time();
	} else {
		gettimeoffset = wmt_gettimeoffset;
		tv.tv_nsec    = 0;
		tv.tv_sec     = 0;
	}

	do_settimeofday(&tv);

	/* Set initial match */
	while (OSTA_VAL & OSTA_MWA1)
		;
	OSM1_VAL = 0;

	/* Clear status on all timers. */
	OSTS_VAL = OSTS_MASK;     /* 0xF */

	/* Use OS Timer 1 as kernel timer because watchdog may use OS Timer 0. */
	i = setup_irq(IRQ_OST1, &wmt_timer_irq);

	/* Enable match on timer 1 to cause interrupts. */
	OSTI_VAL |= OSTI_E1;

	/* Let OS Timer free run. */
	OSTC_VAL = OSTC_ENABLE;

	/* Initialize free-running timer and force first match. */
	while (OSTA_VAL & OSTA_CWA)
		;
	OSCR_VAL = 0;
}
OS Timer0をwatchdogで使い、OS Timer1をカーネルタイマで使う、というコメントがあります。なるほど、複数の役割のタイマが必要だから、OS Timerが四つもあるのですね。

初期化は次のステップで実装されているようです。
  1. OS Timerを止める (OSTC_VAL = 0)
  2. OS Timer1のマッチレジスタが書き込み可能になるまで待つ (while (OSTA_VAL & OSTA_MWA1))
  3. OS Timer1のマッチレジスタに0を入れる(OSM1_VAL = 0)
  4. すべてのOS Timerのステータスをクリアする(OSTS_VAL = OSTS_MASK)
  5. OS Timer1の割り込みをセットアップ(setup_irq(xxx))
  6. OS Timer1の割り込みをenableにする
  7. OS Timerを有効にする(OSTC_VAL = OSTC_ENABLE)
  8. カウンタが書き込み可能になるまで待つ (while (OSTA_VAL & OSTA_CWA))
  9. カウンタに0を書き込む
これを、NetBSDで実装すればよさそうです。レジスタの読み書きはbus_space_read/writeで、割り込みのセットアップは先のbcm2835_intr_establishに相当する処理でしょう。

ところで、bcm2835_tmr.cの140行目にあるOS Timerの周波数は今回どうすればよいのでしょう。
    140 	counts_per_hz = BCM2835_STIMER_HZ / hz;
おそらく、https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/mach-wmt/include/mach/timex.h#L25にあるとおり、3MHzというのがそれっぽいです。
/*
 * WMT SoC timer parameters
 */
#define CLOCK_TICK_RATE 3000000
#define CLOCK_TICK_FACTOR 80
ほぼ丸コピーで芸がないですが、こんな感じになりました。
cpu_initclocks(void)
{
        struct wmttmr_softc *sc = wmttmr_sc;
        void *clock_ih;

        KASSERT(sc != NULL);

        wmttmr_timecounter.tc_priv = sc;

        counts_per_hz = WMT_OSTMR_HZ / hz;

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TC, 0); /* stop os timer */

        while(bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TA) & OSTA_MWA1)
                ;
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_M1, counts_per_hz);

        /* clear status on all timers */
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TS, 0xF);
        clock_ih = wmt_intr_establish(sc->sc_intr, IPL_CLOCK,
            clockhandler, NULL);
        if (clock_ih == NULL)
                panic("%s: unable to register timer interrupt", __func__);

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TI, OSTI_E1); /* enable intr timer1 */

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TC, OSTC_ENABLE); /* enable os timer */

        /* Initaialize counter register */
        while(bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TA) & OSTA_CWA)
                ;
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_CR, 0);

        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TC,
          bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TC) | OSTC_RDREQ);
        while (bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TA) & OSTA_RCA) {
                ;
        }
        tc_init(&wmttmr_timecounter);
}

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/0842/tb/