APC8750にNetBSD/evbarmを移植するための記録 9/28
2014/01/25(土) 14:33 NetBSD はてブ情報 はてブに登録 はてブ数

ひとまずコンパイルを通す作業を始めたが、wm8750と打つのが面倒なのでwmtに全部変えることに。なんとなくuartもcomに名前を変更。

evbarm側にwmtcom_apc.cなるcom用のエントリを作成。
conf/APCもそれに合わせて修正。コンパイルが通るまで一苦労。結局、ixp12x0ベースに直しまくることに。

wmtcomcnputcさえ動いてくれれば、もとのレベルに戻せるということで、ぽつぽ
つと実装を始める。

しかし、文字は出ない。
困ったので、apc_machdep.cにあった文字出力ルーチンを復活させる。

consinitは通っているけど、putcが動いていない模様。無限ループでTX busyを見ているのがいけないのかと思い、comドライバにならってタイムアウト用timo変数を追加してループを抜けるようにしてみたら、ちょろっと文字が出た。

やはりputcが正しく動いていないようだ。状況としては、文字が抜けたりしながら表示を続け、root deviceを聞くところまで進んでいるみたい。表示はぐちゃぐちゃだけど。

u-bootのメモリダンプを眺めつつ、TXONというのもチェックしたほうがいいかなー、ってことでTX BusyとTX Onの両方がOKならループを抜けるというロジックに変更したところ、これがビンゴだったらしく、ちゃんと表示されるようになった。

むしろ、以前よりも高速な気がするぐらいだ。ここまでくれば、入力も動かしたい。

getcのロジックは、実はいいところまで行っていたのだけど、戻り値をもどさず、スケルトンで書いていたreturn 0;をreturn c;にしてやると動くことがわかった。

ということで、めでたく入力と出力ができるようになりました。
WMT # set serverip 192.168.0.10
WMT # tftpboot 8000; go 8000
EEP-less strapping = TRUE
*** Warning: no boot file name; using 'C0A80002.img'
TFTP from server 192.168.0.10; our IP address is 192.168.0.2
Filename 'C0A80002.img'.
Load address: 0x8000
Loading: #################################################################
         ###############################T ##################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         ####################T ##################
done
Bytes transferred = 2520192 (267480 hex)
## Starting application at 0x00008000 ...
00008328
NetBSD
00000000 00000000 004808cb 00000412
00000000 00000412 00000004 00000412
c0000000 00000000 004808cb 0000041e
00003000 0000041e 00000004 0000041e
fe000000 d8000000 010fffff 00000412
00003f80 d8000412 00000010 00000412
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
NS
NetBSD/evbarm (APC) booting ...
initarm: Configuring system ...
arm32_bootmem_init: memstart=0, memsize=0x20000000, kernelstart=0x8000
arm32_bootmem_init: kernelend=0x281000
arm32_bootmem_init: adding 130431 free pages: [0x281000..0x1fffffff] (VA 0xc0281000)
arm32_bootmem_init: adding 8 free pages: [0..0x7fff] (VA 0xc0000000)
arm32_kernel_vm_init: 1 L2 pages are needed to map 0x2ac000 kernel bytes
arm32_kernel_vm_init: allocating page tables for vectoradd_pages: adding pv 0xc026fba8 (pa 0x281000, va 0xc0281000, 1 pages) at tail
 kerneladd_pages: appending pv 0xc026fe30 (0x282000..0x282fff) to 0x281000..0x281fff
 vmadd_pages: appending pv 0xc026fe44 (0x283000..0x283fff) to 0x281000..0x282fff
add_pages: appending pv 0xc027886c (0x284000..0x287fff) to 0x281000..0x283fff
add_pages: appending pv 0xc026fe58 (0x288000..0x288fff) to 0x281000..0x287fff
add_pages: appending pv 0xc026fe6c (0x289000..0x289fff) to 0x281000..0x288fff
add_pages: appending pv 0xc026fe80 (0x28a000..0x28afff) to 0x281000..0x289fff
add_pages: appending pv 0xc026fe94 (0x28b000..0x28bfff) to 0x281000..0x28afff
add_pages: appending pv 0xc026fea8 (0x28c000..0x28cfff) to 0x281000..0x28bfff
add_pages: appending pv 0xc026febc (0x28d000..0x28dfff) to 0x281000..0x28cfff
add_pages: appending pv 0xc026fed0 (0x28e000..0x28efff) to 0x281000..0x28dfff
add_pages: appending pv 0xc027012c (0x28f000..0x28ffff) to 0x281000..0x28efff
add_pages: appending pv 0xc0270140 (0x290000..0x290fff) to 0x281000..0x28ffff
add_pages: appending pv 0xc0270154 (0x291000..0x291fff) to 0x281000..0x290fff
add_pages: appending pv 0xc0270168 (0x292000..0x292fff) to 0x281000..0x291fff
add_pages: appending pv 0xc027017c (0x293000..0x294fff) to 0x281000..0x292fff
add_pages: appending pv 0xc0270118 (0x295000..0x296fff) to 0x281000..0x294fff
add_pages: appending pv 0xc026fc24 (0x297000..0x29afff) to 0x281000..0x296fff
Creating L1 page table at 0x00284000
arm32_kernel_vm_init: adding L2 pt (VA 0xc0281000, PA 0x281000) for VA 0xffff0000
 (vectors)arm32_kernel_vm_init: adding L2 pt (VA 0xc0282000, PA 0x282000) for VA 0xc0000000 (kernel)
