読者です 読者をやめる 読者になる 読者になる

OpenBSD/sgi on octane2 - exception.S・tlbhandler.SにおけるGET_CPU_INFO()とコンテキスト保存、割り込みの話

exception.S・tlbhandler.Sでは、以下のような処理が行われている:

1. Exception発生、ベクタアドレスにジャンプ
2. ベクタアドレスに設置されたハンドラはException要因を調べexceptionテーブルに書いてある関数へジャンプ
3. 割り込み前のコンテキストを保存
4. 要因毎に必要な処理を行う
5. 割り込み前のコンテキストを復帰、eret

ここで注目したいのはコンテキストの保存・復帰なのだが、保存の前や復帰後に保持対象のレジスタに書き込みを行うと当然正しく復帰出来なくなる。
ただ、レジスタ一つも使えないんじゃ保存・復帰も行えないのでカーネル用にリザーブされたレジスタとしてk0, k1が存在している。
このレジスタはException時に壊してしまっていい事になっていて、事実上この辺りの処理専用のレジスタだ。

TLB Miss時に呼ばれるtlb_miss()、xtlb_miss()では高速化の為にexceptionテーブルの参照、コンテキストの保存・復帰の手順を省略してしまっている。
#TLB Missの度に呼ばれるので、一命令分でも速くしたいのだ。
が、省略したという事はレジスタを壊せないという事なので、この関数ではk0, k1レジスタのみを使用してレジスタを使って全処理を行っている:

	.globl	tlb_miss	/* 0xffffffff80000000 */
	.set	noat
	.ent	tlb_miss, 0
tlb_miss:
	PTR_L	k1, curprocpaddr
	dmfc0	k0, COP_0_BAD_VADDR
	bltz	k0, _k_miss		# kernel address space
	PTR_SRL	k0, k0, SEGSHIFT - LOGREGSZ
	PTR_L	k1, PCB_SEGTAB(k1)
	andi	k0, k0, (PMAP_SEGTABSIZE - 1) << LOGREGSZ
	PTR_ADDU k1, k1, k0
	PTR_L	k1, 0(k1)		# get pointer to page table
	dmfc0	k0, COP_0_BAD_VADDR
	PTR_SRL	k0, k0, PGSHIFT - 2
	andi	k0, k0, ((NPTEPG/2) - 1) << 3
	beqz	k1, _inv_seg		# invalid segment map
	PTR_ADDU k1, k1, k0		# index into segment map
	lw	k0, 0(k1)		# get page PTE
tlb_load:
	lw	k1, 4(k1)
	dsll	k0, k0, 34
	dsrl	k0, k0, 34
	dmtc0	k0, COP_0_TLB_LO0
	dsll	k1, k1, 34
	dsrl	k1, k1, 34
	dmtc0	k1, COP_0_TLB_LO1
	nop				# RM7000 needs 4 nops
	nop
	nop
	nop
	tlbwr				# update TLB
	nop
	nop
	nop
	nop
	eret				# RM7000 need 4 for JTLB usage.
	.end	tlb_miss

	.globl	e_tlb_miss
e_tlb_miss:

こっから本題

SMP化コードでは、GET_CPU_INFO(ci, tmp)というマクロを書いた。
これはexception.Sでcurprocpaddrへの参照などをcpu_info[cpuid]->ci_curprocpaddrを参照するように変更する為のものだ。
このマクロでは引数に与えたci, tmpをレジスタ名として使用する。 ciがcpu_info[cpuid]でtmpは一時的に使うレジスタだ。

u_intr()などexception.Sの中のハンドラでは、コンテキスト保存先のアドレスとしてcurprocpaddrを参照している。
これをGET_CPU_INFO()を使うように書き換えたのだが、コンテキスト保存前なのでk0, k1を使うようにした:

NNON_LEAF(u_intr, FRAMESZ(CF_SZ), ra)
	.set	noat
	.mask	0x80000000, (CF_RA_OFFS - FRAMESZ(CF_SZ))
	GET_CPU_INFO(k1, k0)
	PTR_L	k0, CI_CURPROCPADDR(k1)
	SAVE_CPU(k0, 0)

この調子で、exception.Sのそこら中にGET_CPU_INFO(k1, k0)と書いていたのだが、時折この辺りで落ちるバグが発生している:

dsrtc at ioc0 not configured
"SGI Rad1" rev 0xc0 at pci0 dev 3 function 0 not configured
rd0: fixed, 8192 blocks
boot device: sd0
root on rd0a swap on rd0b dump on rd0b
WARNING: No TOD clock, believing file system.
WARNING: CHECK AND RESET THE DATE!

Trap cause = 4 Frame 0xffffffffcffc7e78
Trap PC 0xa800000020156f34 RA 0xa800000020156ef8 fault 0x24d
0xa800000020156f34 ra 0xa800000020156ef8 sp 0xffffffffcffc7fd0 (0xffffffffc9107138,0x8,0x0,0x0)
0xa800000020156ef8 ra 0x0 sp 0xffffffffcffc7fd0
User-level: pid 32099
stopped on non ddb fault
Stopped at      0xa800000020156f34:     lw      v0,588(k0)
ddb{0}>

落ちる確立は50%以下、場所は毎回違う。
そこでふと気がついた。
もしかして、割り込みがかかるポイントでk0, k1を使ったコードを書いてるとまずいんちゃうか?
だって、k0, k1は保存・復帰されないんでしょ?
やべ。

ってな訳で、GET_CPU_INFO()を呼んでいる場所で使用レジスタの選択が妥当かチェック中。