APC8750にNetBSD/evbarmを移植するための記録 4/27
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
ふたたび、Linuxのソースを見てみます。
https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/kernel/time.c#L558
read要求を行い、read可能になるまで待ってから、カウントレジスタ(OSCR)を返している、と。
NetBSDのbcm2835_tmr.cでは現在のカウンタ取得は、
こんな感じで現在のカウンタを取る関数を作ってみました。
次は、clockhandler関数です。
NetBSDのbcm2835_tmr.cのclockhandlerを見てみます。
Linux側ではhttps://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/kernel/time.c#L718が割り込み処理っぽいですね。
あっ、つまりタイマー割り込みで共通的な処理っていうのは、NetBSDでいうところの
だとすると、OSTS_M1、OSM1_VALを更新する処理をもらってくるだけでよさそうな気がしてきました。念のため、OSM1_VALを更新する際、OSTA_MWA1もチェックしています。これはinitclocksでもそういう風にしていたのをまねました。
これは、これまでの想定から、カウントレジスタ(OSCR)を返してあげればよいだけで、すでにget_current関数で実装済みです。
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が動いてくれるんでしょうかねぇ。コメント(0件)
- TB-URL http://www.tokuda.net/diary/adiary.cgi/0843/tb/