arm32_kernel_vm_init: adding L2 pt (VA 0xc0283000, PA 0x283000) for VA 0xc1000000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc0288000, PA 0x288000) for VA 0xc1400000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc0289000, PA 0x289000) for VA 0xc1800000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc028a000, PA 0x28a000) for VA 0xc1c00000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc028b000, PA 0x28b000) for VA 0xc2000000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc028c000, PA 0x28c000) for VA 0xc2400000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc028d000, PA 0x28d000) for VA 0xc2800000 (vm)
arm32_kernel_vm_init: adding L2 pt (VA 0xc028e000, PA 0x28e000) for VA 0xc2c00000 (vm)
Mapping kernel
arm32_kernel_vm_init: adding chunk for kernel text 0x8000..0x1adfff (VA 0xc0008000)
add_pages: adding pv 0xc026fb94 (pa 0x8000, va 0xc0008000, 422 pages) before pa 0x281000
arm32_kernel_vm_init: adding chunk for kernel data/bss 0x1ae000..0x280fff (VA 0xc01ae000)
add_pages: appending pv 0xc026fbfc (0x1ae000..0x280fff) to 0x8000..0x1adfff
add_pages: merging pv 0xc026fba8 (0x281000..0x29afff) to 0x8000..0x280fff
Listing Chunks
arm32_kernel_vm_init: pv 0xc026fb94: chunk VA 0xc0008000..0xc029afff (PA 0x8000, prot 3, cache 1)

Mapping Chunks
arm32_kernel_vm_init: mapping last chunk VA 0xc0008000..0xc029afff (PA 0x8000, prot 3, cache 1)
pmap_map_chunk: pa=0x8000 va=0xc0008000 size=0x293000 resid=0x293000 prot=0x3 cache=1
PPPPPPPPLLLLLLLLLLLLLLLSLLLLLLLLLPPPPPPPPPPP
devmap: d8000000 -> d8ffffff @ fe000000
pmap_map_chunk: pa=0xd8000000 va=0xfe000000 size=0x1000000 resid=0x1000000 prot=0x3 cache=0
SSSSSSSSSSSSSSSS
                             Physical              Virtual        Num
                       Starting    Ending    Starting    Ending   Pages
               SDRAM: 0x00000000 0x1fffffff 0xc0000000 0xdfffffff 131072
        text section: 0x00008000 0x001adfff 0xc0008000 0xc01adfff 422
        data section: 0x001b0000 0x0026f480 0xc01b0000 0xc026f480 192
         bss section: 0x0026f480 0x002808cc 0xc026f480 0xc02808cc 18
   L1 page directory: 0x00284000 0x00287fff 0xc0284000 0xc0287fff 4
   ABT stack (CPU 0): 0x0028f000 0x0028ffff 0xc028f000 0xc028ffff 1
   FIQ stack (CPU 0): 0x00290000 0x00290fff 0xc0290000 0xc0290fff 1
   IRQ stack (CPU 0): 0x00291000 0x00291fff 0xc0291000 0xc0291fff 1
   UND stack (CPU 0): 0x00292000 0x00292fff 0xc0292000 0xc0292fff 1
  IDLE stack (CPU 0): 0x00293000 0x00294fff 0xc0293000 0xc0294fff 2
           SVC stack: 0x00295000 0x00296fff 0xc0295000 0xc0296fff 2
      Message Buffer: 0x00297000 0x0029afff 0xc0297000 0xc029afff 4
   Exception Vectors: 0x0029b000 0x0029bfff 0xffff0000 0xffff0fff 1
         Free Memory: 0x0029c000 0x1fffffff                       130404
         Free Memory: 0x00000000 0x00007fff                       8
switching to new L1 page table @0x284000...TTBR0=0x284000 OK
done.
nfreeblocks = 2, free_pages = 130412 (0x1fd6c)
bootstrap done.
vectorsvectors = ffff0000, vectors_data = ffff011c
vectors[0] = e59ff114, vectors_data[0] = c001d610
vectors[1] = e59ff114, vectors_data[1] = c001d878
vectors[2] = e59ff114, vectors_data[2] = c001d640
vectors[3] = e59ff114, vectors_data[3] = c001d6d4
vectors[4] = e59ff114, vectors_data[4] = c001d74c
vectors[5] = e59ff114, vectors_data[5] = c001d7c0
vectors[6] = e59ff114, vectors_data[6] = c0049560
vectors[7] = e25ef004, vectors_data[7] = 00000000
 0xffff0000
init subsystems: stacks vectors undefined page pmap_physload pmap [ Kernel symbol table missing! ]
done.
Loaded initial symtab at 0xc01b431c, strtab at 0xc01d319c, # entries 7897
pmap_postinit: Allocated 35 static L1 descriptor tables
Copyright (c) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
    2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013
    The NetBSD Foundation, Inc.  All rights reserved.
Copyright (c) 1982, 1986, 1989, 1991, 1993
    The Regents of the University of California.  All rights reserved.

NetBSD 6.99.23 (APC) #17: Sat Sep 28 14:46:19 UTC 2013
        root@builder.localdomain:/usr/obj/sys/arch/evbarm/compile/APC
