少々手直ししてコミット

久しぶりにコードをコミットした。リハビリ中な気分。

+#ifdef MULTIPROCESSOR
+LEAF(getcurcpu, 0)
+	GET_CPU_INFO(v0, v1)
+	jr	ra
+END(getcurcpu)

nopで埋め忘れてる。

+#ifdef MULTIPROCESSOR
+LEAF(getcurcpu, 0)
+	GET_CPU_INFO(v0, v1)
+	jr	ra
+	nop
+END(getcurcpu)
+	error = uvm_pglistalloc(size, 0, -1, 0, 0,
+		&mlist, 1, UVM_PLA_NOWAIT);

上限アドレスの指定は-1Lじゃなくて0xffffffff。
また、alloc_contiguous_pagesを呼んでる全ての関数で直後にbzero()してるので、アロケート時点でそもそもZEROフィルするように変更。

+	error = uvm_pglistalloc(roundup(size, USPACE), 0, 0xffffffff, 0, 0,
+		&mlist, 1, UVM_PLA_NOWAIT | UVM_PLA_ZERO);

curcpu()の仕様を変えたい in OpenBSD/sgi

OpenBSDでカレントプロセッサのcpu_info(※per-CPUなデータを保存する構造体)のポインタを取得するにはcurcpu()というマクロを呼ぶのだが、現状のOpenBSD/sgiではこんな風になっている:

#define curcpu() cpu_info[cpu_number()]

で、cpu_number()はハードウェアレジスタを読んでCPUの物理IDを取得している。
が、先日書いたようにOrigin 350においては物理IDとautoconfで割り当てるIDを分けたい。

また、curcpu()の度に毎回物理IDをロードして配列からポインタを取り出すのはあまり速くはない。
出来ればあるレジスタにポインタが入ってて、取り出すだけで使えるというような状態だと嬉しい。
ここで、CP0のLLAddrレジスタが未使用で使えるという話が以前からあった。
しかし、32bit幅なので64bitポインタをツッコむにはちょっと工夫が必要。でも不可能じゃない。

この二点を一度に解決するパッチを書いた。

Index: mips64/include/asm.h
===================================================================
RCS file: /cvs/src/sys/arch/mips64/include/asm.h,v
retrieving revision 1.10
diff -u -p -r1.10 asm.h
--- mips64/include/asm.h	9 Jan 2010 23:34:29 -0000	1.10
+++ mips64/include/asm.h	25 Apr 2010 20:53:02 -0000
@@ -299,11 +299,9 @@ x: ;				\
 
 #ifdef MULTIPROCESSOR
 #define GET_CPU_INFO(ci, tmp)		\
-	HW_CPU_NUMBER(tmp);		\
-	PTR_SLL	 tmp, tmp, LOGREGSZ;	\
-	LA	 ci, cpu_info;		\
-	PTR_ADDU ci, ci, tmp;		\
-	PTR_L	 ci, 0(ci)
+	LOAD_XKPHYS(ci, CCA_CACHED);	\
+	mfc0	tmp, COP_0_LLADDR;	\
+	or	ci, ci, tmp
 #else  /* MULTIPROCESSOR */
 #define GET_CPU_INFO(ci, tmp)		\
 	LA	ci, cpu_info_primary

curcpu()のアセンブリ版マクロ。
ここで、配列からポインタを取り出す代わりにLLADDRからポインタを取り出し64bitアドレスを復元。

Index: mips64/include/cpu.h
===================================================================
RCS file: /cvs/src/sys/arch/mips64/include/cpu.h,v
retrieving revision 1.57
diff -u -p -r1.57 cpu.h
--- mips64/include/cpu.h	28 Feb 2010 18:01:36 -0000	1.57
+++ mips64/include/cpu.h	25 Apr 2010 20:53:02 -0000
@@ -428,18 +428,22 @@ extern struct cpu_info *cpu_info_list;
 
 #ifdef MULTIPROCESSOR
 #define MAXCPUS				4
-#define curcpu()			(cpu_info[cpu_number()])
+extern struct cpu_info *getcurcpu(void);
+extern void setcurcpu(struct cpu_info *);
+#ifdef DEBUG
+extern struct cpu_info *get_cpu_info(int);
+#endif
+#define curcpu() getcurcpu()
 #define	CPU_IS_PRIMARY(ci)		((ci)->ci_flags & CPUF_PRIMARY)
-#define cpu_number()			hw_cpu_number()
+#define cpu_number()			(curcpu()->ci_cpuid)
 
 extern struct cpuset cpus_running;
-extern struct cpu_info *cpu_info[];
 void cpu_unidle(struct cpu_info *);
 void cpu_boot_secondary_processors(void);
 #define cpu_boot_secondary(ci)          hw_cpu_boot_secondary(ci)
 #define cpu_hatch(ci)                   hw_cpu_hatch(ci)
 
-vaddr_t smp_malloc(size_t);
+vaddr_t alloc_contiguous_pages(size_t);
 
 #define MIPS64_IPI_NOP		0x00000001
 #define MIPS64_IPI_RENDEZVOUS	0x00000002

cpu_info[]配列を削除。代わりにlladdrへアクセスするgetcurcpu(), setcurcpu(ci)を追加。
curcpu()はgetcurcpu()を呼ぶだけ。

