MIPS: Initial implementation of a VDSO

Add an initial implementation of a proper (i.e. an ELF shared library)
VDSO. With this commit it does not export any symbols, it only replaces
the current signal return trampoline page. A later commit will add user
implementations of gettimeofday()/clock_gettime().

To support both new toolchains and old ones which don't generate ABI
flags section, we define its content manually and then use a tool
(genvdso) to patch up the section to have the correct name and type.
genvdso also extracts symbol offsets ({,rt_}sigreturn) needed by the
kernel, and generates a C file containing a "struct mips_vdso_image"
containing both the VDSO data and these offsets. This C file is
compiled into the kernel.

On 64-bit kernels we require a different VDSO for each supported ABI,
so we may build up to 3 different VDSOs. The VDSO to use is selected by
the mips_abi structure.

A kernel/user shared data page is created and mapped below the VDSO
image. This is currently empty, but will be used by the user time
function implementations which are added later.

[markos.chandras@imgtec.com:
- Add more comments
- Move abi detection in genvdso.h since it's the get_symbol function
that needs it.
- Add an R6 specific way to calculate the base address of VDSO in order
to avoid the branch instruction which affects performance.
- Do not patch .gnu.attributes since it's not needed for dynamic linking.
- Simplify Makefile a little bit.
- checkpatch fixes
- Restrict VDSO support for binutils < 2.25 for pre-R6
- Include atomic64.h for O32 variant on MIPS64]

Signed-off-by: Alex Smith <alex.smith@imgtec.com>
Signed-off-by: Markos Chandras <markos.chandras@imgtec.com>
Cc: Matthew Fortune <matthew.fortune@imgtec.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/11337/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
diff --git a/arch/mips/kernel/signal.c b/arch/mips/kernel/signal.c
index 2fec67b..bf792e2 100644
--- a/arch/mips/kernel/signal.c
+++ b/arch/mips/kernel/signal.c
@@ -36,7 +36,6 @@
 #include <asm/ucontext.h>
 #include <asm/cpu-features.h>
 #include <asm/war.h>
-#include <asm/vdso.h>
 #include <asm/dsp.h>
 #include <asm/inst.h>
 #include <asm/msa.h>
@@ -752,16 +751,15 @@
 struct mips_abi mips_abi = {
 #ifdef CONFIG_TRAD_SIGNALS
 	.setup_frame	= setup_frame,
-	.signal_return_offset = offsetof(struct mips_vdso, signal_trampoline),
 #endif
 	.setup_rt_frame = setup_rt_frame,
-	.rt_signal_return_offset =
-		offsetof(struct mips_vdso, rt_signal_trampoline),
 	.restart	= __NR_restart_syscall,
 
 	.off_sc_fpregs = offsetof(struct sigcontext, sc_fpregs),
 	.off_sc_fpc_csr = offsetof(struct sigcontext, sc_fpc_csr),
 	.off_sc_used_math = offsetof(struct sigcontext, sc_used_math),
+
+	.vdso		= &vdso_image,
 };
 
 static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
@@ -801,11 +799,11 @@
 	}
 
 	if (sig_uses_siginfo(&ksig->ka))
-		ret = abi->setup_rt_frame(vdso + abi->rt_signal_return_offset,
+		ret = abi->setup_rt_frame(vdso + abi->vdso->off_rt_sigreturn,
 					  ksig, regs, oldset);
 	else
-		ret = abi->setup_frame(vdso + abi->signal_return_offset, ksig,
-				       regs, oldset);
+		ret = abi->setup_frame(vdso + abi->vdso->off_sigreturn,
+				       ksig, regs, oldset);
 
 	signal_setup_done(ret, ksig, 0);
 }
diff --git a/arch/mips/kernel/signal32.c b/arch/mips/kernel/signal32.c
index f7e89524..4909639 100644
--- a/arch/mips/kernel/signal32.c
+++ b/arch/mips/kernel/signal32.c
@@ -31,7 +31,6 @@
 #include <asm/ucontext.h>
 #include <asm/fpu.h>
 #include <asm/war.h>
-#include <asm/vdso.h>
 #include <asm/dsp.h>
 
 #include "signal-common.h"
