diff options
Diffstat (limited to 'drivers/tty/sysrq.c')
| -rw-r--r-- | drivers/tty/sysrq.c | 474 |
1 files changed, 395 insertions, 79 deletions
diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c index eaa5d3efa79..454b65898e2 100644 --- a/drivers/tty/sysrq.c +++ b/drivers/tty/sysrq.c @@ -15,6 +15,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/sched.h> +#include <linux/sched/rt.h> #include <linux/interrupt.h> #include <linux/mm.h> #include <linux/fs.h> @@ -32,7 +33,6 @@ #include <linux/module.h> #include <linux/suspend.h> #include <linux/writeback.h> -#include <linux/buffer_head.h> /* for fsync_bdev() */ #include <linux/swap.h> #include <linux/spinlock.h> #include <linux/vt_kern.h> @@ -41,14 +41,23 @@ #include <linux/oom.h> #include <linux/slab.h> #include <linux/input.h> +#include <linux/uaccess.h> +#include <linux/moduleparam.h> +#include <linux/jiffies.h> +#include <linux/syscalls.h> +#include <linux/of.h> +#include <linux/rcupdate.h> #include <asm/ptrace.h> #include <asm/irq_regs.h> /* Whether we react on sysrq keys or just ignore them */ -static int __read_mostly sysrq_enabled = 1; +static int __read_mostly sysrq_enabled = CONFIG_MAGIC_SYSRQ_DEFAULT_ENABLE; static bool __read_mostly sysrq_always_enabled; +unsigned short platform_sysrq_reset_seq[] __weak = { KEY_RESERVED }; +int sysrq_reset_downtime_ms __weak; + static bool sysrq_on(void) { return sysrq_enabled || sysrq_always_enabled; @@ -80,7 +89,7 @@ static void sysrq_handle_loglevel(int key) int i; i = key - '0'; - console_loglevel = 7; + console_loglevel = CONSOLE_LOGLEVEL_DEFAULT; printk("Loglevel set to %d\n", i); console_loglevel = i; } @@ -99,7 +108,7 @@ static void sysrq_handle_SAK(int key) } static struct sysrq_key_op sysrq_SAK_op = { .handler = sysrq_handle_SAK, - .help_msg = "saK", + .help_msg = "sak(k)", .action_msg = "SAK", .enable_mask = SYSRQ_ENABLE_KEYBOARD, }; @@ -110,14 +119,12 @@ static struct sysrq_key_op sysrq_SAK_op = { #ifdef CONFIG_VT static void sysrq_handle_unraw(int key) { - struct kbd_struct *kbd = &kbd_table[fg_console]; - - if (kbd) - kbd->kbdmode = default_utf8 ? VC_UNICODE : VC_XLATE; + vt_reset_unicode(fg_console); } + static struct sysrq_key_op sysrq_unraw_op = { .handler = sysrq_handle_unraw, - .help_msg = "unRaw", + .help_msg = "unraw(r)", .action_msg = "Keyboard mode set to system default", .enable_mask = SYSRQ_ENABLE_KEYBOARD, }; @@ -135,7 +142,7 @@ static void sysrq_handle_crash(int key) } static struct sysrq_key_op sysrq_crash_op = { .handler = sysrq_handle_crash, - .help_msg = "Crash", + .help_msg = "crash(c)", .action_msg = "Trigger a crash", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -148,7 +155,7 @@ static void sysrq_handle_reboot(int key) } static struct sysrq_key_op sysrq_reboot_op = { .handler = sysrq_handle_reboot, - .help_msg = "reBoot", + .help_msg = "reboot(b)", .action_msg = "Resetting", .enable_mask = SYSRQ_ENABLE_BOOT, }; @@ -159,7 +166,7 @@ static void sysrq_handle_sync(int key) } static struct sysrq_key_op sysrq_sync_op = { .handler = sysrq_handle_sync, - .help_msg = "Sync", + .help_msg = "sync(s)", .action_msg = "Emergency Sync", .enable_mask = SYSRQ_ENABLE_SYNC, }; @@ -171,7 +178,7 @@ static void sysrq_handle_show_timers(int key) static struct sysrq_key_op sysrq_show_timers_op = { .handler = sysrq_handle_show_timers, - .help_msg = "show-all-timers(Q)", + .help_msg = "show-all-timers(q)", .action_msg = "Show clockevent devices & pending hrtimers (no others)", }; @@ -181,7 +188,7 @@ static void sysrq_handle_mountro(int key) } static struct sysrq_key_op sysrq_mountro_op = { .handler = sysrq_handle_mountro, - .help_msg = "Unmount", + .help_msg = "unmount(u)", .action_msg = "Emergency Remount R/O", .enable_mask = SYSRQ_ENABLE_REMOUNT, }; @@ -194,7 +201,7 @@ static void sysrq_handle_showlocks(int key) static struct sysrq_key_op sysrq_showlocks_op = { .handler = sysrq_handle_showlocks, - .help_msg = "show-all-locks(D)", + .help_msg = "show-all-locks(d)", .action_msg = "Show Locks Held", }; #else @@ -245,7 +252,7 @@ static void sysrq_handle_showallcpus(int key) static struct sysrq_key_op sysrq_showallcpus_op = { .handler = sysrq_handle_showallcpus, - .help_msg = "show-backtrace-all-active-cpus(L)", + .help_msg = "show-backtrace-all-active-cpus(l)", .action_msg = "Show backtrace of all active CPUs", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -260,7 +267,7 @@ static void sysrq_handle_showregs(int key) } static struct sysrq_key_op sysrq_showregs_op = { .handler = sysrq_handle_showregs, - .help_msg = "show-registers(P)", + .help_msg = "show-registers(p)", .action_msg = "Show Regs", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -271,7 +278,7 @@ static void sysrq_handle_showstate(int key) } static struct sysrq_key_op sysrq_showstate_op = { .handler = sysrq_handle_showstate, - .help_msg = "show-task-states(T)", + .help_msg = "show-task-states(t)", .action_msg = "Show State", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -282,7 +289,7 @@ static void sysrq_handle_showstate_blocked(int key) } static struct sysrq_key_op sysrq_showstate_blocked_op = { .handler = sysrq_handle_showstate_blocked, - .help_msg = "show-blocked-tasks(W)", + .help_msg = "show-blocked-tasks(w)", .action_msg = "Show Blocked State", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -296,7 +303,7 @@ static void sysrq_ftrace_dump(int key) } static struct sysrq_key_op sysrq_ftrace_dump_op = { .handler = sysrq_ftrace_dump, - .help_msg = "dump-ftrace-buffer(Z)", + .help_msg = "dump-ftrace-buffer(z)", .action_msg = "Dump ftrace buffer", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -306,11 +313,11 @@ static struct sysrq_key_op sysrq_ftrace_dump_op = { static void sysrq_handle_showmem(int key) { - show_mem(); + show_mem(0); } static struct sysrq_key_op sysrq_showmem_op = { .handler = sysrq_handle_showmem, - .help_msg = "show-memory-usage(M)", + .help_msg = "show-memory-usage(m)", .action_msg = "Show Memory", .enable_mask = SYSRQ_ENABLE_DUMP, }; @@ -322,28 +329,34 @@ static void send_sig_all(int sig) { struct task_struct *p; + read_lock(&tasklist_lock); for_each_process(p) { - if (p->mm && !is_global_init(p)) - /* Not swapper, init nor kernel thread */ - force_sig(sig, p); + if (p->flags & PF_KTHREAD) + continue; + if (is_global_init(p)) + continue; + + do_send_sig_info(sig, SEND_SIG_FORCED, p, true); } + read_unlock(&tasklist_lock); } static void sysrq_handle_term(int key) { send_sig_all(SIGTERM); - console_loglevel = 8; + console_loglevel = CONSOLE_LOGLEVEL_DEBUG; } static struct sysrq_key_op sysrq_term_op = { .handler = sysrq_handle_term, - .help_msg = "terminate-all-tasks(E)", + .help_msg = "terminate-all-tasks(e)", .action_msg = "Terminate All Tasks", .enable_mask = SYSRQ_ENABLE_SIGNAL, }; static void moom_callback(struct work_struct *ignored) { - out_of_memory(node_zonelist(0, GFP_KERNEL), GFP_KERNEL, 0, NULL); + out_of_memory(node_zonelist(first_online_node, GFP_KERNEL), GFP_KERNEL, + 0, NULL, true); } static DECLARE_WORK(moom_work, moom_callback); @@ -354,7 +367,7 @@ static void sysrq_handle_moom(int key) } static struct sysrq_key_op sysrq_moom_op = { .handler = sysrq_handle_moom, - .help_msg = "memory-full-oom-kill(F)", + .help_msg = "memory-full-oom-kill(f)", .action_msg = "Manual OOM execution", .enable_mask = SYSRQ_ENABLE_SIGNAL, }; @@ -366,7 +379,7 @@ static void sysrq_handle_thaw(int key) } static struct sysrq_key_op sysrq_thaw_op = { .handler = sysrq_handle_thaw, - .help_msg = "thaw-filesystems(J)", + .help_msg = "thaw-filesystems(j)", .action_msg = "Emergency Thaw of all frozen filesystems", .enable_mask = SYSRQ_ENABLE_SIGNAL, }; @@ -375,11 +388,11 @@ static struct sysrq_key_op sysrq_thaw_op = { static void sysrq_handle_kill(int key) { send_sig_all(SIGKILL); - console_loglevel = 8; + console_loglevel = CONSOLE_LOGLEVEL_DEBUG; } static struct sysrq_key_op sysrq_kill_op = { .handler = sysrq_handle_kill, - .help_msg = "kill-all-tasks(I)", + .help_msg = "kill-all-tasks(i)", .action_msg = "Kill All Tasks", .enable_mask = SYSRQ_ENABLE_SIGNAL, }; @@ -390,7 +403,7 @@ static void sysrq_handle_unrt(int key) } static struct sysrq_key_op sysrq_unrt_op = { .handler = sysrq_handle_unrt, - .help_msg = "nice-all-RT-tasks(N)", + .help_msg = "nice-all-RT-tasks(n)", .action_msg = "Nice All RT Tasks", .enable_mask = SYSRQ_ENABLE_RTNICE, }; @@ -449,6 +462,7 @@ static struct sysrq_key_op *sysrq_key_table[36] = { NULL, /* v */ &sysrq_showstate_blocked_op, /* w */ /* x: May be registered on ppc/powerpc for xmon */ + /* x: May be registered on sparc64 for global PMU dump */ NULL, /* x */ /* y: May be registered on sparc64 for global register dump */ NULL, /* y */ @@ -497,9 +511,9 @@ void __handle_sysrq(int key, bool check_mask) struct sysrq_key_op *op_p; int orig_log_level; int i; - unsigned long flags; - spin_lock_irqsave(&sysrq_key_table_lock, flags); + rcu_sysrq_start(); + rcu_read_lock(); /* * Raise the apparent loglevel to maximum so that the sysrq header * is shown to provide the user with positive feedback. We do not @@ -507,7 +521,7 @@ void __handle_sysrq(int key, bool check_mask) * routing in the consumers of /proc/kmsg. */ orig_log_level = console_loglevel; - console_loglevel = 7; + console_loglevel = CONSOLE_LOGLEVEL_DEFAULT; printk(KERN_INFO "SysRq : "); op_p = __sysrq_get_key_op(key); @@ -541,7 +555,8 @@ void __handle_sysrq(int key, bool check_mask) printk("\n"); console_loglevel = orig_log_level; } - spin_unlock_irqrestore(&sysrq_key_table_lock, flags); + rcu_read_unlock(); + rcu_sysrq_end(); } void handle_sysrq(int key) @@ -554,7 +569,7 @@ EXPORT_SYMBOL(handle_sysrq); #ifdef CONFIG_INPUT /* Simple translation table for the SysRq keys */ -static const unsigned char sysrq_xlate[KEY_MAX + 1] = +static const unsigned char sysrq_xlate[KEY_CNT] = "\000\0331234567890-=\177\t" /* 0x00 - 0x0f */ "qwertyuiop[]\r\000as" /* 0x10 - 0x1f */ "dfghjkl;'`\000\\zxcv" /* 0x20 - 0x2f */ @@ -563,52 +578,291 @@ static const unsigned char sysrq_xlate[KEY_MAX + 1] = "230\177\000\000\213\214\000\000\000\000\000\000\000\000\000\000" /* 0x50 - 0x5f */ "\r\000/"; /* 0x60 - 0x6f */ -static bool sysrq_down; -static int sysrq_alt_use; -static int sysrq_alt; -static DEFINE_SPINLOCK(sysrq_event_lock); +struct sysrq_state { + struct input_handle handle; + struct work_struct reinject_work; + unsigned long key_down[BITS_TO_LONGS(KEY_CNT)]; + unsigned int alt; + unsigned int alt_use; + bool active; + bool need_reinject; + bool reinjecting; + + /* reset sequence handling */ + bool reset_canceled; + bool reset_requested; + unsigned long reset_keybit[BITS_TO_LONGS(KEY_CNT)]; + int reset_seq_len; + int reset_seq_cnt; + int reset_seq_version; + struct timer_list keyreset_timer; +}; + +#define SYSRQ_KEY_RESET_MAX 20 /* Should be plenty */ +static unsigned short sysrq_reset_seq[SYSRQ_KEY_RESET_MAX]; +static unsigned int sysrq_reset_seq_len; +static unsigned int sysrq_reset_seq_version = 1; -static bool sysrq_filter(struct input_handle *handle, unsigned int type, - unsigned int code, int value) +static void sysrq_parse_reset_sequence(struct sysrq_state *state) { - bool suppress; + int i; + unsigned short key; + + state->reset_seq_cnt = 0; + + for (i = 0; i < sysrq_reset_seq_len; i++) { + key = sysrq_reset_seq[i]; + + if (key == KEY_RESERVED || key > KEY_MAX) + break; + + __set_bit(key, state->reset_keybit); + state->reset_seq_len++; + + if (test_bit(key, state->key_down)) + state->reset_seq_cnt++; + } + + /* Disable reset until old keys are not released */ + state->reset_canceled = state->reset_seq_cnt != 0; + + state->reset_seq_version = sysrq_reset_seq_version; +} + +static void sysrq_do_reset(unsigned long _state) +{ + struct sysrq_state *state = (struct sysrq_state *) _state; + + state->reset_requested = true; + + sys_sync(); + kernel_restart(NULL); +} + +static void sysrq_handle_reset_request(struct sysrq_state *state) +{ + if (state->reset_requested) + __handle_sysrq(sysrq_xlate[KEY_B], false); + + if (sysrq_reset_downtime_ms) + mod_timer(&state->keyreset_timer, + jiffies + msecs_to_jiffies(sysrq_reset_downtime_ms)); + else + sysrq_do_reset((unsigned long)state); +} + +static void sysrq_detect_reset_sequence(struct sysrq_state *state, + unsigned int code, int value) +{ + if (!test_bit(code, state->reset_keybit)) { + /* + * Pressing any key _not_ in reset sequence cancels + * the reset sequence. Also cancelling the timer in + * case additional keys were pressed after a reset + * has been requested. + */ + if (value && state->reset_seq_cnt) { + state->reset_canceled = true; + del_timer(&state->keyreset_timer); + } + } else if (value == 0) { + /* + * Key release - all keys in the reset sequence need + * to be pressed and held for the reset timeout + * to hold. + */ + del_timer(&state->keyreset_timer); + + if (--state->reset_seq_cnt == 0) + state->reset_canceled = false; + } else if (value == 1) { + /* key press, not autorepeat */ + if (++state->reset_seq_cnt == state->reset_seq_len && + !state->reset_canceled) { + sysrq_handle_reset_request(state); + } + } +} + +#ifdef CONFIG_OF +static void sysrq_of_get_keyreset_config(void) +{ + u32 key; + struct device_node *np; + struct property *prop; + const __be32 *p; + + np = of_find_node_by_path("/chosen/linux,sysrq-reset-seq"); + if (!np) { + pr_debug("No sysrq node found"); + return; + } + + /* Reset in case a __weak definition was present */ + sysrq_reset_seq_len = 0; + + of_property_for_each_u32(np, "keyset", prop, p, key) { + if (key == KEY_RESERVED || key > KEY_MAX || + sysrq_reset_seq_len == SYSRQ_KEY_RESET_MAX) + break; + + sysrq_reset_seq[sysrq_reset_seq_len++] = (unsigned short)key; + } + + /* Get reset timeout if any. */ + of_property_read_u32(np, "timeout-ms", &sysrq_reset_downtime_ms); +} +#else +static void sysrq_of_get_keyreset_config(void) +{ +} +#endif - /* We are called with interrupts disabled, just take the lock */ - spin_lock(&sysrq_event_lock); +static void sysrq_reinject_alt_sysrq(struct work_struct *work) +{ + struct sysrq_state *sysrq = + container_of(work, struct sysrq_state, reinject_work); + struct input_handle *handle = &sysrq->handle; + unsigned int alt_code = sysrq->alt_use; + + if (sysrq->need_reinject) { + /* we do not want the assignment to be reordered */ + sysrq->reinjecting = true; + mb(); + + /* Simulate press and release of Alt + SysRq */ + input_inject_event(handle, EV_KEY, alt_code, 1); + input_inject_event(handle, EV_KEY, KEY_SYSRQ, 1); + input_inject_event(handle, EV_SYN, SYN_REPORT, 1); + + input_inject_event(handle, EV_KEY, KEY_SYSRQ, 0); + input_inject_event(handle, EV_KEY, alt_code, 0); + input_inject_event(handle, EV_SYN, SYN_REPORT, 1); + + mb(); + sysrq->reinjecting = false; + } +} - if (type != EV_KEY) - goto out; +static bool sysrq_handle_keypress(struct sysrq_state *sysrq, + unsigned int code, int value) +{ + bool was_active = sysrq->active; + bool suppress; switch (code) { case KEY_LEFTALT: case KEY_RIGHTALT: - if (value) - sysrq_alt = code; - else { - if (sysrq_down && code == sysrq_alt_use) - sysrq_down = false; + if (!value) { + /* One of ALTs is being released */ + if (sysrq->active && code == sysrq->alt_use) + sysrq->active = false; - sysrq_alt = 0; + sysrq->alt = KEY_RESERVED; + + } else if (value != 2) { + sysrq->alt = code; + sysrq->need_reinject = false; } break; case KEY_SYSRQ: - if (value == 1 && sysrq_alt) { - sysrq_down = true; - sysrq_alt_use = sysrq_alt; + if (value == 1 && sysrq->alt != KEY_RESERVED) { + sysrq->active = true; + sysrq->alt_use = sysrq->alt; + /* + * If nothing else will be pressed we'll need + * to re-inject Alt-SysRq keysroke. + */ + sysrq->need_reinject = true; } + + /* + * Pretend that sysrq was never pressed at all. This + * is needed to properly handle KGDB which will try + * to release all keys after exiting debugger. If we + * do not clear key bit it KGDB will end up sending + * release events for Alt and SysRq, potentially + * triggering print screen function. + */ + if (sysrq->active) + clear_bit(KEY_SYSRQ, sysrq->handle.dev->key); + break; default: - if (sysrq_down && value && value != 2) + if (sysrq->active && value && value != 2) { + sysrq->need_reinject = false; __handle_sysrq(sysrq_xlate[code], true); + } break; } -out: - suppress = sysrq_down; - spin_unlock(&sysrq_event_lock); + suppress = sysrq->active; + + if (!sysrq->active) { + + /* + * See if reset sequence has changed since the last time. + */ + if (sysrq->reset_seq_version != sysrq_reset_seq_version) + sysrq_parse_reset_sequence(sysrq); + + /* + * If we are not suppressing key presses keep track of + * keyboard state so we can release keys that have been + * pressed before entering SysRq mode. + */ + if (value) + set_bit(code, sysrq->key_down); + else + clear_bit(code, sysrq->key_down); + + if (was_active) + schedule_work(&sysrq->reinject_work); + + /* Check for reset sequence */ + sysrq_detect_reset_sequence(sysrq, code, value); + + } else if (value == 0 && test_and_clear_bit(code, sysrq->key_down)) { + /* + * Pass on release events for keys that was pressed before + * entering SysRq mode. + */ + suppress = false; + } + + return suppress; +} + +static bool sysrq_filter(struct input_handle *handle, + unsigned int type, unsigned int code, int value) +{ + struct sysrq_state *sysrq = handle->private; + bool suppress; + + /* + * Do not filter anything if we are in the process of re-injecting + * Alt+SysRq combination. + */ + if (sysrq->reinjecting) + return false; + + switch (type) { + + case EV_SYN: + suppress = false; + break; + + case EV_KEY: + suppress = sysrq_handle_keypress(sysrq, code, value); + break; + + default: + suppress = sysrq->active; + break; + } return suppress; } @@ -617,28 +871,30 @@ static int sysrq_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { - struct input_handle *handle; + struct sysrq_state *sysrq; int error; - sysrq_down = false; - sysrq_alt = 0; - - handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); - if (!handle) + sysrq = kzalloc(sizeof(struct sysrq_state), GFP_KERNEL); + if (!sysrq) return -ENOMEM; - handle->dev = dev; - handle->handler = handler; - handle->name = "sysrq"; + INIT_WORK(&sysrq->reinject_work, sysrq_reinject_alt_sysrq); - error = input_register_handle(handle); + sysrq->handle.dev = dev; + sysrq->handle.handler = handler; + sysrq->handle.name = "sysrq"; + sysrq->handle.private = sysrq; + setup_timer(&sysrq->keyreset_timer, + sysrq_do_reset, (unsigned long)sysrq); + + error = input_register_handle(&sysrq->handle); if (error) { pr_err("Failed to register input sysrq handler, error %d\n", error); goto err_free; } - error = input_open_device(handle); + error = input_open_device(&sysrq->handle); if (error) { pr_err("Failed to open input device, error %d\n", error); goto err_unregister; @@ -647,17 +903,21 @@ static int sysrq_connect(struct input_handler *handler, return 0; err_unregister: - input_unregister_handle(handle); + input_unregister_handle(&sysrq->handle); err_free: - kfree(handle); + kfree(sysrq); return error; } static void sysrq_disconnect(struct input_handle *handle) { + struct sysrq_state *sysrq = handle->private; + input_close_device(handle); + cancel_work_sync(&sysrq->reinject_work); + del_timer_sync(&sysrq->keyreset_timer); input_unregister_handle(handle); - kfree(handle); + kfree(sysrq); } /* @@ -687,7 +947,24 @@ static bool sysrq_handler_registered; static inline void sysrq_register_handler(void) { + unsigned short key; int error; + int i; + + /* First check if a __weak interface was instantiated. */ + for (i = 0; i < ARRAY_SIZE(sysrq_reset_seq); i++) { + key = platform_sysrq_reset_seq[i]; + if (key == KEY_RESERVED || key > KEY_MAX) + break; + + sysrq_reset_seq[sysrq_reset_seq_len++] = key; + } + + /* + * DT configuration takes precedence over anything that would + * have been defined via the __weak interface. + */ + sysrq_of_get_keyreset_config(); error = input_register_handler(&sysrq_handler); if (error) @@ -704,6 +981,38 @@ static inline void sysrq_unregister_handler(void) } } +static int sysrq_reset_seq_param_set(const char *buffer, + const struct kernel_param *kp) +{ + unsigned long val; + int error; + + error = kstrtoul(buffer, 0, &val); + if (error < 0) + return error; + + if (val > KEY_MAX) + return -EINVAL; + + *((unsigned short *)kp->arg) = val; + sysrq_reset_seq_version++; + + return 0; +} + +static struct kernel_param_ops param_ops_sysrq_reset_seq = { + .get = param_get_ushort, + .set = sysrq_reset_seq_param_set, +}; + +#define param_check_sysrq_reset_seq(name, p) \ + __param_check(name, p, unsigned short) + +module_param_array_named(reset_seq, sysrq_reset_seq, sysrq_reset_seq, + &sysrq_reset_seq_len, 0644); + +module_param_named(sysrq_downtime_ms, sysrq_reset_downtime_ms, int, 0644); + #else static inline void sysrq_register_handler(void) @@ -736,16 +1045,23 @@ static int __sysrq_swap_key_ops(int key, struct sysrq_key_op *insert_op_p, struct sysrq_key_op *remove_op_p) { int retval; - unsigned long flags; - spin_lock_irqsave(&sysrq_key_table_lock, flags); + spin_lock(&sysrq_key_table_lock); if (__sysrq_get_key_op(key) == remove_op_p) { __sysrq_put_key_op(key, insert_op_p); retval = 0; } else { retval = -1; } - spin_unlock_irqrestore(&sysrq_key_table_lock, flags); + spin_unlock(&sysrq_key_table_lock); + + /* + * A concurrent __handle_sysrq either got the old op or the new op. + * Wait for it to go away before returning, so the code for an old + * op is not freed (eg. on module unload) while it is in use. + */ + synchronize_rcu(); + return retval; } |