Index: mips64/mips64/context.S
===================================================================
RCS file: /cvs/src/sys/arch/mips64/mips64/context.S,v
retrieving revision 1.44
diff -u -p -r1.44 context.S
--- mips64/mips64/context.S	13 Feb 2010 14:04:45 -0000	1.44
+++ mips64/mips64/context.S	25 Apr 2010 20:53:03 -0000
@@ -145,7 +145,8 @@ NON_LEAF(cpu_switchto_asm, FRAMESZ(CF_SZ
 	PTR_L	t0, P_VMSPACE(s0)		# p->p_vmspace
 	PTR_L	t1, VMSPACE_PMAP(t0)		# ->vm_map.pmap
 #ifdef MULTIPROCESSOR
-	HW_CPU_NUMBER(v0)			# cpuid
+	GET_CPU_INFO(v0, t2)
+	PTR_L	v0, CI_CPUID(v0)
 	PTR_SLL	v0, v0, 0x3			# size of pmap_asid_info
 	PTR_ADDU t1, t1, v0
 #endif

物理IDをコードから排除。全てautoconfの番号へ。

Index: mips64/mips64/cp0access.S
===================================================================
RCS file: /cvs/src/sys/arch/mips64/mips64/cp0access.S,v
retrieving revision 1.12
diff -u -p -r1.12 cp0access.S
--- mips64/mips64/cp0access.S	9 Jan 2010 23:34:29 -0000	1.12
+++ mips64/mips64/cp0access.S	25 Apr 2010 20:53:03 -0000
@@ -192,3 +192,17 @@ LEAF(cp0_setperfctrl, 0)
 	j	ra
 	nop
 END(cp0_setperfctrl)
+
+#ifdef MULTIPROCESSOR
+LEAF(getcurcpu, 0)
+	GET_CPU_INFO(v0, v1)
+	jr	ra
+END(getcurcpu)
+
+LEAF(setcurcpu, 0)
+	mtc0	a0, COP_0_LLADDR
+	nop; nop
+	j	ra
+	nop
+END(setcurcpu)
+#endif

getcurcpu(), setcurcpu()の実体。
単にLLADDRへアクセスするだけ。

Index: mips64/mips64/cpu.c
===================================================================
RCS file: /cvs/src/sys/arch/mips64/mips64/cpu.c,v
retrieving revision 1.28
diff -u -p -r1.28 cpu.c
--- mips64/mips64/cpu.c	28 Mar 2010 17:09:36 -0000	1.28
+++ mips64/mips64/cpu.c	25 Apr 2010 20:53:03 -0000
@@ -41,15 +41,9 @@ void	cpuattach(struct device *, struct d
 
 struct cpu_info cpu_info_primary;
 struct cpu_info *cpu_info_list = &cpu_info_primary;
+struct cpu_info *cpu_info_secondaries;
 #ifdef MULTIPROCESSOR
 struct cpuset cpus_running;
-
-/*
- * Array of CPU info structures.  Must be statically-allocated because
- * curproc, etc. are used early.
- */
-
-struct cpu_info *cpu_info[MAXCPUS] = { &cpu_info_primary };
 #endif
 
 vaddr_t	CpuCacheAliasMask;
@@ -91,18 +85,19 @@ cpuattach(struct device *parent, struct 
 #ifdef MULTIPROCESSOR
 		ci->ci_flags |= CPUF_RUNNING | CPUF_PRESENT | CPUF_PRIMARY;
 		cpuset_add(&cpus_running, ci);
+		cpu_info_secondaries = (struct cpu_info *)alloc_contiguous_pages(
+			sizeof(struct cpu_info) * ncpusfound - 1);
+		if (cpu_info_secondaries == NULL)
+			panic("unable to allocate cpu_info\n");
+		bzero((char *)cpu_info_secondaries, sizeof(struct cpu_info) * ncpusfound - 1);
 #endif
 	}
 #ifdef MULTIPROCESSOR
 	else {
-		ci = (struct cpu_info *)smp_malloc(sizeof(*ci));
-		if (ci == NULL)
-			panic("unable to allocate cpu_info\n");
-		bzero((char *)ci, sizeof(*ci));
+		ci = &cpu_info_secondaries[cpuno - 1];
 		ci->ci_next = cpu_info_list->ci_next;
 		cpu_info_list->ci_next = ci;
 		ci->ci_flags |= CPUF_PRESENT;
-		cpu_info[cpuno] = ci;
 	}
 #endif
 	ci->ci_self = ci;
@@ -373,16 +368,28 @@ save_fpu(void)
 }
 
 #ifdef MULTIPROCESSOR
+#ifdef DEBUG
+struct cpu_info *
+get_cpu_info(int cpuno)
+{
+	struct cpu_info *ci;
+	CPU_INFO_ITERATOR cii;
+
+	CPU_INFO_FOREACH(cii, ci) {
+		if (ci->ci_cpuid == cpuno)
+			return ci;
+	}
+	return NULL;
+}
+#endif
+
 void
 cpu_boot_secondary_processors(void)
 {
        struct cpu_info *ci;
-       u_long i;
+	CPU_INFO_ITERATOR cii;
 
-       for (i = 0; i < MAXCPUS; i++) {
-               ci = cpu_info[i];
-               if (ci == NULL)
-                       continue;
+	CPU_INFO_FOREACH(cii, ci) {
                if ((ci->ci_flags & CPUF_PRESENT) == 0)
                        continue;
                if (ci->ci_flags & CPUF_PRIMARY)
@@ -391,7 +398,7 @@ cpu_boot_secondary_processors(void)
                sched_init_cpu(ci);
                ci->ci_randseed = random();
                cpu_boot_secondary(ci);
-       }
+	}
 
        /* This must called after xheart0 has initialized, so here is 
 	* the best place to do so.
@@ -407,31 +414,21 @@ cpu_unidle(struct cpu_info *ci)
 }
 
 vaddr_t 
-smp_malloc(size_t size)
+alloc_contiguous_pages(size_t size)
 {
-       struct pglist mlist;
-       struct vm_page *m;
-       int error;
-       vaddr_t va;
-       paddr_t pa;
-
-       if (size < PAGE_SIZE) {
-	       va = (vaddr_t)malloc(size, M_DEVBUF, M_NOWAIT);
-	       if (va == NULL)
-		       return NULL;
-	       error = pmap_extract(pmap_kernel(), va, &pa);
-	       if (error == FALSE)
-		       return NULL;
-       } else { 
-	       TAILQ_INIT(&mlist);
-	       error = uvm_pglistalloc(size, 0, -1L, 0, 0,
-		   &mlist, 1, UVM_PLA_NOWAIT);
-	       if (error)
-		       return NULL;
-	       m = TAILQ_FIRST(&mlist);
-	       pa = VM_PAGE_TO_PHYS(m);
-       }
+	struct pglist mlist;
+	struct vm_page *m;
+	int error;
+	paddr_t pa;
+
+	TAILQ_INIT(&mlist);
+	error = uvm_pglistalloc(size, 0, -1, 0, 0,
+		&mlist, 1, UVM_PLA_NOWAIT);
+	if (error)
+		return NULL;
+	m = TAILQ_FIRST(&mlist);
+	pa = VM_PAGE_TO_PHYS(m);
 
-       return PHYS_TO_XKPHYS(pa, CCA_CACHED);
+	return PHYS_TO_XKPHYS(pa, CCA_CACHED);
 }
 #endif

複数の問題を一度に片付けていて少々ややこしくなっている。
まず、cpu_info[]をやめた。
lladdrが32bit幅な関係上、smp_malloc()内でアロケート出来るのは32bit空間で表現出来るアドレスまでなので、位置が指定できないmalloc()をやめてuvm_pglistalloc()だけにする。
ページ単位でアロケートしなければならなくなったので、毎度呼ぶのをやめて一括でcpu_info_secondariesへアロケートすることに。
なので関数名もalloc_contiguous_pages()へ変えてみた。

Index: mips64/mips64/genassym.cf
===================================================================
RCS file: /cvs/src/sys/arch/mips64/mips64/genassym.cf,v
retrieving revision 1.6
diff -u -p -r1.6 genassym.cf
--- mips64/mips64/genassym.cf	9 Jan 2010 23:43:43 -0000	1.6
+++ mips64/mips64/genassym.cf	25 Apr 2010 20:53:03 -0000
@@ -58,6 +58,7 @@ member	pcb_onfault
 member	pcb_segtab
 
 struct	cpu_info
+member	ci_cpuid
 member	ci_curproc
 member	ci_curprocpaddr
 member	ci_fpuproc

CI_CPUIDマクロの生成。

Index: mips64/mips64/ipifuncs.c
===================================================================
RCS file: /cvs/src/sys/arch/mips64/mips64/ipifuncs.c,v
retrieving revision 1.3
diff -u -p -r1.3 ipifuncs.c
--- mips64/mips64/ipifuncs.c	21 Apr 2010 14:57:11 -0000	1.3
+++ mips64/mips64/ipifuncs.c	25 Apr 2010 20:53:03 -0000
@@ -119,10 +119,10 @@ mips64_ipi_intr(void *arg)
 void
 mips64_send_ipi(unsigned int cpuid, unsigned int ipimask)
 {
-#ifdef DIAGNOSTIC
-	if (cpuid >= CPU_MAXID || cpu_info[cpuid] == NULL)
+#ifdef DEBUG
+	if (cpuid >= CPU_MAXID || get_cpu_info(cpuid) == NULL)
 		panic("mips_send_ipi: bogus cpu_id");
-	if (!cpuset_isset(&cpus_running, cpu_info[cpuid]))
+	if (!cpuset_isset(&cpus_running, get_cpu_info(cpuid)))
 	        panic("mips_send_ipi: CPU %ld not running", cpuid);
 #endif

cpu_info[]の排除。
代わりにcpu.cへget_cpu_info()を用意した。

Index: sgi/include/asm.h
===================================================================
RCS file: /cvs/src/sys/arch/sgi/include/asm.h,v
retrieving revision 1.2
diff -u -p -r1.2 asm.h
--- sgi/include/asm.h	30 Sep 2009 06:22:00 -0000	1.2
+++ sgi/include/asm.h	25 Apr 2010 20:53:03 -0000
@@ -2,13 +2,4 @@
 
 /* Use Mips generic include file */
 
-#ifdef MULTIPROCESSOR
-#define HW_CPU_NUMBER(reg)			\
-	LA reg, HW_CPU_NUMBER_REG;		\
-	PTR_L reg, 0(reg)
-#else  /* MULTIPROCESSOR */
-#define HW_CPU_NUMBER(reg)			\
-	LI reg, 0
-#endif /* MULTIPROCESSOR */
-
 #include <mips64/asm.h>

物理IDの排除。

Index: sgi/include/cpu.h
===================================================================
RCS file: /cvs/src/sys/arch/sgi/include/cpu.h,v
retrieving revision 1.8
diff -u -p -r1.8 cpu.h
--- sgi/include/cpu.h	9 Jan 2010 23:34:29 -0000	1.8
+++ sgi/include/cpu.h	25 Apr 2010 20:53:03 -0000
@@ -45,16 +45,7 @@
 #define _SGI_CPU_H_
 
 #ifdef _KERNEL
-
-#ifdef MULTIPROCESSOR
-
-#if defined(TGT_OCTANE)
-#define HW_CPU_NUMBER_REG 0x900000000ff50000 /* HEART_PRID */
-#else
-#error MULTIPROCESSOR kernel not supported on this configuration
-#endif
-
-#if !defined(_LOCORE)
+#if defined(MULTIPROCESSOR) && !defined(_LOCORE)
 struct cpu_info;
 void hw_cpu_boot_secondary(struct cpu_info *);
 void hw_cpu_hatch(struct cpu_info *);
@@ -62,15 +53,7 @@ void hw_cpu_spinup_trampoline(struct cpu
 int  hw_ipi_intr_establish(int (*)(void *), u_long);
 void hw_ipi_intr_set(u_long);
 void hw_ipi_intr_clear(u_long);
-#endif
-
-#define hw_cpu_number() (*(uint64_t *)HW_CPU_NUMBER_REG)
-
-#else	/* MULTIPROCESSOR */
-
-#define hw_cpu_number() 0
-
-#endif	/* MULTIPROCESSOR */
+#endif	/* MULTIPROCESSOR && !_LOCORE */
 
 /*
  * Define soft selected cache functions.

物理IDの排除。

Index: sgi/sgi/ip30_machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/sgi/sgi/ip30_machdep.c,v
retrieving revision 1.41
diff -u -p -r1.41 ip30_machdep.c
--- sgi/sgi/ip30_machdep.c	21 Apr 2010 14:57:11 -0000	1.41
+++ sgi/sgi/ip30_machdep.c	25 Apr 2010 20:53:03 -0000
@@ -531,7 +531,7 @@ hw_cpu_boot_secondary(struct cpu_info *c
 	    scachesz, fanloads, launch, rndvz,
 	    stackaddr, lparam, rparam, idleflag);
 #endif
-	kstack = smp_malloc(USPACE);
+	kstack = alloc_contiguous_pages(USPACE);
 	if (kstack == NULL)
 		panic("unable to allocate idle stack\n");
 	bzero((char *)kstack, USPACE);
@@ -552,6 +552,11 @@ void
 hw_cpu_hatch(struct cpu_info *ci)
 {
 	int s;
+
+	/*
+	 * Set curcpu address on this processor.
+	 */
+	setcurcpu(ci);
 
 	/*
 	 * Make sure we can access the extended address space.

smp_malloc()の名称変更、あと重要なlladdrへのcurcpuのセット。

Index: sgi/sgi/ip30_nmi.S
===================================================================
RCS file: /cvs/src/sys/arch/sgi/sgi/ip30_nmi.S,v
retrieving revision 1.2
diff -u -p -r1.2 ip30_nmi.S
--- sgi/sgi/ip30_nmi.S	13 Jan 2010 23:24:27 -0000	1.2
+++ sgi/sgi/ip30_nmi.S	25 Apr 2010 20:53:03 -0000
@@ -23,9 +23,7 @@
 
 #include <arch/sgi/sgi/ip30.h>
 
-#ifndef MULTIPROCESSOR
 #define	HW_CPU_NUMBER_REG	0x900000000ff50000	/* HEART_PRID */
-#endif
 
 #include "assym.h"

つじつま合わせ。

Index: sgi/sgi/machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/sgi/sgi/machdep.c,v
retrieving revision 1.100
diff -u -p -r1.100 machdep.c
--- sgi/sgi/machdep.c	3 Mar 2010 12:25:09 -0000	1.100
+++ sgi/sgi/machdep.c	25 Apr 2010 20:53:04 -0000
@@ -150,6 +150,11 @@ mips_init(int argc, void *argv, caddr_t 
 	extern char *hw_vendor;
 
 	/*
+	 * Set curcpu address on primary processor.
+	 */
+	setcurcpu(&cpu_info_primary);
+
+	/*
 	 * Make sure we can access the extended address space.
 	 * Note that r10k and later do not allow XUSEG accesses
 	 * from kernel mode unless SR_UX is set.

こちらも重要なlladdrへのセット。

Origin 350でのセカンダリプロセッサ起動テスト/Secondary processor launch test on Origin 350

前にも書いた通り、cpu0,cpu1,cpu2の3つを認識させ、cpu1を無効なまま放置してcpu2を起動するのがファーストステップである。

As I mentioned before, detect cpu0, cpu1, cpu2 and leave cpu1 disabled and launch cpu2 is the first step.

Index: sgi/sgi/ip27_machdep.c
===================================================================
RCS file: /cvs/src/sys/arch/sgi/sgi/ip27_machdep.c,v
retrieving revision 1.49
diff -r1.49 ip27_machdep.c
284a285,286
> 	ncpusfound = 3;
> 

よって、cpu数は3。

Therefore number of cpus set to 3.

291a294,296
> #define IP27_MAXCPUS 3
> //static int ip27_cpu_exists(int);
> 
299a305,307
> #ifdef MULTIPROCESSOR
> 	int cpuid;
> #endif
312a321,340
> 
> #ifdef MULTIPROCESSOR
> 	for (cpuid = 1; cpuid < IP27_MAXCPUS; cpuid++) {
> //		if (ip27_cpu_exists(cpuid)) {
> /*
> 			struct cpu_hwinfo hw;
> 			bcopy(&bootcpu_hwinfo, &hw, sizeof(struct cpu_hwinfo));
> 			hw.c0prid = 
> 		           *(volatile uint32_t *)(mpconf + MPCONF_PRID(cpuid));
> 			hw.type = (hw.c0prid >> 8) & 0xff;
> 			hw.l2size = 1 << *(volatile uint32_t *)
> 			    (mpconf + MPCONF_SCACHESZ(cpuid));
> 			u.caa.caa_hw = &hw;
> */
> 			u.caa.caa_hw = &bootcpu_hwinfo;
> 			config_found(parent, &u, ip27_print);
> //		}
> 	}
> #endif
> 

config_found()でcpu1, cpu2をattachする。

Attach cpu1, cpu2 on config_found().

916a947,1042
> 
> #ifdef MULTIPROCESSOR
> #define IP27PROM_LAUNCHSLAVE	PHYS_TO_CKSEG1(0x1fc00038)
> #define LAUNCH_SLAVE	(*(void (*)(int nasid, int cpu, \
> 				    void *call_addr, \
> 				    uint64_t call_parm, \
> 				    void *stack_addr, \
> 				    void *gp_addr)) \
> 			 IP27PROM_LAUNCHSLAVE)

Linux, IRIXのヘッダを見ると、IP27PROMへN64ABIで関数コールを行なう事でセカンダリプロセッサをする事が出来る事がわかる。
これにアクセスする為のマクロを定義。

We can see on Linux and IRIX headers that secondary processor can launch by calling function to IP27PROM, using N64ABI.
Then define the macro to access it.

> void
> hw_cpu_boot_secondary(struct cpu_info *ci)
> {
> 	int cpuid =  ci->ci_cpuid;
> 	vaddr_t kstack;
> 
> #ifdef DEBUG
> #endif
> 	kstack = smp_malloc(USPACE);
> 	if (kstack == NULL)
> 		panic("unable to allocate idle stack\n");
> 	bzero((char *)kstack, USPACE);
> 	ci->ci_curprocpaddr = (void *)kstack;
> 
> 	LAUNCH_SLAVE(0, cpuid, (void *)hw_cpu_spinup_trampoline, (uint64_t)ci, 
> 		(void *)(kstack + USPACE), (void *)0);
> 
> 	while (!cpuset_isset(&cpus_running, ci))
> 		;
> }
> 

hw_cpu_boot_secondary()からLAUNCH_SLAVEマクロを叩いてセカンダリプロセッサを起動。
取り敢えず1ノードなのでnasidは0固定。

Bootup secondary processor to call LAUNCH_SLAVE macro on hw_cpu_boot_secondary().
nasid set to 0 because this time we test with single node configuration for now.

> void
> hw_cpu_hatch(struct cpu_info *ci)
> {
> 	int s;
> 
> 	/*
> 	 * Make sure we can access the extended address space.
> 	 * Note that r10k and later do not allow XUSEG accesses
> 	 * from kernel mode unless SR_UX is set.
> 	 */
> 	setsr(getsr() | SR_KX | SR_UX);
> 
> 	tlb_set_page_mask(TLB_PAGE_MASK);
> 	tlb_set_wired(0);
> 	tlb_flush(64);
> 	tlb_set_wired(UPAGES / 2);
> 
> 	tlb_set_pid(0);
> 
> 	/*
> 	 * Turn off bootstrap exception vectors.
> 	 */
> 	setsr(getsr() & ~SR_BOOT_EXC_VEC);
> 
> 	/*
> 	 * Clear out the I and D caches.
> 	 */
> 	Mips10k_ConfigCache(ci);
> 	Mips_SyncCache(ci);
> 
> 	while(1)
> 		printf("cpu2 launched\n");
> 
> 	cpu_startclock(ci);
> 
> 	ncpus++;
> 	cpuset_add(&cpus_running, ci);
> 
> 	mips64_ipi_init();
> //	xheart_setintrmask(0);
> 
> 	spl0();
> 	(void)updateimask(0);
> 
> 	SCHED_LOCK(s);
> 	cpu_switchto(NULL, sched_chooseproc());
> }
> 
> int
> hw_ipi_intr_establish(int (*func)(void *), u_long cpuid)
> {
> 	return 0;
> };
> 
> void
> hw_ipi_intr_set(u_long cpuid)
> {
> }
> 
> void
> hw_ipi_intr_clear(u_long cpuid)
> {
> }
> 
> #endif

セカンダリプロセッサの起動コードは取り敢えずprintfを入れた。
結果、このコードでprintfの出力が得られる事が確認出来た。

Put debug print on boot code for secondary processor for testing propose.
In the result, I could get debug print output with the code.

Origin 350 SMP化作業の見立て

取り敢えず、最小限の作業で手元の2コア1ノード構成が動くまでにやらねばならぬ事。

・スレーブCPUの起動、LAUNCHで。
・Exceptionテーブルコピー、uvmでの使用禁止などプライベートエリアの手当て
・取り敢えずcpu0,cpu2がオンラインでcpu1がオフラインに見えるようにautoconfをコンフィグレーションする
 最初は手動でいい。次にKLCONFIGを使用する。
・cpuidを取得する手段を獲得する。多分、cp0のLLAddrに入れる事になるだろう。
・IP27ベースのIPIを実装する。割り込みコントローラのコードを弄る事になるのかな
・IP27 specificなコードをMP-safe化。多分割り込み周りだけかな。

LAUNCH on SGI Origin 350

プロセッサの検出はKLCONFIGで出来る事が分かったが、起動はどうしているのだろうか。
Linuxでは、arch/mips/sgi-ip27/ip27-smp.cにこんな事が書いてある:

static void __cpuinit ip27_boot_secondary(int cpu, struct task_struct *idle)
{
	unsigned long gp = (unsigned long)task_thread_info(idle);
	unsigned long sp = __KSTK_TOS(idle);

	LAUNCH_SLAVE(cputonasid(cpu), cputoslice(cpu),
		(launch_proc_t)MAPPED_KERN_RW_TO_K0(smp_bootstrap),
		0, (void *) sp, (void *) gp);
}

LAUNCH_SLAVEマクロはarch/mips/include/asm/sn/launch.hに入ってる:

/*
 * PROM entry points for launch routines are determined by IPxxprom/start.s
 */

#define LAUNCH_SLAVE	(*(void (*)(int nasid, int cpu, \
				    launch_proc_t call_addr, \
				    u64 call_parm, \
				    void *stack_addr, \
				    void *gp_addr)) \
			 IP27PROM_LAUNCHSLAVE)

