lguest: fix race in halt code
Rusty Russell [Sat, 13 Jun 2009 04:27:02 +0000 (22:27 -0600)]
When the Guest does the LHCALL_HALT hypercall, we go to sleep, expecting
that a timer or the Waker will wake_up_process() us.

But we do it in a stupid way, leaving a classic missing wakeup race.

So split maybe_do_interrupt() into interrupt_pending() and
try_deliver_interrupt(), and check maybe_do_interrupt() and the
"break_out" flag before calling schedule.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>

drivers/lguest/core.c
drivers/lguest/interrupts_and_traps.c
drivers/lguest/lg.h

index 4845fb3..8ca1def 100644 (file)
@@ -188,6 +188,8 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
 {
        /* We stop running once the Guest is dead. */
        while (!cpu->lg->dead) {
+               unsigned int irq;
+
                /* First we run any hypercalls the Guest wants done. */
                if (cpu->hcall)
                        do_hypercalls(cpu);
@@ -211,7 +213,9 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
                /* Check if there are any interrupts which can be delivered now:
                 * if so, this sets up the hander to be executed when we next
                 * run the Guest. */
-               maybe_do_interrupt(cpu);
+               irq = interrupt_pending(cpu);
+               if (irq < LGUEST_IRQS)
+                       try_deliver_interrupt(cpu, irq);
 
                /* All long-lived kernel loops need to check with this horrible
                 * thing called the freezer.  If the Host is trying to suspend,
@@ -227,7 +231,13 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user)
                 * clock timer or LHREQ_BREAK from the Waker will wake us. */
                if (cpu->halted) {
                        set_current_state(TASK_INTERRUPTIBLE);
-                       schedule();
+                       /* Just before we sleep, make sure nothing snuck in
+                        * which we should be doing. */
+                       if (interrupt_pending(cpu) < LGUEST_IRQS
+                           || cpu->break_out)
+                               set_current_state(TASK_RUNNING);
+                       else
+                               schedule();
                        continue;
                }
 
index 9ea26ad..a8c966f 100644 (file)
@@ -128,30 +128,38 @@ static void set_guest_interrupt(struct lg_cpu *cpu, u32 lo, u32 hi,
 /*H:205
  * Virtual Interrupts.
  *
- * maybe_do_interrupt() gets called before every entry to the Guest, to see if
- * we should divert the Guest to running an interrupt handler. */
-void maybe_do_interrupt(struct lg_cpu *cpu)
+ * interrupt_pending() returns the first pending interrupt which isn't blocked
+ * by the Guest.  It is called before every entry to the Guest, and just before
+ * we go to sleep when the Guest has halted itself. */
+unsigned int interrupt_pending(struct lg_cpu *cpu)
 {
        unsigned int irq;
        DECLARE_BITMAP(blk, LGUEST_IRQS);
-       struct desc_struct *idt;
 
        /* If the Guest hasn't even initialized yet, we can do nothing. */
        if (!cpu->lg->lguest_data)
-               return;
+               return LGUEST_IRQS;
 
        /* Take our "irqs_pending" array and remove any interrupts the Guest
         * wants blocked: the result ends up in "blk". */
        if (copy_from_user(&blk, cpu->lg->lguest_data->blocked_interrupts,
                           sizeof(blk)))
-               return;
+               return LGUEST_IRQS;
        bitmap_andnot(blk, cpu->irqs_pending, blk, LGUEST_IRQS);
 
        /* Find the first interrupt. */
        irq = find_first_bit(blk, LGUEST_IRQS);
-       /* None?  Nothing to do */
-       if (irq >= LGUEST_IRQS)
-               return;
+
+       return irq;
+}
+
+/* This actually diverts the Guest to running an interrupt handler, once an
+ * interrupt has been identified by interrupt_pending(). */
+void try_deliver_interrupt(struct lg_cpu *cpu, unsigned int irq)
+{
+       struct desc_struct *idt;
+
+       BUG_ON(irq >= LGUEST_IRQS);
 
        /* They may be in the middle of an iret, where they asked us never to
         * deliver interrupts. */
index af92a17..6743cf1 100644 (file)
@@ -139,7 +139,8 @@ int run_guest(struct lg_cpu *cpu, unsigned long __user *user);
 #define pgd_pfn(x)     (pgd_val(x) >> PAGE_SHIFT)
 
 /* interrupts_and_traps.c: */
-void maybe_do_interrupt(struct lg_cpu *cpu);
+unsigned int interrupt_pending(struct lg_cpu *cpu);
+void try_deliver_interrupt(struct lg_cpu *cpu, unsigned int irq);
 bool deliver_trap(struct lg_cpu *cpu, unsigned int num);
 void load_guest_idt_entry(struct lg_cpu *cpu, unsigned int i,
                          u32 low, u32 hi);