@@ -406,14 +405,12 @@
  */
 struct mips_abi mips_abi_32 = {
 	.setup_frame	= setup_frame_32,
-	.signal_return_offset =
-		offsetof(struct mips_vdso, o32_signal_trampoline),
 	.setup_rt_frame = setup_rt_frame_32,
-	.rt_signal_return_offset =
-		offsetof(struct mips_vdso, o32_rt_signal_trampoline),
 	.restart	= __NR_O32_restart_syscall,
 
 	.off_sc_fpregs = offsetof(struct sigcontext32, sc_fpregs),
 	.off_sc_fpc_csr = offsetof(struct sigcontext32, sc_fpc_csr),
 	.off_sc_used_math = offsetof(struct sigcontext32, sc_used_math),
+
+	.vdso		= &vdso_image_o32,
 };
diff --git a/arch/mips/kernel/signal_n32.c b/arch/mips/kernel/signal_n32.c
index 0d017fd..a7bc384 100644
--- a/arch/mips/kernel/signal_n32.c
+++ b/arch/mips/kernel/signal_n32.c
@@ -38,7 +38,6 @@
 #include <asm/fpu.h>
 #include <asm/cpu-features.h>
 #include <asm/war.h>
-#include <asm/vdso.h>
 
 #include "signal-common.h"
 
@@ -151,11 +150,11 @@
 
 struct mips_abi mips_abi_n32 = {
 	.setup_rt_frame = setup_rt_frame_n32,
-	.rt_signal_return_offset =
-		offsetof(struct mips_vdso, n32_rt_signal_trampoline),
 	.restart	= __NR_N32_restart_syscall,
 
 	.off_sc_fpregs = offsetof(struct sigcontext, sc_fpregs),
 	.off_sc_fpc_csr = offsetof(struct sigcontext, sc_fpc_csr),
 	.off_sc_used_math = offsetof(struct sigcontext, sc_used_math),
+
+	.vdso		= &vdso_image_n32,
 };
diff --git a/arch/mips/kernel/vdso.c b/arch/mips/kernel/vdso.c
index ed2a278..56cc3c4 100644
--- a/arch/mips/kernel/vdso.c
+++ b/arch/mips/kernel/vdso.c
@@ -1,122 +1,116 @@
 /*
- * This file is subject to the terms and conditions of the GNU General Public
- * License.  See the file "COPYING" in the main directory of this archive
- * for more details.
+ * Copyright (C) 2015 Imagination Technologies
+ * Author: Alex Smith <alex.smith@imgtec.com>
  *
- * Copyright (C) 2009, 2010 Cavium Networks, Inc.
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
  */
 
-
-#include <linux/kernel.h>
-#include <linux/err.h>
-#include <linux/sched.h>
-#include <linux/mm.h>
-#include <linux/init.h>
 #include <linux/binfmts.h>
 #include <linux/elf.h>
-#include <linux/vmalloc.h>
-#include <linux/unistd.h>
-#include <linux/random.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
 
+#include <asm/abi.h>
 #include <asm/vdso.h>
-#include <asm/uasm.h>
-#include <asm/processor.h>
+
+/* Kernel-provided data used by the VDSO. */
+static union mips_vdso_data vdso_data __page_aligned_data;
 
 /*
- * Including <asm/unistd.h> would give use the 64-bit syscall numbers ...
+ * Mapping for the VDSO data pages. The real pages are mapped manually, as
+ * what we map and where within the area they are mapped is determined at
+ * runtime.
  */
-#define __NR_O32_sigreturn		4119
-#define __NR_O32_rt_sigreturn		4193
-#define __NR_N32_rt_sigreturn		6211
+static struct page *no_pages[] = { NULL };
+static struct vm_special_mapping vdso_vvar_mapping = {
+	.name = "[vvar]",
+	.pages = no_pages,
+};
 