要するに、PROMの関数をそのまま叩いている訳だ。
叩くと何が起きるのかについてもコメントが書かれていて、

/*
 * The launch data structure resides at a fixed place in each node's memory
 * and is used to communicate between the master processor and the slave
 * processors.
 *
 * The master stores launch parameters in the launch structure
 * corresponding to a target processor that is in a slave loop, then sends
 * an interrupt to the slave processor.  The slave calls the desired
 * function, then returns to the slave loop.  The master may poll or wait
 * for the slaves to finish.
 *
 * There is an array of launch structures, one per CPU on the node.  One
 * interrupt level is used per local CPU.
 */

パラメータをメモリ上にある構造体に突っ込んでからスレーブがループしているのを割り込みで起こし、起こされた側は構造体をチェックして指定アドレスへジャンプする、という仕掛けっぽい。
これはソフト的な実装なのね。

この部分はOpenBSD/sgiに無いので持ってくる必要がある。

所で、このコードはIP27向けでOrigin 350はIP35なんだけど、PROMのアドレスは違わないのかな?という点が若干疑問なんだけれども、Miodに聞いたら「多分同じっぽ」って言ってるので大丈夫かな。

KLCONFIG on SGI Origin 350

Linuxソースコードを読んでいくと、どうもCPU構成の情報はKLCONFIGという所から読み込んでやればいいらしい事が分かる。

