Skip to content

Commit

Permalink
MIPS: kdump: Add support
Browse files Browse the repository at this point in the history
[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/1025/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
  • Loading branch information
Ralf Baechle committed Dec 13, 2012
1 parent 98cdee0 commit 7aa1c8f
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 9 deletions.
23 changes: 23 additions & 0 deletions arch/mips/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2379,6 +2379,29 @@ config KEXEC
support. As of this writing the exact hardware interface is
strongly in flux, so no good recommendation can be made.

config CRASH_DUMP
bool "Kernel crash dumps"
help
Generate crash dump after being started by kexec.
This should be normally only set in special crash dump kernels
which are loaded in the main kernel with kexec-tools into
a specially reserved region and then later executed after
a crash by kdump/kexec. The crash dump kernel must be compiled
to a memory address not used by the main kernel or firmware using
PHYSICAL_START.

config PHYSICAL_START
hex "Physical address where the kernel is loaded"
default "0xffffffff84000000" if 64BIT
default "0x84000000" if 32BIT
depends on CRASH_DUMP
help
This gives the CKSEG0 or KSEG0 address where the kernel is loaded.
If you plan to use kernel for capturing the crash dump change
this value to start of the reserved region (the "X" value as
specified in the "crashkernel=YM@XM" command line boot parameter
passed to the panic-ed kernel).

config SECCOMP
bool "Enable seccomp to safely compute untrusted bytecode"
depends on PROC_FS
Expand Down
27 changes: 24 additions & 3 deletions arch/mips/include/asm/kexec.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,43 @@
#ifndef _MIPS_KEXEC
# define _MIPS_KEXEC

#include <asm/stacktrace.h>

/* Maximum physical address we can use pages from */
#define KEXEC_SOURCE_MEMORY_LIMIT (0x20000000)
/* Maximum address we can reach in physical address mode */
#define KEXEC_DESTINATION_MEMORY_LIMIT (0x20000000)
/* Maximum address we can use for the control code buffer */
#define KEXEC_CONTROL_MEMORY_LIMIT (0x20000000)

#define KEXEC_CONTROL_PAGE_SIZE 4096
/* Reserve 3*4096 bytes for board-specific info */
#define KEXEC_CONTROL_PAGE_SIZE (4096 + 3*4096)

/* The native architecture */
#define KEXEC_ARCH KEXEC_ARCH_MIPS
#define MAX_NOTE_BYTES 1024

static inline void crash_setup_regs(struct pt_regs *newregs,
struct pt_regs *oldregs)
{
/* Dummy implementation for now */
if (oldregs)
memcpy(newregs, oldregs, sizeof(*newregs));
else
prepare_frametrace(newregs);
}

#ifdef CONFIG_KEXEC
struct kimage;
extern unsigned long kexec_args[4];
extern int (*_machine_kexec_prepare)(struct kimage *);
extern void (*_machine_kexec_shutdown)(void);
extern void (*_machine_crash_shutdown)(struct pt_regs *regs);
extern void default_machine_crash_shutdown(struct pt_regs *regs);
#ifdef CONFIG_SMP
extern const unsigned char kexec_smp_wait[];
extern unsigned long secondary_kexec_args[4];
extern void (*relocated_kexec_smp_wait) (void *);
extern atomic_t kexec_ready_to_reboot;
#endif
#endif

#endif /* !_MIPS_KEXEC */
6 changes: 6 additions & 0 deletions arch/mips/include/asm/smp.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ extern int __cpu_logical_map[NR_CPUS];
#define SMP_CALL_FUNCTION 0x2
/* Octeon - Tell another core to flush its icache */
#define SMP_ICACHE_FLUSH 0x4
/* Used by kexec crashdump to save all cpu's state */
#define SMP_DUMP 0x8

extern volatile cpumask_t cpu_callin_map;

Expand Down Expand Up @@ -91,4 +93,8 @@ static inline void arch_send_call_function_ipi_mask(const struct cpumask *mask)
mp_ops->send_ipi_mask(mask, SMP_CALL_FUNCTION);
}