total memory = 512 MB
avail memory = 499 MB
mainbus0 (root)
cpu0 at mainbus0 core 0: ARM1176JZ-S r0p7 (ARM11J V6ZK core)
cpu0: DC enabled IC enabled WB enabled LABT
cpu0: 16KB/32B 4-way L1 Instruction cache
cpu0: 16KB/32B 4-way write-back-locking-C L1 Data cache
vfp0 at cpu0: VFP11
obio0 at mainbus0
wmicu0 at obio0 addr 0xd8140000-0xd814ffff
icu-debug:
d8140040: 00000000 -> 00000000
d8140044: 00000000 -> 00000000
d8140048: 00000000 -> 00000000
d814004c: 00000000 -> 00000000
d8140050: 00000000 -> 00000000
d8140054: 00000000 -> 00000000
d8140058: 00000000 -> 00000000
d814005c: 00000000 -> 00000000
d8140060: 00000000 -> 00000000
d8140064: 00000000 -> 00000000
d8140068: 00000000 -> 00000000
d814006c: 00000000 -> 00000000
d8140070: 00000000 -> 00000000
d8140074: 00000000 -> 00000000
d8140078: 00000000 -> 00000000
d814007c: 00000000 -> 00000000
wmtmr0 at obio0 addr 0xd8100000-0xd810ffff intr 48 : WMT System Timer
wmtcom0 at obio0 addr 0xd8200000-0xd820ffff intr 32 : WMT UART
icu: establish_irq: is->is_irq=0x20
icu: unblock: irqbase=0x00000020, irqmask=0x00000001, irq_num=32, reg=0x00000008
icu: establish_irq: is->is_irq=0x30
icu: unblock: irqbase=0x00000020, irqmask=0x00010000, irq_num=48, reg=0x00000008
rn_init: radix functions require max_keylen be set
boot device: <unknown>
root device:
use one of: ddb halt reboot
root device: reboot
syncing disks... done
unmounting file systems...
unmounting done
rebooting...
ただし、まだ割り込みモードでは動いていないので、それはゆくゆく動かせばいいかと。

ということで、ixp12x0_com.cで割り込みがどのように制御されているかを調べた。

CR_RIE, CR_XIEが割り込みのビットなので、これをON/OFFしているところを探した。関数ごとにOn/Offを見てみると次のような関係になっている。
open : RX On
comintr : TX Off, RX Off
rxsoft RX on
start TX on
comintrでなんらか割り込みが上がってきたら、割り込みをOffにして、ソフトウェア割込みに処理を委譲。

ソフトウェア割込みは処理を終わらせたら割り込みをOn、ということのようだ。txsoftがOnにするのではなく、startでOnにしているのは、txsoftで*tp->linesw->l_start)(tp);という行があるので、間接的ではあるがstartが呼ばれるのではないかなぁと思っている。

しかし謎なのは、openでRXだけOnにしているところだ。もしかしたら、ほかの場所でもstartが呼ばれるということなのかもしれない。
ixpcomparamからもstartが呼ばれているし、きっとそのあたりが割り込みをOnにしてくれているのですかね。

ということで、割込みの有効化はmatchやattachなどでやるのではなく、openされた時にやるものだ、ということがわかった。/dev/consoleがopenされるのはいつなのだろう。

APC8750にNetBSD/evbarmを移植するための記録 9/26
2014/01/25(土) 28:06 NetBSD はてブ情報 はてブに登録 はてブ数

シリアルコンソールのドライバを書くにあたって、まずはRPIをお手本にするということでsys/arch/evbarm/dev/plcom.cだけど、comドライバに似ているつくり、というコメントがある。16550Aを意識しているってことかなぁ。ひとまず避けるか。

シリアルコンソールドライバのサイズを比較してみる。
25,419    arm/clps711x/clpscom.c
26,322    arm/ep93xx/epcom.c
18,175    arm/footbridge/footbridge_com.c
26,658    arm/ixp12x0/ixp12x0_com.c
48,257    arm/s3c2xx0/sscom.c
34,717    arm/sa11x0/sa11x0_com.c
58,808    arm/imx/imxuart.c
一番ちいさいfootbridge_com.cを見てみるか。

短くてわかりやすい。

が、fcomcons_bs_tagの扱いがほかと比べると違う。ほかというのはepcom.c, ixp12x0_com.cなど。

単独でbus_space_tag, handleを定義しているが、そうではなく、softcを定義して構造体でアクセスしている。_io.cというのがついている。なんかこれだけ。ふしぎ。

footbridgeってnetwinderなのか。evbarmかと思ったら、NetBSD/netwinderなのね。だから風情が違うって感じ。
epcom, ixp12x0あたりが似ていて、footbridgeが違うのはそこらへんが由来か。

シンプルなのはいいけど... 参考にするならevbarmあたりのほうがよさげって感じ?

とりあえず、sys/arch/evbarm/ixp12x0/ixp12x0_com.cをベースにスケルトンを作成。

ザックリソースを読んでわかったこと。

dev_type_open(xxx)はシステムコールなのかな。関数ポインタで該当するAPIを呼び出すのが主な仕事。
割り込みルーチンは、下準備をした後ソフトウェア割込みを発生させている模様。
   1173 	/* Wake up the poller. */
   1174 	softint_schedule(sc->sc_si);
