vt: fix race in vt_waitactive()
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>
This commit is contained in:
parent
ed85c60457
commit
a7b12929be
|
@ -110,6 +110,34 @@ void vt_event_post(unsigned int event, unsigned int old, unsigned int new)
|
|||
wake_up_interruptible(&vt_event_waitqueue);
|
||||
}
|
||||
|
||||
static void __vt_event_queue(struct vt_event_wait *vw)
|
||||
{
|
||||
unsigned long flags;
|
||||
/* Prepare the event */
|
||||
INIT_LIST_HEAD(&vw->list);
|
||||
vw->done = 0;
|
||||
/* Queue our event */
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
spin_unlock_irqrestore(&vt_event_lock, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* vt_event_wait - wait for an event
|
||||
* @vw: our event
|
||||
|
@ -121,20 +149,9 @@ void vt_event_post(unsigned int event, unsigned int old, unsigned int new)
|
|||
|
||||
static void vt_event_wait(struct vt_event_wait *vw)
|
||||
{
|
||||
unsigned long flags;
|
||||
/* Prepare the event */
|
||||
INIT_LIST_HEAD(&vw->list);
|
||||
vw->done = 0;
|
||||
/* Queue our event */
|
||||
spin_lock_irqsave(&vt_event_lock, flags);
|
||||
list_add(&vw->list, &vt_events);
|
||||
spin_unlock_irqrestore(&vt_event_lock, flags);
|
||||
/* Wait for it to pass */
|
||||
wait_event_interruptible(vt_event_waitqueue, vw->done);
|
||||
/* Dequeue it */
|
||||
spin_lock_irqsave(&vt_event_lock, flags);
|
||||
list_del(&vw->list);
|
||||
spin_unlock_irqrestore(&vt_event_lock, flags);
|
||||
__vt_event_queue(vw);
|
||||
__vt_event_wait(vw);
|
||||
__vt_event_dequeue(vw);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,10 +194,14 @@ int vt_waitactive(int n)
|
|||
{
|
||||
struct vt_event_wait vw;
|
||||
do {
|
||||
if (n == fg_console + 1)
|
||||
break;
|
||||
vw.event.event = VT_EVENT_SWITCH;
|
||||
vt_event_wait(&vw);
|
||||
__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);
|
||||
|
|
Loading…
Reference in a new issue