APC8750にNetBSD/evbarmを移植するための記録 9/22
2014/01/13(月) 27:42 NetBSD はてブ情報 はてブに登録 はてブ数

ということで、落ち着いてLinuxのソースをあさる。シリアルのソースはarch/armの下ではなくdrivers/serialの下にあった。
なんてこった。一か所においてほしいもんだ。

https://github.com/apc-io/apc-8750/blob/master/kernel/drivers/serial/serial_wmt.c

非常に長い。
が、初期化しているところできっと割り込みをONにしているのだろうから、それっぽいところを探す。

IERにビットを立てるのでierをORしている奴を探せばよい。
いかにも初期化している関数を発見。
static int wmt_startup(struct uart_port *port)の1601行目から。
    /*
     * Enable RX FIFO almost full, timeout, and overrun interrupts.
     */
    uart->urier = URIER_ERXFAF | URIER_ERXFF | URIER_ERXTOUT | URIER_EPER | URIER_EFER | URIER_ERXDOVR;
きっとここだろう。URIERシリーズはarch/arm/mach-wmt/include/mach/wmt_uart.hにある。
/*
* UART Interrupt Enable Register Bit Definitions
*/
#define URIER_ETXDE     BIT0    /* Enable for TX data register empty    */
#define URIER_ERXDF     BIT1    /* Enable for RX data register full     */
#define URIER_ETXFAE    BIT2    /* Enable for TX FIFO almost full       */
#define URIER_ETXFE     BIT3    /* Enable for TX FIFO full              */
#define URIER_ERXFAF    BIT4    /* Enable for RX FIFO almost full       */
#define URIER_ERXFF     BIT5    /* Enable for RX FIFO full              */
#define URIER_ETXDUDR   BIT6    /* Enable for TX underrun               */
#define URIER_ERXDOVR   BIT7    /* Enable for RX overrun                */
#define URIER_EPER      BIT8    /* Enable for parity error              */
#define URIER_EFER      BIT9    /* Enable for frame error               */
#define URIER_EMODM     BIT10   /* Enable for modem control signal      */
#define URIER_ERXTOUT   BIT11   /* Enable for receive time out          */
#define URIER_EBK       BIT12   /* Enable for break signal done         */
/* Bit[13:31] are reserved. */
これで、Linuxで立てているbitがわかったので、これを真似することに。
デバッグルーチンにちゃんと入ってくれる。が、割り込みコントローラからはUARTのどの割り込みが入ったかわからない。
いよいよ、割り込みコントローラからcomドライバに制御を移す時が来たといえる。

irq_handlerに入ったら嬉しそうにデバッグ文を出していたが、それ以降を見なければならない。

RPIのsys/arch/arm/broadcom/bcm2835_intr.c#bcm2835_irq_handlerを見ると構造としては、自身(MD)のbcm2835_pic_find_pending_irqsを呼び、そのあとにpic_do_pending_intsを読んでいる。
    155 void
    156 bcm2835_irq_handler(void *frame)
    157 {
    158 	struct cpu_info * const ci = curcpu();
    159 	const int oldipl = ci->ci_cpl;
    160 	const uint32_t oldipl_mask = __BIT(oldipl);
    161 	int ipl_mask = 0;
    162 
    163 	ci->ci_data.cpu_nintr++;
    164 
    165 	bcm2835_barrier();
    166 	ipl_mask = bcm2835_pic_find_pending_irqs(&bcm2835_pic);
    167 
    168 	/*
    169 	 * Record the pending_ipls and deliver them if we can.
    170 	 */
    171 	if ((ipl_mask & ~oldipl_mask) > oldipl_mask)
    172 		pic_do_pending_ints(I32_bit, oldipl, frame);
    173 }
