Subject: NPTL support for the kernel

From: Daniel Jacobowitz <[email-masked]>
Date: Wed, 16 Mar 2005 09:11:52 -0500

Here's a kernel patch to enable NPTL support.  This doesn't include Maciej's
uber-fast rdhwr emulation; I believe we ought to include both the fast and
slow paths, since the slow path will handle use of other destination
registers.  Changes:

  - Clone takes five arguments, not four.  Um, this bit is gross.
  - New syscall sys_set_thread_area.  Only glibc uses this.
  - Emulation of the rdhwr instruction.  This version is only loosely
    based on the emulation on the malta branch; the major difference
    is that I fixed ll/sc/rdhwr emulation in branch delay slots.
    GCC 4.1 will generate rdhwr in branch delay slots in some
    conditions.
  - PTRACE_GET_THREAD_AREA support for GDB.

How's it look?  I've just finished hopefully final tests for the
glibc bits, and will post them once this patch is resolved.

[Credit goes to Manish Lachwani at MontaVista for writing the
first draft of this patch, though he won't recognize most of this
copy :-)]

Index: linux/arch/mips/kernel/syscall.c
===================================================================
--- linux.orig/arch/mips/kernel/syscall.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/syscall.c	2005-03-15 10:30:09.000000000 -0500
@@ -176,14 +176,28 @@ _sys_clone(nabi_no_regargs struct pt_reg
 {
 	unsigned long clone_flags;
 	unsigned long newsp;
-	int *parent_tidptr, *child_tidptr;
+	int __user *parent_tidptr, *child_tidptr;
 
 	clone_flags = regs.regs[4];
 	newsp = regs.regs[5];
 	if (!newsp)
 		newsp = regs.regs[29];
-	parent_tidptr = (int *) regs.regs[6];
-	child_tidptr = (int *) regs.regs[7];
+	parent_tidptr = (int __user *) regs.regs[6];
+#ifdef CONFIG_MIPS32
+	/* We need to fetch the fifth argument off the stack.  */
+	child_tidptr = NULL;
+	if (clone_flags & (CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID)) {
+		int __user *__user *usp = (int __user *__user *) regs.regs[29];
+		if (regs.regs[2] == __NR_syscall) {
+			if (get_user (child_tidptr, &usp[5]))
+				return -EFAULT;
+		}
+		else if (get_user (child_tidptr, &usp[4]))
+			return -EFAULT;
+	}
+#else
+	child_tidptr = (int __user *) regs.regs[8];
+#endif
 	return do_fork(clone_flags, newsp, &regs, 0,
 	               parent_tidptr, child_tidptr);
 }
@@ -245,6 +259,16 @@ asmlinkage int sys_olduname(struct oldol
 	return error;
 }
 
+void sys_set_thread_area(unsigned long addr)
+{
+	struct thread_info *ti = current->thread_info;
+
+	ti->tp_value = addr;
+
+	/* If some future MIPS implementation has this register in hardware,
+	 * we will need to update it here (and in context switches).  */
+}
+
 asmlinkage int _sys_sysmips(int cmd, long arg1, int arg2, int arg3)
 {
 	int	tmp, len;
Index: linux/arch/mips/kernel/traps.c
===================================================================
--- linux.orig/arch/mips/kernel/traps.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/traps.c	2005-03-15 10:33:48.000000000 -0500
@@ -360,6 +360,10 @@ static inline int get_insn_opcode(struct
 #define OFFSET 0x0000ffff
 #define LL     0xc0000000
 #define SC     0xe0000000
+#define SPEC3  0x7c000000
+#define RD     0x0000f800
+#define FUNC   0x0000003f
+#define RDHWR  0x0000003b
 
 /*
  * The ll_bit is cleared by r*_switch.S
@@ -408,9 +412,10 @@ static inline void simulate_ll(struct pt
 
 	preempt_enable();
 
+	compute_return_epc(regs);
+
 	regs->regs[(opcode & RT) >> 16] = value;
 
-	compute_return_epc(regs);
 	return;
 
 sig:
@@ -446,9 +451,9 @@ static inline void simulate_sc(struct pt
 	preempt_disable();
 
 	if (ll_bit == 0 || ll_task != current) {
-		regs->regs[reg] = 0;
 		preempt_enable();
 		compute_return_epc(regs);
+		regs->regs[reg] = 0;
 		return;
 	}
 
@@ -459,9 +464,8 @@ static inline void simulate_sc(struct pt
 		goto sig;
 	}
 
-	regs->regs[reg] = 1;
-
 	compute_return_epc(regs);
+	regs->regs[reg] = 1;
 	return;
 
 sig:
@@ -494,6 +498,37 @@ static inline int simulate_llsc(struct p
 	return -EFAULT;			/* Strange things going on ... */
 }
 
+/*
+ * Simulate trapping 'rdhwr' instructions to provide user accessible
+ * registers not implemented in hardware.  The only current use of this
+ * is the thread area pointer.
+ */
+static inline int simulate_rdhwr(struct pt_regs *regs)
+{
+	struct thread_info *ti = current->thread_info;
+	unsigned int opcode;
+
+	if (unlikely(get_insn_opcode(regs, &opcode)))
+		return -EFAULT;
+
+	if (unlikely(compute_return_epc(regs)))
+		return -EFAULT;
+
+	if ((opcode & OPCODE) == SPEC3 && (opcode & FUNC) == RDHWR) {
+		int rd = (opcode & RD) >> 11;
+		int rt = (opcode & RT) >> 16;
+		switch (rd) {
+			case 29:
+				regs->regs[rt] = ti->tp_value;
+				break;
+			default:
+				return -EFAULT;
+		}
+	}
+
+	return 0;
+}
+
 asmlinkage void do_ov(struct pt_regs *regs)
 {
 	siginfo_t info;
@@ -640,6 +675,9 @@ asmlinkage void do_ri(struct pt_regs *re
 		if (!simulate_llsc(regs))
 			return;
 
+	if (!simulate_rdhwr(regs))
+		return;
+
 	force_sig(SIGILL, current);
 }
 
@@ -653,11 +691,13 @@ asmlinkage void do_cpu(struct pt_regs *r
 
 	switch (cpid) {
 	case 0:
-		if (cpu_has_llsc)
-			break;
+		if (!cpu_has_llsc)
+			if (!simulate_llsc(regs))
+				return;
 
-		if (!simulate_llsc(regs))
+		if (!simulate_rdhwr(regs))
 			return;
+
 		break;
 
 	case 1:
Index: linux/arch/mips/kernel/process.c
===================================================================
--- linux.orig/arch/mips/kernel/process.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/process.c	2005-03-16 09:04:04.049856461 -0500
@@ -89,6 +89,7 @@ int copy_thread(int nr, unsigned long cl
 	struct thread_info *ti = p->thread_info;
 	struct pt_regs *childregs;
 	long childksp;
+	p->set_child_tid = p->clear_child_tid = NULL;
 
 	childksp = (unsigned long)ti + THREAD_SIZE - 32;
 
@@ -134,6 +135,9 @@ int copy_thread(int nr, unsigned long cl
 	childregs->cp0_status &= ~(ST0_CU2|ST0_CU1);
 	clear_tsk_thread_flag(p, TIF_USEDFPU);
 
+	if (clone_flags & CLONE_SETTLS)
+		ti->tp_value = regs->regs[7];
+	
 	return 0;
 }
 
Index: linux/include/asm-mips/inst.h
===================================================================
--- linux.orig/include/asm-mips/inst.h	2005-03-15 10:09:23.000000000 -0500
+++ linux/include/asm-mips/inst.h	2005-03-15 10:09:29.000000000 -0500
@@ -28,7 +28,7 @@ enum major_op {
 	sdl_op, sdr_op, swr_op, cache_op,
 	ll_op, lwc1_op, lwc2_op, pref_op,
 	lld_op, ldc1_op, ldc2_op, ld_op,
-	sc_op, swc1_op, swc2_op, major_3b_op, /* Opcode 0x3b is unused */
+	sc_op, swc1_op, swc2_op, rdhwr_op,
 	scd_op, sdc1_op, sdc2_op, sd_op
 };
 
Index: linux/arch/mips/kernel/ptrace.c
===================================================================
--- linux.orig/arch/mips/kernel/ptrace.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/ptrace.c	2005-03-15 10:09:29.000000000 -0500
@@ -287,6 +287,11 @@ asmlinkage int sys_ptrace(long request, 
 		ret = ptrace_detach(child, data);
 		break;
 
+	case PTRACE_GET_THREAD_AREA:
+		ret = put_user(child->thread_info->tp_value,
+				(unsigned long __user *) data);
+		break;
+
 	default:
 		ret = ptrace_request(child, request, addr, data);
 		break;
Index: linux/include/asm-mips/thread_info.h
===================================================================
--- linux.orig/include/asm-mips/thread_info.h	2005-03-15 10:09:23.000000000 -0500
+++ linux/include/asm-mips/thread_info.h	2005-03-15 10:34:51.000000000 -0500
@@ -26,6 +26,7 @@ struct thread_info {
 	struct task_struct	*task;		/* main task structure */
 	struct exec_domain	*exec_domain;	/* execution domain */
 	unsigned long		flags;		/* low level flags */
+	unsigned long		tp_value;	/* thread pointer */
 	__u32			cpu;		/* current CPU */
 	__s32			preempt_count; /* 0 => preemptable, <0 => BUG */
 
Index: linux/arch/mips/kernel/offset.c
===================================================================
--- linux.orig/arch/mips/kernel/offset.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/offset.c	2005-03-15 10:09:29.000000000 -0500
@@ -95,6 +95,7 @@ void output_thread_info_defines(void)
 	offset("#define TI_PRE_COUNT       ", struct thread_info, preempt_count);
 	offset("#define TI_ADDR_LIMIT      ", struct thread_info, addr_limit);
 	offset("#define TI_RESTART_BLOCK   ", struct thread_info, restart_block);
+	offset("#define TI_TP_VALUE	   ", struct thread_info, tp_value);
 	constant("#define _THREAD_SIZE_ORDER ", THREAD_SIZE_ORDER);
 	constant("#define _THREAD_SIZE       ", THREAD_SIZE);
 	constant("#define _THREAD_MASK       ", THREAD_MASK);
Index: linux/arch/mips/kernel/scall64-o32.S
===================================================================
--- linux.orig/arch/mips/kernel/scall64-o32.S	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/scall64-o32.S	2005-03-15 10:29:46.000000000 -0500
@@ -322,7 +322,7 @@ sys_call_table:
 	PTR	sys32_ipc
 	PTR	sys_fsync
 	PTR	sys32_sigreturn
-	PTR	sys_clone			/* 4120 */
+	PTR	sys32_clone			/* 4120 */
 	PTR	sys_setdomainname
 	PTR	sys32_newuname
 	PTR	sys_ni_syscall			/* sys_modify_ldt */
@@ -485,4 +485,5 @@ sys_call_table:
 	PTR	sys_add_key			/* 4280 */
 	PTR	sys_request_key
 	PTR	sys_keyctl
+	PTR	sys_set_thread_area
 	.size	sys_call_table,.-sys_call_table
Index: linux/arch/mips/kernel/linux32.c
===================================================================
--- linux.orig/arch/mips/kernel/linux32.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/linux32.c	2005-03-15 10:29:02.000000000 -0500
@@ -1471,3 +1471,38 @@ sysn32_rt_sigtimedwait(const sigset_t __
 	}
 	return sys_rt_sigtimedwait(uthese, uinfo, uts, sigsetsize);
 }
+
+save_static_function(sys32_clone);
+__attribute_used__ noinline static int
+_sys32_clone(unsigned long __dummy0,
+	     unsigned long __dummy1,
+	     unsigned long __dummy2,
+	     unsigned long __dummy3,
+	     unsigned long __dummy4,
+	     unsigned long __dummy5,
+	     unsigned long __dummy6,
+	     unsigned long __dummy7,
+	     struct pt_regs regs)
+{
+	unsigned long clone_flags;
+	unsigned long newsp;
+	int __user *parent_tidptr, *child_tidptr;
+
+	clone_flags = regs.regs[4];
+	newsp = regs.regs[5];
+	if (!newsp)
+		newsp = regs.regs[29];
+	parent_tidptr = (int *) regs.regs[6];
+
+	/* Use __dummy4 instead of getting it off the stack, so that
+	   syscall() works.  */
+	child_tidptr = (int __user *) __dummy4;
+	return do_fork(clone_flags, newsp, &regs, 0,
+	               parent_tidptr, child_tidptr);
+}
+
+extern asmlinkage void sys_set_thread_area(u32 addr);
+asmlinkage void sys32_set_thread_area(u32 addr)
+{
+	sys_set_thread_area(AA(addr));
+}
Index: linux/arch/mips/kernel/ptrace32.c
===================================================================
--- linux.orig/arch/mips/kernel/ptrace32.c	2005-03-15 10:09:23.000000000 -0500
+++ linux/arch/mips/kernel/ptrace32.c	2005-03-15 10:09:29.000000000 -0500
@@ -268,6 +268,11 @@ asmlinkage int sys32_ptrace(int request,
 		wake_up_process(child);
 		break;
 
+	case PTRACE_GET_THREAD_AREA:
+		ret = put_user(child->thread_info->tp_value,
+				(unsigned int __user *) (unsigned long) data);
+		break;
+
 	case PTRACE_DETACH: /* detach a process that was attached. */
 		ret = ptrace_detach(child, data);
 		break;
Index: linux/arch/mips/kernel/scall32-o32.S
===================================================================
--- linux.orig/arch/mips/kernel/scall32-o32.S	2005-03-15 10:04:30.000000000 -0500
+++ linux/arch/mips/kernel/scall32-o32.S	2005-03-15 10:29:24.000000000 -0500
@@ -623,6 +623,7 @@ einval:	li	v0, -EINVAL
 	sys	sys_add_key		5
 	sys	sys_request_key		4
 	sys	sys_keyctl		5
+	sys	sys_set_thread_area	1
 
 	.endm
 
Index: linux/include/asm-mips/unistd.h
===================================================================
--- linux.orig/include/asm-mips/unistd.h	2005-02-02 09:17:33.000000000 -0500
+++ linux/include/asm-mips/unistd.h	2005-03-15 10:26:02.000000000 -0500
@@ -303,16 +303,17 @@
 #define __NR_add_key			(__NR_Linux + 280)
 #define __NR_request_key		(__NR_Linux + 281)
 #define __NR_keyctl			(__NR_Linux + 282)
+#define __NR_set_thread_area		(__NR_Linux + 283)
 
 /*
  * Offset of the last Linux o32 flavoured syscall
  */
-#define __NR_Linux_syscalls		282
+#define __NR_Linux_syscalls		283
 
 #endif /* _MIPS_SIM == _MIPS_SIM_ABI32 */
 
 #define __NR_O32_Linux			4000
-#define __NR_O32_Linux_syscalls		282
+#define __NR_O32_Linux_syscalls		283
 
 #if _MIPS_SIM == _MIPS_SIM_ABI64
 
@@ -562,16 +563,17 @@
 #define __NR_add_key			(__NR_Linux + 239)
 #define __NR_request_key		(__NR_Linux + 240)
 #define __NR_keyctl			(__NR_Linux + 241)
+#define __NR_set_thread_area		(__NR_Linux + 242)
 
 /*
  * Offset of the last Linux 64-bit flavoured syscall
  */
-#define __NR_Linux_syscalls		241
+#define __NR_Linux_syscalls		242
 
 #endif /* _MIPS_SIM == _MIPS_SIM_ABI64 */
 
 #define __NR_64_Linux			5000
-#define __NR_64_Linux_syscalls		241
+#define __NR_64_Linux_syscalls		242
 
 #if _MIPS_SIM == _MIPS_SIM_NABI32
 
@@ -825,16 +827,17 @@
 #define __NR_add_key			(__NR_Linux + 243)
 #define __NR_request_key		(__NR_Linux + 244)
 #define __NR_keyctl			(__NR_Linux + 245)
+#define __NR_set_thread_area		(__NR_Linux + 246)
 
 /*
  * Offset of the last N32 flavoured syscall
  */
-#define __NR_Linux_syscalls		245
+#define __NR_Linux_syscalls		246
 
 #endif /* _MIPS_SIM == _MIPS_SIM_NABI32 */
 
 #define __NR_N32_Linux			6000
-#define __NR_N32_Linux_syscalls		245
+#define __NR_N32_Linux_syscalls		246
 
 #ifndef __ASSEMBLY__
 
Index: linux/arch/mips/kernel/scall64-64.S
===================================================================
--- linux.orig/arch/mips/kernel/scall64-64.S	2005-01-20 16:26:58.000000000 -0500
+++ linux/arch/mips/kernel/scall64-64.S	2005-03-15 10:29:48.000000000 -0500
@@ -449,3 +449,4 @@ sys_call_table:
 	PTR	sys_add_key
 	PTR	sys_request_key			/* 5240 */
 	PTR	sys_keyctl
+	PTR	sys_set_thread_area
Index: linux/arch/mips/kernel/scall64-n32.S
===================================================================
--- linux.orig/arch/mips/kernel/scall64-n32.S	2005-03-15 10:09:21.000000000 -0500
+++ linux/arch/mips/kernel/scall64-n32.S	2005-03-16 09:04:03.388011517 -0500
@@ -363,3 +363,4 @@ EXPORT(sysn32_call_table)
 	PTR	sys_add_key
 	PTR	sys_request_key
 	PTR	sys_keyctl			/* 6245 */
+	PTR	sys_set_thread_area

-- 
Daniel Jacobowitz
CodeSourcery, LLC

---

From: Daniel Jacobowitz <[email-masked]>
Date: Mon, 21 Mar 2005 15:34:45 -0500

>   - PTRACE_GET_THREAD_AREA support for GDB.

Ping?

-- 
Daniel Jacobowitz
CodeSourcery, LLC

---

From: Kumba <[email-masked]>
Date: Mon, 21 Mar 2005 20:21:09 -0500

> Ping?

Doesn't this need the glibc side of things to be effective?, or is it testable 
  w/o that component?

--Kumba

-- 
"Such is oft the course of deeds that move the wheels of the world: small 
hands do them because they must, while the eyes of the great are elsewhere." 
--Elrond

---

From: David Daney <[email-masked]>
Date: Mon, 21 Mar 2005 17:41:51 -0800

Kumba wrote:
> Daniel Jacobowitz wrote:
> 
>>
>> Ping?
>>
> 
> Doesn't this need the glibc side of things to be effective?, or is it 
> testable  w/o that component?

I think the main point is that it should not break existing code.

We need NPTL support in all three of GCC, Linux kernel and glibc before 
it can be tested.  If it doesn't break existing code, I think it should 
go in the kernel so that we have something on which to test gcc and glibc.

Just my $0.02

David Daney.

---

From: Daniel Jacobowitz <[email-masked]>
Date: Mon, 21 Mar 2005 22:21:58 -0500

> >testable  w/o that component?

It is testable independently.  Also, I posted the glibc bits last week.

> I think the main point is that it should not break existing code.

Of course.  It doesn't.  The only thing it could possibly break would
be four-argument clone (it's supposed to be five argument, and the
missing argument conventionally goes in the middle... oops).  But
I strongly believe nothing is yet using the four-argument form so I
synced MIPS with the rest of the world.

> go in the kernel so that we have something on which to test gcc and glibc.

GCC support was committed two weeks ago, BTW.

-- 
Daniel Jacobowitz
CodeSourcery, LLC