]> xenbits.xensource.com Git - xenclient/kernel.git/commitdiff
xen suspend: Fix suspend-via-evtchn reentrancy.
authorKeir Fraser <keir.fraser@citrix.com>
Thu, 31 Jul 2008 14:33:54 +0000 (15:33 +0100)
committerKeir Fraser <keir.fraser@citrix.com>
Thu, 31 Jul 2008 14:33:54 +0000 (15:33 +0100)
Signed-off-by: Keir Fraser <keir.fraser@citrix.com>
drivers/xen/core/machine_reboot.c
drivers/xen/core/reboot.c

index 1c4f9096ca817b344ca47c0a8ef793b73768f142..5a13599c29b269df073e8251efec694fc43624ed 100644 (file)
@@ -26,8 +26,6 @@
 void (*pm_power_off)(void);
 EXPORT_SYMBOL(pm_power_off);
 
-int setup_suspend_evtchn(void);
-
 void machine_emergency_restart(void)
 {
        /* We really want to get pending console data out before we die. */
@@ -133,7 +131,7 @@ static void post_suspend(int suspend_cancelled)
 
 struct suspend {
        int fast_suspend;
-       void (*resume_notifier)(void);
+       void (*resume_notifier)(int);
 };
 
 static int take_machine_down(void *_suspend)
@@ -175,7 +173,7 @@ static int take_machine_down(void *_suspend)
         */
        suspend_cancelled = HYPERVISOR_suspend(virt_to_mfn(xen_start_info));
 
-       suspend->resume_notifier();
+       suspend->resume_notifier(suspend_cancelled);
        post_suspend(suspend_cancelled);
        gnttab_resume();
        if (!suspend_cancelled) {
@@ -204,7 +202,7 @@ static int take_machine_down(void *_suspend)
        return suspend_cancelled;
 }
 
-int __xen_suspend(int fast_suspend, void (*resume_notifier)(void))
+int __xen_suspend(int fast_suspend, void (*resume_notifier)(int))
 {
        int err, suspend_cancelled;
        struct suspend suspend;
@@ -243,7 +241,6 @@ int __xen_suspend(int fast_suspend, void (*resume_notifier)(void))
        if (!suspend_cancelled) {
                xencons_resume();
                xenbus_resume();
-               setup_suspend_evtchn();
        } else {
                xenbus_suspend_cancel();
        }
index 1c0078eb572608ff38e9715cdbaac0442407ed4e..20a0e9e5fd6fcc4c75f34285af3f13c9224db41a 100644 (file)
@@ -27,13 +27,18 @@ MODULE_LICENSE("Dual BSD/GPL");
 /* Ignore multiple shutdown requests. */
 static int shutting_down = SHUTDOWN_INVALID;
 
+/* Was last suspend request cancelled? */
+static int suspend_cancelled;
+
 /* Can we leave APs online when we suspend? */
 static int fast_suspend;
 
 static void __shutdown_handler(void *unused);
 static DECLARE_WORK(shutdown_work, __shutdown_handler, NULL);
 
-int __xen_suspend(int fast_suspend, void (*resume_notifier)(void));
+static int setup_suspend_evtchn(void);
+
+int __xen_suspend(int fast_suspend, void (*resume_notifier)(int));
 
 static int shutdown_process(void *__unused)
 {
@@ -62,10 +67,11 @@ static int shutdown_process(void *__unused)
        return 0;
 }
 
-static void xen_resume_notifier(void)
+static void xen_resume_notifier(int _suspend_cancelled)
 {
        int old_state = xchg(&shutting_down, SHUTDOWN_RESUMING);
        BUG_ON(old_state != SHUTDOWN_SUSPEND);
+       suspend_cancelled = _suspend_cancelled;
 }
 
 static int xen_suspend(void *__unused)
@@ -85,6 +91,8 @@ static int xen_suspend(void *__unused)
                        printk(KERN_ERR "Xen suspend failed (%d)\n", err);
                        goto fail;
                }
+               if (!suspend_cancelled)
+                       setup_suspend_evtchn();
                old_state = cmpxchg(
                        &shutting_down, SHUTDOWN_RESUMING, SHUTDOWN_INVALID);
        } while (old_state == SHUTDOWN_SUSPEND);
@@ -108,6 +116,31 @@ static int xen_suspend(void *__unused)
        return 0;
 }
 
+static void switch_shutdown_state(int new_state)
+{
+       int prev_state, old_state = SHUTDOWN_INVALID;
+
+       /* We only drive shutdown_state into an active state. */
+       if (new_state == SHUTDOWN_INVALID)
+               return;
+
+       do {
+               /* We drop this transition if already in an active state. */
+               if ((old_state != SHUTDOWN_INVALID) &&
+                   (old_state != SHUTDOWN_RESUMING))
+                       return;
+               /* Attempt to transition. */
+               prev_state = old_state;
+               old_state = cmpxchg(&shutting_down, old_state, new_state);
+       } while (old_state != prev_state);
+
+       /* Either we kick off the work, or we leave it to xen_suspend(). */
+       if (old_state == SHUTDOWN_INVALID)
+               schedule_work(&shutdown_work);
+       else
+               BUG_ON(old_state != SHUTDOWN_RESUMING);
+}
+
 static void __shutdown_handler(void *unused)
 {
        int err;
@@ -129,7 +162,7 @@ static void shutdown_handler(struct xenbus_watch *watch,
        extern void ctrl_alt_del(void);
        char *str;
        struct xenbus_transaction xbt;
-       int err, old_state, new_state = SHUTDOWN_INVALID;
+       int err, new_state = SHUTDOWN_INVALID;
 
        if ((shutting_down != SHUTDOWN_INVALID) &&
            (shutting_down != SHUTDOWN_RESUMING))
@@ -166,13 +199,7 @@ static void shutdown_handler(struct xenbus_watch *watch,
        else
                printk("Ignoring shutdown request: %s\n", str);
 
-       if (new_state != SHUTDOWN_INVALID) {
-               old_state = xchg(&shutting_down, new_state);
-               if (old_state == SHUTDOWN_INVALID)
-                       schedule_work(&shutdown_work);
-               else
-                       BUG_ON(old_state != SHUTDOWN_RESUMING);
-       }
+       switch_shutdown_state(new_state);
 
        kfree(str);
 }
@@ -220,26 +247,24 @@ static struct xenbus_watch sysrq_watch = {
 
 static irqreturn_t suspend_int(int irq, void* dev_id, struct pt_regs *ptregs)
 {
-       shutting_down = SHUTDOWN_SUSPEND;
-       schedule_work(&shutdown_work);
-
+       switch_shutdown_state(SHUTDOWN_SUSPEND);
        return IRQ_HANDLED;
 }
 
-int setup_suspend_evtchn(void)
+static int setup_suspend_evtchn(void)
 {
-       static int irq = -1;
+       static int irq;
        int port;
-       char portstr[5]; /* 1024 max */
+       char portstr[16];
 
        if (irq > 0)
                unbind_from_irqhandler(irq, NULL);
 
        irq = bind_listening_port_to_irqhandler(0, suspend_int, 0, "suspend",
                                                NULL);
-       if (irq <= 0) {
+       if (irq <= 0)
                return -1;
-       }
+
        port = irq_to_evtchn_port(irq);
        printk(KERN_INFO "suspend: event channel %d\n", port);
        sprintf(portstr, "%d", port);