]> Pileus Git - ~andy/linux/blobdiff - arch/mips/cavium-octeon/setup.c
MIPS: Octeon: Add kexec and kdump support
[~andy/linux] / arch / mips / cavium-octeon / setup.c
index 04dd8ff0e0d8979b639c60b932be4eabee951903..7c2b7aab77ba911cea55ed894d84402a02577a91 100644 (file)
@@ -4,9 +4,11 @@
  * for more details.
  *
  * Copyright (C) 2004-2007 Cavium Networks
- * Copyright (C) 2008 Wind River Systems
+ * Copyright (C) 2008, 2009 Wind River Systems
+ *   written by Ralf Baechle <ralf@linux-mips.org>
  */
 #include <linux/init.h>
+#include <linux/kernel.h>
 #include <linux/console.h>
 #include <linux/delay.h>
 #include <linux/export.h>
@@ -23,6 +25,7 @@
 #include <linux/serial_8250.h>
 #include <linux/of_fdt.h>
 #include <linux/libfdt.h>
+#include <linux/kexec.h>
 
 #include <asm/processor.h>
 #include <asm/reboot.h>
@@ -56,11 +59,208 @@ struct octeon_boot_descriptor *octeon_boot_desc_ptr;
 struct cvmx_bootinfo *octeon_bootinfo;
 EXPORT_SYMBOL(octeon_bootinfo);
 
