| /* | |
| * Copyright IBM Corp. 2016 | |
| * Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com> | |
| */ | |
| #include <linux/kernel.h> | |
| #include <linux/syscalls.h> | |
| #include <linux/signal.h> | |
| #include <linux/mm.h> | |
| #include <linux/slab.h> | |
| #include <asm/guarded_storage.h> | |
| #include "entry.h" | |
| void exit_thread_gs(void) | |
| { | |
| kfree(current->thread.gs_cb); | |
| kfree(current->thread.gs_bc_cb); | |
| current->thread.gs_cb = current->thread.gs_bc_cb = NULL; | |
| } | |
| static int gs_enable(void) | |
| { | |
| struct gs_cb *gs_cb; | |
| if (!current->thread.gs_cb) { | |
| gs_cb = kzalloc(sizeof(*gs_cb), GFP_KERNEL); | |
| if (!gs_cb) | |
| return -ENOMEM; | |
| gs_cb->gsd = 25; | |
| preempt_disable(); | |
| __ctl_set_bit(2, 4); | |
| load_gs_cb(gs_cb); | |
| current->thread.gs_cb = gs_cb; | |
| preempt_enable(); | |
| } | |
| return 0; | |
| } | |
| static int gs_disable(void) | |
| { | |
| if (current->thread.gs_cb) { | |
| preempt_disable(); | |
| kfree(current->thread.gs_cb); | |
| current->thread.gs_cb = NULL; | |
| __ctl_clear_bit(2, 4); | |
| preempt_enable(); | |
| } | |
| return 0; | |
| } | |
| static int gs_set_bc_cb(struct gs_cb __user *u_gs_cb) | |
| { | |
| struct gs_cb *gs_cb; | |
| gs_cb = current->thread.gs_bc_cb; | |
| if (!gs_cb) { | |
| gs_cb = kzalloc(sizeof(*gs_cb), GFP_KERNEL); | |
| if (!gs_cb) | |
| return -ENOMEM; | |
| current->thread.gs_bc_cb = gs_cb; | |
| } | |
| if (copy_from_user(gs_cb, u_gs_cb, sizeof(*gs_cb))) | |
| return -EFAULT; | |
| return 0; | |
| } | |
| static int gs_clear_bc_cb(void) | |
| { | |
| struct gs_cb *gs_cb; | |
| gs_cb = current->thread.gs_bc_cb; | |
| current->thread.gs_bc_cb = NULL; | |
| kfree(gs_cb); | |
| return 0; | |
| } | |
| void gs_load_bc_cb(struct pt_regs *regs) | |
| { | |
| struct gs_cb *gs_cb; | |
| preempt_disable(); | |
| clear_thread_flag(TIF_GUARDED_STORAGE); | |
| gs_cb = current->thread.gs_bc_cb; | |
| if (gs_cb) { | |
| kfree(current->thread.gs_cb); | |
| current->thread.gs_bc_cb = NULL; | |
| __ctl_set_bit(2, 4); | |
| load_gs_cb(gs_cb); | |
| current->thread.gs_cb = gs_cb; | |
| } | |
| preempt_enable(); | |
| } | |
| static int gs_broadcast(void) | |
| { | |
| struct task_struct *sibling; | |
| read_lock(&tasklist_lock); | |
| for_each_thread(current, sibling) { | |
| if (!sibling->thread.gs_bc_cb) | |
| continue; | |
| if (test_and_set_tsk_thread_flag(sibling, TIF_GUARDED_STORAGE)) | |
| kick_process(sibling); | |
| } | |
| read_unlock(&tasklist_lock); | |
| return 0; | |
| } | |
| SYSCALL_DEFINE2(s390_guarded_storage, int, command, | |
| struct gs_cb __user *, gs_cb) | |
| { | |
| if (!MACHINE_HAS_GS) | |
| return -EOPNOTSUPP; | |
| switch (command) { | |
| case GS_ENABLE: | |
| return gs_enable(); | |
| case GS_DISABLE: | |
| return gs_disable(); | |
| case GS_SET_BC_CB: | |
| return gs_set_bc_cb(gs_cb); | |
| case GS_CLEAR_BC_CB: | |
| return gs_clear_bc_cb(); | |
| case GS_BROADCAST: | |
| return gs_broadcast(); | |
| default: | |
| return -EINVAL; | |
| } | |
| } |