mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-16 00:46:47 +07:00
432c6bacbd
In some cases the kernel needs to execute an instruction from the delay slot of an emulated branch instruction. These cases include: - Emulated floating point branch instructions (bc1[ft]l?) for systems which don't include an FPU, or upon which the kernel is run with the "nofpu" parameter. - MIPSr6 systems running binaries targeting older revisions of the architecture, which may include branch instructions whose encodings are no longer valid in MIPSr6. Executing instructions from such delay slots is done by writing the instruction to memory followed by a trap, as part of an "emuframe", and executing it. This avoids the requirement of an emulator for the entire MIPS instruction set. Prior to this patch such emuframes are written to the user stack and executed from there. This patch moves FP branch delay emuframes off of the user stack and into a per-mm page. Allocating a page per-mm leaves userland with access to only what it had access to previously, and compared to other solutions is relatively simple. When a thread requires a delay slot emulation, it is allocated a frame. A thread may only have one frame allocated at any one time, since it may only ever be executing one instruction at any one time. In order to ensure that we can free up allocated frame later, its index is recorded in struct thread_struct. In the typical case, after executing the delay slot instruction we'll execute a break instruction with the BRK_MEMU code. This traps back to the kernel & leads to a call to do_dsemulret which frees the allocated frame & moves the user PC back to the instruction that would have executed following the emulated branch. In some cases the delay slot instruction may be invalid, such as a branch, or may trigger an exception. In these cases the BRK_MEMU break instruction will not be hit. In order to ensure that frames are freed this patch introduces dsemul_thread_cleanup() and calls it to free any allocated frame upon thread exit. If the instruction generated an exception & leads to a signal being delivered to the thread, or indeed if a signal simply happens to be delivered to the thread whilst it is executing from the struct emuframe, then we need to take care to exit the frame appropriately. This is done by either rolling back the user PC to the branch or advancing it to the continuation PC prior to signal delivery, using dsemul_thread_rollback(). If this were not done then a sigreturn would return to the struct emuframe, and if that frame had meanwhile been used in response to an emulated branch instruction within the signal handler then we would execute the wrong user code. Whilst a user could theoretically place something like a compact branch to self in a delay slot and cause their thread to become stuck in an infinite loop with the frame never being deallocated, this would: - Only affect the users single process. - Be architecturally invalid since there would be a branch in the delay slot, which is forbidden. - Be extremely unlikely to happen by mistake, and provide a program with no more ability to harm the system than a simple infinite loop would. If a thread requires a delay slot emulation & no frame is available to it (ie. the process has enough other threads that all frames are currently in use) then the thread joins a waitqueue. It will sleep until a frame is freed by another thread in the process. Since we now know whether a thread has an allocated frame due to our tracking of its index, the cookie field of struct emuframe is removed as we can be more certain whether we have a valid frame. Since a thread may only ever have a single frame at any given time, the epc field of struct emuframe is also removed & the PC to continue from is instead stored in struct thread_struct. Together these changes simplify & shrink struct emuframe somewhat, allowing twice as many frames to fit into the page allocated for them. The primary benefit of this patch is that we are now free to mark the user stack non-executable where that is possible. Signed-off-by: Paul Burton <paul.burton@imgtec.com> Cc: Leonid Yegoshin <leonid.yegoshin@imgtec.com> Cc: Maciej Rozycki <maciej.rozycki@imgtec.com> Cc: Faraz Shahbazker <faraz.shahbazker@imgtec.com> Cc: Raghu Gandham <raghu.gandham@imgtec.com> Cc: Matthew Fortune <matthew.fortune@imgtec.com> Cc: linux-mips@linux-mips.org Patchwork: https://patchwork.linux-mips.org/patch/13764/ Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
187 lines
4.6 KiB
C
187 lines
4.6 KiB
C
/*
|
|
* Copyright (C) 2015 Imagination Technologies
|
|
* Author: Alex Smith <alex.smith@imgtec.com>
|
|
*
|
|
* 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/binfmts.h>
|
|
#include <linux/elf.h>
|
|
#include <linux/err.h>
|
|
#include <linux/init.h>
|
|
#include <linux/ioport.h>
|
|
#include <linux/irqchip/mips-gic.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/timekeeper_internal.h>
|
|
|
|
#include <asm/abi.h>
|
|
#include <asm/vdso.h>
|
|
|
|
/* Kernel-provided data used by the VDSO. */
|
|
static union mips_vdso_data vdso_data __page_aligned_data;
|
|
|
|
/*
|
|
* Mapping for the VDSO data/GIC pages. The real pages are mapped manually, as
|
|
* what we map and where within the area they are mapped is determined at
|
|
* runtime.
|
|
*/
|
|
static struct page *no_pages[] = { NULL };
|
|
static struct vm_special_mapping vdso_vvar_mapping = {
|
|
.name = "[vvar]",
|
|
.pages = no_pages,
|
|
};
|
|
|
|
static void __init init_vdso_image(struct mips_vdso_image *image)
|
|
{
|
|
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)
|
|
{
|
|
init_vdso_image(&vdso_image);
|
|
|
|
#ifdef CONFIG_MIPS32_O32
|
|
init_vdso_image(&vdso_image_o32);
|
|
#endif
|
|
|
|
#ifdef CONFIG_MIPS32_N32
|
|
init_vdso_image(&vdso_image_n32);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
subsys_initcall(init_vdso);
|
|
|
|
void update_vsyscall(struct timekeeper *tk)
|
|
{
|
|
vdso_data_write_begin(&vdso_data);
|
|
|
|
vdso_data.xtime_sec = tk->xtime_sec;
|
|
vdso_data.xtime_nsec = tk->tkr_mono.xtime_nsec;
|
|
vdso_data.wall_to_mono_sec = tk->wall_to_monotonic.tv_sec;
|
|
vdso_data.wall_to_mono_nsec = tk->wall_to_monotonic.tv_nsec;
|
|
vdso_data.cs_shift = tk->tkr_mono.shift;
|
|
|
|
vdso_data.clock_mode = tk->tkr_mono.clock->archdata.vdso_clock_mode;
|
|
if (vdso_data.clock_mode != VDSO_CLOCK_NONE) {
|
|
vdso_data.cs_mult = tk->tkr_mono.mult;
|
|
vdso_data.cs_cycle_last = tk->tkr_mono.cycle_last;
|
|
vdso_data.cs_mask = tk->tkr_mono.mask;
|
|
}
|
|
|
|
vdso_data_write_end(&vdso_data);
|
|
}
|
|
|
|
void update_vsyscall_tz(void)
|
|
{
|
|
if (vdso_data.clock_mode != VDSO_CLOCK_NONE) {
|
|
vdso_data.tz_minuteswest = sys_tz.tz_minuteswest;
|
|
vdso_data.tz_dsttime = sys_tz.tz_dsttime;
|
|
}
|
|
}
|
|
|
|
int arch_setup_additional_pages(struct linux_binprm *bprm, int uses_interp)
|
|
{
|
|
struct mips_vdso_image *image = current->thread.abi->vdso;
|
|
struct mm_struct *mm = current->mm;
|
|
unsigned long gic_size, vvar_size, size, base, data_addr, vdso_addr;
|
|
struct vm_area_struct *vma;
|
|
struct resource gic_res;
|
|
int ret;
|
|
|
|
if (down_write_killable(&mm->mmap_sem))
|
|
return -EINTR;
|
|
|
|
/* Map delay slot emulation page */
|
|
base = mmap_region(NULL, STACK_TOP, PAGE_SIZE,
|
|
VM_READ|VM_WRITE|VM_EXEC|
|
|
VM_MAYREAD|VM_MAYWRITE|VM_MAYEXEC,
|
|
0);
|
|
if (IS_ERR_VALUE(base)) {
|
|
ret = base;
|
|
goto out;
|
|
}
|
|
|
|
/*
|
|
* Determine total area size. This includes the VDSO data itself, the
|
|
* data page, and the GIC user page if present. Always create a mapping
|
|
* for the GIC user area if the GIC is present regardless of whether it
|
|
* is the current clocksource, in case it comes into use later on. We
|
|
* only map a page even though the total area is 64K, as we only need
|
|
* the counter registers at the start.
|
|
*/
|
|
gic_size = gic_present ? PAGE_SIZE : 0;
|
|
vvar_size = gic_size + PAGE_SIZE;
|
|
size = vvar_size + image->size;
|
|
|
|
base = get_unmapped_area(NULL, 0, size, 0, 0);
|
|
if (IS_ERR_VALUE(base)) {
|
|
ret = base;
|
|
goto out;
|
|
}
|
|
|
|
data_addr = base + gic_size;
|
|
vdso_addr = data_addr + PAGE_SIZE;
|
|
|
|
vma = _install_special_mapping(mm, base, vvar_size,
|
|
VM_READ | VM_MAYREAD,
|
|
&vdso_vvar_mapping);
|
|
if (IS_ERR(vma)) {
|
|
ret = PTR_ERR(vma);
|
|
goto out;
|
|
}
|
|
|
|
/* Map GIC user page. */
|
|
if (gic_size) {
|
|
ret = gic_get_usm_range(&gic_res);
|
|
if (ret)
|
|
goto out;
|
|
|
|
ret = io_remap_pfn_range(vma, base,
|
|
gic_res.start >> PAGE_SHIFT,
|
|
gic_size,
|
|
pgprot_noncached(PAGE_READONLY));
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
/* Map data page. */
|
|
ret = remap_pfn_range(vma, data_addr,
|
|
virt_to_phys(&vdso_data) >> PAGE_SHIFT,
|
|
PAGE_SIZE, PAGE_READONLY);
|
|
if (ret)
|
|
goto out;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
mm->context.vdso = (void *)vdso_addr;
|
|
ret = 0;
|
|
|
|
out:
|
|
up_write(&mm->mmap_sem);
|
|
return ret;
|
|
}
|