xen: fix hang on suspend.
[safe/jmp/linux-2.6] / drivers / xen / manage.c
1 /*
2  * Handle extern requests for shutdown, reboot and sysrq
3  */
4 #include <linux/kernel.h>
5 #include <linux/err.h>
6 #include <linux/reboot.h>
7 #include <linux/sysrq.h>
8 #include <linux/stop_machine.h>
9 #include <linux/freezer.h>
10
11 #include <xen/xenbus.h>
12 #include <xen/grant_table.h>
13 #include <xen/events.h>
14 #include <xen/hvc-console.h>
15 #include <xen/xen-ops.h>
16
17 #include <asm/xen/hypercall.h>
18 #include <asm/xen/page.h>
19
20 enum shutdown_state {
21         SHUTDOWN_INVALID = -1,
22         SHUTDOWN_POWEROFF = 0,
23         SHUTDOWN_SUSPEND = 2,
24         /* Code 3 is SHUTDOWN_CRASH, which we don't use because the domain can only
25            report a crash, not be instructed to crash!
26            HALT is the same as POWEROFF, as far as we're concerned.  The tools use
27            the distinction when we return the reason code to them.  */
28          SHUTDOWN_HALT = 4,
29 };
30
31 /* Ignore multiple shutdown requests. */
32 static enum shutdown_state shutting_down = SHUTDOWN_INVALID;
33
34 #ifdef CONFIG_PM_SLEEP
35 static int xen_suspend(void *data)
36 {
37         int *cancelled = data;
38         int err;
39
40         BUG_ON(!irqs_disabled());
41
42         err = sysdev_suspend(PMSG_SUSPEND);
43         if (err) {
44                 printk(KERN_ERR "xen_suspend: sysdev_suspend failed: %d\n",
45                         err);
46                 return err;
47         }
48
49         xen_mm_pin_all();
50         gnttab_suspend();
51         xen_pre_suspend();
52
53         /*
54          * This hypercall returns 1 if suspend was cancelled
55          * or the domain was merely checkpointed, and 0 if it
56          * is resuming in a new domain.
57          */
58         *cancelled = HYPERVISOR_suspend(virt_to_mfn(xen_start_info));
59
60         xen_post_suspend(*cancelled);
61         gnttab_resume();
62         xen_mm_unpin_all();
63
64         if (!*cancelled) {
65                 xen_irq_resume();
66                 xen_console_resume();
67                 xen_timer_resume();
68         }
69
70         sysdev_resume();
71
72         return 0;
73 }
74
75 static void do_suspend(void)
76 {
77         int err;
78         int cancelled = 1;
79
80         shutting_down = SHUTDOWN_SUSPEND;
81
82         err = stop_machine_create();
83         if (err) {
84                 printk(KERN_ERR "xen suspend: failed to setup stop_machine %d\n", err);
85                 goto out;
86         }
87
88 #ifdef CONFIG_PREEMPT
89         /* If the kernel is preemptible, we need to freeze all the processes
90            to prevent them from being in the middle of a pagetable update
91            during suspend. */
92         err = freeze_processes();
93         if (err) {
94                 printk(KERN_ERR "xen suspend: freeze failed %d\n", err);
95                 goto out_destroy_sm;
96         }
97 #endif
98
99         err = dpm_suspend_start(PMSG_SUSPEND);
100         if (err) {
101                 printk(KERN_ERR "xen suspend: dpm_suspend_start %d\n", err);
102                 goto out_thaw;
103         }
104
105         printk(KERN_DEBUG "suspending xenstore...\n");
106         xs_suspend();
107
108         err = dpm_suspend_noirq(PMSG_SUSPEND);
109         if (err) {
110                 printk(KERN_ERR "dpm_suspend_noirq failed: %d\n", err);
111                 goto out_resume;
112         }
113
114         err = stop_machine(xen_suspend, &cancelled, cpumask_of(0));
115
116         dpm_resume_noirq(PMSG_RESUME);
117
118         if (err) {
119                 printk(KERN_ERR "failed to start xen_suspend: %d\n", err);
120                 cancelled = 1;
121         }
122
123 out_resume:
124         if (!cancelled) {
125                 xen_arch_resume();
126                 xs_resume();
127         } else
128                 xs_suspend_cancel();
129
130         dpm_resume_end(PMSG_RESUME);
131
132         /* Make sure timer events get retriggered on all CPUs */
133         clock_was_set();
134
135 out_thaw:
136 #ifdef CONFIG_PREEMPT
137         thaw_processes();
138
139 out_destroy_sm:
140 #endif
141         stop_machine_destroy();
142
143 out:
144         shutting_down = SHUTDOWN_INVALID;
145 }
146 #endif  /* CONFIG_PM_SLEEP */
147
148 static void shutdown_handler(struct xenbus_watch *watch,
149                              const char **vec, unsigned int len)
150 {
151         char *str;
152         struct xenbus_transaction xbt;
153         int err;
154
155         if (shutting_down != SHUTDOWN_INVALID)
156                 return;
157
158  again:
159         err = xenbus_transaction_start(&xbt);
160         if (err)
161                 return;
162
163         str = (char *)xenbus_read(xbt, "control", "shutdown", NULL);
164         /* Ignore read errors and empty reads. */
165         if (XENBUS_IS_ERR_READ(str)) {
166                 xenbus_transaction_end(xbt, 1);
167                 return;
168         }
169
170         xenbus_write(xbt, "control", "shutdown", "");
171
172         err = xenbus_transaction_end(xbt, 0);
173         if (err == -EAGAIN) {
174                 kfree(str);
175                 goto again;
176         }
177
178         if (strcmp(str, "poweroff") == 0 ||
179             strcmp(str, "halt") == 0) {
180                 shutting_down = SHUTDOWN_POWEROFF;
181                 orderly_poweroff(false);
182         } else if (strcmp(str, "reboot") == 0) {
183                 shutting_down = SHUTDOWN_POWEROFF; /* ? */
184                 ctrl_alt_del();
185 #ifdef CONFIG_PM_SLEEP
186         } else if (strcmp(str, "suspend") == 0) {
187                 do_suspend();
188 #endif
189         } else {
190                 printk(KERN_INFO "Ignoring shutdown request: %s\n", str);
191                 shutting_down = SHUTDOWN_INVALID;
192         }
193
194         kfree(str);
195 }
196
197 static void sysrq_handler(struct xenbus_watch *watch, const char **vec,
198                           unsigned int len)
199 {
200         char sysrq_key = '\0';
201         struct xenbus_transaction xbt;
202         int err;
203
204  again:
205         err = xenbus_transaction_start(&xbt);
206         if (err)
207                 return;
208         if (!xenbus_scanf(xbt, "control", "sysrq", "%c", &sysrq_key)) {
209                 printk(KERN_ERR "Unable to read sysrq code in "
210                        "control/sysrq\n");
211                 xenbus_transaction_end(xbt, 1);
212                 return;
213         }
214
215         if (sysrq_key != '\0')
216                 xenbus_printf(xbt, "control", "sysrq", "%c", '\0');
217
218         err = xenbus_transaction_end(xbt, 0);
219         if (err == -EAGAIN)
220                 goto again;
221
222         if (sysrq_key != '\0')
223                 handle_sysrq(sysrq_key, NULL);
224 }
225
226 static struct xenbus_watch shutdown_watch = {
227         .node = "control/shutdown",
228         .callback = shutdown_handler
229 };
230
231 static struct xenbus_watch sysrq_watch = {
232         .node = "control/sysrq",
233         .callback = sysrq_handler
234 };
235
236 static int setup_shutdown_watcher(void)
237 {
238         int err;
239
240         err = register_xenbus_watch(&shutdown_watch);
241         if (err) {
242                 printk(KERN_ERR "Failed to set shutdown watcher\n");
243                 return err;
244         }
245
246         err = register_xenbus_watch(&sysrq_watch);
247         if (err) {
248                 printk(KERN_ERR "Failed to set sysrq watcher\n");
249                 return err;
250         }
251
252         return 0;
253 }
254
255 static int shutdown_event(struct notifier_block *notifier,
256                           unsigned long event,
257                           void *data)
258 {
259         setup_shutdown_watcher();
260         return NOTIFY_DONE;
261 }
262
263 static int __init setup_shutdown_event(void)
264 {
265         static struct notifier_block xenstore_notifier = {
266                 .notifier_call = shutdown_event
267         };
268         register_xenstore_notifier(&xenstore_notifier);
269
270         return 0;
271 }
272
273 subsys_initcall(setup_shutdown_event);