This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / arch / ppc / kernel / machine_kexec.c
1 /*
2  * machine_kexec.c - handle transition of Linux booting another kernel
3  * Copyright (C) 2002-2003 Eric Biederman  <ebiederm@xmission.com>
4  *
5  * GAMECUBE/PPC32 port Copyright (C) 2004 Albert Herranz
6  *
7  * This source code is licensed under the GNU General Public License,
8  * Version 2.  See the file COPYING for more details.
9  */
10
11 #include <linux/mm.h>
12 #include <linux/kexec.h>
13 #include <linux/delay.h>
14 #include <asm/pgtable.h>
15 #include <asm/pgalloc.h>
16 #include <asm/mmu_context.h>
17 #include <asm/io.h>
18 #include <asm/hw_irq.h>
19 #include <asm/cacheflush.h>
20
21 typedef void (*relocate_new_kernel_t)(
22         unsigned long indirection_page, unsigned long reboot_code_buffer,
23         unsigned long start_address);
24
25 const extern unsigned char relocate_new_kernel[];
26 const extern unsigned int relocate_new_kernel_size;
27 extern void use_mm(struct mm_struct *mm);
28
29 static int identity_map_pages(struct page *pages, int order)
30 {
31         struct mm_struct *mm;
32         struct vm_area_struct *vma;
33         int error;
34
35         mm = &init_mm;
36         vma = NULL;
37
38         down_write(&mm->mmap_sem);
39         error = -ENOMEM;
40         vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
41         if (!vma) {
42                 goto out;
43         }
44
45         memset(vma, 0, sizeof(*vma));
46         vma->vm_mm = mm;
47         vma->vm_start = page_to_pfn(pages) << PAGE_SHIFT;
48         vma->vm_end = vma->vm_start + (1 << (order + PAGE_SHIFT));
49         vma->vm_ops = NULL;
50         vma->vm_flags = VM_SHARED \
51                 | VM_READ | VM_WRITE | VM_EXEC \
52                 | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC \
53                 | VM_DONTCOPY | VM_RESERVED;
54         vma->vm_page_prot = protection_map[vma->vm_flags & 0xf];
55         vma->vm_file = NULL;
56         vma->vm_private_data = NULL;
57         insert_vm_struct(mm, vma);
58
59         error = remap_page_range(vma, vma->vm_start, vma->vm_start,
60                 vma->vm_end - vma->vm_start, vma->vm_page_prot);
61         if (error) {
62                 goto out;
63         }
64
65         error = 0;
66  out:
67         if (error && vma) {
68                 kmem_cache_free(vm_area_cachep, vma);
69                 vma = NULL;
70         }
71         up_write(&mm->mmap_sem);
72
73         return error;
74 }
75
76 /*
77  * Do what every setup is needed on image and the
78  * reboot code buffer to allow us to avoid allocations
79  * later.
80  */
81 int machine_kexec_prepare(struct kimage *image)
82 {
83         unsigned int order;
84         order = get_order(KEXEC_CONTROL_CODE_SIZE);
85         return identity_map_pages(image->control_code_page, order);
86 }
87
88 void machine_kexec_cleanup(struct kimage *image)
89 {
90         unsigned int order;
91         order = get_order(KEXEC_CONTROL_CODE_SIZE);
92         do_munmap(&init_mm,
93                 page_to_pfn(image->control_code_page) << PAGE_SHIFT,
94                 1 << (order + PAGE_SHIFT));
95 }
96
97 void machine_shutdown(void)
98 {
99 }
100
101 /*
102  * Do not allocate memory (or fail in any way) in machine_kexec().
103  * We are past the point of no return, committed to rebooting now.
104  */
105 void machine_kexec(struct kimage *image)
106 {
107         unsigned long indirection_page;
108         unsigned long reboot_code_buffer;
109         relocate_new_kernel_t rnk;
110
111         /* switch to an mm where the reboot_code_buffer is identity mapped */
112         use_mm(&init_mm);
113
114         /* Interrupts aren't acceptable while we reboot */
115         local_irq_disable();
116
117         reboot_code_buffer = page_to_pfn(image->control_code_page) <<PAGE_SHIFT;
118         indirection_page = image->head & PAGE_MASK;
119
120         /* copy it out */
121         memcpy((void *)reboot_code_buffer,
122                 relocate_new_kernel, relocate_new_kernel_size);
123
124         flush_icache_range(reboot_code_buffer,
125                 reboot_code_buffer + KEXEC_CONTROL_CODE_SIZE);
126         printk(KERN_INFO "Bye!\n");
127
128         /* now call it */
129         rnk = (relocate_new_kernel_t) reboot_code_buffer;
130         (*rnk)(indirection_page, reboot_code_buffer, image->start);
131 }
132