SSブログ

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


割り込み処理について見てみましょう。

ARM プロセッサーでは、割り込み(またはその他例外)が発生すると、ベクターテーブルにジャンプします。
ベクターテーブルの位置は、system control coprocessor control register の V ビットによって決まります。

V ビットが 0 の時:Vector Base Address Register が指すアドレス
V ビットが 1 の時:0xFFFF0000

起動時は、V ビットが 0 で Vector Base Address Register の値が 0x14000 なので、ベクターテーブルは 0x14000 にあるということになります。
OMAP3530 の場合、アドレス 0x14000 には Boot ROM がマップされています。
(正確には、Boot ROM は、本来 0x40000000 - 0x4001BFFF にマップされていて、リマップレジスタによって 0x00000000- 0x0001BFFF にもマップされているように見える・・・ハズ)
Boot ROM 上にあらかじめベクターテールが用意されているわけですね。


0x14000 に配置されているベクターテーブルの内容は、OMAP35x TRM に載っています。

ROMvector2.png

リセットベクターは、Boot ROM 内の startup コードにジャンプします。
それ以外は、0x4020FFxx というアドレスにジャンプします。
0x4020FFxx は、内臓 SRAM の一番終端部分ですね。

割り込み (IRQ) が発生した場合、

0x14018 にジャンプ
    ↓
0x4020FFDC にジャンプ
    ↓(0x4020FFDC に IRQ ハンドラへのジャンプ命令を書いておく)
IRQ ハンドラにジャンプ

という経路を経て、IRQ ハンドラに到達します。

0x4020FFC8 には未定義例外ハンドラへのジャンプ命令を、0x4020FFCC には SWI 例外ハンドラへのジャンプ命令を・・・ 0x4020FFE0 には FIQ ハンドラへのジャンプ命令を、それぞれ書き込んでおく必要があります。

でも、ジャンプを 2回もして、効率があまりよくなさそうです。

vector_jump.png

これとは別に、Vector Base Address Register を書き直して、SRAM 上のベクターテーブルに直接ジャンプさせることもできます。
この方法だと、上の方法よりジャンプが 1回少なくなって、経済的です。
しかし、x-loader は、2回ジャンプする方法を採っています。

・・・

ところが、、、

x-loader は、なぜか Boot ROM のベクターテーブルからのジャンプ先を 0x4020F800 として中継テーブルを構成しちゃってるんですよねえ・・・。
0x4020F800 じゃなくて 0x4020FFC8 じゃないですかあ。

