MIPS: Octeon: Add kexec and kdump support
authorRalf Baechle <ralf@linux-mips.org>
Thu, 25 Oct 2012 14:23:31 +0000 (16:23 +0200)
committerRalf Baechle <ralf@linux-mips.org>
Thu, 13 Dec 2012 16:00:39 +0000 (17:00 +0100)
[ralf@linux-mips.org: Original patch by Maxim Uvarov <muvarov@gmail.com>
with plenty of further shining, polishing, debugging and testing by me.]

Signed-off-by: Maxim Uvarov <muvarov@gmail.com>
Cc: linux-mips@linux-mips.org
Cc: kexec@lists.infradead.org
Cc: horms@verge.net.au
Patchwork: https://patchwork.linux-mips.org/patch/1026/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/Makefile
arch/mips/cavium-octeon/executive/cvmx-bootmem.c
arch/mips/cavium-octeon/setup.c
arch/mips/include/asm/octeon/cvmx-bootmem.h
arch/mips/kernel/crash_dump.c
arch/mips/kernel/relocate_kernel.S

index 654b1ad..033395e 100644 (file)
@@ -192,6 +192,10 @@ endif
 #
 include $(srctree)/arch/mips/Kbuild.platforms
 
+ifdef CONFIG_PHYSICAL_START
+load-y                                  = $(CONFIG_PHYSICAL_START)
+endif
+
 cflags-y                       += -I$(srctree)/arch/mips/include/asm/mach-generic
 drivers-$(CONFIG_PCI)          += arch/mips/pci/
 
index fdf5f19..6d5ddbc 100644 (file)
@@ -688,3 +688,8 @@ int64_t cvmx_bootmem_phy_named_block_alloc(uint64_t size, uint64_t min_addr,
                cvmx_spinlock_unlock((cvmx_spinlock_t *)&(cvmx_bootmem_desc->lock));
        return addr_allocated;
 }
+
+struct cvmx_bootmem_desc *cvmx_bootmem_get_desc(void)
+{
+       return cvmx_bootmem_desc;
+}
index 67aa3b9..7c2b7aa 100644 (file)
@@ -25,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>
@@ -58,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);
@@ -417,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
@@ -568,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++) {
@@ -575,16 +784,30 @@ 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) {
                        strcat(arcs_cmdline, " ");
@@ -619,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) {
@@ -638,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
@@ -658,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
@@ -672,6 +920,9 @@ void __init plat_mem_setup(void)
                                                CVMX_BOOTMEM_FLAG_NO_LOCKING);
                if (memory >= 0) {
                        u64 size = mem_alloc_size;
+#ifdef CONFIG_KEXEC
+                       uint64_t end;
+#endif
 
                        /*
                         * exclude a page at the beginning and end of
@@ -684,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
        /*
index 877845b..42db2be 100644 (file)
@@ -370,4 +370,6 @@ void cvmx_bootmem_lock(void);
  */
 void cvmx_bootmem_unlock(void);
 
+extern struct cvmx_bootmem_desc *cvmx_bootmem_get_desc(void);
+
 #endif /*   __CVMX_BOOTMEM_H__ */
index d9ec389..35bed0d 100644 (file)
@@ -3,8 +3,6 @@
 #include <linux/crash_dump.h>
 #include <asm/uaccess.h>
 
-unsigned long long elfcorehdr_addr = ELFCORE_ADDR_MAX;
-
 static int __init parse_savemaxmem(char *p)
 {
        if (p)
index 0b10858..e4142c5 100644 (file)
@@ -78,7 +78,19 @@ done:
        LONG_S          zero,(t0)
 #endif
 
+#ifdef CONFIG_CPU_CAVIUM_OCTEON
+       /* We need to flush I-cache before jumping to new kernel.
+        * Unfortunatelly, this code is cpu-specific.
+        */
+       .set push
+       .set noreorder
+       syncw
+       syncw
+       synci           0($0)
+       .set pop
+#else
        sync
+#endif
        /* jump to kexec_start_address */
        j               s1
        END(relocate_new_kernel)
@@ -110,7 +122,14 @@ LEAF(kexec_smp_wait)
 1:     LONG_L          s0, (t0)
        bne             s0, zero,1b
 
+#ifdef CONFIG_CPU_CAVIUM_OCTEON
+       .set push
+       .set noreorder
+       synci           0($0)
+       .set pop
+#else
        sync
+#endif
        j               s1
        END(kexec_smp_wait)
 #endif