mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 16:41:58 +00:00
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: 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>
This commit is contained in:
parent
7aa1c8f47e
commit
abe77f90dc
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user