pm_restore_console() is called from the suspend/resume path, and this
calls vt_move_to_console(), which calls vt_waitactive().
There's a race in this path which causes the process which requests the
suspend to sleep indefinitely waiting for an event which already
happened:
P1 P2
vt_move_to_console()
set_console()
schedule_console_callback()
vt_waitactive()
check n == fg_console +1
console_callback()
switch_screen()
vt_event_post() // no waiters
vt_event_wait() // forever
Fix the race by ensuring we're registered for the event before we check
if it's already completed.
Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
Acked-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
wake_up_interruptible(&vt_event_waitqueue);
}
wake_up_interruptible(&vt_event_waitqueue);
}
-/**
- * vt_event_wait - wait for an event
- * @vw: our event
- *
- * Waits for an event to occur which completes our vt_event_wait
- * structure. On return the structure has wv->done set to 1 for success
- * or 0 if some event such as a signal ended the wait.
- */
-
-static void vt_event_wait(struct vt_event_wait *vw)
+static void __vt_event_queue(struct vt_event_wait *vw)
{
unsigned long flags;
/* Prepare the event */
{
unsigned long flags;
/* Prepare the event */
spin_lock_irqsave(&vt_event_lock, flags);
list_add(&vw->list, &vt_events);
spin_unlock_irqrestore(&vt_event_lock, flags);
spin_lock_irqsave(&vt_event_lock, flags);
list_add(&vw->list, &vt_events);
spin_unlock_irqrestore(&vt_event_lock, flags);
+}
+
+static void __vt_event_wait(struct vt_event_wait *vw)
+{
/* Wait for it to pass */
wait_event_interruptible(vt_event_waitqueue, vw->done);
/* Wait for it to pass */
wait_event_interruptible(vt_event_waitqueue, vw->done);
+}
+
+static void __vt_event_dequeue(struct vt_event_wait *vw)
+{
+ unsigned long flags;
+
/* Dequeue it */
spin_lock_irqsave(&vt_event_lock, flags);
list_del(&vw->list);
/* Dequeue it */
spin_lock_irqsave(&vt_event_lock, flags);
list_del(&vw->list);
+ * vt_event_wait - wait for an event
+ * @vw: our event
+ *
+ * Waits for an event to occur which completes our vt_event_wait
+ * structure. On return the structure has wv->done set to 1 for success
+ * or 0 if some event such as a signal ended the wait.
+ */
+
+static void vt_event_wait(struct vt_event_wait *vw)
+{
+ __vt_event_queue(vw);
+ __vt_event_wait(vw);
+ __vt_event_dequeue(vw);
+}
+
+/**
* vt_event_wait_ioctl - event ioctl handler
* @arg: argument to ioctl
*
* vt_event_wait_ioctl - event ioctl handler
* @arg: argument to ioctl
*
{
struct vt_event_wait vw;
do {
{
struct vt_event_wait vw;
do {
- if (n == fg_console + 1)
- break;
vw.event.event = VT_EVENT_SWITCH;
vw.event.event = VT_EVENT_SWITCH;
+ __vt_event_queue(&vw);
+ if (n == fg_console + 1) {
+ __vt_event_dequeue(&vw);
+ break;
+ }
+ __vt_event_wait(&vw);
+ __vt_event_dequeue(&vw);
if (vw.done == 0)
return -EINTR;
} while (vw.event.newev != n);
if (vw.done == 0)
return -EINTR;
} while (vw.event.newev != n);