pic_do_pending_intsはsys/arch/arm/pic/pic.c#pic_do_pending_intsにあるので、まずはbrm2835_pic_find_pending_irqは何をするのか読み解かねばならない。が、さっぱりわからない。
    194 /*
    195  * Called with interrupts disabled
    196  */
    197 static int
    198 bcm2835_pic_find_pending_irqs(struct pic_softc *pic)
    199 {
    200 	int ipl = 0;
    201 	uint32_t bpending, gpu0irq, gpu1irq, armirq;
    202 
    203 	bcm2835_barrier();
    204 	bpending = read_bcm2835reg(BCM2835_INTC_IRQBPENDING);
    205 	if (bpending == 0)
    206 		return 0;
    207 
    208 	armirq = bpending & BCM2835_INTBIT_ARM;
    209 	gpu0irq = bpending & BCM2835_INTBIT_GPU0;
    210 	gpu1irq = bpending & BCM2835_INTBIT_GPU1;
    211 
    212 	if (armirq) {
    213 		ipl |= pic_mark_pending_sources(pic, BCM2835_INT_BASICBASE,
    214 		    armirq);
    215 
    216 	}
    217 
    218 	if (gpu0irq || (bpending & BCM2835_INTBIT_PENDING1)) {
    219 		uint32_t pending1;
    220 
    221 		pending1 = read_bcm2835reg(BCM2835_INTC_IRQ1PENDING);
    222 		ipl |= pic_mark_pending_sources(pic, BCM2835_INT_GPU0BASE,
    223 		    pending1);
    224 	}
    225 	if (gpu1irq || (bpending & BCM2835_INTBIT_PENDING2)) {
    226 		uint32_t pending2;
    227 
    228 		pending2 = read_bcm2835reg(BCM2835_INTC_IRQ2PENDING);
    229 		ipl |= pic_mark_pending_sources(pic, BCM2835_INT_GPU1BASE,
    230 		    pending2);
    231 	}
    232 
    233 	return ipl;
    234 }
ということで、ほかのpicを使っている実装を見てみることにした。
おなじみgeminiのsys/arch/arm/gemini/gemini_icu.c#find_pending_irqsを見てみる。
    154 /*
    155  * Called with interrupts disabled
    156  */
    157 static int
    158 find_pending_irqs(struct geminiicu_softc *sc)
    159 {
    160 	uint32_t pending = INTC_READ(sc, GEMINI_ICU_IRQ_STATUS);
    161 
    162 	KASSERT((sc->sc_enabled_mask & pending) == pending);
    163 
    164 	if (pending == 0)
    165 		return 0;
    166 
    167 	return pic_mark_pending_sources(&sc->sc_pic, 0, pending);
    168 }
おぉ、なんかあっさりしているぞ。
  • なんかレジスタを読む。結果が0なら0を返す。
  • pic_mark_pending_sourceを返す
pendingというのは、いわゆるペンディング、先送りのことだろうか。pic_mark_pending_sourcesは先送りのソースに印をつけるということだろうか。先送りされているのは、きっと割り込みなんだろう。先送りというのはちょっと変な日本語だな。宙ぶらりんの割り込み? まぁ、未処理の割り込みぐらいにとらえようか。
なので、この関数は、未処理の割り込みをレジスタから読み込み、picに印をつけておく。と想像できる。

sys/arch/arm/pic/pic.c#pic_mark_pending_sourcesを見てみよう。
    191 uint32_t
    192 pic_mark_pending_sources(struct pic_softc *pic, size_t irq_base,
    193 	uint32_t pending)
    194 {
    195 	struct intrsource ** const isbase = &pic->pic_sources[irq_base];
    196 	struct intrsource *is;
    197 	volatile uint32_t *ipending = &pic->pic_pending_irqs[irq_base >> 5];
    198 	uint32_t ipl_mask = 0;
    199 
    200 	if (pending == 0)
    201 		return ipl_mask;
    202 
    203 	KASSERT((irq_base & 31) == 0);
    204 
    205 	(*pic->pic_ops->pic_block_irqs)(pic, irq_base, pending);
    206 
    207 	atomic_or_32(ipending, pending);
    208         while (pending != 0) {
    209 		int n = ffs(pending);
    210 		if (n-- == 0)
    211 			break;
    212 		is = isbase[n];
    213 		KASSERT(is != NULL);
    214 		KASSERT(irq_base <= is->is_irq && is->is_irq < irq_base + 32);
    215 		pending &= ~__BIT(n);
    216 		ipl_mask |= __BIT(is->is_ipl);
    217 	}
    218 
    219 	atomic_or_32(&pic->pic_pending_ipls, ipl_mask);
    220 	atomic_or_32(&pic_pending_ipls, ipl_mask);
    221 	atomic_or_32(&pic_pending_pics, __BIT(pic->pic_id));
    222 
    223 	return ipl_mask;
    224 }
