Commit abe77f90 authored by Ralf Baechle's avatar Ralf Baechle

MIPS: Octeon: Add kexec and kdump support

[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: default avatarMaxim 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: default avatarRalf Baechle <ralf@linux-mips.org>
parent 7aa1c8f4
......@@ -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/
......
......@@ -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;
}
......@@ -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,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) {
......@@ -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
/*
......
......@@ -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__ */
......@@ -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)
......
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment