ia64/xen-unstable

view linux-2.6-xen-sparse/arch/xen/kernel/reboot.c @ 6422:e24fd7012ffb

merge?
author cl349@firebug.cl.cam.ac.uk
date Thu Aug 25 10:09:39 2005 +0000 (2005-08-25)
parents 2f20c2fce2c5 522bc50588ed
children 4abd299ef2f6
line source
1 #define __KERNEL_SYSCALLS__
2 #include <linux/version.h>
3 #include <linux/kernel.h>
4 #include <linux/mm.h>
5 #include <linux/unistd.h>
6 #include <linux/module.h>
7 #include <linux/reboot.h>
8 #include <linux/sysrq.h>
9 #include <linux/stringify.h>
10 #include <asm/irq.h>
11 #include <asm/mmu_context.h>
12 #include <asm-xen/evtchn.h>
13 #include <asm-xen/hypervisor.h>
14 #include <asm-xen/xen-public/dom0_ops.h>
15 #include <asm-xen/linux-public/suspend.h>
16 #include <asm-xen/queues.h>
17 #include <asm-xen/xenbus.h>
18 #include <asm-xen/ctrl_if.h>
19 #include <linux/cpu.h>
20 #include <linux/kthread.h>
22 #define SHUTDOWN_INVALID -1
23 #define SHUTDOWN_POWEROFF 0
24 #define SHUTDOWN_REBOOT 1
25 #define SHUTDOWN_SUSPEND 2
27 void machine_restart(char * __unused)
28 {
29 /* We really want to get pending console data out before we die. */
30 extern void xencons_force_flush(void);
31 xencons_force_flush();
32 HYPERVISOR_reboot();
33 }
35 void machine_halt(void)
36 {
37 machine_power_off();
38 }
40 void machine_power_off(void)
41 {
42 /* We really want to get pending console data out before we die. */
43 extern void xencons_force_flush(void);
44 xencons_force_flush();
45 HYPERVISOR_shutdown();
46 }
48 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
49 int reboot_thru_bios = 0; /* for dmi_scan.c */
50 EXPORT_SYMBOL(machine_restart);
51 EXPORT_SYMBOL(machine_halt);
52 EXPORT_SYMBOL(machine_power_off);
53 #endif
56 /******************************************************************************
57 * Stop/pickle callback handling.
58 */
60 /* Ignore multiple shutdown requests. */
61 static int shutting_down = SHUTDOWN_INVALID;
63 #ifndef CONFIG_HOTPLUG_CPU
64 #define cpu_down(x) (-EOPNOTSUPP)
65 #define cpu_up(x) (-EOPNOTSUPP)
66 #endif
68 static void save_vcpu_context(int vcpu, vcpu_guest_context_t *ctxt)
69 {
70 int r;
71 int gdt_pages;
72 r = HYPERVISOR_vcpu_pickle(vcpu, ctxt);
73 if (r != 0)
74 panic("pickling vcpu %d -> %d!\n", vcpu, r);
76 /* Translate from machine to physical addresses where necessary,
77 so that they can be translated to our new machine address space
78 after resume. libxc is responsible for doing this to vcpu0,
79 but we do it to the others. */
80 gdt_pages = (ctxt->gdt_ents + 511) / 512;
81 ctxt->ctrlreg[3] = machine_to_phys(ctxt->ctrlreg[3]);
82 for (r = 0; r < gdt_pages; r++)
83 ctxt->gdt_frames[r] = mfn_to_pfn(ctxt->gdt_frames[r]);
84 }
86 void _restore_vcpu(int cpu);
88 atomic_t vcpus_rebooting;
90 static int restore_vcpu_context(int vcpu, vcpu_guest_context_t *ctxt)
91 {
92 int r;
93 int gdt_pages = (ctxt->gdt_ents + 511) / 512;
95 /* This is kind of a hack, and implicitly relies on the fact that
96 the vcpu stops in a place where all of the call clobbered
97 registers are already dead. */
98 ctxt->user_regs.esp -= 4;
99 ((unsigned long *)ctxt->user_regs.esp)[0] = ctxt->user_regs.eip;
100 ctxt->user_regs.eip = (unsigned long)_restore_vcpu;
102 /* De-canonicalise. libxc handles this for vcpu 0, but we need
103 to do it for the other vcpus. */
104 ctxt->ctrlreg[3] = phys_to_machine(ctxt->ctrlreg[3]);
105 for (r = 0; r < gdt_pages; r++)
106 ctxt->gdt_frames[r] = pfn_to_mfn(ctxt->gdt_frames[r]);
108 atomic_set(&vcpus_rebooting, 1);
109 r = HYPERVISOR_boot_vcpu(vcpu, ctxt);
110 if (r != 0) {
111 printk(KERN_EMERG "Failed to reboot vcpu %d (%d)\n", vcpu, r);
112 return -1;
113 }
115 /* Make sure we wait for the new vcpu to come up before trying to do
116 anything with it or starting the next one. */
117 while (atomic_read(&vcpus_rebooting))
118 barrier();
120 return 0;
121 }
123 static int __do_suspend(void *ignore)
124 {
125 int i, j;
126 suspend_record_t *suspend_record;
127 static vcpu_guest_context_t suspended_cpu_records[NR_CPUS];
129 /* Hmmm... a cleaner interface to suspend/resume blkdevs would be nice. */
130 /* XXX SMH: yes it would :-( */
132 #ifdef CONFIG_XEN_USB_FRONTEND
133 extern void usbif_resume();
134 #else
135 #define usbif_resume() do{}while(0)
136 #endif
138 extern int gnttab_suspend(void);
139 extern int gnttab_resume(void);
141 #ifdef CONFIG_SMP
142 extern void smp_suspend(void);
143 extern void smp_resume(void);
144 #endif
145 extern void time_suspend(void);
146 extern void time_resume(void);
147 extern unsigned long max_pfn;
148 extern unsigned int *pfn_to_mfn_frame_list;
150 cpumask_t prev_online_cpus, prev_present_cpus;
151 int err = 0;
153 BUG_ON(smp_processor_id() != 0);
154 BUG_ON(in_interrupt());
156 #if defined(CONFIG_SMP) && !defined(CONFIG_HOTPLUG_CPU)
157 if (num_online_cpus() > 1) {
158 printk(KERN_WARNING "Can't suspend SMP guests without CONFIG_HOTPLUG_CPU\n");
159 return -EOPNOTSUPP;
160 }
161 #endif
163 suspend_record = (suspend_record_t *)__get_free_page(GFP_KERNEL);
164 if ( suspend_record == NULL )
165 goto out;
167 /* Take all of the other cpus offline. We need to be careful not
168 to get preempted between the final test for num_online_cpus()
169 == 1 and disabling interrupts, since otherwise userspace could
170 bring another cpu online, and then we'd be stuffed. At the
171 same time, cpu_down can reschedule, so we need to enable
172 preemption while doing that. This kind of sucks, but should be
173 correct. */
174 /* (We don't need to worry about other cpus bringing stuff up,
175 since by the time num_online_cpus() == 1, there aren't any
176 other cpus) */
177 cpus_clear(prev_online_cpus);
178 preempt_disable();
179 while (num_online_cpus() > 1) {
180 preempt_enable();
181 for_each_online_cpu(i) {
182 if (i == 0)
183 continue;
184 err = cpu_down(i);
185 if (err != 0) {
186 printk(KERN_CRIT "Failed to take all CPUs down: %d.\n", err);
187 goto out_reenable_cpus;
188 }
189 cpu_set(i, prev_online_cpus);
190 }
191 preempt_disable();
192 }
194 suspend_record->nr_pfns = max_pfn; /* final number of pfns */
196 __cli();
198 preempt_enable();
200 cpus_clear(prev_present_cpus);
201 for_each_present_cpu(i) {
202 if (i == 0)
203 continue;
204 save_vcpu_context(i, &suspended_cpu_records[i]);
205 cpu_set(i, prev_present_cpus);
206 }
208 #ifdef __i386__
209 mm_pin_all();
210 kmem_cache_shrink(pgd_cache);
211 #endif
213 time_suspend();
215 #ifdef CONFIG_SMP
216 smp_suspend();
217 #endif
219 xenbus_suspend();
221 ctrl_if_suspend();
223 irq_suspend();
225 gnttab_suspend();
227 HYPERVISOR_shared_info = (shared_info_t *)empty_zero_page;
228 clear_fixmap(FIX_SHARED_INFO);
230 memcpy(&suspend_record->resume_info, &xen_start_info,
231 sizeof(xen_start_info));
233 /* We'll stop somewhere inside this hypercall. When it returns,
234 we'll start resuming after the restore. */
235 HYPERVISOR_suspend(virt_to_mfn(suspend_record));
237 shutting_down = SHUTDOWN_INVALID;
239 memcpy(&xen_start_info, &suspend_record->resume_info,
240 sizeof(xen_start_info));
242 set_fixmap(FIX_SHARED_INFO, xen_start_info.shared_info);
244 HYPERVISOR_shared_info = (shared_info_t *)fix_to_virt(FIX_SHARED_INFO);
246 memset(empty_zero_page, 0, PAGE_SIZE);
248 for ( i=0, j=0; i < max_pfn; i+=(PAGE_SIZE/sizeof(unsigned long)), j++ )
249 {
250 pfn_to_mfn_frame_list[j] =
251 virt_to_mfn(&phys_to_machine_mapping[i]);
252 }
253 HYPERVISOR_shared_info->arch.pfn_to_mfn_frame_list =
254 virt_to_mfn(pfn_to_mfn_frame_list);
256 gnttab_resume();
258 irq_resume();
260 ctrl_if_resume();
262 xenbus_resume();
264 #ifdef CONFIG_SMP
265 smp_resume();
266 #endif
268 time_resume();
270 usbif_resume();
272 for_each_cpu_mask(i, prev_present_cpus) {
273 restore_vcpu_context(i, &suspended_cpu_records[i]);
274 }
276 __sti();
278 out_reenable_cpus:
279 for_each_cpu_mask(i, prev_online_cpus) {
280 j = cpu_up(i);
281 if (j != 0) {
282 printk(KERN_CRIT "Failed to bring cpu %d back up (%d).\n",
283 i, j);
284 err = j;
285 }
286 }
288 out:
289 if ( suspend_record != NULL )
290 free_page((unsigned long)suspend_record);
291 return err;
292 }
294 static int shutdown_process(void *__unused)
295 {
296 static char *envp[] = { "HOME=/", "TERM=linux",
297 "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
298 static char *restart_argv[] = { "/sbin/reboot", NULL };
299 static char *poweroff_argv[] = { "/sbin/poweroff", NULL };
301 extern asmlinkage long sys_reboot(int magic1, int magic2,
302 unsigned int cmd, void *arg);
304 daemonize(
305 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
306 "shutdown"
307 #endif
308 );
310 switch ( shutting_down )
311 {
312 case SHUTDOWN_POWEROFF:
313 if ( execve("/sbin/poweroff", poweroff_argv, envp) < 0 )
314 {
315 sys_reboot(LINUX_REBOOT_MAGIC1,
316 LINUX_REBOOT_MAGIC2,
317 LINUX_REBOOT_CMD_POWER_OFF,
318 NULL);
319 }
320 break;
322 case SHUTDOWN_REBOOT:
323 if ( execve("/sbin/reboot", restart_argv, envp) < 0 )
324 {
325 sys_reboot(LINUX_REBOOT_MAGIC1,
326 LINUX_REBOOT_MAGIC2,
327 LINUX_REBOOT_CMD_RESTART,
328 NULL);
329 }
330 break;
331 }
333 shutting_down = SHUTDOWN_INVALID; /* could try again */
335 return 0;
336 }
338 static struct task_struct *kthread_create_on_cpu(int (*f)(void *arg),
339 void *arg,
340 const char *name,
341 int cpu)
342 {
343 struct task_struct *p;
344 p = kthread_create(f, arg, name);
345 kthread_bind(p, cpu);
346 wake_up_process(p);
347 return p;
348 }
350 static void __shutdown_handler(void *unused)
351 {
352 int err;
354 if ( shutting_down != SHUTDOWN_SUSPEND )
355 {
356 err = kernel_thread(shutdown_process, NULL, CLONE_FS | CLONE_FILES);
357 if ( err < 0 )
358 printk(KERN_ALERT "Error creating shutdown process!\n");
359 }
360 else
361 {
362 kthread_create_on_cpu(__do_suspend, NULL, "suspender", 0);
363 }
364 }
366 static void shutdown_handler(struct xenbus_watch *watch, const char *node)
367 {
368 static DECLARE_WORK(shutdown_work, __shutdown_handler, NULL);
370 char *str;
372 str = (char *)xenbus_read("control", "shutdown", NULL);
373 /* Ignore read errors. */
374 if (IS_ERR(str))
375 return;
376 if (strlen(str) == 0) {
377 kfree(str);
378 return;
379 }
381 xenbus_write("control", "shutdown", "", O_CREAT);
383 if (strcmp(str, "poweroff") == 0)
384 shutting_down = SHUTDOWN_POWEROFF;
385 else if (strcmp(str, "reboot") == 0)
386 shutting_down = SHUTDOWN_REBOOT;
387 else if (strcmp(str, "suspend") == 0)
388 shutting_down = SHUTDOWN_SUSPEND;
389 else {
390 printk("Ignoring shutdown request: %s\n", str);
391 shutting_down = SHUTDOWN_INVALID;
392 }
394 kfree(str);
396 if (shutting_down != SHUTDOWN_INVALID)
397 schedule_work(&shutdown_work);
398 }
400 #ifdef CONFIG_MAGIC_SYSRQ
401 static void sysrq_handler(struct xenbus_watch *watch, const char *node)
402 {
403 char sysrq_key = '\0';
405 if (!xenbus_scanf("control", "sysrq", "%c", &sysrq_key)) {
406 printk(KERN_ERR "Unable to read sysrq code in control/sysrq\n");
407 return;
408 }
410 xenbus_printf("control", "sysrq", "%c", '\0');
412 if (sysrq_key != '\0') {
414 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0)
415 handle_sysrq(sysrq_key, NULL, NULL);
416 #else
417 handle_sysrq(sysrq_key, NULL, NULL, NULL);
418 #endif
419 }
420 }
421 #endif
423 static struct xenbus_watch shutdown_watch = {
424 .node = "control/shutdown",
425 .callback = shutdown_handler
426 };
428 #ifdef CONFIG_MAGIC_SYSRQ
429 static struct xenbus_watch sysrq_watch = {
430 .node ="control/sysrq",
431 .callback = sysrq_handler
432 };
433 #endif
435 static struct notifier_block xenstore_notifier;
437 /* Setup our watcher
438 NB: Assumes xenbus_lock is held!
439 */
440 static int setup_shutdown_watcher(struct notifier_block *notifier,
441 unsigned long event,
442 void *data)
443 {
444 int err1 = 0;
445 #ifdef CONFIG_MAGIC_SYSRQ
446 int err2 = 0;
447 #endif
449 BUG_ON(down_trylock(&xenbus_lock) == 0);
451 err1 = register_xenbus_watch(&shutdown_watch);
452 #ifdef CONFIG_MAGIC_SYSRQ
453 err2 = register_xenbus_watch(&sysrq_watch);
454 #endif
456 if (err1) {
457 printk(KERN_ERR "Failed to set shutdown watcher\n");
458 }
460 #ifdef CONFIG_MAGIC_SYSRQ
461 if (err2) {
462 printk(KERN_ERR "Failed to set sysrq watcher\n");
463 }
464 #endif
466 return NOTIFY_DONE;
467 }
469 static int __init setup_shutdown_event(void)
470 {
472 xenstore_notifier.notifier_call = setup_shutdown_watcher;
474 register_xenstore_notifier(&xenstore_notifier);
476 return 0;
477 }
479 subsys_initcall(setup_shutdown_event);