これも難しいな。なんか、
    205 	(*pic->pic_ops->pic_block_irqs)(pic, irq_base, pending);
でMDのblockルーチンを読んでいる。これはきっと当該割り込みをOFF(ブロック)するということだろう。

そのあと、なにやらループを回している。

ん?ffs()とはなんだろう。と調べると、おどろきの common/lib/libc/arch/arm/string/ffs.Sである。Find First Set bitのことであろう。

つまり、pendingに立っているビットの数ぶんループ処理をしているようだ。
  • pendingに立っているビットを見つける(nとする)
  • 割込みnの割り込みソース(is)を取得
  • pendingに立っているビットをクリア
  • ipl_maskにis->is_iplを立てる
つまり、引き数で渡されきたpendingをひとつづつクリアしつつ、iplのマスクを作りこんでいく感じであろう。iplってなんだったっけ? そうそう、ずいぶん前に割り込みにレベルがあるという話があったやつだ。再掲しとく。
     43 /* Interrupt priority "levels". */
     44 #define    IPL_NONE    0        /* nothing */
     45 #define    IPL_SOFTCLOCK    1        /* clock */
     46 #define    IPL_SOFTBIO    2        /* block I/O */
     47 #define    IPL_SOFTNET    3        /* software network interrupt */
     48 #define    IPL_SOFTSERIAL    4        /* software serial interrupt */
     49 #define    IPL_VM        5        /* memory allocation */
     50 #define    IPL_SCHED    6        /* clock interrupt */
     51 #define    IPL_HIGH    7        /* everything */
     52
     53 #define    NIPL        8
なので、ipl_maskにはそれぞれのIPLに応じたビットを立てるのがこの関数の役目である。

ちょっとまてよ、pendingでループを回したということは、pendingには複数のビットが立っていることが前提なんだな。そういえば、さっきMDのblockを呼んでいたけれども、それは、現時点で未処理の割り込みは「すべて」ブロックせよということか。

ははぁ、block, unblockの引数が、IRQの数字ではなくビットで渡っていた理由はそれか。複数ある未処理の割り込みをblock/unblockに渡すためにビットを使っていたのだな。irq_baseの存在理由もわかった気がする。割込みの数を32個単位で管理するためのベース、というわけだな。
そう考えると、以下の5ビットシフトも納得がいく。
    197 	volatile uint32_t *ipending = &pic->pic_pending_irqs[irq_base >> 5];
pic->pic_pending_irqsは32個単位で管理されている割り込みの配列(ビットだけど)というわけね。

で、最初にirq_baseをもとに割込みの配列をipendingとして取り出して、引き数のpendingとorをとって保存。この保存行為が関数名のmarkってことなんだろうかね。

そのあと、さっきのループを回してipl_maskを作って、picの構造体にorで保存(mark)しているってことなんだろうね。

適当に実装したblock/unblockは、ビットが一つしかたっていない前提で作っていたから、一つの割り込みしかOn/Offできないので、ちゃんと複数ビットをみてすべてのOn/Offをしないとダメですね。

ふーむ。つーことで、find_pending_irqsはipl_maskを返せばよいってことか。
で、さっきほったらかしていた、
  • なんかレジスタから読む
というのは、現在、未処理の割り込みを全部調べ上げて、それをビットで表現したpendingというのに入れる。ということがわかった。

そういえば、以前icuを全部ダンプしたときに、eth0とpmc_wakeupの二つの割り込みが同時に上がってきていた際に、そういうレジスタがあった。こいつがそのまま使えるってことだな。

まてよ、APCはgeminiと違って、割り込みが64個あるので、二つのレジスタを両方調べにゃいかんはずだな。

似たようなicuのソースないかなということで、find_pending_irqsを検索してみると、sys/arch/arm/omap/omap2_icu.c#omap_irq_handler
    145 		ipl_mask |= find_pending_irqs(sc, 0);
...
    147 		ipl_mask |= find_pending_irqs(sc, 1);
...
    149 		ipl_mask |= find_pending_irqs(sc, 2);
