#include <linux/sched.h>
#include <linux/mm.h>
#include <linux/smp.h>
#include <linux/kernel.h>
#include <linux/signal.h>
#include <linux/errno.h>
#include <linux/elf.h>
#include <linux/ptrace.h>
#include <linux/pagemap.h>
#include <linux/ratelimit.h>
#include <linux/syscalls.h>
#ifdef CONFIG_PPC64
#include <linux/compat.h>
#else
#include <linux/wait.h>
#include <linux/unistd.h>
#include <linux/stddef.h>
#include <linux/tty.h>
#include <linux/binfmts.h>
#endif
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
#include <asm/syscalls.h>
#include <asm/sigcontext.h>
#include <asm/vdso.h>
#include <asm/switch_to.h>
#include <asm/tm.h>
#include <asm/asm-prototypes.h>
#ifdef CONFIG_PPC64
#include <asm/syscalls_32.h>
#include <asm/unistd.h>
#else
#include <asm/ucontext.h>
#endif
#include "signal.h"
#ifdef CONFIG_PPC64
#define old_sigaction old_sigaction32
#define sigcontext sigcontext32
#define mcontext mcontext32
#define ucontext ucontext32
#define UCONTEXTSIZEWITHOUTVSX \
(sizeof(struct ucontext) - sizeof(elf_vsrreghalf_t32))
#define GP_REGS_SIZE min(sizeof(elf_gregset_t32), sizeof(struct pt_regs32))
#undef __SIGNAL_FRAMESIZE
#define __SIGNAL_FRAMESIZE __SIGNAL_FRAMESIZE32
#undef ELF_NVRREG
#define ELF_NVRREG ELF_NVRREG32
#define unsafe_put_sigset_t unsafe_put_compat_sigset
#define unsafe_get_sigset_t unsafe_get_compat_sigset
#define to_user_ptr(p) ptr_to_compat(p)
#define from_user_ptr(p) compat_ptr(p)
static __always_inline int
__unsafe_save_general_regs(struct pt_regs *regs, struct mcontext __user *frame)
{
elf_greg_t64 *gregs = (elf_greg_t64 *)regs;
int val, i;
for (i = 0; i <= PT_RESULT; i ++) {
if (i == PT_SOFTE)
val = 1;
else
val = gregs[i];
unsafe_put_user(val, &frame->mc_gregs[i], failed);
}
return 0;
failed:
return 1;
}
static __always_inline int
__unsafe_restore_general_regs(struct pt_regs *regs, struct mcontext __user *sr)
{
elf_greg_t64 *gregs = (elf_greg_t64 *)regs;
int i;
for (i = 0; i <= PT_RESULT; i++) {
if ((i == PT_MSR) || (i == PT_SOFTE))
continue;
unsafe_get_user(gregs[i], &sr->mc_gregs[i], failed);
}
return 0;
failed:
return 1;
}
#else
#define GP_REGS_SIZE min(sizeof(elf_gregset_t), sizeof(struct pt_regs))
#define unsafe_put_sigset_t(uset, set, label) do { \
sigset_t __user *__us = uset ; \
const sigset_t *__s = set; \
\
unsafe_copy_to_user(__us, __s, sizeof(*__us), label); \
} while (0)
#define unsafe_get_sigset_t unsafe_get_user_sigset
#define to_user_ptr(p) ((unsigned long)(p))
#define from_user_ptr(p) ((void __user *)(p))
static __always_inline int
__unsafe_save_general_regs(struct pt_regs *regs, struct mcontext __user *frame)
{
unsafe_copy_to_user(&frame->mc_gregs, regs, GP_REGS_SIZE, failed);
return 0;
failed:
return 1;
}
static __always_inline
int __unsafe_restore_general_regs(struct pt_regs *regs, struct mcontext __user *sr)
{
unsafe_copy_from_user(regs, &sr->mc_gregs, PT_MSR * sizeof(elf_greg_t), failed);
unsafe_copy_from_user(®s->orig_gpr3, &sr->mc_gregs[PT_ORIG_R3],
GP_REGS_SIZE - PT_ORIG_R3 * sizeof(elf_greg_t), failed);
return 0;
failed:
return 1;
}
#endif
#define unsafe_save_general_regs(regs, frame, label) do { \
if (__unsafe_save_general_regs(regs, frame)) \
goto label; \
} while (0)
#define unsafe_restore_general_regs(regs, frame, label) do { \
if (__unsafe_restore_general_regs(regs, frame)) \
goto label; \
} while (0)
struct sigframe {
struct sigcontext sctx;
struct mcontext mctx;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
struct sigcontext sctx_transact;
struct mcontext mctx_transact;
#endif
int abigap[56];
};
struct rt_sigframe {
#ifdef CONFIG_PPC64
compat_siginfo_t info;
#else
struct siginfo info;
#endif
struct ucontext uc;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
struct ucontext uc_transact;
#endif
int abigap[56];
};
unsigned long get_min_sigframe_size_32(void)
{
return max(sizeof(struct rt_sigframe) + __SIGNAL_FRAMESIZE + 16,
sizeof(struct sigframe) + __SIGNAL_FRAMESIZE);
}
static void prepare_save_user_regs(int ctx_has_vsx_region)
{
flush_fp_to_thread(current);
#ifdef CONFIG_ALTIVEC
if (current->thread.used_vr)
flush_altivec_to_thread(current);
if (cpu_has_feature(CPU_FTR_ALTIVEC))
current->thread.vrsave = mfspr(SPRN_VRSAVE);
#endif
#ifdef CONFIG_VSX
if (current->thread.used_vsr && ctx_has_vsx_region)
flush_vsx_to_thread(current);
#endif
#ifdef CONFIG_SPE
if (current->thread.used_spe)
flush_spe_to_thread(current);
#endif
}
static __always_inline int
__unsafe_save_user_regs(struct pt_regs *regs, struct mcontext __user *frame,
struct mcontext __user *tm_frame, int ctx_has_vsx_region)
{
unsigned long msr = regs->msr;
unsafe_save_general_regs(regs, frame, failed);
#ifdef CONFIG_ALTIVEC
if (current->thread.used_vr) {
unsafe_copy_to_user(&frame->mc_vregs, ¤t->thread.vr_state,
ELF_NVRREG * sizeof(vector128), failed);
msr |= MSR_VEC;
}
unsafe_put_user(current->thread.vrsave, (u32 __user *)&frame->mc_vregs[32],
failed);
#endif
unsafe_copy_fpr_to_user(&frame->mc_fregs, current, failed);
msr &= ~MSR_VSX;
#ifdef CONFIG_VSX
if (current->thread.used_vsr && ctx_has_vsx_region) {
unsafe_copy_vsx_to_user(&frame->mc_vsregs, current, failed);
msr |= MSR_VSX;
}
#endif
#ifdef CONFIG_SPE
if (current->thread.used_spe) {
unsafe_copy_to_user(&frame->mc_vregs, current->thread.evr,
ELF_NEVRREG * sizeof(u32), failed);
msr |= MSR_SPE;
}
unsafe_put_user(current->thread.spefscr,
(u32 __user *)&frame->mc_vregs + ELF_NEVRREG, failed);
#endif
unsafe_put_user(msr, &frame->mc_gregs[PT_MSR], failed);
if (tm_frame)
unsafe_put_user(0, &tm_frame->mc_gregs[PT_MSR], failed);
return 0;
failed:
return 1;
}
#define unsafe_save_user_regs(regs, frame, tm_frame, has_vsx, label) do { \
if (__unsafe_save_user_regs(regs, frame, tm_frame, has_vsx)) \
goto label; \
} while (0)
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
static void prepare_save_tm_user_regs(void)
{
WARN_ON(tm_suspend_disabled);
if (cpu_has_feature(CPU_FTR_ALTIVEC))
current->thread.ckvrsave = mfspr(SPRN_VRSAVE);
}
static __always_inline int
save_tm_user_regs_unsafe(struct pt_regs *regs, struct mcontext __user *frame,
struct mcontext __user *tm_frame, unsigned long msr)
{
unsafe_save_general_regs(¤t->thread.ckpt_regs, frame, failed);
unsafe_save_general_regs(regs, tm_frame, failed);
unsafe_put_user((msr >> 32), &tm_frame->mc_gregs[PT_MSR], failed);
if (current->thread.used_vr) {
unsafe_copy_to_user(&frame->mc_vregs, ¤t->thread.ckvr_state,
ELF_NVRREG * sizeof(vector128), failed);
if (msr & MSR_VEC)
unsafe_copy_to_user(&tm_frame->mc_vregs,
¤t->thread.vr_state,
ELF_NVRREG * sizeof(vector128), failed);
else
unsafe_copy_to_user(&tm_frame->mc_vregs,
¤t->thread.ckvr_state,
ELF_NVRREG * sizeof(vector128), failed);
msr |= MSR_VEC;
}
unsafe_put_user(current->thread.ckvrsave,
(u32 __user *)&frame->mc_vregs[32], failed);
if (msr & MSR_VEC)
unsafe_put_user(current->thread.vrsave,
(u32 __user *)&tm_frame->mc_vregs[32], failed);
else
unsafe_put_user(current->thread.ckvrsave,
(u32 __user *)&tm_frame->mc_vregs[32], failed);
unsafe_copy_ckfpr_to_user(&frame->mc_fregs, current, failed);
if (msr & MSR_FP)
unsafe_copy_fpr_to_user(&tm_frame->mc_fregs, current, failed);
else
unsafe_copy_ckfpr_to_user(&tm_frame->mc_fregs, current, failed);
if (current->thread.used_vsr) {
unsafe_copy_ckvsx_to_user(&frame->mc_vsregs, current, failed);
if (msr & MSR_VSX)
unsafe_copy_vsx_to_user(&tm_frame->mc_vsregs, current, failed);
else
unsafe_copy_ckvsx_to_user(&tm_frame->mc_vsregs, current, failed);
msr |= MSR_VSX;
}
unsafe_put_user(msr, &frame->mc_gregs[PT_MSR], failed);
return 0;
failed:
return 1;
}
#else
static void prepare_save_tm_user_regs(void) { }
static __always_inline int
save_tm_user_regs_unsafe(struct pt_regs *regs, struct mcontext __user *frame,
struct mcontext __user *tm_frame, unsigned long msr)
{
return 0;
}
#endif
#define unsafe_save_tm_user_regs(regs, frame, tm_frame, msr, label) do { \
if (save_tm_user_regs_unsafe(regs, frame, tm_frame, msr)) \
goto label; \
} while (0)
static long restore_user_regs(struct pt_regs *regs,
struct mcontext __user *sr, int sig)
{
unsigned int save_r2 = 0;
unsigned long msr;
#ifdef CONFIG_VSX
int i;
#endif
if (!user_read_access_begin(sr, sizeof(*sr)))
return 1;
if (!sig)
save_r2 = (unsigned int)regs->gpr[2];
unsafe_restore_general_regs(regs, sr, failed);
set_trap_norestart(regs);
unsafe_get_user(msr, &sr->mc_gregs[PT_MSR], failed);
if (!sig)
regs->gpr[2] = (unsigned long) save_r2;
if (sig)
regs_set_return_msr(regs, (regs->msr & ~MSR_LE) | (msr & MSR_LE));
#ifdef CONFIG_ALTIVEC
regs_set_return_msr(regs, regs->msr & ~MSR_VEC);
if (msr & MSR_VEC) {
unsafe_copy_from_user(¤t->thread.vr_state, &sr->mc_vregs,
sizeof(sr->mc_vregs), failed);
current->thread.used_vr = true;
} else if (current->thread.used_vr)
memset(¤t->thread.vr_state, 0,
ELF_NVRREG * sizeof(vector128));
unsafe_get_user(current->thread.vrsave, (u32 __user *)&sr->mc_vregs[32], failed);
if (cpu_has_feature(CPU_FTR_ALTIVEC))
mtspr(SPRN_VRSAVE, current->thread.vrsave);
#endif
unsafe_copy_fpr_from_user(current, &sr->mc_fregs, failed);
#ifdef CONFIG_VSX
regs_set_return_msr(regs, regs->msr & ~MSR_VSX);
if (msr & MSR_VSX) {
unsafe_copy_vsx_from_user(current, &sr->mc_vsregs, failed);
current->thread.used_vsr = true;
} else if (current->thread.used_vsr)
for (i = 0; i < 32 ; i++)
current->thread.fp_state.fpr[i][TS_VSRLOWOFFSET] = 0;
#endif
regs_set_return_msr(regs, regs->msr & ~(MSR_FP | MSR_FE0 | MSR_FE1));
#ifdef CONFIG_SPE
BUILD_BUG_ON(sizeof(current->thread.spe) != ELF_NEVRREG * sizeof(u32));
regs_set_return_msr(regs, regs->msr & ~MSR_SPE);
if (msr & MSR_SPE) {
unsafe_copy_from_user(¤t->thread.spe, &sr->mc_vregs,
sizeof(current->thread.spe), failed);
current->thread.used_spe = true;
} else if (current->thread.used_spe)
memset(¤t->thread.spe, 0, sizeof(current->thread.spe));
unsafe_get_user(current->thread.spefscr, (u32 __user *)&sr->mc_vregs + ELF_NEVRREG, failed);
#endif
user_read_access_end();
return 0;
failed:
user_read_access_end();
return 1;
}
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
static long restore_tm_user_regs(struct pt_regs *regs,
struct mcontext __user *sr,
struct mcontext __user *tm_sr)
{
unsigned long msr, msr_hi;
int i;
if (tm_suspend_disabled)
return 1;
if (!user_read_access_begin(sr, sizeof(*sr)))
return 1;
unsafe_restore_general_regs(¤t->thread.ckpt_regs, sr, failed);
unsafe_get_user(current->thread.tm_tfhar, &sr->mc_gregs[PT_NIP], failed);
unsafe_get_user(msr, &sr->mc_gregs[PT_MSR], failed);
regs_set_return_msr(regs, (regs->msr & ~MSR_LE) | (msr & MSR_LE));
regs_set_return_msr(regs, regs->msr & ~MSR_VEC);
if (msr & MSR_VEC) {
unsafe_copy_from_user(¤t->thread.ckvr_state, &sr->mc_vregs,
sizeof(sr->mc_vregs), failed);
current->thread.used_vr = true;
} else if (current->thread.used_vr) {
memset(¤t->thread.vr_state, 0,
ELF_NVRREG * sizeof(vector128));
memset(¤t->thread.ckvr_state, 0,
ELF_NVRREG * sizeof(vector128));
}
unsafe_get_user(current->thread.ckvrsave,
(u32 __user *)&sr->mc_vregs[32], failed);
if (cpu_has_feature(CPU_FTR_ALTIVEC))
mtspr(SPRN_VRSAVE, current->thread.ckvrsave);
regs_set_return_msr(regs, regs->msr & ~(MSR_FP | MSR_FE0 | MSR_FE1));
unsafe_copy_fpr_from_user(current, &sr->mc_fregs, failed);
regs_set_return_msr(regs, regs->msr & ~MSR_VSX);
if (msr & MSR_VSX) {
unsafe_copy_ckvsx_from_user(current, &sr->mc_vsregs, failed);
current->thread.used_vsr = true;
} else if (current->thread.used_vsr)
for (i = 0; i < 32 ; i++) {
current->thread.fp_state.fpr[i][TS_VSRLOWOFFSET] = 0;
current->thread.ckfp_state.fpr[i][TS_VSRLOWOFFSET] = 0;
}
user_read_access_end();
if (!user_read_access_begin(tm_sr, sizeof(*tm_sr)))
return 1;
unsafe_restore_general_regs(regs, tm_sr, failed);
if (msr & MSR_VEC)
unsafe_copy_from_user(¤t->thread.vr_state, &tm_sr->mc_vregs,
sizeof(sr->mc_vregs), failed);
unsafe_get_user(current->thread.vrsave,
(u32 __user *)&tm_sr->mc_vregs[32], failed);
unsafe_copy_ckfpr_from_user(current, &tm_sr->mc_fregs, failed);
if (msr & MSR_VSX) {
unsafe_copy_vsx_from_user(current, &tm_sr->mc_vsregs, failed);
current->thread.used_vsr = true;
}
unsafe_get_user(msr_hi, &tm_sr->mc_gregs[PT_MSR], failed);
msr_hi <<= 32;
user_read_access_end();
if (MSR_TM_RESV(msr_hi))
return 1;
preempt_disable();
regs_set_return_msr(regs, (regs->msr & ~MSR_TS_MASK) | (msr_hi & MSR_TS_MASK));
tm_enable();
current->thread.tm_texasr |= TEXASR_FS;
tm_recheckpoint(¤t->thread);
msr_check_and_set(msr & (MSR_FP | MSR_VEC));
if (msr & MSR_FP) {
load_fp_state(¤t->thread.fp_state);
regs_set_return_msr(regs, regs->msr | (MSR_FP | current->thread.fpexc_mode));
}
if (msr & MSR_VEC) {
load_vr_state(¤t->thread.vr_state);
regs_set_return_msr(regs, regs->msr | MSR_VEC);
}
preempt_enable();
return 0;
failed:
user_read_access_end();
return 1;
}
#else
static long restore_tm_user_regs(struct pt_regs *regs, struct mcontext __user *sr,
struct mcontext __user *tm_sr)
{
return 0;
}
#endif
#ifdef CONFIG_PPC64
#define copy_siginfo_to_user copy_siginfo_to_user32
#endif
int handle_rt_signal32(struct ksignal *ksig, sigset_t *oldset,
struct task_struct *tsk)
{
struct rt_sigframe __user *frame;
struct mcontext __user *mctx;
struct mcontext __user *tm_mctx = NULL;
unsigned long newsp = 0;
unsigned long tramp;
struct pt_regs *regs = tsk->thread.regs;
unsigned long msr = regs->msr;
frame = get_sigframe(ksig, tsk, sizeof(*frame), 1);
mctx = &frame->uc.uc_mcontext;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
tm_mctx = &frame->uc_transact.uc_mcontext;
#endif
if (MSR_TM_ACTIVE(msr))
prepare_save_tm_user_regs();
else
prepare_save_user_regs(1);
if (!user_access_begin(frame, sizeof(*frame)))
goto badframe;
unsafe_put_user(0, &frame->uc.uc_flags, failed);
#ifdef CONFIG_PPC64
unsafe_compat_save_altstack(&frame->uc.uc_stack, regs->gpr[1], failed);
#else
unsafe_save_altstack(&frame->uc.uc_stack, regs->gpr[1], failed);
#endif
unsafe_put_user(to_user_ptr(&frame->uc.uc_mcontext), &frame->uc.uc_regs, failed);
if (MSR_TM_ACTIVE(msr)) {
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
unsafe_put_user((unsigned long)&frame->uc_transact,
&frame->uc.uc_link, failed);
unsafe_put_user((unsigned long)tm_mctx,
&frame->uc_transact.uc_regs, failed);
#endif
unsafe_save_tm_user_regs(regs, mctx, tm_mctx, msr, failed);
} else {
unsafe_put_user(0, &frame->uc.uc_link, failed);
unsafe_save_user_regs(regs, mctx, tm_mctx, 1, failed);
}
if (tsk->mm->context.vdso) {
tramp = VDSO32_SYMBOL(tsk->mm->context.vdso, sigtramp_rt32);
} else {
tramp = (unsigned long)mctx->mc_pad;
unsafe_put_user(PPC_RAW_LI(_R0, __NR_rt_sigreturn), &mctx->mc_pad[0], failed);
unsafe_put_user(PPC_RAW_SC(), &mctx->mc_pad[1], failed);
asm("dcbst %y0; sync; icbi %y0; sync" :: "Z" (mctx->mc_pad[0]));
}
unsafe_put_sigset_t(&frame->uc.uc_sigmask, oldset, failed);
user_access_end();
if (copy_siginfo_to_user(&frame->info, &ksig->info))
goto badframe;
regs->link = tramp;
#ifdef CONFIG_PPC_FPU_REGS
tsk->thread.fp_state.fpscr = 0;
#endif
newsp = ((unsigned long)frame) - (__SIGNAL_FRAMESIZE + 16);
if (put_user(regs->gpr[1], (u32 __user *)newsp))
goto badframe;
regs->gpr[1] = newsp;
regs->gpr[3] = ksig->sig;
regs->gpr[4] = (unsigned long)&frame->info;
regs->gpr[5] = (unsigned long)&frame->uc;
regs->gpr[6] = (unsigned long)frame;
regs_set_return_ip(regs, (unsigned long) ksig->ka.sa.sa_handler);
regs_set_return_msr(regs, (regs->msr & ~MSR_LE) | (MSR_KERNEL & MSR_LE));
return 0;
failed:
user_access_end();
badframe:
signal_fault(tsk, regs, "handle_rt_signal32", frame);
return 1;
}
int handle_signal32(struct ksignal *ksig, sigset_t *oldset,
struct task_struct *tsk)
{
struct sigcontext __user *sc;
struct sigframe __user *frame;
struct mcontext __user *mctx;
struct mcontext __user *tm_mctx = NULL;
unsigned long newsp = 0;
unsigned long tramp;
struct pt_regs *regs = tsk->thread.regs;
unsigned long msr = regs->msr;
frame = get_sigframe(ksig, tsk, sizeof(*frame), 1);
mctx = &frame->mctx;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
tm_mctx = &frame->mctx_transact;
#endif
if (MSR_TM_ACTIVE(msr))
prepare_save_tm_user_regs();
else
prepare_save_user_regs(1);
if (!user_access_begin(frame, sizeof(*frame)))
goto badframe;
sc = (struct sigcontext __user *) &frame->sctx;
#if _NSIG != 64
#error "Please adjust handle_signal()"
#endif
unsafe_put_user(to_user_ptr(ksig->ka.sa.sa_handler), &sc->handler, failed);
unsafe_put_user(oldset->sig[0], &sc->oldmask, failed);
#ifdef CONFIG_PPC64
unsafe_put_user((oldset->sig[0] >> 32), &sc->_unused[3], failed);
#else
unsafe_put_user(oldset->sig[1], &sc->_unused[3], failed);
#endif
unsafe_put_user(to_user_ptr(mctx), &sc->regs, failed);
unsafe_put_user(ksig->sig, &sc->signal, failed);
if (MSR_TM_ACTIVE(msr))
unsafe_save_tm_user_regs(regs, mctx, tm_mctx, msr, failed);
else
unsafe_save_user_regs(regs, mctx, tm_mctx, 1, failed);
if (tsk->mm->context.vdso) {
tramp = VDSO32_SYMBOL(tsk->mm->context.vdso, sigtramp32);
} else {
tramp = (unsigned long)mctx->mc_pad;
unsafe_put_user(PPC_RAW_LI(_R0, __NR_sigreturn), &mctx->mc_pad[0], failed);
unsafe_put_user(PPC_RAW_SC(), &mctx->mc_pad[1], failed);
asm("dcbst %y0; sync; icbi %y0; sync" :: "Z" (mctx->mc_pad[0]));
}
user_access_end();
regs->link = tramp;
#ifdef CONFIG_PPC_FPU_REGS
tsk->thread.fp_state.fpscr = 0;
#endif
newsp = ((unsigned long)frame) - __SIGNAL_FRAMESIZE;
if (put_user(regs->gpr[1], (u32 __user *)newsp))
goto badframe;
regs->gpr[1] = newsp;
regs->gpr[3] = ksig->sig;
regs->gpr[4] = (unsigned long) sc;
regs_set_return_ip(regs, (unsigned long) ksig->ka.sa.sa_handler);
regs_set_return_msr(regs, (regs->msr & ~MSR_LE) | (MSR_KERNEL & MSR_LE));
return 0;
failed:
user_access_end();
badframe:
signal_fault(tsk, regs, "handle_signal32", frame);
return 1;
}
static int do_setcontext(struct ucontext __user *ucp, struct pt_regs *regs, int sig)
{
sigset_t set;
struct mcontext __user *mcp;
if (!user_read_access_begin(ucp, sizeof(*ucp)))
return -EFAULT;
unsafe_get_sigset_t(&set, &ucp->uc_sigmask, failed);
#ifdef CONFIG_PPC64
{
u32 cmcp;
unsafe_get_user(cmcp, &ucp->uc_regs, failed);
mcp = (struct mcontext __user *)(u64)cmcp;
}
#else
unsafe_get_user(mcp, &ucp->uc_regs, failed);
#endif
user_read_access_end();
set_current_blocked(&set);
if (restore_user_regs(regs, mcp, sig))
return -EFAULT;
return 0;
failed:
user_read_access_end();
return -EFAULT;
}
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
static int do_setcontext_tm(struct ucontext __user *ucp,
struct ucontext __user *tm_ucp,
struct pt_regs *regs)
{
sigset_t set;
struct mcontext __user *mcp;
struct mcontext __user *tm_mcp;
u32 cmcp;
u32 tm_cmcp;
if (!user_read_access_begin(ucp, sizeof(*ucp)))
return -EFAULT;
unsafe_get_sigset_t(&set, &ucp->uc_sigmask, failed);
unsafe_get_user(cmcp, &ucp->uc_regs, failed);
user_read_access_end();
if (__get_user(tm_cmcp, &tm_ucp->uc_regs))
return -EFAULT;
mcp = (struct mcontext __user *)(u64)cmcp;
tm_mcp = (struct mcontext __user *)(u64)tm_cmcp;
set_current_blocked(&set);
if (restore_tm_user_regs(regs, mcp, tm_mcp))
return -EFAULT;
return 0;
failed:
user_read_access_end();
return -EFAULT;
}
#endif
#ifdef CONFIG_PPC64
COMPAT_SYSCALL_DEFINE3(swapcontext, struct ucontext __user *, old_ctx,
struct ucontext __user *, new_ctx, int, ctx_size)
#else
SYSCALL_DEFINE3(swapcontext, struct ucontext __user *, old_ctx,
struct ucontext __user *, new_ctx, long, ctx_size)
#endif
{
struct pt_regs *regs = current_pt_regs();
int ctx_has_vsx_region = 0;
#ifdef CONFIG_PPC64
unsigned long new_msr = 0;
if (new_ctx) {
struct mcontext __user *mcp;
u32 cmcp;
if (__get_user(cmcp, &new_ctx->uc_regs))
return -EFAULT;
mcp = (struct mcontext __user *)(u64)cmcp;
if (__get_user(new_msr, &mcp->mc_gregs[PT_MSR]))
return -EFAULT;
}
if (ctx_size < UCONTEXTSIZEWITHOUTVSX)
return -EINVAL;
if ((ctx_size < sizeof(struct ucontext)) &&
(new_msr & MSR_VSX))
return -EINVAL;
if (ctx_size >= sizeof(struct ucontext))
ctx_has_vsx_region = 1;
#else
if (ctx_size < sizeof(struct ucontext))
return -EINVAL;
#endif
if (old_ctx != NULL) {
struct mcontext __user *mctx;
mctx = (struct mcontext __user *)
((unsigned long) &old_ctx->uc_mcontext & ~0xfUL);
prepare_save_user_regs(ctx_has_vsx_region);
if (!user_write_access_begin(old_ctx, ctx_size))
return -EFAULT;
unsafe_save_user_regs(regs, mctx, NULL, ctx_has_vsx_region, failed);
unsafe_put_sigset_t(&old_ctx->uc_sigmask, ¤t->blocked, failed);
unsafe_put_user(to_user_ptr(mctx), &old_ctx->uc_regs, failed);
user_write_access_end();
}
if (new_ctx == NULL)
return 0;
if (!access_ok(new_ctx, ctx_size) ||
fault_in_readable((char __user *)new_ctx, ctx_size))
return -EFAULT;
if (do_setcontext(new_ctx, regs, 0)) {
force_exit_sig(SIGSEGV);
return -EFAULT;
}
set_thread_flag(TIF_RESTOREALL);
return 0;
failed:
user_write_access_end();
return -EFAULT;
}
#ifdef CONFIG_PPC64
COMPAT_SYSCALL_DEFINE0(rt_sigreturn)
#else
SYSCALL_DEFINE0(rt_sigreturn)
#endif
{
struct rt_sigframe __user *rt_sf;
struct pt_regs *regs = current_pt_regs();
int tm_restore = 0;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
struct ucontext __user *uc_transact;
unsigned long msr_hi;
unsigned long tmp;
#endif
current->restart_block.fn = do_no_restart_syscall;
rt_sf = (struct rt_sigframe __user *)
(regs->gpr[1] + __SIGNAL_FRAMESIZE + 16);
if (!access_ok(rt_sf, sizeof(*rt_sf)))
goto bad;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
if (MSR_TM_SUSPENDED(mfmsr()))
tm_reclaim_current(0);
if (__get_user(tmp, &rt_sf->uc.uc_link))
goto bad;
uc_transact = (struct ucontext __user *)(uintptr_t)tmp;
if (uc_transact) {
u32 cmcp;
struct mcontext __user *mcp;
if (__get_user(cmcp, &uc_transact->uc_regs))
return -EFAULT;
mcp = (struct mcontext __user *)(u64)cmcp;
if (__get_user(msr_hi, &mcp->mc_gregs[PT_MSR]))
goto bad;
if (MSR_TM_ACTIVE(msr_hi<<32)) {
if (!cpu_has_feature(CPU_FTR_TM))
goto bad;
tm_restore = 1;
if (do_setcontext_tm(&rt_sf->uc, uc_transact, regs))
goto bad;
}
}
if (!tm_restore) {
regs_set_return_msr(regs, regs->msr & ~MSR_TS_MASK);
}
#endif
if (!tm_restore)
if (do_setcontext(&rt_sf->uc, regs, 1))
goto bad;
#ifdef CONFIG_PPC64
if (compat_restore_altstack(&rt_sf->uc.uc_stack))
goto bad;
#else
if (restore_altstack(&rt_sf->uc.uc_stack))
goto bad;
#endif
set_thread_flag(TIF_RESTOREALL);
return 0;
bad:
signal_fault(current, regs, "sys_rt_sigreturn", rt_sf);
force_sig(SIGSEGV);
return 0;
}
#ifdef CONFIG_PPC32
SYSCALL_DEFINE3(debug_setcontext, struct ucontext __user *, ctx,
int, ndbg, struct sig_dbg_op __user *, dbg)
{
struct pt_regs *regs = current_pt_regs();
struct sig_dbg_op op;
int i;
unsigned long new_msr = regs->msr;
#ifdef CONFIG_PPC_ADV_DEBUG_REGS
unsigned long new_dbcr0 = current->thread.debug.dbcr0;
#endif
for (i=0; i<ndbg; i++) {
if (copy_from_user(&op, dbg + i, sizeof(op)))
return -EFAULT;
switch (op.dbg_type) {
case SIG_DBG_SINGLE_STEPPING:
#ifdef CONFIG_PPC_ADV_DEBUG_REGS
if (op.dbg_value) {
new_msr |= MSR_DE;
new_dbcr0 |= (DBCR0_IDM | DBCR0_IC);
} else {
new_dbcr0 &= ~DBCR0_IC;
if (!DBCR_ACTIVE_EVENTS(new_dbcr0,
current->thread.debug.dbcr1)) {
new_msr &= ~MSR_DE;
new_dbcr0 &= ~DBCR0_IDM;
}
}
#else
if (op.dbg_value)
new_msr |= MSR_SE;
else
new_msr &= ~MSR_SE;
#endif
break;
case SIG_DBG_BRANCH_TRACING:
#ifdef CONFIG_PPC_ADV_DEBUG_REGS
return -EINVAL;
#else
if (op.dbg_value)
new_msr |= MSR_BE;
else
new_msr &= ~MSR_BE;
#endif
break;
default:
return -EINVAL;
}
}
regs_set_return_msr(regs, new_msr);
#ifdef CONFIG_PPC_ADV_DEBUG_REGS
current->thread.debug.dbcr0 = new_dbcr0;
#endif
if (!access_ok(ctx, sizeof(*ctx)) ||
fault_in_readable((char __user *)ctx, sizeof(*ctx)))
return -EFAULT;
if (do_setcontext(ctx, regs, 1)) {
signal_fault(current, regs, "sys_debug_setcontext", ctx);
force_sig(SIGSEGV);
goto out;
}
restore_altstack(&ctx->uc_stack);
set_thread_flag(TIF_RESTOREALL);
out:
return 0;
}
#endif
#ifdef CONFIG_PPC64
COMPAT_SYSCALL_DEFINE0(sigreturn)
#else
SYSCALL_DEFINE0(sigreturn)
#endif
{
struct pt_regs *regs = current_pt_regs();
struct sigframe __user *sf;
struct sigcontext __user *sc;
struct sigcontext sigctx;
struct mcontext __user *sr;
sigset_t set;
struct mcontext __user *mcp;
struct mcontext __user *tm_mcp = NULL;
unsigned long long msr_hi = 0;
current->restart_block.fn = do_no_restart_syscall;
sf = (struct sigframe __user *)(regs->gpr[1] + __SIGNAL_FRAMESIZE);
sc = &sf->sctx;
if (copy_from_user(&sigctx, sc, sizeof(sigctx)))
goto badframe;
#ifdef CONFIG_PPC64
set.sig[0] = sigctx.oldmask + ((long)(sigctx._unused[3]) << 32);
#else
set.sig[0] = sigctx.oldmask;
set.sig[1] = sigctx._unused[3];
#endif
set_current_blocked(&set);
mcp = (struct mcontext __user *)&sf->mctx;
#ifdef CONFIG_PPC_TRANSACTIONAL_MEM
tm_mcp = (struct mcontext __user *)&sf->mctx_transact;
if (__get_user(msr_hi, &tm_mcp->mc_gregs[PT_MSR]))
goto badframe;
#endif
if (MSR_TM_ACTIVE(msr_hi<<32)) {
if (!cpu_has_feature(CPU_FTR_TM))
goto badframe;
if (restore_tm_user_regs(regs, mcp, tm_mcp))
goto badframe;
} else {
sr = (struct mcontext __user *)from_user_ptr(sigctx.regs);
if (restore_user_regs(regs, sr, 1)) {
signal_fault(current, regs, "sys_sigreturn", sr);
force_sig(SIGSEGV);
return 0;
}
}
set_thread_flag(TIF_RESTOREALL);
return 0;
badframe:
signal_fault(current, regs, "sys_sigreturn", sc);
force_sig(SIGSEGV);
return 0;
}