arch/mips/sgi-ip27/ip27-smp.cというファイルを覗くとこんな関数がある:

static int do_cpumask(cnodeid_t cnode, nasid_t nasid, int highest)
{
	static int tot_cpus_found = 0;
	lboard_t *brd;
	klcpu_t *acpu;
	int cpus_found = 0;
	cpuid_t cpuid;

	brd = find_lboard((lboard_t *)KL_CONFIG_INFO(nasid), KLTYPE_IP27);

	do {
		acpu = (klcpu_t *)find_first_component(brd, KLSTRUCT_CPU);
		while (acpu) {
			cpuid = acpu->cpu_info.virtid;
			/* cnode is not valid for completely disabled brds */
			if (get_actual_nasid(brd) == brd->brd_nasid)
				cpuid_to_compact_node[cpuid] = cnode;
			if (cpuid > highest)
				highest = cpuid;
			/* Only let it join in if it's marked enabled */
			if ((acpu->cpu_info.flags & KLINFO_ENABLE) &&
			    (tot_cpus_found != NR_CPUS)) {
				cpu_set(cpuid, cpu_possible_map);
				alloc_cpupda(cpuid, tot_cpus_found);
				cpus_found++;
				tot_cpus_found++;
			}
			acpu = (klcpu_t *)find_component(brd, (klinfo_t *)acpu,
								KLSTRUCT_CPU);
		}
		brd = KLCF_NEXT(brd);
		if (!brd)
			break;

		brd = find_lboard(brd, KLTYPE_IP27);
	} while (brd);

	return highest;
}

