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

割り込みコントローラをちゃんとしないといけないらしいが、まるで分らない。

ARMのドキュメントをあたると、Cortexなどは標準の割り込みコントローラが内蔵されているようにみえるが、ARM11は英語しかないのでわからん。が、Chapter 12. Vectored Interrupt Controller Portとかいうのがある。こいつはPL192とかいうやつらしいのでその仕様書を読めと書いてある。

Linuxのソースをあたっても、PL192とかいうのはWM8750では使ってなさそうな気がする。

そういえば、http://www.spinics.net/lists/arm-kernel/msg240125.htmlとかいうページを見つけた。vt8500-intcとか読めるので、VT8500というCPU(おそらく昔のVIAのARMで、それがWonderMediaに子会社化されたとかそういうの?)とcompatibleといっておる。VT8500のirc.cとWM8750のirc.cを比較すると、似ているといえば似ているけれど、だいぶ違うような。

http://lxr.free-electrons.com/source/drivers/irqchip/irq-vt8500.c

https://github.com/apc-io/apc-8750/blob/master/kernel/arch/arm/mach-wmt/irq.c

そもそもソースツリーの構造が違うのを比較しても意味ないかもしれんな。

NetBSDの割り込み処理を読んでも、よくわからん。もしかしたら、LinuxとNetBSDの両方にある評価ボードの割り込み処理を比較して、Linuxでこう書くところをNetBSDでこう書けばよい、とわかれば、それをWM8750のirq.cからNetBSDに変換することができるかもしれん。

例によってLinux側のgeminiを見てみるか。ソースはapc-8750/kernel/arch/arm/mach-gemini/irq.cだな。

まずは、レジスタは4刻みで定義されている。
#define IRQ_SOURCE(base_addr) (base_addr + 0x00)
#define IRQ_MASK(base_addr) (base_addr + 0x04)
#define IRQ_CLEAR(base_addr) (base_addr + 0x08)
#define IRQ_TMODE(base_addr) (base_addr + 0x0C)
#define IRQ_TLEVEL(base_addr) (base_addr + 0x10)
#define IRQ_STATUS(base_addr) (base_addr + 0x14)
#define FIQ_SOURCE(base_addr) (base_addr + 0x20)
#define FIQ_MASK(base_addr) (base_addr + 0x24)
#define FIQ_CLEAR(base_addr) (base_addr + 0x28)
#define FIQ_TMODE(base_addr) (base_addr + 0x2C)
#define FIQ_LEVEL(base_addr) (base_addr + 0x30)
#define FIQ_STATUS(base_addr) (base_addr + 0x34)
geminiとwmtで関数を比較する。
  • gemini_ack_irq
  • gemini_mask_irq
  • gemini_unmask_irq
  • gemini_init_irq
  • wmt_ack_irq
  • wmt_mask_irq
  • wmt_unmask_irq
  • wmt_init_irq
  • wmt_irq_suspend
  • wmt_irq_resume
  • wmt_irq_init_devicefs
最初の四つは似たような関数名だ。suspend, resumeはPMのifdefで囲われているので、パワーマネジメント関係が必要になってきたら頑張ればよいとして、関数の有無レベルでの差分はwmt_irq_init_devicefsですかね。

関数の直後にdevice_initcall(wmt_irq_init_devicefs);という文があるのは初期化の際に呼ばれるもののよう。

関数の中身はデバイスクラスを登録する関数を読んでいるようだ。こいつもその直前の構造体を読むとsuspend,resumeはこの関数名ですよと言っているので、まぁ、最初は気にしなくてよいことにしよう。

ack_irqは先ほどのマクロIRQ_CLEARに1<< irqをraw_write1しているだけ。
mask_irqはIRQ_MASKを読んで~(1<<irq)のANDをとって再びIRQ_MASKに書いているだけ。

unmask_irqは|=(1<<irq)しているだけ。maskってintだから32bitとして、32個しか割り込みないのかな?

init_irqが初期化処理みたい。なんか、forループでNR_IRQSの数だけ回している。

geminiのヘッダを見ると
#define NORMAL_IRQ_NUM 32

#define GPIO_IRQ_BASE NORMAL_IRQ_NUM
#define GPIO_IRQ_NUM (3 * 32)
#define NR_IRQS (NORMAL_IRQ_NUM + GPIO_IRQ_NUM)
つうことは、割り込みの数は32じゃなくて32 + 32 * 3 で128あるってことか。
GPIOだけマクロが
#define IRQ_GPIO(x)    (22 + (x))
ってなっているのがポイント?  GPIOだけ特別扱いされているっぽい、って思っていればいいか。

ループの中では、set_irq_chip, set_irq_handler, set_irq_flagsを割り込みそれぞれに対して呼び出している模様。
handle_edge_irqの時だけはmode, levelのビットを立てている。

ループの後、すべての割り込みを禁止して、割り込みモードとして先のmode, levelをraw_writelで書き込んでいる。