-static struct page *vdso_page;
-
-static void __init install_trampoline(u32 *tramp, unsigned int sigreturn)
+static void __init init_vdso_image(struct mips_vdso_image *image)
 {
-	uasm_i_addiu(&tramp, 2, 0, sigreturn);	/* li v0, sigreturn */
-	uasm_i_syscall(&tramp, 0);
+	unsigned long num_pages, i;
+
+	BUG_ON(!PAGE_ALIGNED(image->data));
+	BUG_ON(!PAGE_ALIGNED(image->size));
+
+	num_pages = image->size / PAGE_SIZE;
+
+	for (i = 0; i < num_pages; i++) {
+		image->mapping.pages[i] =
+			virt_to_page(image->data + (i * PAGE_SIZE));
+	}
 }
 
 static int __init init_vdso(void)
 {
-	struct mips_vdso *vdso;
+	init_vdso_image(&vdso_image);
 
-	vdso_page = alloc_page(GFP_KERNEL);
-	if (!vdso_page)
-		panic("Cannot allocate vdso");
-
-	vdso = vmap(&vdso_page, 1, 0, PAGE_KERNEL);
-	if (!vdso)
-		panic("Cannot map vdso");
-	clear_page(vdso);
-
-	install_trampoline(vdso->rt_signal_trampoline, __NR_rt_sigreturn);
-#ifdef CONFIG_32BIT
-	install_trampoline(vdso->signal_trampoline, __NR_sigreturn);
-#else
-	install_trampoline(vdso->n32_rt_signal_trampoline,
-			   __NR_N32_rt_sigreturn);
-	install_trampoline(vdso->o32_signal_trampoline, __NR_O32_sigreturn);
-	install_trampoline(vdso->o32_rt_signal_trampoline,
-			   __NR_O32_rt_sigreturn);
+#ifdef CONFIG_MIPS32_O32
+	init_vdso_image(&vdso_image_o32);
 #endif
 
-	vunmap(vdso);
+#ifdef CONFIG_MIPS32_N32
+	init_vdso_image(&vdso_image_n32);
+#endif
 
 	return 0;
 }
 subsys_initcall(init_vdso);
 
-static unsigned long vdso_addr(unsigned long start)
-{
-	unsigned long offset = 0UL;
-
-	if (current->flags & PF_RANDOMIZE) {
-		offset = get_random_int();
-		offset <<= PAGE_SHIFT;
-		if (TASK_IS_32BIT_ADDR)
-			offset &= 0xfffffful;
-		else
-			offset &= 0xffffffful;
-	}
-
-	return STACK_TOP + offset;
-}
-
 int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
 {
-	int ret;
-	unsigned long addr;
+	struct mips_vdso_image *image = current->thread.abi->vdso;
 	struct mm_struct *mm = current->mm;
+	unsigned long base, vdso_addr;
+	struct vm_area_struct *vma;
+	int ret;
 
 	down_write(&mm->mmap_sem);
 
-	addr = vdso_addr(mm->start_stack);
-
-	addr = get_unmapped_area(NULL, addr, PAGE_SIZE, 0, 0);
-	if (IS_ERR_VALUE(addr)) {
-		ret = addr;
-		goto up_fail;
+	base = get_unmapped_area(NULL, 0, PAGE_SIZE + image->size, 0, 0);
+	if (IS_ERR_VALUE(base)) {
+		ret = base;
+		goto out;
 	}
 
-	ret = install_special_mapping(mm, addr, PAGE_SIZE,
-				      VM_READ|VM_EXEC|
-				      VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
-				      &vdso_page);
+	vdso_addr = base + PAGE_SIZE;
 
+	vma = _install_special_mapping(mm, base, PAGE_SIZE,
+				       VM_READ | VM_MAYREAD,
+				       &vdso_vvar_mapping);
+	if (IS_ERR(vma)) {
+		ret = PTR_ERR(vma);
+		goto out;
+	}
+
+	/* Map data page. */
+	ret = remap_pfn_range(vma, base,
+			      virt_to_phys(&vdso_data) >> PAGE_SHIFT,
+			      PAGE_SIZE, PAGE_READONLY);
 	if (ret)
-		goto up_fail;
+		goto out;
 
-	mm->context.vdso = (void *)addr;
+	/* Map VDSO image. */
+	vma = _install_special_mapping(mm, vdso_addr, image->size,
+				       VM_READ | VM_EXEC |
+				       VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC,
+				       &image->mapping);
+	if (IS_ERR(vma)) {
+		ret = PTR_ERR(vma);
+		goto out;
+	}
 
-up_fail:
+	mm->context.vdso = (void *)vdso_addr;
+	ret = 0;
+
+out:
 	up_write(&mm->mmap_sem);
 	return ret;
 }
-
-const char *arch_vma_name(struct vm_area_struct *vma)
-{
-	if (vma->vm_mm && vma->vm_start == (long)vma->vm_mm->context.vdso)
-		return "[vdso]";
-	return NULL;
-}