ソフトウェア割り込みルーチンは、割り込みの種別(送信・受信)を判断して、それぞれの割り込みルーチンに飛ばしている。
ハードウェア割込みでそれぞれの割り込み処理をやらないのはなぜなんだろう。
ハードウェア割込みからはできるだけ早く復帰しましょう、っていうことなのか?

先ほどのリスト、
25,419    arm/clps711x/clpscom.c
26,322    arm/ep93xx/epcom.c
18,175    arm/footbridge/footbridge_com.c
26,658    arm/ixp12x0/ixp12x0_com.c
48,257    arm/s3c2xx0/sscom.c
34,717    arm/sa11x0/sa11x0_com.c
58,808    arm/imx/imxuart.c
の素性を調べてみる。footbridgeはさっきのとおりnetwinder。
clpsはepoc32だったり。
sscomはevbarm/smdk2xx0, MINI2440、
sacomはhpcarmのJORNADAだったり。
imxはわれらがnetwalkerだったり。

ixp12x0とepcomはそっくりさん。どっちが先に作られたのかを見てみたら、ixp12x0が祖先の模様。
matchとattachはevbarmがわで定義しているみたいですね。
CFATTACH_DECL_NEWがarm/evbarmのどっちに所属するかはそこらへんが違い。

とりあえず、ixp12x0, epcomをベースに進めていくことにしよう。

wm8750というのを頭につけてこれまでやってきたけど、いかにも冗長だなと。
wmtぐらいでよさそうな気がする。どうせwmtなシリアルは似たようなもんだろうし、割り込みあたりは結構違うかもしれないけど、wm8xxxぐらいには同じなんじゃないかな。

そもそもドライバがuart, icu, tmrぐらいしかないので。ていうか、現時点で理解して(したつもりで)作っているのってicuぐらいしかない気がする。

この調子だと、いつになったらインタラクティブな入出力ができるかわからんなぁ。

やっぱり、新しいCPUは無謀だったか。後悔先に立たず。小さな目標を立ててクリアしていこう。

まずはcnシリーズぐらいは動かしたい。すでにputcは動いているのだから、現状ぐらいまでは戻そう。当然TXBUSYはCOMドライバじゃないから面倒見れるわけだし。うむ。

まとめると、
  • evbarmがわにcomドライバを用意する(apccomとか?)
  • dev_type_xxxシリーズは簡単そうなので実装
  • cnシリーズを除くその他の関数は、#ifdefあたりで逃げる
  • cnシリーズを実装
ぐらいはやらないと、コンパイルが通らない&consinitが動作しない。

Linux久々に調べてみるか。UBOOTのコンフィグはここらしいな。

https://github.com/apc-io/apc-8750/blob/master/u-boot/include/configs/wmt.h

なんとここにwmtのソースがいっぱい。

https://github.com/apc-io/apc-8750/tree/master/u-boot/cpu/arm920t/wmt

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シリーズを作ってみましょうか。

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

もしかしたら、UARTの初期化が足らないのかもしれない。

U-bootでシリアル付近をダンプ
WMT # md.l d8200000 64
d8200000: 0000003a 0000000d 00010007 00000007    :...............
d8200010: 00000000 00000000 00000081 00000003    ................
d8200020: 00000000 00000000 000001d8 0000000a    ................
bus spaceで読んでみる。
0000: 0000000d
0004: 0000000d
0008: 00010007
000c: 00000007
0010: 00000000
0014: 00000000
0018: 00000041
001c: 00000003
0020: 00000000
0024: 00000000
0028: 000001d8
わかりやすく並べる。
d8200000: 0000003a 0000000d 00010007 00000007    :...............
d8200000: 0000000d 0000000d 00010007 00000007

d8200010: 00000000 00000000 00000081 00000003    ................
d8200010: 00000000 00000000 00000041 00000003

d8200020: 00000000 00000000 000001d8 0000000a    ................
d8200020: 00000000 00000000 000001d8
ほとんど変わらない。00000081と00000041ぐらいか。無理やり上書きしてもダメであった。

ひとまず、IC Highest-priority Status registerのやつ、本当かどうか試してみる。

任意のIRQをON/OFFできる配列を作り、attachで書き込み、自在に割り込みをコントロールしようとしたのだが、うまくいかない。

気づいたのだが、割り込みは、ビット的に右から割り振られるようだ。
つまり、IRQ0からIRQ3を32bitで表現すると0x[IRQ3][IRQ2][IRQ1][IRQ0]という並び順になるようだ。どうりでOn/Offがうまくいかないわけだ。左からだとてっきり思っていた。ということで、このルールにしたがうと、ちゃんと割り込みを制御できることがわかった。

ついでに気づいたのだが、IC Interrupt Status registerをみてみると、当該IRQ番目のビットに1が立っている。
たとえば、IRQ22であれば左から0から始めて22番目にビットが立った0x00400000になっている。

調子に乗って、eth0のIRQ10も同時に発生させると、22番目と10番目の両方にビットが立った0x00400400が得られるようだ。
同時に複数の割り込みが入った時には、ここを見ればよいことがわかった。で、複数入ったうちの優先度の高いものが、その名のとおりIC Highest-priority Status registerに数字として格納されるということのようだ。