見たところ、board情報を取り出す→board情報からcpu情報を取り出す→プロセッサ数カウントアップ、となっている。
ここで出て来るKLCONFIGってなんだろなーと見ていくと、
arch/mips/include/asm/sn/klconfig.hという所に色々な定義が入っていて、そこに興味深いコメントが書いてある。

ローカルボードとリモートボードのリンクドリストがあって、それぞれのエントリからCPU情報が取れるよ、と言っているようだ。

/*
 * The KLCONFIG area is organized as a LINKED LIST of BOARDs. A BOARD
 * can be either 'LOCAL' or 'REMOTE'. LOCAL means it is attached to
 * the LOCAL/current NODE. REMOTE means it is attached to a different
 * node.(TBD - Need a way to treat ROUTER boards.)
 *
 * There are 2 different structures to represent these boards -
 * lboard - Local board, rboard - remote board. These 2 structures
 * can be arbitrarily mixed in the LINKED LIST of BOARDs. (Refer
 * Figure below). The first byte of the rboard or lboard structure
 * is used to find out its type - no unions are used.
 * If it is a lboard, then the config info of this board will be found
 * on the local node. (LOCAL NODE BASE + offset value gives pointer to
 * the structure.
 * If it is a rboard, the local structure contains the node number
 * and the offset of the beginning of the LINKED LIST on the remote node.
 * The details of the hardware on a remote node can be built locally,
 * if required, by reading the LINKED LIST on the remote node and
 * ignoring all the rboards on that node.
 *
 * The local node uses the REMOTE NODE NUMBER + OFFSET to point to the
 * First board info on the remote node. The remote node list is
 * traversed as the local list, using the REMOTE BASE ADDRESS and not
 * the local base address and ignoring all rboard values.
 *
 *
 KLCONFIG

 +------------+      +------------+      +------------+      +------------+
 |  lboard    |  +-->|   lboard   |  +-->|   rboard   |  +-->|   lboard   |
 +------------+  |   +------------+  |   +------------+  |   +------------+
 | board info |  |   | board info |  |   |errinfo,bptr|  |   | board info |
 +------------+  |   +------------+  |   +------------+  |   +------------+
 | offset     |--+   |  offset    |--+   |  offset    |--+   |offset=NULL |
 +------------+      +------------+      +------------+      +------------+


 +------------+
 | board info |
 +------------+       +--------------------------------+
 | compt 1    |------>| type, rev, diaginfo, size ...  |  (CPU)
 +------------+       +--------------------------------+
 | compt 2    |--+
 +------------+  |    +--------------------------------+
 |  ...       |  +--->| type, rev, diaginfo, size ...  |  (MEM_BANK)
 +------------+       +--------------------------------+
 | errinfo    |--+
 +------------+  |    +--------------------------------+
                 +--->|r/l brd errinfo,compt err flags |
                      +--------------------------------+

 *
 * Each BOARD consists of COMPONENTs and the BOARD structure has
 * pointers (offsets) to its COMPONENT structure.
 * The COMPONENT structure has version info, size and speed info, revision,
 * error info and the NIC info. This structure can accommodate any
 * BOARD with arbitrary COMPONENT composition.
 *
 * The ERRORINFO part of each BOARD has error information
 * that describes errors about the BOARD itself. It also has flags to
 * indicate the COMPONENT(s) on the board that have errors. The error
 * information specific to the COMPONENT is present in the respective
 * COMPONENT structure.
 *
 * The ERRORINFO structure is also treated like a COMPONENT, ie. the
 * BOARD has pointers(offset) to the ERRORINFO structure. The rboard
 * structure also has a pointer to the ERRORINFO structure. This is
 * the place to store ERRORINFO about a REMOTE NODE, if the HUB on
 * that NODE is not working or if the REMOTE MEMORY is BAD. In cases where
 * only the CPU of the REMOTE NODE is disabled, the ERRORINFO pointer can
 * be a NODE NUMBER, REMOTE OFFSET combination, pointing to error info
 * which is present on the REMOTE NODE.(TBD)
 * REMOTE ERRINFO can be stored on any of the nearest nodes
 * or on all the nearest nodes.(TBD)
 * Like BOARD structures, REMOTE ERRINFO structures can be built locally
 * using the rboard errinfo pointer.
 *
 * In order to get useful information from this Data organization, a set of
 * interface routines are provided (TBD). The important thing to remember while
 * manipulating the structures, is that, the NODE number information should
 * be used. If the NODE is non-zero (remote) then each offset should
 * be added to the REMOTE BASE ADDR else it should be added to the LOCAL BASE ADDR.
 * This includes offsets for BOARDS, COMPONENTS and ERRORINFO.
 *
 * Note that these structures do not provide much info about connectivity.
 * That info will be part of HWGRAPH, which is an extension of the cfg_t
 * data structure. (ref IP27prom/cfg.h) It has to be extended to include
 * the IO part of the Network(TBD).
 *
 * The data structures below define the above concepts.
 */

