APC8750にNetBSD/evbarmを移植するための記録 4/27
2014/04/27(日) 19:28 NetBSD はてブ情報 はてブに登録 はてブ数

wmt_tmr.cのつづきです。

cpu_initclocksが終わったので、delay, clockhandler, bcm2835_get_timecountという三つの関数を実装していきます。

まずは、delayです。

bcm2835_tmr.cを見てみると、タイマーレジスタから値を取って、delayさせたいマイクロ秒数経過するまでループする(つまりdelayする)、という処理のようです。

今回の場合、参照すべきタイマーレジスタはどれでしょうか。
あらためて、レジスタ名を見てみます。
https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/mach-wmt/include/mach/wmt_pmc.h#L89
 * OSM0_REG OS Timer Match Register 0
 *
 * OSM1_REG OS Timer Match Register 1
 *
 * OSM2_REG OS Timer Match Register 2
 *
 * OSM3_REG OS Timer Match Register 3
 *
 * OSCR_REG OS Timer Count Register.
 *
 * OSTS_REG OS Timer Status Register.
 *
 * OSTW_REG OS Timer Watchdog enable Register.
 *
 * OSTI_REG OS Timer Interrupt enable Register.
 *
 * OSTC_REG OS Timer Control Register.
 *
 * OSTA_REG OS Timer Access status Register.
カウンタを扱いそうなのは、OS Timer Count RegisterつまりOSCRじゃないかなぁと。

ふたたび、Linuxのソースを見てみます。

https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/kernel/time.c#L558
/* wmt_read_oscr()
 *
 * Return the current count of OS Timer.
 *
 * Note: This function will be call by other driver such as hwtimer
 *       or watchdog, but we don't recommand you to include this header,
 *       instead with using extern...
 *       Move it to other appropriate place if you have any good idea.
 */
inline unsigned int wmt_read_oscr(void)
{
	/*
	 * Request the reading of OS Timer Count Register.
	 */
	OSTC_VAL |= OSTC_RDREQ;

	/*
	 * Polling until free to reading.
	 * Although it looks dangerous, we have to do this.
	 * That means if this bit not worked, ostimer fcuntion
	 * might be not working, Apr.04.2005 by Harry.
	 */
	while (OSTA_VAL & OSTA_RCA)
		;

	return OSCR_VAL;
}
現在のカウンタを取ってくる関数ですね。
read要求を行い、read可能になるまで待ってから、カウントレジスタ(OSCR)を返している、と。

NetBSDのbcm2835_tmr.cでは現在のカウンタ取得は、
    163 	last = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_CLO);
というレジスタ読み込み一発でとってきていますが、今回は先のwmt_read_oscr関数のように、ちょっとした手順を踏んでからカウンタの値を取ってくる必要があるようです。

こんな感じで現在のカウンタを取る関数を作ってみました。
uint32_t
get_current(void)
{
        struct wmttmr_softc *sc = wmttmr_sc;

        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) {
                ;
        }

        return bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_CR);
}
さて、元に戻ってdelayです。ほぼ、bcm2835_tmr.cのdelayと同じになりました。
void
delay(unsigned int n)
{
        struct wmttmr_softc *sc = wmttmr_sc;
        uint32_t last, curr;
        uint32_t delta, usecs;

        KASSERT(sc != NULL);
        last = get_current();
        delta = usecs = 0;
        while (n > usecs) {
                curr = get_current();
                /* Check to see if the timer has wrapped around. */
                if (curr < last)
                        delta += curr + (UINT32_MAX - last);
                else
                        delta += curr - last;

                last = curr;

                if (delta >= counts_per_usec) {
                        usecs += delta / counts_per_usec;
                        delta %= counts_per_usec;
                }
        }
}
あぁ、これでdelayをインチキなしで動作させることができる、はずです。

次は、clockhandler関数です。

NetBSDのbcm2835_tmr.cのclockhandlerを見てみます。
    186 /*
    187  * clockhandler:
    188  *
    189  *	Handle the hardclock interrupt.
    190  */
    191 static int
    192 clockhandler(void *arg)
    193 {
    194 	struct bcm2835tmr_softc *sc = bcm2835tmr_sc;
    195 	struct clockframe *frame = arg;
    196 	uint32_t curr, status;
    197 
    198 	status = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
    199 	    BCM2835_STIMER_CS);
    200 
    201 	if (!(status & BCM2835_STIMER_M3))
    202 		return 0;
    203 
    204 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_CS, status);
    205 
    206 	hardclock(frame);
    207 
    208 	curr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_CLO);
    209 
    210 	curr += counts_per_hz;
    211 	bus_space_write_4(sc->sc_iot, sc->sc_ioh, BCM2835_STIMER_C3, curr);
    212 
    213 	return 1;
    214 }
うーん、タイマーステータスを読んで、書き戻し、現在時刻にcounts_per_hzを足してSTIMER_C3に書き込んでいるという感じかなぁ。ちょっとよくわかりません。

Linux側ではhttps://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/kernel/time.c#L718が割り込み処理っぽいですね。
static irqreturn_t
wmt_timer_interrupt(int irq, void *dev_id)
{
	unsigned int next_match;

	do {
		do_leds();
		timer_tick();

		/* Clear match on OS Timer 1 */
		OSTS_VAL = OSTS_M1;
		next_match = (OSM1_VAL += LATCH);
		do_set_rtc();
	} while ((signed long)(next_match - wmt_read_oscr()) <= 0);

	/* TODO: add do_profile()  */
//	do_profile(regs);

	return IRQ_HANDLED;
}
ふーむ、レジスタ操作だけ見るとOS Timer 1のMatchをクリアして、OS Timer Match Register 1(OSTS_M1) を更新しているだけです。あとはきっとタイマー割り込みで共通的な処理 (do_leds, timer_tick, do_set_rtc) を読んでいるのでしょう。

あっ、つまりタイマー割り込みで共通的な処理っていうのは、NetBSDでいうところの
    206 	hardclock(frame);
ってことか。

だとすると、OSTS_M1、OSM1_VALを更新する処理をもらってくるだけでよさそうな気がしてきました。念のため、OSM1_VALを更新する際、OSTA_MWA1もチェックしています。これはinitclocksでもそういう風にしていたのをまねました。
static int
clockhandler(void *arg)
{
        struct wmttmr_softc *sc = wmttmr_sc;
        struct clockframe *frame = arg;
        uint32_t curr;
        hardclock(frame);
        while(bus_space_read_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TA) & OSTA_MWA1)
                ;
        curr = get_current();
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_TS, OSTS_M1);
        bus_space_write_4(sc->sc_iot, sc->sc_ioh, WMT_OSTMR_M1, curr + counts_per_hz);

        return 1;
}
最後は、bcm2835_get_timecountあらためwmt_get_timecountです。

これは、これまでの想定から、カウントレジスタ(OSCR)を返してあげればよいだけで、すでにget_current関数で実装済みです。

static u_int
wmttmr_get_timecount(struct timecounter *tc)
{
        uint32_t curr;
        curr = get_current();

        return curr;
}
これで、ひととおり書き換えたことになります。さて、ちゃんとdelayとかsleepが動いてくれるんでしょうかねぇ。

名前:  非公開コメント   

  • TB-URL  http://www.tokuda.net/diary/adiary.cgi/0843/tb/