090619

|


Interface 2009/5月号の付録基板の続き。

割り込みまわりのスレッド処理を実装します。
割り込みはARMの場合0x0-0x1cのに例外ベクタが配置される。LPC23XXの場合、
MEMMAPレジスタ(0xe01fc040)の設定によって、この位置のフェッチが起きた場
合に、フラッシュROMの0x0、フラッシュロムの0x7fffe000(ブートローダ)、内
蔵SRAMの0x40000000、外部拡張RAMの、4つの場所をマップすることができる。

リセット直後はブートローダがマップされているので、ブートローダのが使わ
れ、ブートローダによってユーザプログラムに移行されるとフラッシュロムの
0x0が使われるようになる。

今はフラッシュロムに乗せたモニタによって0x40000000のSRAMにプログラム
をロードしてプログラムを動かしている。このプログラムから割込みを使うの
にはMEMMAPによって0x40000000を例外ベクタとするのが楽だ。

ARMの分岐命令は+-32MBまでのオフセットなので、分岐命令では1G以上先の
SRAMのプログラムには届かない。MEMMAPはベクタ位置の32byteじゃなく、その先の
32byte、計64byteをマップしてくれるので、

	.section	.vector, "ax"	// "A"llocate. e"X"ecute.
	.align	2
	.global	vector_table
