Merge branch 'usb-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh...
[pandora-kernel.git] / drivers / tty / sysrq.c
index eaa5d3e..8e0dd25 100644 (file)
@@ -46,7 +46,7 @@
 #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 = SYSRQ_DEFAULT_ENABLE;
 static bool __read_mostly sysrq_always_enabled;
 
 static bool sysrq_on(void)
@@ -554,7 +554,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,53 +563,129 @@ 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;
+};
+
+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) {
+               /* 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);
+       }
+}
 
-static bool sysrq_filter(struct input_handle *handle, unsigned int type,
-                        unsigned int code, int value)
+static bool sysrq_filter(struct input_handle *handle,
+                        unsigned int type, unsigned int code, int value)
 {
+       struct sysrq_state *sysrq = handle->private;
+       bool was_active = sysrq->active;
        bool suppress;
 
-       /* We are called with interrupts disabled, just take the lock */
-       spin_lock(&sysrq_event_lock);
+       switch (type) {
 
-       if (type != EV_KEY)
-               goto out;
+       case EV_SYN:
+               suppress = false;
+               break;
 
-       switch (code) {
+       case EV_KEY:
+               switch (code) {
 
-       case KEY_LEFTALT:
-       case KEY_RIGHTALT:
-               if (value)
-                       sysrq_alt = code;
-               else {
-                       if (sysrq_down && code == sysrq_alt_use)
-                               sysrq_down = false;
+               case KEY_LEFTALT:
+               case KEY_RIGHTALT:
+                       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 != 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, handle->dev->key);
+
+                       break;
+
+               default:
+                       if (sysrq->active && value && value != 2) {
+                               sysrq->need_reinject = false;
+                               __handle_sysrq(sysrq_xlate[code], true);
+                       }
+                       break;
                }
-               break;
 
-       case KEY_SYSRQ:
-               if (value == 1 && sysrq_alt) {
-                       sysrq_down = true;
-                       sysrq_alt_use = sysrq_alt;
+               suppress = sysrq->active;
+
+               if (!sysrq->active) {
+                       /*
+                        * 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);
+
+               } 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;
                }
                break;
 
        default:
-               if (sysrq_down && value && value != 2)
-                       __handle_sysrq(sysrq_xlate[code], true);
+               suppress = sysrq->active;
                break;
        }
 
-out:
-       suppress = sysrq_down;
-       spin_unlock(&sysrq_event_lock);
-
        return suppress;
 }
 
@@ -617,28 +693,28 @@ 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);
+
+       sysrq->handle.dev = dev;
+       sysrq->handle.handler = handler;
+       sysrq->handle.name = "sysrq";
+       sysrq->handle.private = sysrq;
 
-       error = input_register_handle(handle);
+       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 +723,20 @@ 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);
        input_unregister_handle(handle);
-       kfree(handle);
+       kfree(sysrq);
 }
 
 /*