mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-10-28 07:13:34 +00:00
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
057eb856ed
commit
2fc46915ec
1 changed files with 38 additions and 17 deletions
|
@ -110,6 +110,34 @@ void vt_event_post(unsigned int event, unsigned int old, unsigned int new)
|
||||||
wake_up_interruptible(&vt_event_waitqueue);
|
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
|
* vt_event_wait - wait for an event
|
||||||
* @vw: our 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)
|
static void vt_event_wait(struct vt_event_wait *vw)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
__vt_event_queue(vw);
|
||||||
/* Prepare the event */
|
__vt_event_wait(vw);
|
||||||
INIT_LIST_HEAD(&vw->list);
|
__vt_event_dequeue(vw);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -177,10 +194,14 @@ int vt_waitactive(int n)
|
||||||
{
|
{
|
||||||
struct vt_event_wait vw;
|
struct vt_event_wait vw;
|
||||||
do {
|
do {
|
||||||
if (n == fg_console + 1)
|
|
||||||
break;
|
|
||||||
vw.event.event = VT_EVENT_SWITCH;
|
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)
|
if (vw.done == 0)
|
||||||
return -EINTR;
|
return -EINTR;
|
||||||
} while (vw.event.newev != n);
|
} while (vw.event.newev != n);
|
||||||
|
|
Loading…
Reference in a new issue