geminiは割り込み32個をビットにして管理しているってことかな?

NetBSDのgemini_icu.cをみると、確かに割り込み32個あるようだ。sources[32]がそれだろう。

関数として、block, unblockというのがLinuxのmask, unmaskっぽいな。
Linuxのack_irqに相当するものはなさそう。

NetBSDの場合、find_pending_irqs, irq_handler,establish_irqがある。

find_pending_irqs, irq_handlerそれぞれ、最後はpic_から始まる関数を呼んでいる。attachでもadd_picなどとしている。こいつが共通的な何かをしてくれるものなのだろうか。

しかし、irq_handlerは何をしているのだろう、というか、pending処理以外、何もしていないような。ハンドリングしてくれるのかと思ったが...

establish_irqは、IRQの確立、つまり初期化処理なのだろう。UARTのattachで似たような関数を呼んでいた。つまり、何らかのデバイスドライバがこいつを呼ぶということであろうか。

Linuxは最初にループでIRQの初期化を回す。NetBSDはデバイスドライバが初期化されるごとに個々のIRQの初期化処理が走るということなのかもしれない。

WMTのLinuxコードを見ると、割り込みコントローラICDC0は次のようになっている。
#define    ICDC0_ADDR      (__IC_BASE + 0x40)      /* dest_ctl_addr[0]  */
__IC_BASEは
#ifdef __IC_BASE
#error  "__IC_BASE has already been defined in another file."
#endif
#ifdef INTERRUPT_CTRL_BASE_ADDR        /* From wmt_mmap.h  */
#define __IC_BASE       INTERRUPT0_CTRL_BASE_ADDR
#else
#define __IC_BASE       0xFE140000      /* 64K */
#endif
てなぐあい。割り込みコントローラのVAであろう。

ちなみにmmap.cでは
#define INTERRUPT0_CTRL_BASE_ADDR               (0xD8140000 + WMT_MMAP_OFFSET)  /* 64K  */
なのだ。いずれにせよVAのアドレスになっている。bus_space_mapはPAばかりだったけど...

それはともかく、irq.cではICDC0_VAL(irq) = val;という値の代入しかやっていない。

初期化(wmt_init_irq)では0を入れており、mask, unmaskではICDC_ENABLEを立てたり・クリアしたりしている。

ICDC_ENABLEは
#define    ICDC_ENABLE     BIT3                  /* Interrupt enable bit.*/
なのでBIT3を立てている・クリアしているだけである。

ICDC0_VAL(irq)を追っかけてみる。まずは、元の定義は
#define ICDC0_VAL(x)     (REG8_VAL(ICDC0_ADDR + ICDC_OFFSET_WMT(x)))
となり、マクロが絡み合っているので読み解こう。

まず、ICDC_OFFSET_WMT(irq)は次のように定義されている。
#define ICDC_OFFSET_WMT(x)  ((x) & ICDC_NUMMASK_WMT)
NUMMASKなどと言っているが、
#define ICDC_NUMMASK_WMT    0x7F           /* mask to avoid overflow */
ということでほぼxと思えばよい。つまり、ICDC0_VALマクロは
#define ICDC0_VAL(x)     (REG8_VAL(ICDC0_ADDR + x))
である。で、残ったREG8_VALは最終的にこうなる。hardware.hに定義がある。
# define __REG8P(x)     (((__regbase8 *)((x)&~4095))->offset[((x)&4095)>>0])
typedef struct {
    volatile u8 offset[4096];       /* 4K * 1 = SZ_4K */

} __regbase8;
つまり、ICDC0_ADDRを先頭に、割り込み番号ごとに8ビット割り当てられて64個ならんでいると。で、ENABLEのやつにはその8ビットの3ビット目に1が立っていると。

初期化ルーチンを読んでみる。

まず、ICDC0_ADDRを先頭とした8ビット単位で64個のテーブルを作り、0で初期化しているというわけか。

次に、おのおののIRQで使うルーチンをset_irq_chipで指定。すべて、
static struct irq_chip wmt_normal_chip = {
    .name   = "normal",
    .ack    = wmt_ack_irq,
    .mask   = wmt_mask_irq,
    .unmask = wmt_unmask_irq,
};
なので、自ファイル(irq.c)にある関数を呼び出すと。

次に、set_irq_handlerでデフォルトのハンドラhandle_level_irq(kernel/irq/chip.c)をセット。set_irq_flagにデフォルトのフラグをセットしている。
デフォルトのフラグは
#define IRQF_VALID    (1 << 0)
なので0x01ということか。

つうことで、結局、gemini, wmtともにirq.cにはMDの部分だけが書かれていたということですね。

geminiは32ビットのうち、IRQビット目を立てる、という方式だし、WMTは8ビットつまり1バイトを64個ならべて、IRQ個目の1バイトの3ビット目を立てたり、クリアしたり、という部分だけでした。

そこをNetBSDで実装してあげればいいんですね、たぶん。

名前:  非公開コメント   

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