メッセージ欄

2014年4月の日記

一覧で表示する

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

さて、タイマードライバの実装が終わったので、これまで/sbin/initに手を入れていた部分を戻していきます。

手を入れていたところは、
  1. nanosleepをすっとばしていた
  2. busy loopを作って、そのなかでsleepを読んでいた
という二か所。片方づつ元に戻していったところ、ちゃんと動くようになっていました。

やりました!

次に、sleepコマンドをramdisk.fsに入れ込んで、実際sleep 10で10秒間スリープするかを確認してみました。

こちらも、ほぼちゃんと動いているように思えます。ほぼ、というのは近くのデジタル時計の秒表示でざっくり測ったレベル、という意味です。

OSのシステムタイマがいろいろなところに影響しているのがよくわかりました。

これで、必要最低限のドライバである、シリアルコンソール・割り込みコントローラ・システムタイマが揃いました。

割り込みコントローラは二つあるうちの一つしか動いていないし、なぜかカーネルデバッガに落ちた時に最後の入力が延々と発生し続ける、などとおかしなところは多々ありますが。

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が動いてくれるんでしょうかねぇ。

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