さて、ここまできたが、一向にUARTの割り込みが入らない。

良く考えてみたら、u-bootは割り込みで動いていないのではないか?
そういえば、割り込みの64個あるディスクリプタは全部ゼロだったではないか。割り込みが上がるはずはない。しかしながら、入力はできている。もしかして、ポーリングモードなのかもしれない。

そういえば、wmt_uart.hにはFIFO full/emptyだのregister full/emptyだのとあった。そもそも16550AはFIFOを持っていて割り込みで動くとWikipediaにも書いてあって、割り込みじゃなきゃいけないと思い込んでいたのか?だんだん混乱してきたぞ。

UART register fifoでググると素晴らしいページが見つかった。

beagleboard を触ろう - 割り込み [組み込みソフト]

ここにかいてあるところによると、割り込みモードというのがあるらしい。
IER Interrupt Enable RegisterとFCR FIFO Control Registerやらに設定を行えば、割り込みが入ると書いてある。割り込みがアサートされる、というのが言い回しのようで勉強になる。

とりあえず、いっぱいビットを立てて動かしてみたれ、IERとFCRの両方に大量にビットを立ててみた。

なんと、割り込みが入った。ちゃんとInterrput status registerの32番目に1が立っているし、IC Highest-priority Status registerにも0x20が表示されている。よしよし。やっぱりUARTの初期化が足らなかったのだな。

とにかく、いっぱいビットを立てすぎてしまったので、割り込みが入りまくって、当初の狙いであるシリアル入力の割り込みだけを捕捉したい。

おそらく、IERとFCRのビットをそれぞれうまくセットすればよいのだろう。

ということで、TXっぽいビットをかたっぱしから落としたが、こんどはシリアル出力ができなくなった。
そもそもIERのまえにFCRを設定しなければ、意味がないのでは、などと順番を入れ替えたり、あれこれ試したが、どうにも入力だけに反応するような状態にはならない。

場当たり的な試行錯誤で頑張るのはやめて、すこしほかの実装を見るほうが今は近道かな。

とりえず、UARTからの割り込みの上げ方は(制御はできないけれども)わかったのでよしとしよう。

動作確認するために無理やりコードをいじっていて、収拾がつかなくなっていたりするし、unblock・blockのルーチンも間違ったままなので、すこし割り込みコントローラ周りを本来の動作にしてあげなければならないと思う。

irq_handlerが本来何をすべきなのか、そこから呼ばれるルーチンからcomドライバの割り込み処理まで繋いでであげないといけないし。複数の同時割込みも、レジスタを見たらわかる、ということが判明したし。

そういえば、UARTの表示が遅くて手を入れた件は、もしかしたらレジスタはポーリングモードだがcomドライバは割り込み前提で作られていたりするからタイムアウトに時間がかかっているのかもしれないな。ということは、手を入れたところをもどして、割り込みのOn/Offで挙動が変わるか調べてみればよいかもしれない。いらないかと思っていたTXの割り込みも実際には必要なのかも?

今日はここまでにしよう。

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

割り込み処理がどう呼ばれるのかを追っかけてみる。

comの場合、comintrという関数が割り込み処理で呼ばれる。
それを登録するのは、シリアルドライバのattach処理で呼ばれているintr_establishである。
intr_establishはsys/arch/arm/pic/pic.c#699にあり、同ファイル内のpic_establish_intrを呼ぶ。
    699 intr_establish(int irq, int ipl, int type, int (*func)(void *), void *arg)
    700 {
    701     KASSERT(!cpu_intr_p());
    702     KASSERT(!cpu_softintr_p());
    703
    704     for (size_t slot = 0; slot < PIC_MAXPICS; slot++) {
    705         struct pic_softc * const pic = pic_list[slot];
    706         if (pic == NULL || pic->pic_irqbase < 0)
    707             continue;
    708         if (pic->pic_irqbase <= irq
    709             && irq < pic->pic_irqbase + pic->pic_maxsources) {
    710             return pic_establish_intr(pic, irq - pic->pic_irqbase,
    711                 ipl, type, func, arg);
    712         }
    713     }
    714
    715     return NULL;
    716 }
sys/arch/arm/pic/pic.c#pic_establish_intrでは様々な前処理を行った後、関数ポインタをつかって、機種依存の割り込みドライバに書かれたestablish処理とunblock処理を呼んでいる。
    660     (*pic->pic_ops->pic_establish_irq)(pic, is);
    661
    662     (*pic->pic_ops->pic_unblock_irqs)(pic, is->is_irq & ~0x1f,
    663         __BIT(is->is_irq & 0x1f));
機種依存側の処理を見てみると、RPIなどは何もしていない。結局のところ、pic.cに渡されたfuncがpic_sources[]に格納されて、随時呼び出されているということなのだろうか。