#if defined(CONFIG_KEXEC)
extern void (*dump_ipi_function_ptr)(void *);
void dump_send_ipi(void (*dump_ipi_callback)(void *));
#endif
#endif /* __ASM_SMP_H */
3 changes: 2 additions & 1 deletion arch/mips/kernel/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ obj-$(CONFIG_I8253) += i8253.o

obj-$(CONFIG_GPIO_TXX9) += gpio_txx9.o

obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o
obj-$(CONFIG_KEXEC) += machine_kexec.o relocate_kernel.o crash.o
obj-$(CONFIG_CRASH_DUMP) += crash_dump.o
obj-$(CONFIG_EARLY_PRINTK) += early_printk.o
obj-$(CONFIG_SPINLOCK_TEST) += spinlock_test.o
obj-$(CONFIG_MIPS_MACHINE) += mips_machine.o
Expand Down
71 changes: 71 additions & 0 deletions arch/mips/kernel/crash.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/reboot.h>
#include <linux/kexec.h>
#include <linux/bootmem.h>
#include <linux/crash_dump.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/irq.h>
#include <linux/types.h>
#include <linux/sched.h>

/* This keeps a track of which one is crashing cpu. */
static int crashing_cpu = -1;
static cpumask_t cpus_in_crash = CPU_MASK_NONE;

#ifdef CONFIG_SMP
static void crash_shutdown_secondary(void *ignore)
{
struct pt_regs *regs;
int cpu = smp_processor_id();

regs = task_pt_regs(current);

if (!cpu_online(cpu))
return;

local_irq_disable();
if (!cpu_isset(cpu, cpus_in_crash))
crash_save_cpu(regs, cpu);
cpu_set(cpu, cpus_in_crash);

while (!atomic_read(&kexec_ready_to_reboot))
cpu_relax();
relocated_kexec_smp_wait(NULL);
/* NOTREACHED */
}

static void crash_kexec_prepare_cpus(void)
{
unsigned int msecs;

unsigned int ncpus = num_online_cpus() - 1;/* Excluding the panic cpu */

dump_send_ipi(crash_shutdown_secondary);
smp_wmb();

/*
* The crash CPU sends an IPI and wait for other CPUs to
* respond. Delay of at least 10 seconds.
*/
pr_emerg("Sending IPI to other cpus...\n");
msecs = 10000;
while ((cpus_weight(cpus_in_crash) < ncpus) && (--msecs > 0)) {
cpu_relax();
mdelay(1);
}
}

#else /* !defined(CONFIG_SMP) */
static void crash_kexec_prepare_cpus(void) {}
#endif /* !defined(CONFIG_SMP) */

void default_machine_crash_shutdown(struct pt_regs *regs)
{
local_irq_disable();
crashing_cpu = smp_processor_id();
crash_save_cpu(regs, crashing_cpu);
crash_kexec_prepare_cpus();
cpu_set(crashing_cpu, cpus_in_crash);
}
77 changes: 77 additions & 0 deletions arch/mips/kernel/crash_dump.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#include <linux/highmem.h>
#include <linux/bootmem.h>
#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)
saved_max_pfn = (memparse(p, &p) >> PAGE_SHIFT) - 1;

return 1;
}
__setup("savemaxmem=", parse_savemaxmem);


static void *kdump_buf_page;

/**
* copy_oldmem_page - copy one page from "oldmem"
* @pfn: page frame number to be copied
* @buf: target memory address for the copy; this can be in kernel address
* space or user address space (see @userbuf)
* @csize: number of bytes to copy
* @offset: offset in bytes into the page (based on pfn) to begin the copy
* @userbuf: if set, @buf is in user address space, use copy_to_user(),
* otherwise @buf is in kernel address space, use memcpy().
*
* Copy a page from "oldmem". For this page, there is no pte mapped
* in the current kernel.
*
* Calling copy_to_user() in atomic context is not desirable. Hence first
* copying the data to a pre-allocated kernel page and then copying to user
* space in non-atomic context.
*/
ssize_t copy_oldmem_page(unsigned long pfn, char *buf,
size_t csize, unsigned long offset, int userbuf)
{
void *vaddr;

if (!csize)
return 0;

vaddr = kmap_atomic_pfn(pfn);

if (!userbuf) {
memcpy(buf, (vaddr + offset), csize);
kunmap_atomic(vaddr);
} else {
if (!kdump_buf_page) {
pr_warning("Kdump: Kdump buffer page not allocated\n");

return -EFAULT;
}
copy_page(kdump_buf_page, vaddr);
kunmap_atomic(vaddr);
if (copy_to_user(buf, (kdump_buf_page + offset), csize))
return -EFAULT;
}

return csize;
}

