* machine_kexec.c - handle transition of Linux booting another kernel
* Copyright (C) 2002-2003 Eric Biederman <ebiederm@xmission.com>
*
- * GAMECUBE/PPC32 port Copyright (C) 2004 Albert Herranz
+ * GameCube/ppc32 port Copyright (C) 2004 Albert Herranz
*
* This source code is licensed under the GNU General Public License,
* Version 2. See the file COPYING for more details.
#include <linux/mm.h>
#include <linux/kexec.h>
#include <linux/delay.h>
+#include <linux/reboot.h>
#include <asm/pgtable.h>
#include <asm/pgalloc.h>
#include <asm/mmu_context.h>
#include <asm/io.h>
#include <asm/hw_irq.h>
#include <asm/cacheflush.h>
+#include <asm/machdep.h>
-typedef void (*relocate_new_kernel_t)(
- unsigned long indirection_page, unsigned long reboot_code_buffer,
- unsigned long start_address);
+typedef NORET_TYPE void (*relocate_new_kernel_t)(
+ unsigned long indirection_page,
+ unsigned long reboot_code_buffer,
+ unsigned long start_address) ATTRIB_NORET;
-const extern unsigned char relocate_new_kernel[];
-const extern unsigned int relocate_new_kernel_size;
-extern void use_mm(struct mm_struct *mm);
+extern const unsigned char relocate_new_kernel[];
+extern const unsigned int relocate_new_kernel_size;
-static int identity_map_pages(struct page *pages, int order)
+void machine_shutdown(void)
{
- struct mm_struct *mm;
- struct vm_area_struct *vma;
- int error;
-
- mm = &init_mm;
- vma = NULL;
-
- down_write(&mm->mmap_sem);
- error = -ENOMEM;
- vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
- if (!vma) {
- goto out;
- }
-
- memset(vma, 0, sizeof(*vma));
- vma->vm_mm = mm;
- vma->vm_start = page_to_pfn(pages) << PAGE_SHIFT;
- vma->vm_end = vma->vm_start + (1 << (order + PAGE_SHIFT));
- vma->vm_ops = NULL;
- vma->vm_flags = VM_SHARED \
- | VM_READ | VM_WRITE | VM_EXEC \
- | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC \
- | VM_DONTCOPY | VM_RESERVED;
- vma->vm_page_prot = protection_map[vma->vm_flags & 0xf];
- vma->vm_file = NULL;
- vma->vm_private_data = NULL;
- insert_vm_struct(mm, vma);
-
- error = remap_page_range(vma, vma->vm_start, vma->vm_start,
- vma->vm_end - vma->vm_start, vma->vm_page_prot);
- if (error) {
- goto out;
- }
-
- error = 0;
- out:
- if (error && vma) {
- kmem_cache_free(vm_area_cachep, vma);
- vma = NULL;
- }
- up_write(&mm->mmap_sem);
+ if (ppc_md.machine_shutdown)
+ ppc_md.machine_shutdown();
+}
- return error;
+void machine_crash_shutdown(struct pt_regs *regs)
+{
+ if (ppc_md.machine_crash_shutdown)
+ ppc_md.machine_crash_shutdown();
}
/*
*/
int machine_kexec_prepare(struct kimage *image)
{
- unsigned int order;
- order = get_order(KEXEC_CONTROL_CODE_SIZE);
- return identity_map_pages(image->control_code_page, order);
+ if (ppc_md.machine_kexec_prepare)
+ return ppc_md.machine_kexec_prepare(image);
+ /*
+ * Fail if platform doesn't provide its own machine_kexec_prepare
+ * implementation.
+ */
+ return -ENOSYS;
}
void machine_kexec_cleanup(struct kimage *image)
{
- unsigned int order;
- order = get_order(KEXEC_CONTROL_CODE_SIZE);
- do_munmap(&init_mm,
- page_to_pfn(image->control_code_page) << PAGE_SHIFT,
- 1 << (order + PAGE_SHIFT));
+ if (ppc_md.machine_kexec_cleanup)
+ ppc_md.machine_kexec_cleanup(image);
}
-void machine_shutdown(void)
+/*
+ * Do not allocate memory (or fail in any way) in machine_kexec().
+ * We are past the point of no return, committed to rebooting now.
+ */
+NORET_TYPE void machine_kexec(struct kimage *image)
{
+ if (ppc_md.machine_kexec)
+ ppc_md.machine_kexec(image);
+ else {
+ /*
+ * Fall back to normal restart if platform doesn't provide
+ * its own kexec function, and user insist to kexec...
+ */
+ machine_restart(NULL);
+ }
+ for(;;);
}
/*
- * Do not allocate memory (or fail in any way) in machine_kexec().
- * We are past the point of no return, committed to rebooting now.
+ * This is a generic machine_kexec function suitable at least for
+ * non-OpenFirmware embedded platforms.
+ * It merely copies the image relocation code to the control page and
+ * jumps to it.
+ * A platform specific function may just call this one.
*/
-void machine_kexec(struct kimage *image)
+void machine_kexec_simple(struct kimage *image)
{
- unsigned long indirection_page;
- unsigned long reboot_code_buffer;
+ unsigned long page_list;
+ unsigned long reboot_code_buffer, reboot_code_buffer_phys;
relocate_new_kernel_t rnk;
- /* switch to an mm where the reboot_code_buffer is identity mapped */
- use_mm(&init_mm);
-
/* Interrupts aren't acceptable while we reboot */
local_irq_disable();
- reboot_code_buffer = page_to_pfn(image->control_code_page) <<PAGE_SHIFT;
- indirection_page = image->head & PAGE_MASK;
+ page_list = image->head;
+
+ /* we need both effective and real address here */
+ reboot_code_buffer =
+ (unsigned long)page_address(image->control_code_page);
+ reboot_code_buffer_phys = virt_to_phys((void *)reboot_code_buffer);
- /* copy it out */
- memcpy((void *)reboot_code_buffer,
- relocate_new_kernel, relocate_new_kernel_size);
+ /* copy our kernel relocation code to the control code page */
+ memcpy((void *)reboot_code_buffer, relocate_new_kernel,
+ relocate_new_kernel_size);
flush_icache_range(reboot_code_buffer,
- reboot_code_buffer + KEXEC_CONTROL_CODE_SIZE);
+ reboot_code_buffer + KEXEC_CONTROL_CODE_SIZE);
printk(KERN_INFO "Bye!\n");
/* now call it */
rnk = (relocate_new_kernel_t) reboot_code_buffer;
- (*rnk)(indirection_page, reboot_code_buffer, image->start);
+ (*rnk)(page_list, reboot_code_buffer_phys, image->start);
}