まあ、x-loader 実行中は割り込み禁止にしてあるので、どうでもいいのかもしれませんが (^^;
そもそも、x-loader の例外ハンドラーは do_hang() という関数で、"X-Loader hangs" というメッセージをシリアルコンソールに残して無限ループするだけなので、例外をちゃんと処理するようにはできていません。
絶対例外発生しないはずなんだけど、万が一発生したら "X-Loader hangs" を出して通知する、くらいの感じかもしれません。

割り込みを扱うならば、ちゃんとやっておく必要があります。
以下のようなアセンブリコードを、0x4020FFC8 に配置しておきます。

_vector:
	b	reset
 	ldr	pc, _und
	ldr	pc, _swi
	ldr	pc, _pabort
	ldr	pc, _dabort
	ldr	pc, _unused
	ldr	pc, _irq
	ldr	pc, _fiq
_und:	
	.word _do_und
_swi:	
	.word _do_swi
_pabort:	
	.word _do_pabort
_dabort:	
	.word _do_dabort
_unused:	
	.word _do_unused
_irq:	
	.word _do_irq
_fiq:	
	.word _do_fiq


SRAM_vector2.png


これ以降は、とりあえず IRQ だけに注目します。

IRQ は、割り込みコントローラから CPU に上げられてきます。
OMAP3530 では 96個の IRQ がサポートされており、各番号ごとに特定のデバイスが割り当てられています。

interrupt_mapping2.png

デバイスからアサートされた IRQ が CPU に到達するには、2つの関門があります。
1つ目が、デバイスから割り込みコントローラへ IRQ を通すかどうかを決定する Mask Interrupt Register (MIR) によるマスクです。
MIR は MIR0 - MIR2 までの計 96 ビットのレジスタで、各ビットが IRQ 番号に対応しています。
MIR0 のビット 0 が IRQ_0, ビット 1 が IRQ_1,... MIR1 のビット 0 が IRQ_32,... MIR2 のビット 0 が IRQ_64,... のような感じです。
MIR のビットが立っていると、対応する IRQ はマスクされます。
即ち、割り込みコントローラは IRQ を受け付けません。
マスクされていた場合は、IRQ をアサートしているデバイスは、アサート信号を出し続けることになります。
そして、マスクが解除された時に、アサート信号が拾われて CPU に伝達されます。

2つ目が、Current Program Status Register (CPSR) の I ビットによるマスクです。
このビットが立っていると、CPU に割り込みコントローラからのアサート信号が上がっても、CPU は割り込みを受け付けません。
マスクされていた場合は、アサートしている割り込みコントローラは、アサート信号を出し続けることになります。
そして、マスクが解除された時に、アサート信号が拾われて割り込みが CPU に受け付けられます。

CPSR_MIR2.png

デバイスからの IRQ アサートが CPU に受け付けられるようにするには、MIR の対応するビットと CPSR の I ビットの両方を 0 にしなければいけないことになります。

割り込みが発生した場合、CPSR の I ビットは、自動的にマスクされます。
つまり、割り込み処理は、割り込み禁止状態で開始することになります。
割り込み処理が完了した後、元の処理に戻る際に、CPSR の I ビットは復元され、マスク解除されます。

MIR の方は、このような自動マスク機能はありません。
マスクしたい場合は、S/W が明示的に行う必要があります。


さて、割り込みハンドラー _do_irq はどのような処理をすればいいでしょう?

1. まず、IRQ 発生時に処理を行い、処理を行ったら元の処理に戻らなければならないので、レジスタ値を保存する処理が必要です。
2. 次に、本来の目的である、デバイスの割り込みに対応した、デバイス特有の処理を行う必要があります。
3. 最後に、保存したレジスタ値を復帰して、元の処理に戻ります。

270: _do_irq:	
271: 	sub lr,lr,#4
272: 	str lr,[sp,#-4]!
273:	mrs r14,spsr
274:	stmfd sp!,{r0-r3,r12,r14}
275:	bl do_irq
276:	ldmfd sp!,{r0-r3,r12,r14}
277:	msr spsr_csxf,r14
278:	ldmfd sp!,{pc}^


271 - 274 行目が 1 の処理、276 - 278 行目が 3 の処理になります。

_do_irq に到達した時点で、r13 (= sp) と r14 (= lr) は、Cortex-A8 の H/W が自動的に保存してくれます。
また、r15 (= pc) の値は、r14 にコピーされます。
これも H/W が自動的にやってくれます。
S/W として行わなければならないのは、r0 - r12, r14 の保存です。

まず r14 (= lr) ですが、272 行目でスタックに保存しています。
r14 には、割り込みが発生した時の r15 (= pc = program counter) の値が入っています。
この r15 の値は、割り込みが発生した時の命令アドレスが入っているわけではなく、+4 された値が入っています。
そのため、271 行目で lr の値を -4 しています。
こうしないと、割り込み処理終了後に元の処理に戻るとき、元の処理から +4 したところに戻ってしまいます。

273 行目で Saved Program Status Register (SPSR) の値を r14 にコピーしていますが、これは IRQ がネストした場合に備えてですね。
IRQ がネストしないように作ってやれば、この処理は不要なはずです。

274 行目で、r0 - r3, r12, r14 (= SPSR の値が入っている) のレジスタ値をスタックに保存しています。
これらの値を、276 行目でスタックから復帰させています。
なので、275 行目の呼び出し先 do_irq() で、これらのレジスタがどのように使われようが、元の状態に戻すことができますね。

S/W で保存しなければならないレジスタは r0 - r12, r14 なのですが、274 行目までに r0 - r3, r12, r14 の値しかスタックに保存していません。
残りの r4 - r11 の値は保存していませんが、いいんでしょうか。

ARM Procedure Call Standard では、r0 - r3, r12, r14 を caller で保障し、それ以外は callee が保障しなければならないことになっています。
275 行目で呼び出している do_irq より先で、r0 - r3, r12, r14 以外は破壊されないことが保証されるため、r4 - r11 は放っておいても大丈夫です。
コンパイラが r4 - r11 を保障するようなアセンブリコードを生成してくれます。


275 行目で呼び出している do_irq の先で、割り込み処理を行います。

14: static void (*irq_handler[96])(int);
15:
16: void do_irq(void)
17: {
18: 	int irq;
19: 	void (*handler)(int);
20:
21:	irq = __raw_readl(INTCPS_SIR_IRQ) & 0x7F;
22:	handler = irq_handler[irq];
23:	if (handler)
24:		handler(irq);
25:	sr32(INTCPS_CONTROL, 0, 1, 1);
26:	
27:	/* Data synchronization barrier */
28:	__asm__ volatile("mov r0, #0\n\t"
29:			 "mcr p15, 0, r0, c7, c10, 4\n");
30:}


21 行目でアクセスしている INTCPS_SIR_IRQ レジスタは、割り込みコントローラのレジスタで、現在アクティブになっている IRQ 番号を下位7ビットに保持しています。

この IRQ 番号で登録されているハンドラーがあれば、それを呼び出します。
この呼び出したハンドラーの中で、デバイス特有の処理を行います。
ハンドラーでは、通常は、割り込み要因を取り下げ、データを読み書きする処理を行います。

25 行目でアクセスしている INTCPS_CONTROL レジスタは、同じく割り込みコントローラのレジスタで、IRQ シグナルをリセットして、新しい IRQ が上がってくることを許可するためのレジスタです。

25 行目は分かりにくいですが、INTCPS_CONTROL[0] NEWIRQAGR ビットに 1 を立てています。
INTCPS_CONTROL[0] NEWIRQAGR に 1 を立てるまでは、割り込みコントローラから IRQ がアサートされ続けています。
INTCPS_CONTROL[0] NEWIRQAGR に 1 を立てることにより、アサートを止めると同時に、同じ IRQ 番号による次のアサートが可能になります。

24 行目で呼び出しているハンドラーの中で、デバイスの割り込み要因を取り下げないとどうなるでしょうか。
デバイスからは割り込みコントローラにアサートし続ける状態になってしまいます。
さらに、割り込み処理が終わって CPU の割り込みマスクが解除されると、CPU は IRQ を受け付けるようになりますので、今処理が終わったばかりの割り込みを再度処理することになります。
処理しても処理しても、割り込み要因が取り除かれないので、永久ループになってしまいますね。

こうなってはまずいので、24 行目で呼び出すハンドラーは、必ずデバイスの割り込み要因を取り下げなくてはなりません。(もしくは MIR で対応するビットを立ててマスクする。)

28 行目、29 行目では、data synchronization barrier を張っています。
この命令は、この行までのデータ読み書きを完了させてから、次の行以降を実行します。
これがあると、「INTCPS_CONTROL[0] NEWIRQAGR ビットを 1 にする ⇒ CPSR の I ビットを 0 にする」という順番が保証されます。
仮に、これが逆になって、「CPSR の I ビットを 0 にする ⇒ INTCPS_CONTROL[0] NEWIRQAGR ビットを 1 にする」という順番になってしまうとすると、IRQ がネストしてしまいます。
INTCPS_CONTROL[0] NEWIRQAGR ビットを 1 にしないと、割り込みコントローラは IRQ アサートを取り下げないからです。
・・・という理由で data synchronization barrier を配置するんだと思いますが、合ってるかな?
data synchronization barrier を張れ、というのは、OMAP35x TRM の Interrupt Controller 章の Interrupt Controller Basic Programming Model 節に書いてあります。


全体を通して、IRQ のアサートは、↓のように変遷します。

IRQ_assert_1of2.png
IRQ_assert_2of2.png

あ、そうだ。
事前に IRQ モードのスタックをちゃんと設定しておかないといけないのでした。
ARM は割り込みが発生すると、IRQ モードに自動的に移行し、スタックを IRQ モード用のスタックに変更します。
割り込みを有効にする前に、これをやっておかねばなりません。
こんな感じのコードです。

187:	/* stack setup for IRQ mode */
188:	mrs	r0,cpsr
189:	bic	r0,r0,#0x1f
190:	orr	r0,r0,#0xd2
191:	msr	cpsr,r0
192:	ldr	sp,=SRAM_STACK_IRQ


・・・

こんな感じで、割り込みを扱うには、少々の準備が必要です。
実際に自分で割り込みを動かしてみようとすると、結構はまってしまいます。
ちょっと間違えるとすぐに吹っ飛ぶし・・・。
ICE があればいいんですけどねえ。

nice!(0)  コメント(1)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 0

コメント 1

StevReok

Acheter Cialis France Ligne http://buycialisuss.com - cialis 20mg for sale Dapoxetina Australia <a href=http://buycialisuss.com>cialis cheapest online prices</a> Cialis Gгјnstig Kaufen
by StevReok (2020-02-29 01:27) 

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。