みたいにorで重ねていたりする。

ふーむ。orで重ねりゃいいのかな。
sys/arch/arm/marvell/kirkwood.c#kirkwood_find_pending_irqsがかなり似ている。
二つのレジスタからpendingを読んできて、それのORをとっている。
    181 kirkwood_find_pending_irqs(void)
    182 {
    183     int ipl = 0;
    184 
    185     uint32_t causelow = read_mlmbreg(KIRKWOOD_MLMB_MICLR);
    186     uint32_t pendinglow = read_mlmbreg(KIRKWOOD_MLMB_MIRQIMLR);
    187 
    188     pendinglow &= causelow;
    189     if (pendinglow != 0)
    190         ipl |= pic_mark_pending_sources(&kirkwood_pic, 0, pendinglow);
    191 
    192     if ((causelow & (1 << KIRKWOOD_IRQ_HIGH)) == (1 << KIRKWOOD_IRQ_HIGH)) {
    193         uint32_t causehigh = read_mlmbreg(KIRKWOOD_MLMB_MICHR);
    194         uint32_t pendinghigh = read_mlmbreg(KIRKWOOD_MLMB_MIRQIMHR);
    195         pendinghigh &= causehigh;
    196         ipl |= pic_mark_pending_sources(&kirkwood_pic, 32, pendinghigh);
    197     }
    198 
    199     return ipl;
    200 }
APCだと、causeとか関係なくとれちゃいそう。こんなのでどうだろう。
/*
 * Called with interrupts disabled
 */
static int
wm8750_pic_find_pending_irqs(struct pic_softc *pic)
{
        int ipl = 0;
        uint32_t pending_l = read_icureg(ICIS_L);
        uint32_t pending_h = read_icureg(ICIS_H);

        ipl |= pic_mark_pending_sources(pic, 0, pending_l);
        ipl |= pic_mark_pending_sources(pic, 32, pending_h);

        return ipl;
}
block/unblockの複数ビット対応も、すごいすなおにループを回す実装をしてみた。
static void
wm8750_pic_unblock_irqs(struct pic_softc *pic, size_t irqbase,
    uint32_t irq_mask)
{
        uint32_t reg = 0xFFFFFFFF;
        uint32_t irq_num = 9999;
        int i;

        for (i = 0; i < 32; i++) {
                if (irq_mask & (1<<i)) {
                        irq_num = irqbase + i;
                        reg = read_ic0((irq_num/4)*4);
                        reg |= ((INTC_ENABLE) << ((irq_num % 4) * 8));
                        write_ic0((irqbase/4)*4, reg);
                }
        }
        aprint_normal("icu: unblock: irqbase=0x%08x, irqmask=0x%08x, irq_num=%d, reg=0x%08x\n",
                 (int)irqbase, irq_mask, irq_num, reg);
}

static void
wm8750_pic_block_irqs(struct pic_softc *pic, size_t irqbase,
    uint32_t irq_mask)
{
        uint32_t reg=9999, irq_num=0xFFFFFFFF;
        int i;

/*      print_regs(); */
        for (i = 0; i < 32; i++) {
                if (irq_mask == (1<<i)) {
                        irq_num = irqbase + i;
                        reg = read_ic0((irq_num/4)*4);
                        reg &= ~((INTC_ENABLE) << ((irq_num % 4)*8));
                        write_ic0((irqbase/4)*4, reg);
                }
        }
        aprint_normal("icu: block: irqbase=0x%08x, irqmask=0x%08x, irq_num=%d, reg=0x%08x\n",
                 (int)irqbase, irq_mask, irq_num, reg);
}
よくかんがえたら、ffs()をつかったり、read_4/write_4ではなくread_1/write_1を使ったほうが素直な気がしてきた。後から直そうか。

irq_handlerはRPIほぼそのままでよかった。
よくかんがえたら、RPIは複雑なことやっているなぁ。最初に参考にするには難しすぎる。geminiわかりやすいわ。

ということで、動かしてみる。

ちゃんと、pic.cのpic_do_pending_ints(I32_bit, oldipl, frame);に入って、pic_dispatchでis->is_funcの関数ポインタを呼び出してくれればオッケー。

なのだが、wmt_tmrのattachメッセージを出したところで止まる。