で、これ、全部自分で読みに行かないと駄目?と途方にくれていたら、もうちゃんと実装されていた。
きちんとエントリをイテレートして画面表示するデバッグプリントも仕込まれてて、有効にするとこんなのが表示される:

lboard type 11 slot 0 nasid 0 nic 0x5627f3ad ncomp 5
	hub, widget 0 physid 0x00 virtid 0 prt 0 bus 0
	  port -1 flag 0 speed 200MHz
	memory, widget 0 physid 0xff virtid -1 prt 0 bus 0
	  2048MB, select 0 flags 3
		bank 1 1024MB
		bank 2 1024MB
IP35 memory from 0x0 to 0x40000000 (1024 MB)
memrange_register: memory from 0x4000000 to 0x40000000
IP35 memory from 0x40000000 to 0x80000000 (1024 MB)
memrange_register: memory from 0x40000000 to 0x80000000
	hub uart, widget 0 physid 0x00 virtid 0 prt 0 bus 0
	[ARCBios component: class 6 type 18 flags 78 key 0x6 SN1 TTY]
	cpu, widget 0 physid 0x00 virtid 0 prt 0 bus 0
	  type f50/e4ab 1000MHz cache 16MB speed 333MHz
	cpu, widget 0 physid 0x02 virtid 1 prt 0 bus 0
	  type f50/e4ab 1000MHz cache 16MB speed 333MHz