static int __init kdump_buf_page_init(void)
{
int ret = 0;

kdump_buf_page = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!kdump_buf_page) {
pr_warning("Kdump: Failed to allocate kdump buffer page\n");
ret = -ENOMEM;
}

return ret;
}
arch_initcall(kdump_buf_page_init);
33 changes: 29 additions & 4 deletions arch/mips/kernel/machine_kexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* This source code is licensed under the GNU General Public License,
* Version 2. See the file COPYING for more details.
*/

#include <linux/compiler.h>
#include <linux/kexec.h>
#include <linux/mm.h>
#include <linux/delay.h>
Expand All @@ -19,9 +19,19 @@ extern const size_t relocate_new_kernel_size;
extern unsigned long kexec_start_address;
extern unsigned long kexec_indirection_page;

int (*_machine_kexec_prepare)(struct kimage *) = NULL;
void (*_machine_kexec_shutdown)(void) = NULL;
void (*_machine_crash_shutdown)(struct pt_regs *regs) = NULL;
#ifdef CONFIG_SMP
void (*relocated_kexec_smp_wait) (void *);
atomic_t kexec_ready_to_reboot = ATOMIC_INIT(0);
#endif

int
machine_kexec_prepare(struct kimage *kimage)
{
if (_machine_kexec_prepare)
return _machine_kexec_prepare(kimage);
return 0;
}

Expand All @@ -33,14 +43,20 @@ machine_kexec_cleanup(struct kimage *kimage)
void
machine_shutdown(void)
{
if (_machine_kexec_shutdown)
_machine_kexec_shutdown();
}

void
machine_crash_shutdown(struct pt_regs *regs)
{
if (_machine_crash_shutdown)
_machine_crash_shutdown(regs);
else
default_machine_crash_shutdown(regs);
}

typedef void (*noretfun_t)(void) __attribute__((noreturn));
typedef void (*noretfun_t)(void) __noreturn;

void
machine_kexec(struct kimage *image)
Expand All @@ -52,7 +68,9 @@ machine_kexec(struct kimage *image)
reboot_code_buffer =
(unsigned long)page_address(image->control_code_page);

kexec_start_address = image->start;
kexec_start_address =
(unsigned long) phys_to_virt(image->start);

kexec_indirection_page =
(unsigned long) phys_to_virt(image->head & PAGE_MASK);

Expand All @@ -63,7 +81,7 @@ machine_kexec(struct kimage *image)
* The generic kexec code builds a page list with physical
* addresses. they are directly accessible through KSEG0 (or
* CKSEG0 or XPHYS if on 64bit system), hence the
* pys_to_virt() call.
* phys_to_virt() call.
*/
for (ptr = &image->head; (entry = *ptr) && !(entry &IND_DONE);
ptr = (entry & IND_INDIRECTION) ?
Expand All @@ -81,5 +99,12 @@ machine_kexec(struct kimage *image)
printk("Will call new kernel at %08lx\n", image->start);
printk("Bye ...\n");
__flush_cache_all();
#ifdef CONFIG_SMP
/* All secondary cpus now may jump to kexec_wait cycle */
relocated_kexec_smp_wait = reboot_code_buffer +
(void *)(kexec_smp_wait - relocate_new_kernel);
smp_wmb();
atomic_set(&kexec_ready_to_reboot, 1);
#endif
((noretfun_t) reboot_code_buffer)();
}
Loading

0 comments on commit 7aa1c8f

Please sign in to comment.