デバッグメッセージをあちこちに埋め込んで、block/unblockが期待通りに動いているかを見たりする。
unblockの前にblockが呼ばれていたりして不可解な現象に遭遇する。
それはそうか、unblockのレジスタ操作をした瞬間に割り込み処理に飛べばblock処理が動き、blockのログを出した後unblockに復帰してunblockのログを出すのだから。unblockに入った瞬間にログを出せば、それなりの順序性でログを出す。割り込み処理は脳みそ使うなぁ。

動かしていると、すかっとrootデバイスを聞くところまで進んだり、wmt_tmrのattachで止まったりする。

もしかして、キーをバシバシ叩いていれば割り込みが入ってattachで止まっているんじゃないかなと思い、何度か試行してみる。

確かにそれっぽいぞ。

もしやと思って、pic_dispatchの手前にログを埋め込んで、バシバシしながら起動。おぉ、pic_dicpatchまで動いているではないか。

ということは、
    264 		rv = (*is->is_func)(is->is_arg);
の部分が実行され、関数ポインタis->is_funcであるcomintr、つまりcomドライバの割り込みルーチンに飛んでいるということでは!

comintr()にログを仕込む。確かに実行されている!

が、だんまりである。

comintrは長いので、ばらばらとログを埋めて再実行。なんかしらんが、無限ループにはまっている。

今さらだが、よくよく考えてみると、comドライバのレジスタとwmt_uartのレジスタは全然違っているような気がする。

たしかに、IERなどの名前は同じものがあるのだが、レジスタの並びであるとか、comにはないIrDAのレジスタがあったりと。

そもそも16550Aコンパチだというのは、誰が言ったのだ? たしかにcomドライバで文字は出たけれど、たまたまシリアル出力のためのTXレジスタがどちらも先頭アドレスにあって、たまたま出ているだけなのではないのか?

というか、16550Aコンパチと思い込んだのは自分の早とちりではないか。

そう思って、Linuxのコードとcomドライバのコードを見比べると、レジスタの使いっぷりが全然違う。別物だろうこれは。

おいちょっとまて、これってシリアルコンソールドライバを全部自分で作らないとダメなのか? matchとattachぐらい書けば、あとはcomドライバで面倒見てくれると思っていたから、ここまで頑張れたのに。

シリアルコンソールドライバを自前実装している奴らは、1,000行とか2,000行とかあるんだけど。

自前シリアルコンソールドライバを見つけては絶望していたのだが、sys/arch/arm/s3c2xx0/sscom.c#1850を眺めていて気になるコメントが。
   1850 /*
   1851  * Following are all routines needed for SSCOM to act as console
   1852  */
ん? 以下はコンソールとして動かすのに必要なすべてのルーチンである、と言っている?

なんか、cngetc, cnputc, cnpollcとattach/detachだけでよさそう。んー、つまりこれは割り込みベースじゃなくって、ポーリングベースで動かすためのミニマムセットなのか?

まてよ、この名前ってどっかでみたぞ。これってcomドライバにもあった。というか、出力ルーチンでタイムアウトを削ったのってcnputcじゃなかったっけ?

もしかすると、割り込みベースでコンソールを動かすことをあきらめたら、俺でも実装できるんじゃ? cnputcはもうあるようなもの(レジスタに値書き込むだけ)だし、ビジーループで待てば、文字も詰まらないはず?

ふーむ。方針を変更して、FIFOだのDMAだの難しいことはやらず、ポーリングベースで動かせるんだろうか?

じゃぁ、sys/dev/cons.c#cngetcを読んでいるところを調べてみるか。

なんだ、実態は同じところにあるsys/dev/cons.c#cngetsnじゃないか。これは、複数文字をgetcするってことだよな。じゃぁ、cngetsnはどこから呼ばれているのかな。

init_mainで/sbin/initって入力するところとroot device, dump device, file systemきいてくるところだけだ。そこだけポーリングモードで入力できても、だめじゃん。

やっぱり、ぜんぶシリアルコンソールドライバを作らないといけないのか。

まぁ、でもすこしでも入力が動くと楽しそうだから、まずはcnシリーズを作ってみましょうか。

名前:  非公開コメント   

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