lboard type 42 slot 0 nasid 0 nic 0xffffffffffffffff ncomp 1
	xbow, widget 0 physid 0xff virtid 0 prt 0 bus 0
	 hub master link 11
		widget 11 nasid 0 flg 6
		widget 15 nasid 0 flg 5
lboard type 77 slot f nasid 0 nic 0x5627f3ad ncomp 5
	bridge, widget f physid 0x0f virtid 15 prt 0 bus 0
	ioc4, widget f physid 0x01 virtid 0 prt 0 bus 0
	[ARCBios component: class 0 type 32 flags 00 key 0x0 (no name)]
	ioc4 ATA, widget f physid 0x01 virtid -1 prt 0 bus 0
	[ARCBios component: class 0 type 0 flags 00 key 0x0 (no name)]
	dual scsi, widget f physid 0x03 virtid -1 prt 0 bus 0
		bus 0, physid 0x03 virtid 0, specific 0, numdevs 2
		bus 1, physid 0x03 virtid 1, specific 0, numdevs 0
	[ARCBios component: class 0 type 0 flags 00 key 0x0 (no name)]
	gigabit Ethernet, widget f physid 0x04 virtid 1 prt 0 bus 0
	[ARCBios component: class 6 type 36 flags 60 key 0xf240b000000 (no name)]
