Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit abe77f90 authored by Ralf Baechle's avatar Ralf Baechle
Browse files

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
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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/

+5 −0
Original line number Diff line number Diff line
@@ -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;
}
+305 −7
Original line number Diff line number Diff line
@@ -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
			 */
			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)
				/*
			 * This function automatically merges address
			 * regions next to each other if they are
			 * received in incrementing order.
				 * Overlap with the beginning of the region,
				 * chop of end.
				 */
			if (size)
				add_memory_region(memory, size, BOOT_MEM_RAM);
				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
	/*
+2 −0
Original line number Diff line number Diff line
@@ -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__ */
+0 −2
Original line number Diff line number Diff line
@@ -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)
Loading