+static unsigned long long RESERVE_LOW_MEM = 0ull;
+#ifdef CONFIG_KEXEC
+#ifdef CONFIG_SMP
+/*
+ * Wait for relocation code is prepared and send
+ * secondary CPUs to spin until kernel is relocated.
+ */
+static void octeon_kexec_smp_down(void *ignored)
+{
+       int cpu = smp_processor_id();
+
+       local_irq_disable();
+       set_cpu_online(cpu, false);
+       while (!atomic_read(&kexec_ready_to_reboot))
+               cpu_relax();
+
+       asm volatile (
+       "       sync                                            \n"
+       "       synci   ($0)                                    \n");
+
+       relocated_kexec_smp_wait(NULL);
+}
+#endif
+
+#define OCTEON_DDR0_BASE    (0x0ULL)
+#define OCTEON_DDR0_SIZE    (0x010000000ULL)
+#define OCTEON_DDR1_BASE    (0x410000000ULL)
+#define OCTEON_DDR1_SIZE    (0x010000000ULL)
+#define OCTEON_DDR2_BASE    (0x020000000ULL)
+#define OCTEON_DDR2_SIZE    (0x3e0000000ULL)
+#define OCTEON_MAX_PHY_MEM_SIZE (16*1024*1024*1024ULL)
+
+static struct kimage *kimage_ptr;
+
+static void kexec_bootmem_init(uint64_t mem_size, uint32_t low_reserved_bytes)
+{
+       int64_t addr;
+       struct cvmx_bootmem_desc *bootmem_desc;
+
+       bootmem_desc = cvmx_bootmem_get_desc();
+
+       if (mem_size > OCTEON_MAX_PHY_MEM_SIZE) {
+               mem_size = OCTEON_MAX_PHY_MEM_SIZE;
+               pr_err("Error: requested memory too large,"
+                      "truncating to maximum size\n");
+       }
+
+       bootmem_desc->major_version = CVMX_BOOTMEM_DESC_MAJ_VER;
+       bootmem_desc->minor_version = CVMX_BOOTMEM_DESC_MIN_VER;
+
+       addr = (OCTEON_DDR0_BASE + RESERVE_LOW_MEM + low_reserved_bytes);
+       bootmem_desc->head_addr = 0;
+
+       if (mem_size <= OCTEON_DDR0_SIZE) {
+               __cvmx_bootmem_phy_free(addr,
+                               mem_size - RESERVE_LOW_MEM -
+                               low_reserved_bytes, 0);
+               return;
+       }
+
+       __cvmx_bootmem_phy_free(addr,
+                       OCTEON_DDR0_SIZE - RESERVE_LOW_MEM -
+                       low_reserved_bytes, 0);
+
+       mem_size -= OCTEON_DDR0_SIZE;
+
+       if (mem_size > OCTEON_DDR1_SIZE) {
+               __cvmx_bootmem_phy_free(OCTEON_DDR1_BASE, OCTEON_DDR1_SIZE, 0);
+               __cvmx_bootmem_phy_free(OCTEON_DDR2_BASE,
+                               mem_size - OCTEON_DDR1_SIZE, 0);
+       } else
+               __cvmx_bootmem_phy_free(OCTEON_DDR1_BASE, mem_size, 0);
+}
+
+static int octeon_kexec_prepare(struct kimage *image)
+{
+       int i;
+       char *bootloader = "kexec";
+
+       octeon_boot_desc_ptr->argc = 0;
+       for (i = 0; i < image->nr_segments; i++) {
+               if (!strncmp(bootloader, (char *)image->segment[i].buf,
+                               strlen(bootloader))) {
+                       /*
+                        * convert command line string to array
+                        * of parameters (as bootloader does).
+                        */
+                       int argc = 0, offt;
+                       char *str = (char *)image->segment[i].buf;
+                       char *ptr = strchr(str, ' ');
+                       while (ptr && (OCTEON_ARGV_MAX_ARGS > argc)) {
+                               *ptr = '\0';
+                               if (ptr[1] != ' ') {
+                                       offt = (int)(ptr - str + 1);
+                                       octeon_boot_desc_ptr->argv[argc] =
+                                               image->segment[i].mem + offt;
+                                       argc++;
+                               }
+                               ptr = strchr(ptr + 1, ' ');
+                       }
+                       octeon_boot_desc_ptr->argc = argc;
+                       break;
+               }
+       }
+
+       /*
+        * Information about segments will be needed during pre-boot memory
+        * initialization.
+        */
+       kimage_ptr = image;
+       return 0;
+}
+
+static void octeon_generic_shutdown(void)
+{
+       int cpu, i;
+       struct cvmx_bootmem_desc *bootmem_desc;
+       void *named_block_array_ptr;
+
+       bootmem_desc = cvmx_bootmem_get_desc();
+       named_block_array_ptr =
+               cvmx_phys_to_ptr(bootmem_desc->named_block_array_addr);
+
+#ifdef CONFIG_SMP
+       /* disable watchdogs */
+       for_each_online_cpu(cpu)
+               cvmx_write_csr(CVMX_CIU_WDOGX(cpu_logical_map(cpu)), 0);
+#else
+       cvmx_write_csr(CVMX_CIU_WDOGX(cvmx_get_core_num()), 0);
+#endif
+       if (kimage_ptr != kexec_crash_image) {
+               memset(named_block_array_ptr,
+                       0x0,
+                       CVMX_BOOTMEM_NUM_NAMED_BLOCKS *
+                       sizeof(struct cvmx_bootmem_named_block_desc));
+               /*
+                * Mark all memory (except low 0x100000 bytes) as free.
+                * It is the same thing that bootloader does.
+                */
+               kexec_bootmem_init(octeon_bootinfo->dram_size*1024ULL*1024ULL,
+                               0x100000);
+               /*
+                * Allocate all segments to avoid their corruption during boot.
+                */
+               for (i = 0; i < kimage_ptr->nr_segments; i++)
+                       cvmx_bootmem_alloc_address(
+                               kimage_ptr->segment[i].memsz + 2*PAGE_SIZE,
+                               kimage_ptr->segment[i].mem - PAGE_SIZE,
+                               PAGE_SIZE);
+       } else {
+               /*
+                * Do not mark all memory as free. Free only named sections
+                * leaving the rest of memory unchanged.
+                */
+               struct cvmx_bootmem_named_block_desc *ptr =
+                       (struct cvmx_bootmem_named_block_desc *)
+                       named_block_array_ptr;
+
+               for (i = 0; i < bootmem_desc->named_block_num_blocks; i++)
+                       if (ptr[i].size)
+                               cvmx_bootmem_free_named(ptr[i].name);
+       }
+       kexec_args[2] = 1UL; /* running on octeon_main_processor */
+       kexec_args[3] = (unsigned long)octeon_boot_desc_ptr;
+#ifdef CONFIG_SMP
+       secondary_kexec_args[2] = 0UL; /* running on secondary cpu */
+       secondary_kexec_args[3] = (unsigned long)octeon_boot_desc_ptr;
+#endif
+}
+
+static void octeon_shutdown(void)
+{
+       octeon_generic_shutdown();
+#ifdef CONFIG_SMP
+       smp_call_function(octeon_kexec_smp_down, NULL, 0);
+       smp_wmb();
+       while (num_online_cpus() > 1) {
+               cpu_relax();
+               mdelay(1);
+       }
+#endif
+}
+
+static void octeon_crash_shutdown(struct pt_regs *regs)
+{
+       octeon_generic_shutdown();
+       default_machine_crash_shutdown(regs);
+}
+
+#endif /* CONFIG_KEXEC */
+
 #ifdef CONFIG_CAVIUM_RESERVE32
 uint64_t octeon_reserve32_memory;
 EXPORT_SYMBOL(octeon_reserve32_memory);
 #endif
 