vector_table:
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	ldr	pc,	[pc, #24]
	.long	start
	.long	exception_und
	.long	swi_handler
	.long	exception_iabort
	.long	exception_dabort
	.long	0
	.long	irq_handler
	.long	fiq_handler

のようにすることで32bitアドレスどこにでも分岐できるようになっている。

ARMには実行モードによってレジスタバンクがあり、例外の発生とともに自動的
に切り替わる。そのモードはSYS, SVC, UND, ABT, FIQ, IRQの6つがある。モー
ドはさらにUSRがあるけれど、USRに例外で切り替わることはない。

FIQ以外のモードにはR13(sp)とR14(lr)とSPSRがバンクとして用意されている。
例外発生時には、CPSRをSPSRにコピーし、例外発生時のPCをR14にコピーし、例
外ベクタに入る。FIQはR14からR8までバンクがある。

例外ベクタに入った段階で、例外前のCPSR,R13,R14は切り替えられているので
モードを変更しない以外アクセスできない。

例外に入った時のR13は先にスタック位置を設定しておけば、そのまま実行できる。
プログラムの最初で
#define	CPU_MODE_SET(reg, mode)		\
	mrs	reg,	cpsr;		\
	orr	reg,	reg,	mode;	\
	msr	cpsr_c,	reg

	CPU_MODE_SET (r0, #0x13)	//SVC
	ldr	sp,	[r1]
	CPU_MODE_SET (r0, #0x17)	//ABT
	ldr	sp,	[r1]
	CPU_MODE_SET (r0, #0x1b)	//UND
	ldr	sp,	[r1]
	CPU_MODE_SET (r0, #0x11)	//FIQ
	ldr	sp,	[r1]
	CPU_MODE_SET (r0, #0x12)	//IRQ
	ldr	sp,	[r1]
	CPU_MODE_SET (r0, #0x1f)	//SYS
	ldr	sp,	[r1]

	b	machine_startup

.Lstack_start:
	.long	stack_start

のように設定。全部同じスタック。machine_startupの後で実行はスレッドに移
される。その際にスタックが変更されるので、この空いたブートストラップス
タックを利用(SMPだとかちあうけれどこれはUP)。実装ではIRQとSWIはこのスタッ
クを使わない。FIQだけこのスタック使うことにして、IRQ,SWIからのFIQのネス
トをできるようにした。他のUND, ABTはここで中断なので、どこであろうとス
タックがアクセスできればいい。

方針は、FIQは割込み処理だけ。IRQ,SWIは割込み終了後にスレッドの切り替え。

gccは割込みハンドラ用のattributeを用意している。
void irq (void) __attribute__ ((interrupt ("IRQ")));
void fiq (void) __attribute__ ((interrupt ("FIQ")));
void swi (void) __attribute__ ((interrupt ("SWI")));
void abt (void) __attribute__ ((interrupt ("ABORT")));
void undef (void) __attribute__ ((interrupt ("UNDEF")));


void
irq ()
{
}

void
fiq ()
{
}

void
swi ()
{
}

void
abt ()
{
}

void
undef ()
{
}

これはarm-elf-gcc -mcpu=arm7tdmi-s -O -S i.cすると

	.file	"i.c"
	.text
	.align	2
	.global	irq
	.type	irq, %function
irq:
	@ Interrupt Service Routine.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	subs	pc, lr, #4
	.size	irq, .-irq
	.align	2
	.global	fiq
	.type	fiq, %function
fiq:
	@ Fast Interrupt Service Routine.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	subs	pc, lr, #4
	.size	fiq, .-fiq
	.align	2
	.global	swi
	.type	swi, %function
swi:
	@ ARM Exception Handler.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	movs	pc, lr
	.size	swi, .-swi
	.align	2
	.global	abt
	.type	abt, %function
abt:
	@ Interrupt Service Routine.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	subs	pc, lr, #4
	.size	abt, .-abt
	.align	2
	.global	undef
	.type	undef, %function
undef:
	@ ARM Exception Handler.
	@ args = 0, pretend = 0, frame = 0
	@ frame_needed = 0, uses_anonymous_args = 0
	@ link register save eliminated.
	movs	pc, lr
	.size	undef, .-undef
	.ident	"GCC: (GNU) 4.3.2"

のようになる。リターンの仕方がsubs pc, lr, #4か、movs pc, lrかがポイン
ト。このabtはプリフェッチアボート用。データアボートの時はpc, lr, #8のは
ず。これがARMのレジスタバンクをきちんと使った例。SUBS, MOVSが該当モード
のSPSRからCPSRの復帰をやってくれる。'S'がポイント。'S'がつくとデスティ
ネーションがPCならPSRを復帰する。

IRQハンドラからスレッドスイッチをする関係から、IRQに入った所でスレッド
スイッチ用の領域にコンテキストを設定するために、モードをSYSに変更します。
これはSYSに変更しないと例外発生時のモードのR13,R14をアクセスできないの
で。
そして、IRQからの復帰時にはこのコンテキストからの全復帰で戻ります。

	.balign 4
.Lcurrent_thread:
	.long	current_thread

----------------------------------------------------------------------
FIQ
----------------------------------------------------------------------
	.global	fiq_handler
fiq_handler:
	stmdb	r13!,	{r0-r7}
FIQモードではR13,R14の他、R8-R12もレジスタバンクになるので退避するのは
R0-R7だけでよい。
STMDBなので、R13からプレ(B)デクリメント(D)してR0-R7を退避、r13!なの
でこの命令の後のR13はr7をストアした所を差している。

	// user code here
ここにいずれハンドラを。

	ldmia	r13!,	{r0-r7}
LDMIA、R13の位置からポスト(A)インクリメント(I)で復帰してR0-R7を上方に復帰
	subs	pc,	r14,	#4
	b	md_thread_machdep_noreturn_assert	// for debug.

----------------------------------------------------------------------
IRQ
----------------------------------------------------------------------
	.global irq_handler
irq_handler:
この時点でIRQは禁止となるけれど、FIQは禁止とならない。が、FIQからスレッ
ドをいじることはないのでFIQを禁止する必要はない。
R13とR14はIRQモードのバンクに切り替わっている。R14には割り込まれた後に
復帰すべきPCが入っている。R13は自由に使える。

	adr	r13,	.Lcurrent_thread
	ldr	r13,	[r13]
	ldr	r13,	[r13]
割り込まれたcurrent_threadをとります。この時点のR13が*current_thread。

	// save r0-r12 (skip PSR area)
	stmib	r13,	{r0-r12}
STMIB、プレインクリメントでR0-R12を退避します。プレインクリメントなのは

struct reg
{
  uint32_t psr;		// 0x00
  uint32_t a1;		// 0x04
  uint32_t a2;		// 0x08
  uint32_t a3;		// 0x0c
  uint32_t a4;		// 0x10
  uint32_t v1;		// 0x14
  uint32_t v2;		// 0x18
  uint32_t v3;		// 0x1c
  uint32_t v4;		// 0x20
  uint32_t v5;		// 0x24
  uint32_t v6;		// 0x28
  uint32_t v7;		// 0x2c
  uint32_t fp;		// 0x30
  uint32_t ip;		// 0x34
  uint32_t sp;		// 0x38
  uint32_t lr;		// 0x3c
  uint32_t pc;		// 0x40
} __attribute__((packed)); //68Byte

なので。PSRの位置をスキップして、a1の位置からロード。r13に'!'がつかない
のでr13の値はインクリメントされず、*current_threadのまま。(psrを差している)

	// save psr
	mrs	r0,	spsr_all
	str	r0,	[r13]
これは上記のpsrの位置に割り込まれた時のPSRをストア。

	// set return address
	sub	r14,	r14,	#4	// IRQ,FIQ,Prefetch Abort.
	str	r14,	[r13, #0x40]
リターンアドレスを調整して、reg.pcにセット。

ここからモードをIRQからSYSに変換します。変換するとR13がバンク切換えで
変更されるのでR1にとっておきます。R0-R12は既に退避した。
	mov	r1,	r13
	// change to system-mode
	mrs	r2,	cpsr_all
	orr	r2,	#0x1f
	msr	cpsr_c,	r2

この段階で割り込まれた時点のR13,R14がアクセスできる

	// save original r13,r14
	str	r13,	[r1, #0x38]
	str	r14,	[r1, #0x3c]
	// now interrupted thread's stack.
これで、スレッドスイッチスタイルでコンテキストを復帰できる準備ができた。
SYSになったことで、これからのスタックは割り込まれた時点でのスタックに
なります。(ここまではスタックを使っていない。)

	bl	interrupt
これは実際の割り込みハンドラ。VICのステータスレジスタ見てディスパッチします。

ここから先はdo_thread_switchと同様。スレッドスイッチする必要があればスイッチ
します。
	// Switch thread if requested.
	bl	thread_context_switch
	adr	r0,	.Lcurrent_thread
	ldr	r0,	[r0]
	ldr	r0,	[r0]

	ldr	r1,	[r0], #4	// r1 = *r0++;
	msr	cpsr_all,r1		// restore all CPSR
	ldmia	r0,	{r0-r15}


----------------------------------------------------------------------
SWI
----------------------------------------------------------------------
ほとんどIRQと同じ。
	.global swi_handler
swi_handler:
	IRQ_DISABLE (r13)

IRQ,FIQでは該当する割り込みが禁止されるけれど、SWIではそれがない。IRQハ
ンドラ内ではスレッドの処理をするため、IRQのみ禁止する。元に戻すのは例外
発生時のPSRのリロードで完了するので明示的にする必要はない。

	adr	r13,	.Lcurrent_thread
	ldr	r13,	[r13]
	ldr	r13,	[r13]

	// save r0-r12 (skip PSR area)
	stmib	r13,	{r0-r12}

	// save psr
	mrs	r0,	spsr_all
	str	r0,	[r13]

	// set return address
ここでR14の調整がいらないのがSWI。__asm volatile ("swi #0123");のように
呼び出すので
	str	r14,	[r13, #0x40]
	mov	r1,	r13
	// change to system-mode
	mrs	r2,	cpsr_all
	orr	r2,	#0x1f
	msr	cpsr_c,	r2

	// save original r13,r14
	str	r13,	[r1, #0x38]
	str	r14,	[r1, #0x3c]
	// now interrupted thread's stack.

	//------------------------------
ここはSWI命令用のテスト。SWI命令は下位24bitがユーザが自由に使っていい領
域なので、例外発生時の命令を読みとり、下位24bitをexception_swiに渡して
います。
	ldr	r0,	[r1, #0x40]	// return address.
	sub	r0,	#4		// swi instruction address
	ldr	r0,	[r0]		// swi instruction.
	and	r0,	#0xffffff	// user area.
	bl	exception_swi
	//------------------------------

	// Switch thread if requested.
	bl	thread_context_switch
	adr	r0,	.Lcurrent_thread
	ldr	r0,	[r0]
	ldr	r0,	[r0]

	ldr	r1,	[r0], #4	// r1 = *r0++;
	msr	cpsr_all,r1		// restore all CPSR
	ldmia	r0,	{r0-r15}
	b	md_thread_machdep_noreturn_assert	// for debug.

.Lstack_start:
	.long	stack_start