lboard type 77 slot f nasid 0 nic 0x5627f3ad ncomp 5
	bridge, widget f physid 0x1f virtid 31 prt 0 bus 1
	unknown component, widget f physid 0x01 virtid 0 prt 0 bus 1 pcifn 0
	unknown component, widget f physid 0x01 virtid 0 prt 0 bus 1 pcifn 1
	unknown component, widget f physid 0x02 virtid 0 prt 0 bus 1 pcifn 0
	unknown component, widget f physid 0x02 virtid 0 prt 0 bus 1 pcifn 1

まぁなにやら色々とハードが羅列されてるが、CPUは2個見えているのでプロセッサ検出の所ではこの情報が使えるに違いないし、既にコードも存在しているのでここは殆ど手を入れる必要なさそう。

SGI Origin 350でSMP

まだ分かってない事も多いのだが、追々調べる。

Originのコンセプト

これは、ディスプレイコネクタもなく2Uである事からも分かるように、デスクサイドに置くようなタイプのマシンでなくラックに詰め込んで何らかの処理(レンダリングファーム用?それともHPCなのか?)に使う為にデザインされたマシン、だと思う。
大きなポイントは、クラスタリングをハード的にサポートしてる事。
NUMALink用ケーブルを持ってきて二台のOrigin 350を繋ぐと、OSからは一台の高スペックなOriginに見えるような仕組みになっている。シェアードメモリ型で、NUMAアーキテクチャ
スイッチがあれば4台、8台構成も可能で、最初から4/8台でクラスタ組んだラック入りのシステムはOnyxって名前で販売されていた模様。

で、この仕組みがあるが故に色々と複雑な仕様になっててややこしい。

CPUIDについて

Octaneの時は#0と#1だけが存在していて、プライマリプロセッサは常に#0だった。
しかし、Originでは任意のCPUをPROMプロンプトから有効・無効出来る上にプライマリプロセッサを指定する事が出来る。
また、他のノードとクラスタを組むのが前提なせいか、2プロセッサのOrigin 350のCPUIDは#0と#2である。

OpenBSD的にはプライマリプロセッサがcpu0だしautoconfでcpu0の次がcpu2というattachは出来ないはずなので、これに対応するには論理CPUIDと物理CPUIDを別々に管理する必要がある(今はわけてない)。

メモリ管理

基本的にはローカルノードプロセッサ間においても、ノード間においても共有メモリでコヒーレントであるので透過的なはず。
だが、敢えて一部に非共有エリアを持っている。
これをケアしないとメモリがインコンシステントになってしまう。

まず、CAlias空間というのがあって、これは物理アドレス空間の先頭数MBがノード間で非共有になっている。
容量はCAliasレジスタで変更出来る(このレジスタがどこにあるのかは未だ調べていない。性質的に、CP0レジスタではなくてコントローラデバイスのハードウェアレジスタかな?)

一方、先頭128KBのメモリはそれぞれのプロセッサで排他的に使う領域として予約されている。
こちらは非共有というか、別のものが見える為の仕掛けがしてある。

cpu0から見た0-64KBは物理メモリの0-64KB、64-128KBは物理メモリの64-128KBが見えるが、
cpu1から見た0-64KBは物理メモリの64-128KB、64-128KBは物理メモリの0-64KBが見えるようになっているのだ。

なので、CPU固有のデータを持ちたかったら先頭64KBに置けばいい。他のプロセッサには邪魔されない。
が、しかし、この空間にexception vectorとかも存在するので、初期化時にきちんとコピーしてあげないとちゃんと動かない。

他は?

まぁ、他にも色々と面倒な仕掛けがあるんだろう。特にメモリ空間はノードを超えて共有しているが為に色々な仕掛けが入っていると思われる。
ドキュメントは手にしたが、まだきちんと読めてない。