APC8750にNetBSD/evbarmを移植するための記録 4/26
前回が昨年の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
をみると
さて、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を見てみます。
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周りのレジスタ名から検索で絞り込んでみました。
初期化は次のステップで実装されているようです。
ところで、bcm2835_tmr.cの140行目にあるOS Timerの周波数は今回どうすればよいのでしょう。
なんとかそれっぽく見えるになったのですが、/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が四つもあるのですね。
初期化は次のステップで実装されているようです。
- OS Timerを止める (OSTC_VAL = 0)
- OS Timer1のマッチレジスタが書き込み可能になるまで待つ (while (OSTA_VAL & OSTA_MWA1))
- OS Timer1のマッチレジスタに0を入れる(OSM1_VAL = 0)
- すべてのOS Timerのステータスをクリアする(OSTS_VAL = OSTS_MASK)
- OS Timer1の割り込みをセットアップ(setup_irq(xxx))
- OS Timer1の割り込みをenableにする
- OS Timerを有効にする(OSTC_VAL = OSTC_ENABLE)
- カウンタが書き込み可能になるまで待つ (while (OSTA_VAL & OSTA_CWA))
- カウンタに0を書き込む
ところで、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); }
コメント(0件)
- TB-URL http://www.tokuda.net/diary/0842/tb/