is->is_funcを呼び出しているところは、sys/arch/arm/pic/pic.c#pic_dispatchである。
    255 void
    256 pic_dispatch(struct intrsource *is, void *frame)
    257 {
    258     int rv;
    259
    260     if (__predict_false(is->is_arg == NULL)
    261         && __predict_true(frame != NULL)) {
    262         rv = (*is->is_func)(frame);
    263     } else if (__predict_true(is->is_arg != NULL)) {
    264         rv = (*is->is_func)(is->is_arg);
それを呼び出しているのはsys/arch/arm/pic/pic.c#pic_deliver_irqsである。
    277 pic_deliver_irqs(struct pic_softc *pic, int ipl, void *frame)
    278 {
    279     const uint32_t ipl_mask = __BIT(ipl);
    ...
    335             is = pic->pic_sources[irq_base + irq];
    336             if (is != NULL) {
    337                 cpsie(I32_bit);
    338                 pic_dispatch(is, frame);
    339                 cpsid(I32_bit);
さらに、sys/arch/arm/pic/pic.c#pic_list_deliver_irqsに呼ばれており、
    430 pic_list_deliver_irqs(register_t psw, int ipl, void *frame)
    431 {
    432     const uint32_t ipl_mask = __BIT(ipl);
    433     struct pic_softc *pic;
    434
    435     while ((pic = pic_list_find_pic_by_pending_ipl(ipl_mask)) != NULL) {
    436         pic_deliver_irqs(pic, ipl, frame);
    437         KASSERT((pic->pic_pending_ipls & ipl_mask) == 0);
さらにsys/arch/arm/pic/pic.c#pic_do_pending_intsから呼ばれ、
    443 pic_do_pending_ints(register_t psw, int newipl, void *frame)
    444 {
    445     struct cpu_info * const ci = curcpu();
    ….
    458             pic_set_priority(ci, ipl);
    459             pic_list_deliver_irqs(psw, ipl, frame);
    460             pic_list_unblock_irqs();
これを呼び出しているのが機種依存割り込みドライバのirq_handerになる。たとえば、geminiであればgemini_irq_handlerである。

gemini_irq_handlerを呼び出しているのは、検索すると出てこないと思いきや、ヘッダファイルに手掛かりがあった。
geminiであればsys/arch/arm/gemini/gemini_intr.h#6にある。
    6 #define    ARM_IRQ_HANDLER    _C_LABEL(gemini_irq_handler)
ARM_IRQ_HANDLERというのが怪しい。

sys/arch/arm/arm32/irq_dispatch.S#87をみると、ARM_IRQ_HANDLERにジャンプしている。
     87 /*
     88  * irq_entry:
     89  *    Main entry point for the IRQ vector.  This is a generic version
     90  *    which can be used by different platforms.
     91  */
     92     .text
     93     .p2align    5
     94 ARM_ASENTRY_NP(irq_entry)
     95     sub    lr, lr, #0x00000004    /* Adjust the lr */
     96
     97     PUSHFRAMEINSVC            /* Push an interrupt frame */
     98     ENABLE_ALIGNMENT_FAULTS        /* finishes with curcpu() in r4 */
     99
    100     /*
    101      * Increment the interrupt nesting depth and call the interrupt
    102      * dispatch routine.  We've pushed a frame, so we can safely use
    103      * callee-saved regs here.  We use the following registers, which
    104      * we expect to persist:
    105      *
    106      *    r4    address of current cpu_info
    107      *    r6    old value of `ci_intr_depth'
    108      */
    109     ldr    r6, [r4, #CI_INTR_DEPTH]
    110     add    r1, r6, #1
    111     str    r1, [r4, #CI_INTR_DEPTH]
    112
    113     mov    r0, sp            /* arg for dispatcher */
    114     bl    ARM_IRQ_HANDLER
irq_entryで追っかけると、sys/arch/arm/arm/vectors.S#59に記載がある。
     59 _C_LABEL(page0rel):
     60     b    reset_entry
     61     b    undefined_entry
     62     b    swi_entry
     63     b    prefetch_abort_entry
     64     b    data_abort_entry
     65     b    address_exception_entry
     66     b    irq_entry
なんとなく、page0relは色々なエントリポイントにジャンプするテーブルのようである。
page0relはどこだろうか。

sys/arch/arm/arm32/arm32_machdep.c#arm32_vector_initで使われている。
    134 arm32_vector_init(vaddr_t va, int which)
    135 {
    136 #if defined(CPU_ARMV7) || defined(CPU_ARM11) || defined(ARM_HAS_VBAR)
    137     /*
    138      * If this processor has the security extension, don't bother
    139      * to move/map the vector page.  Simply point VBAR to the copy
    140      * that exists in the .text segment.
    141      */
    142 #ifndef ARM_HAS_VBAR
    143     if (va == ARM_VECTORS_LOW
    144         && (armreg_pfr1_read() & ARM_PFR1_SEC_MASK) != 0) {
    145 #endif
    146         extern const uint32_t page0rel[];
    147         vector_page = (vaddr_t)page0rel;
machdepなのできっと機種依存のmachdepから呼ばれているのだろう、と思いつつ、案の定である。initarmの共通関数であるsys/arch/arm/arm32/arm32_boot.c#initarm_commonを代表例として挙げておく。
    152 initarm_common(vaddr_t kvm_base, vsize_t kvm_size,
    153     const struct boot_physmem *bp, size_t nbp)
    154 {
    …
    173 #ifdef VERBOSE_INIT_ARM
    174     printf("vectors");
    175 #endif
    176     arm32_vector_init(systempage.pv_va, ARM_VEC_ALL);
    177 #ifdef VERBOSE_INIT_ARM
    178     printf(" %#"PRIxVADDR"\n", vector_page);
    179 #endif
ということは、VERVOSE_INIT_ARMされていれば、
    147         vector_page = (vaddr_t)page0rel;
なのでpage0relが表示されているはずである。

VERBOSE_INIT_ARMして、該当箇所を見てみよう。
vectors 0xffff0000
どうやら0xFFFF0000というのがpage0relなのですな。

あれ? vbarっていうのが表示されていないな。ブロックごと飛ばされているって感じに見えるな。ARM_HAS_VBARっていうのが関係している?
ARM_HAS_VBARがRPIはあるけどAPCにはつけてないな。
あらためて、RPIと比べるとstd.rpi, std.apcは違いがある。
__HAVE_CPU_COUNTERとか。これは誰をベースにしたんだっけ?

ARM_HAS_VBARを有効にしたら以下のようになった。
vectors vbar=0xc01411c0 0xc01411c0
むむむ。さっきと全然違うな。
しかし、割り込みハンドラは呼ばれない。さて、なぜでしょうか?
  • 割込みが発生していない
  • 割込みを許可していない(だから割り込みハンドラが呼ばれない)
  • 割込み処理にバグがある
  • その他の初期化が足りていない(だから割り込みハンドラが呼ばれない)
うーん???

ARMjp-vH.pdfのA2-16、A2.6 例外によるとIRQ(割り込み)は0x00000018, 0xffff001cのアドレスだと書かれている。

本当にそうなのか試してみよう。

ubootのプロンプトを使って割り込みベクタを表示。リセット割込みのアドレスに書かれているea00002cにジャンプしてみる。
WMT # md.l ffff0000 8
ffff0000: ea00002c e59ff2d4 e59ff2d4 e59ff2d4    ,...............
ffff0010: e59ff2d4 e59ff2d4 e59ff2d4 e59ff2d4    ................
WMT # go ea00002c
## Starting application at 0xEA00002C ...
リセットされている感じがない。ちがうちがう。ffff0000から実行するのだ。
WMT # go ffff0000
## Starting application at 0xFFFF0000 ...


WonderMedia Technologies, Inc.
W-Load Version : 0.23.00.00
uboot set plla cmd ..found
wmt.plla.param=1:800:1,2:5,2:3
device clock is disabledethaddr............found
以下、起動メッセージが続く
おぉ、リセットされた。

VBARが有効ならば、先のc01411c0に飛べばリセットされるはずである。
WMT # go c01411c0
## Starting application at 0xC01411C0 ...
何も起こらない。つまり、VBARは有効にしてはいけないのではないか?

じゃぁ、リセット割込み以外e59ff2d4が実行されるみたいだ。
WMT # go ffff0004
## Starting application at 0xFFFF0004 ...
何も起こらない。
WMT # md.l ffff0000
ffff0000: ea00002c e59ff2d4 e59ff2d4 e59ff2d4    ,...............
WMT # md.l e59ff2d4 128
e59ff2d4:
アドレスを読ませてもくれない。

ところでnetbsdをobjdump -dしてみると
c01411c0 <page0rel>:
c01411c0:       eafb7bea        b       c0020170 <reset_entry>
c01411c4:       eafb7c83        b       c00203d8 <undefined_entry>
c01411c8:       eafb7bf4        b       c00201a0 <swi_entry>
c01411cc:       eafb7c18        b       c0020234 <prefetch_abort_entry>
c01411d0:       eafb7c35        b       c00202ac <data_abort_entry>
c01411d4:       eafb7c51        b       c0020320 <address_exception_entry>
c01411d8:       eafc2b30        b       c004bea0 <irq_entry>
c01411dc:       e25ef004        subs    pc, lr, #4
てな感じである。これはVBARを外してみるとどうなるか。

あれ?変わらない。

obj全部消してやり直そう。-uついてなかったので作り直しているはずなんだけどな。
とおもっていたら、std.rpiを消してた!

うーん、やっぱりだめですね。ffff0000に配置されていません。

と思ったら、違うんですな。よく読むと、ARM_VECTORS_LOWじゃないから以下のルートに入るのですね。
    159 #ifndef ARM_HAS_VBAR
    160     if (CPU_IS_PRIMARY(curcpu())) {
    161         extern unsigned int page0[], page0_data[];
    162         unsigned int *vectors = (int *) va;
    163         unsigned int *vectors_data = vectors + (page0_data - page0);
    164         int vec;
    165
    166         /*
    167          * Loop through the vectors we're taking over, and copy the
    168          * vector's insn and data word.
    169          */
    170         for (vec = 0; vec < ARM_NVEC; vec++) {
    171             if ((which & (1 << vec)) == 0) {
    172                 /* Don't want to take over this vector. */
    173                 continue;
    174             }
    175             vectors[vec] = page0[vec];
    176             vectors_data[vec] = page0_data[vec];
    177         }
    178
    179         /* Now sync the vectors. */
    180         cpu_icache_sync_range(va, (ARM_NVEC * 2) * sizeof(u_int));
    181
    182         vector_page = va;
    183     }
ということでpage0, page0_dataが大事と。
デバッグ文を入れてみると以下のように。
      Message Buffer: 0x002a7000 0x002aafff 0xc02a7000 0xc02aafff 4
   Exception Vectors: 0x002ab000 0x002abfff 0xffff0000 0xffff0fff 1
         Free Memory: 0x002ac000 0x1fffffff                       130388
         Free Memory: 0x00000000 0x00007fff                       8
switching to new L1 page table @0x294000...TTBR0=0x294000 OK
done.
nfreeblocks = 2, free_pages = 130396 (0x1fd5c)
bootstrap done.
vectorsvectors = ffff0000, vectors_data = ffff011c
vectors[0] = e59ff114, vectors_data[0] = c00203d0
vectors[1] = e59ff114, vectors_data[1] = c0020638
vectors[2] = e59ff114, vectors_data[2] = c0020400
vectors[3] = e59ff114, vectors_data[3] = c0020494
vectors[4] = e59ff114, vectors_data[4] = c002050c
vectors[5] = e59ff114, vectors_data[5] = c0020580
vectors[6] = e59ff114, vectors_data[6] = c004c320
vectors[7] = e25ef004, vectors_data[7] = 00000000
 0xffff0000
ちゃんと、ffff000からe59ff114つまり
c0141a20 <page0>:
c0141a20:       e59ff114        ldr     pc, [pc, #276]  ; c0141b3c <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbcc>
c0141a24:       e59ff114        ldr     pc, [pc, #276]  ; c0141b40 <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbd0>
c0141a28:       e59ff114        ldr     pc, [pc, #276]  ; c0141b44 <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbd4>
c0141a2c:       e59ff114        ldr     pc, [pc, #276]  ; c0141b48 <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbd8>
c0141a30:       e59ff114        ldr     pc, [pc, #276]  ; c0141b4c <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbdc>
c0141a34:       e59ff114        ldr     pc, [pc, #276]  ; c0141b50 <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbe0>
c0141a38:       e59ff114        ldr     pc, [pc, #276]  ; c0141b54 <_KERNEL_OPT_ARM_INTR_IMPL+0xc230bbe4>
c0141a3c:       e25ef004        subs    pc, lr, #4
        ...

c0141b3c <page0_data>:
c0141b3c:       c0020390        mulgt   r2, r0, r3
c0141b40:       c00205f8        strdgt  r0, [r2], -r8
c0141b44:       c00203c0        andgt   r0, r2, r0, asr #7
c0141b48:       c0020454        andgt   r0, r2, r4, asr r4
c0141b4c:       c00204cc        andgt   r0, r2, ip, asr #9
c0141b50:       c0020540        andgt   r0, r2, r0, asr #10
c0141b54:       c004c2e0        andgt   ip, r4, r0, ror #5
c0141b58:       00000000        andeq   r0, r0, r0
ということですね。

これで、割り込みが来たらジャンプしてくれるはずです。

が、割込みが来ない。なぜだ。割り込み禁止になっていたりするんですかね?
逆に、どこで割り込みを有効にするんでしょうか?

思い起こせば、start.Sを読んだのがいつだったか。
確か、割り込みを禁止していたような。と思いつつrpiのstartを眺めるも、アセンブラは忘却の彼方。

こういう時には別所さん由来の橋本さんソースを読もう。ということでnetwalkerである。

もう。いい感じでコメントが入っているではないか。

同様にrpiでも同じ処理をしておる。むしろ、冒頭である。

さて、割り込みを禁止しているのだから、どこかでそれを解いているはず。
ここまで読んでくると、cpsrがキーワードだとわかる。

cpsrでnxr検索すればヒントが。
enable_interrputしてるやん。

しかし、すべてがそうではない。
src/sys/arch/armだとat91, ep93xx, footbridge, imx, iomd, ixp12x0, mpcore, ofw, omap, s3c2xx0, xscaleの一派がenable_interruptしている。
おっと、レファレンスモデルのrpiがないじゃん。
なんなの。

ははぁ、initarm_commonしてる人は、共通関数でenableしてるんでしょ、と思って、arm32_bootを読むが、それっぽいところなさそうだなー。
それどころか、initarm_common族のomap2がicuのattachでenable_interruptしてるぞ。
もうよくわからん。initarm_common族と、enable_interrupt族を比較するとomap2だけが特殊?

enable_interruptもほとんどintr_initメソッドで呼ばれてるけどね。initarm_common族のomap2は自前initarmで呼べないもんだから、attachで読んでるってわけか。

つまり、あんまりタイミング関係ないってこと?initarmでもデバイスのattachでもいいってことかな?

しっかし、arm splx 割り込み、でgoogleしたら、もしかして: arm cpsr 割り込み、と言われるのはすごいな。

attachでenable_interruptしたら割り込みルーチンが呼ばれた!

割込みレジスタ周りを全部表示するデバッグルーチンを書く。
IC Highest-priority Status registerをみると0x0000000aというのがある。もしかしたら割り込み番号?
調べるとeth0であった。死ぬほど呼ばれるので0x0aあたりを32ビットごと00000000で初期化する。これでeth0の割り込みは来ないはず。

しかし、まだ割り込みルーチン呼ばれる。eth0以外からも割り込みが上がってきているということか。

調べると、どうやら0x00000016つまり22である。pmc_wakeupのようだ。これも0x00000000で周辺ごと初期化する。

これでシリアルの割り込みが来たら、すぐにわかるはず。

しかし、割り込みは来ない。むー。何がいかんのだろうか。