+#ifdef CONFIG_KEXEC
+/* crashkernel cmdline parameter is parsed _after_ memory setup
+ * we also parse it here (workaround for EHB5200) */
+static uint64_t crashk_size, crashk_base;
+#endif
+
 static int octeon_uart;
 
 extern asmlinkage void handle_int(void);
@@ -415,6 +615,8 @@ void octeon_user_io_init(void)
 void __init prom_init(void)
 {
        struct cvmx_sysinfo *sysinfo;
+       const char *arg;
+       char *p;
        int i;
        int argc;
 #ifdef CONFIG_CAVIUM_RESERVE32
@@ -566,6 +768,15 @@ void __init prom_init(void)
        if (octeon_is_simulation())
                MAX_MEMORY = 64ull << 20;
 
+       arg = strstr(arcs_cmdline, "mem=");
+       if (arg) {
+               MAX_MEMORY = memparse(arg + 4, &p);
+               if (MAX_MEMORY == 0)
+                       MAX_MEMORY = 32ull << 30;
+               if (*p == '@')
+                       RESERVE_LOW_MEM = memparse(p + 1, &p);
+       }
+
        arcs_cmdline[0] = 0;
        argc = octeon_boot_desc_ptr->argc;
        for (i = 0; i < argc; i++) {
@@ -573,15 +784,29 @@ void __init prom_init(void)
                        cvmx_phys_to_ptr(octeon_boot_desc_ptr->argv[i]);
                if ((strncmp(arg, "MEM=", 4) == 0) ||
                    (strncmp(arg, "mem=", 4) == 0)) {
-                       sscanf(arg + 4, "%llu", &MAX_MEMORY);
-                       MAX_MEMORY <<= 20;
+                       MAX_MEMORY = memparse(arg + 4, &p);
                        if (MAX_MEMORY == 0)
                                MAX_MEMORY = 32ull << 30;
+                       if (*p == '@')
+                               RESERVE_LOW_MEM = memparse(p + 1, &p);
                } else if (strcmp(arg, "ecc_verbose") == 0) {
 #ifdef CONFIG_CAVIUM_REPORT_SINGLE_BIT_ECC
                        __cvmx_interrupt_ecc_report_single_bit_errors = 1;
                        pr_notice("Reporting of single bit ECC errors is "
                                  "turned on\n");
+#endif
+#ifdef CONFIG_KEXEC
+               } else if (strncmp(arg, "crashkernel=", 12) == 0) {
+                       crashk_size = memparse(arg+12, &p);
+                       if (*p == '@')
+                               crashk_base = memparse(p+1, &p);
+                       strcat(arcs_cmdline, " ");
+                       strcat(arcs_cmdline, arg);
+                       /*
+                        * To do: switch parsing to new style, something like:
+                        * parse_crashkernel(arg, sysinfo->system_dram_size,
+                        *                &crashk_size, &crashk_base);
+                        */
 #endif
                } else if (strlen(arcs_cmdline) + strlen(arg) + 1 <
                           sizeof(arcs_cmdline) - 1) {
@@ -617,11 +842,18 @@ void __init prom_init(void)
        _machine_restart = octeon_restart;
        _machine_halt = octeon_halt;
 
+#ifdef CONFIG_KEXEC
+       _machine_kexec_shutdown = octeon_shutdown;
+       _machine_crash_shutdown = octeon_crash_shutdown;
+       _machine_kexec_prepare = octeon_kexec_prepare;
+#endif
+
        octeon_user_io_init();
        register_smp_ops(&octeon_smp_ops);
 }
 
 /* Exclude a single page from the regions obtained in plat_mem_setup. */
+#ifndef CONFIG_CRASH_DUMP
 static __init void memory_exclude_page(u64 addr, u64 *mem, u64 *size)
 {
        if (addr > *mem && addr < *mem + *size) {
@@ -636,14 +868,21 @@ static __init void memory_exclude_page(u64 addr, u64 *mem, u64 *size)
                *size -= PAGE_SIZE;
        }
 }
+#endif /* CONFIG_CRASH_DUMP */
 
 void __init plat_mem_setup(void)
 {
        uint64_t mem_alloc_size;
        uint64_t total;
+       uint64_t crashk_end;
+#ifndef CONFIG_CRASH_DUMP
        int64_t memory;
+       uint64_t kernel_start;
+       uint64_t kernel_size;
+#endif
 
        total = 0;
+       crashk_end = 0;
 
        /*
         * The Mips memory init uses the first memory location for
@@ -656,6 +895,17 @@ void __init plat_mem_setup(void)
        if (mem_alloc_size > MAX_MEMORY)
                mem_alloc_size = MAX_MEMORY;
 
+/* Crashkernel ignores bootmem list. It relies on mem=X@Y option */
+#ifdef CONFIG_CRASH_DUMP
+       add_memory_region(RESERVE_LOW_MEM, MAX_MEMORY, BOOT_MEM_RAM);
+       total += MAX_MEMORY;
+#else
+#ifdef CONFIG_KEXEC
+       if (crashk_size > 0) {
+               add_memory_region(crashk_base, crashk_size, BOOT_MEM_RAM);
+               crashk_end = crashk_base + crashk_size;
+       }
+#endif
        /*
         * When allocating memory, we want incrementing addresses from
         * bootmem_alloc so the code in add_memory_region can merge
@@ -664,22 +914,15 @@ void __init plat_mem_setup(void)
        cvmx_bootmem_lock();
        while ((boot_mem_map.nr_map < BOOT_MEM_MAP_MAX)
                && (total < MAX_MEMORY)) {
-#if defined(CONFIG_64BIT) || defined(CONFIG_64BIT_PHYS_ADDR)
                memory = cvmx_bootmem_phy_alloc(mem_alloc_size,
                                                __pa_symbol(&__init_end), -1,
                                                0x100000,
                                                CVMX_BOOTMEM_FLAG_NO_LOCKING);
-#elif defined(CONFIG_HIGHMEM)
-               memory = cvmx_bootmem_phy_alloc(mem_alloc_size, 0, 1ull << 31,
-                                               0x100000,
-                                               CVMX_BOOTMEM_FLAG_NO_LOCKING);
-#else
-               memory = cvmx_bootmem_phy_alloc(mem_alloc_size, 0, 512 << 20,
-                                               0x100000,
-                                               CVMX_BOOTMEM_FLAG_NO_LOCKING);
-#endif
                if (memory >= 0) {
                        u64 size = mem_alloc_size;
+#ifdef CONFIG_KEXEC
+                       uint64_t end;
+#endif
 
                        /*
                         * exclude a page at the beginning and end of
@@ -692,20 +935,67 @@ void __init plat_mem_setup(void)
                        memory_exclude_page(CVMX_PCIE_BAR1_PHYS_BASE +
                                            CVMX_PCIE_BAR1_PHYS_SIZE,
                                            &memory, &size);
+#ifdef CONFIG_KEXEC
+                       end = memory + mem_alloc_size;
 
                        /*
-                        * This function automatically merges address
-                        * regions next to each other if they are
-                        * received in incrementing order.
+                        * This function automatically merges address regions
+                        * next to each other if they are received in
+                        * incrementing order
                         */
-                       if (size)
-                               add_memory_region(memory, size, BOOT_MEM_RAM);
+                       if (memory < crashk_base && end >  crashk_end) {
+                               /* region is fully in */
+                               add_memory_region(memory,
+                                                 crashk_base - memory,
+                                                 BOOT_MEM_RAM);
+                               total += crashk_base - memory;
+                               add_memory_region(crashk_end,
+                                                 end - crashk_end,
+                                                 BOOT_MEM_RAM);
+                               total += end - crashk_end;
+                               continue;
+                       }
+
+                       if (memory >= crashk_base && end <= crashk_end)
+                               /*
+                                * Entire memory region is within the new
+                                *  kernel's memory, ignore it.
+                                */
+                               continue;
+
+                       if (memory > crashk_base && memory < crashk_end &&
+                           end > crashk_end) {
+                               /*
+                                * Overlap with the beginning of the region,
+                                * reserve the beginning.
+                                 */
+                               mem_alloc_size -= crashk_end - memory;
+                               memory = crashk_end;
+                       } else if (memory < crashk_base && end > crashk_base &&
+                                  end < crashk_end)
+                               /*
+                                * Overlap with the beginning of the region,
+                                * chop of end.
+                                */
+                               mem_alloc_size -= end - crashk_base;
+#endif
+                       add_memory_region(memory, mem_alloc_size, BOOT_MEM_RAM);
                        total += mem_alloc_size;
+                       /* Recovering mem_alloc_size */
+                       mem_alloc_size = 4 << 20;
                } else {
                        break;
                }
        }
        cvmx_bootmem_unlock();
+       /* Add the memory region for the kernel. */
+       kernel_start = (unsigned long) _text;
+       kernel_size = ALIGN(_end - _text, 0x100000);
+
+       /* Adjust for physical offset. */
+       kernel_start &= ~0xffffffff80000000ULL;
+       add_memory_region(kernel_start, kernel_size, BOOT_MEM_RAM);
+#endif /* CONFIG_CRASH_DUMP */
 
 #ifdef CONFIG_CAVIUM_RESERVE32
        /*
@@ -821,3 +1111,29 @@ void __init device_tree_init(void)
        }
        unflatten_device_tree();
 }
+
+static char *edac_device_names[] = {
+       "co_l2c_edac",
+       "co_lmc_edac",
+       "co_pc_edac",
+};
+
+static int __init edac_devinit(void)
+{
+       struct platform_device *dev;
+       int i, err = 0;
+       char *name;
+
+       for (i = 0; i < ARRAY_SIZE(edac_device_names); i++) {
+               name = edac_device_names[i];
+               dev = platform_device_register_simple(name, -1, NULL, 0);
+               if (IS_ERR(dev)) {
+                       pr_err("Registation of %s failed!\n", name);
+                       err = PTR_ERR(dev);
+               }
+       }
+
+       return err;
+}
+
+device_initcall(edac_devinit);