From f65aad41772f6a0022e9763fe06f47604449964c Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Wed, 17 Oct 2012 00:39:09 +0200 Subject: MIPS: Cavium: Add EDAC support. Drivers for EDAC on Cavium. Supported subsystems are: o CPU primary caches. These are parity protected only, so only error reporting. o Second level cache - ECC protected, provides SECDED. o Memory: ECC / SECDEC if used with suitable DRAM modules. The driver will will only initialize if ECC is enabled on a system so is safe to run on non-ECC memory. o PCI: Parity error reporting Since it is very hard to test this sort of code the implementation is very conservative and uses polling where possible for now. Signed-off-by: Ralf Baechle Reviewed-by: Borislav Petkov --- arch/mips/cavium-octeon/setup.c | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index 04dd8ff0e0d..60ed700a956 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -4,9 +4,11 @@ * for more details. * * Copyright (C) 2004-2007 Cavium Networks - * Copyright (C) 2008 Wind River Systems + * Copyright (C) 2008, 2009 Wind River Systems + * written by Ralf Baechle */ #include +#include #include #include #include @@ -821,3 +823,29 @@ void __init device_tree_init(void) } unflatten_device_tree(); } + +static char *edac_device_names[] = { + "co_l2c_edac", + "co_lmc_edac", + "co_pc_edac", +}; + +static int __init edac_devinit(void) +{ + struct platform_device *dev; + int i, err = 0; + char *name; + + for (i = 0; i < ARRAY_SIZE(edac_device_names); i++) { + name = edac_device_names[i]; + dev = platform_device_register_simple(name, -1, NULL, 0); + if (IS_ERR(dev)) { + pr_err("Registation of %s failed!\n", name); + err = PTR_ERR(dev); + } + } + + return err; +} + +device_initcall(edac_devinit); -- cgit v1.2.3-70-g09d2 From ce4625f431d064e65eefacfbe59abe0f642de38e Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Thu, 25 Oct 2012 21:12:15 +0200 Subject: MIPS: Octeon: Remove highmem code. On Cavium hardware only 64-bit kernels are supported so CONFIG_HIGHMEM is never set. Signed-off-by: Ralf Baechle --- arch/mips/cavium-octeon/setup.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index 60ed700a956..efc1ed404b5 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -671,10 +671,6 @@ void __init plat_mem_setup(void) __pa_symbol(&__init_end), -1, 0x100000, CVMX_BOOTMEM_FLAG_NO_LOCKING); -#elif defined(CONFIG_HIGHMEM) - memory = cvmx_bootmem_phy_alloc(mem_alloc_size, 0, 1ull << 31, - 0x100000, - CVMX_BOOTMEM_FLAG_NO_LOCKING); #else memory = cvmx_bootmem_phy_alloc(mem_alloc_size, 0, 512 << 20, 0x100000, -- cgit v1.2.3-70-g09d2 From 4fe64af7da539bc58c9e571da5ef0eb4589c9a35 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Thu, 25 Oct 2012 21:14:59 +0200 Subject: MIPS: Octeon: Remove use of CONFIG_64BIT_PHYS_ADDR. Only supporting 64-bit kernels there is no point in depending on this symbol. Signed-off-by: Ralf Baechle --- arch/mips/cavium-octeon/setup.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index efc1ed404b5..c0c2d14be42 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -666,7 +666,7 @@ void __init plat_mem_setup(void) cvmx_bootmem_lock(); while ((boot_mem_map.nr_map < BOOT_MEM_MAP_MAX) && (total < MAX_MEMORY)) { -#if defined(CONFIG_64BIT) || defined(CONFIG_64BIT_PHYS_ADDR) +#ifdef CONFIG_64BIT memory = cvmx_bootmem_phy_alloc(mem_alloc_size, __pa_symbol(&__init_end), -1, 0x100000, -- cgit v1.2.3-70-g09d2 From 5456bd26ae0606a98bc937ec71c748f2de5a6a90 Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Thu, 25 Oct 2012 21:21:29 +0200 Subject: MIPS: Octeon: Simplify code by assuming CONFIG_64BIT is always set. No 32-bit kernels supported on Octeon. Signed-off-by: Ralf Baechle --- arch/mips/cavium-octeon/octeon-memcpy.S | 27 --------------------------- arch/mips/cavium-octeon/setup.c | 6 ------ 2 files changed, 33 deletions(-) (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/cavium-octeon/octeon-memcpy.S b/arch/mips/cavium-octeon/octeon-memcpy.S index db478dbb9c7..0ba0eb96d9a 100644 --- a/arch/mips/cavium-octeon/octeon-memcpy.S +++ b/arch/mips/cavium-octeon/octeon-memcpy.S @@ -79,11 +79,6 @@ /* * Only on the 64-bit kernel we can made use of 64-bit registers. */ -#ifdef CONFIG_64BIT -#define USE_DOUBLE -#endif - -#ifdef USE_DOUBLE #define LOAD ld #define LOADL ldl @@ -119,26 +114,6 @@ #define t6 $14 #define t7 $15 -#else - -#define LOAD lw -#define LOADL lwl -#define LOADR lwr -#define STOREL swl -#define STORER swr -#define STORE sw -#define ADD addu -#define SUB subu -#define SRL srl -#define SLL sll -#define SRA sra -#define SLLV sllv -#define SRLV srlv -#define NBYTES 4 -#define LOG_NBYTES 2 - -#endif /* USE_DOUBLE */ - #ifdef CONFIG_CPU_LITTLE_ENDIAN #define LDFIRST LOADR #define LDREST LOADL @@ -395,12 +370,10 @@ EXC( sb t0, N(dst), s_exc_p1) COPY_BYTE(0) COPY_BYTE(1) -#ifdef USE_DOUBLE COPY_BYTE(2) COPY_BYTE(3) COPY_BYTE(4) COPY_BYTE(5) -#endif EXC( lb t0, NBYTES-2(src), l_exc) SUB len, len, 1 jr ra diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index c0c2d14be42..67aa3b942f0 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -666,16 +666,10 @@ void __init plat_mem_setup(void) cvmx_bootmem_lock(); while ((boot_mem_map.nr_map < BOOT_MEM_MAP_MAX) && (total < MAX_MEMORY)) { -#ifdef CONFIG_64BIT memory = cvmx_bootmem_phy_alloc(mem_alloc_size, __pa_symbol(&__init_end), -1, 0x100000, CVMX_BOOTMEM_FLAG_NO_LOCKING); -#else - memory = cvmx_bootmem_phy_alloc(mem_alloc_size, 0, 512 << 20, - 0x100000, - CVMX_BOOTMEM_FLAG_NO_LOCKING); -#endif if (memory >= 0) { u64 size = mem_alloc_size; -- cgit v1.2.3-70-g09d2 From abe77f90dc9c65a7c9a4d61c2cbb8db4d5566e4f Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Thu, 25 Oct 2012 16:23:31 +0200 Subject: MIPS: Octeon: Add kexec and kdump support [ralf@linux-mips.org: Original patch by Maxim Uvarov with plenty of further shining, polishing, debugging and testing by me.] Signed-off-by: Maxim Uvarov 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 --- arch/mips/Makefile | 4 + arch/mips/cavium-octeon/executive/cvmx-bootmem.c | 5 + arch/mips/cavium-octeon/setup.c | 312 ++++++++++++++++++++++- arch/mips/include/asm/octeon/cvmx-bootmem.h | 2 + arch/mips/kernel/crash_dump.c | 2 - arch/mips/kernel/relocate_kernel.S | 19 ++ 6 files changed, 335 insertions(+), 9 deletions(-) (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/Makefile b/arch/mips/Makefile index 654b1ad39f0..033395ec82f 100644 --- a/arch/mips/Makefile +++ b/arch/mips/Makefile @@ -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/ diff --git a/arch/mips/cavium-octeon/executive/cvmx-bootmem.c b/arch/mips/cavium-octeon/executive/cvmx-bootmem.c index fdf5f19bfdb..6d5ddbc112c 100644 --- a/arch/mips/cavium-octeon/executive/cvmx-bootmem.c +++ b/arch/mips/cavium-octeon/executive/cvmx-bootmem.c @@ -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; +} diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index 67aa3b942f0..7c2b7aab77b 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -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 /* diff --git a/arch/mips/include/asm/octeon/cvmx-bootmem.h b/arch/mips/include/asm/octeon/cvmx-bootmem.h index 877845b84b1..42db2be663f 100644 --- a/arch/mips/include/asm/octeon/cvmx-bootmem.h +++ b/arch/mips/include/asm/octeon/cvmx-bootmem.h @@ -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__ */ diff --git a/arch/mips/kernel/crash_dump.c b/arch/mips/kernel/crash_dump.c index d9ec3898f9f..35bed0d2342 100644 --- a/arch/mips/kernel/crash_dump.c +++ b/arch/mips/kernel/crash_dump.c @@ -3,8 +3,6 @@ #include #include -unsigned long long elfcorehdr_addr = ELFCORE_ADDR_MAX; - static int __init parse_savemaxmem(char *p) { if (p) diff --git a/arch/mips/kernel/relocate_kernel.S b/arch/mips/kernel/relocate_kernel.S index 0b108587f06..e4142c5f7c2 100644 --- a/arch/mips/kernel/relocate_kernel.S +++ b/arch/mips/kernel/relocate_kernel.S @@ -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 -- cgit v1.2.3-70-g09d2 From e1ced09797776dfd4a2a7b04b9ee7e97ab1e64be Mon Sep 17 00:00:00 2001 From: David Daney Date: Thu, 15 Nov 2012 13:58:59 -0800 Subject: MIPS/EDAC: Improve OCTEON EDAC support. Some initialization errors are reported with the existing OCTEON EDAC support patch. Also some parts have more than one memory controller. Fix the errors and add multiple controllers if present. Signed-off-by: David Daney --- arch/mips/cavium-octeon/setup.c | 30 +++++- arch/mips/mm/c-octeon.c | 39 +++++-- arch/mips/pci/pci-octeon.c | 3 +- drivers/edac/octeon_edac-l2c.c | 178 ++++++++++++++++++++++-------- drivers/edac/octeon_edac-lmc.c | 232 +++++++++++++++++++++++----------------- drivers/edac/octeon_edac-lmc.h | 78 -------------- drivers/edac/octeon_edac-pc.c | 137 ++++++++++++------------ drivers/edac/octeon_edac-pci.c | 44 ++------ 8 files changed, 406 insertions(+), 335 deletions(-) delete mode 100644 drivers/edac/octeon_edac-lmc.h (limited to 'arch/mips/cavium-octeon/setup.c') diff --git a/arch/mips/cavium-octeon/setup.c b/arch/mips/cavium-octeon/setup.c index 7c2b7aab77b..d7e0a09f77c 100644 --- a/arch/mips/cavium-octeon/setup.c +++ b/arch/mips/cavium-octeon/setup.c @@ -1112,18 +1112,30 @@ void __init device_tree_init(void) unflatten_device_tree(); } +static int __initdata disable_octeon_edac_p; + +static int __init disable_octeon_edac(char *str) +{ + disable_octeon_edac_p = 1; + return 0; +} +early_param("disable_octeon_edac", disable_octeon_edac); + static char *edac_device_names[] = { - "co_l2c_edac", - "co_lmc_edac", - "co_pc_edac", + "octeon_l2c_edac", + "octeon_pc_edac", }; static int __init edac_devinit(void) { struct platform_device *dev; int i, err = 0; + int num_lmc; char *name; + if (disable_octeon_edac_p) + return 0; + for (i = 0; i < ARRAY_SIZE(edac_device_names); i++) { name = edac_device_names[i]; dev = platform_device_register_simple(name, -1, NULL, 0); @@ -1133,7 +1145,17 @@ static int __init edac_devinit(void) } } + num_lmc = OCTEON_IS_MODEL(OCTEON_CN68XX) ? 4 : + (OCTEON_IS_MODEL(OCTEON_CN56XX) ? 2 : 1); + for (i = 0; i < num_lmc; i++) { + dev = platform_device_register_simple("octeon_lmc_edac", + i, NULL, 0); + if (IS_ERR(dev)) { + pr_err("Registation of octeon_lmc_edac %d failed!\n", i); + err = PTR_ERR(dev); + } + } + return err; } - device_initcall(edac_devinit); diff --git a/arch/mips/mm/c-octeon.c b/arch/mips/mm/c-octeon.c index 9f67553762d..6ec04daf423 100644 --- a/arch/mips/mm/c-octeon.c +++ b/arch/mips/mm/c-octeon.c @@ -286,10 +286,9 @@ void __cpuinit octeon_cache_init(void) board_cache_error_setup = octeon_cache_error_setup; } -/** +/* * Handle a cache error exception */ - static RAW_NOTIFIER_HEAD(co_cache_error_chain); int register_co_cache_error_notifier(struct notifier_block *nb) @@ -304,14 +303,39 @@ int unregister_co_cache_error_notifier(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(unregister_co_cache_error_notifier); -static inline int co_cache_error_call_notifiers(unsigned long val) +static void co_cache_error_call_notifiers(unsigned long val) { - return raw_notifier_call_chain(&co_cache_error_chain, val, NULL); + int rv = raw_notifier_call_chain(&co_cache_error_chain, val, NULL); + if ((rv & ~NOTIFY_STOP_MASK) != NOTIFY_OK) { + u64 dcache_err; + unsigned long coreid = cvmx_get_core_num(); + u64 icache_err = read_octeon_c0_icacheerr(); + + if (val) { + dcache_err = cache_err_dcache[coreid]; + cache_err_dcache[coreid] = 0; + } else { + dcache_err = read_octeon_c0_dcacheerr(); + } + + pr_err("Core%lu: Cache error exception:\n", coreid); + pr_err("cp0_errorepc == %lx\n", read_c0_errorepc()); + if (icache_err & 1) { + pr_err("CacheErr (Icache) == %llx\n", + (unsigned long long)icache_err); + write_octeon_c0_icacheerr(0); + } + if (dcache_err & 1) { + pr_err("CacheErr (Dcache) == %llx\n", + (unsigned long long)dcache_err); + } + } } -/** +/* * Called when the the exception is recoverable */ + asmlinkage void cache_parity_error_octeon_recoverable(void) { co_cache_error_call_notifiers(0); @@ -319,11 +343,8 @@ asmlinkage void cache_parity_error_octeon_recoverable(void) /** * Called when the the exception is not recoverable - * - * The issue not that the cache error exception itself was non-recoverable - * but that due to nesting of exception may have lost some state so can't - * continue. */ + asmlinkage void cache_parity_error_octeon_non_recoverable(void) { co_cache_error_call_notifiers(1); diff --git a/arch/mips/pci/pci-octeon.c b/arch/mips/pci/pci-octeon.c index 8eb2ee345d0..5b5ed76c6f4 100644 --- a/arch/mips/pci/pci-octeon.c +++ b/arch/mips/pci/pci-octeon.c @@ -705,7 +705,8 @@ static int __init octeon_pci_setup(void) */ cvmx_write_csr(CVMX_NPI_PCI_INT_SUM2, -1); - if (IS_ERR(platform_device_register_simple("co_pci_edac", 0, NULL, 0))) + if (IS_ERR(platform_device_register_simple("octeon_pci_edac", + -1, NULL, 0))) pr_err("Registation of co_pci_edac failed!\n"); octeon_pci_dma_init(); diff --git a/drivers/edac/octeon_edac-l2c.c b/drivers/edac/octeon_edac-l2c.c index 5f459aa451b..40fde6a51ed 100644 --- a/drivers/edac/octeon_edac-l2c.c +++ b/drivers/edac/octeon_edac-l2c.c @@ -3,6 +3,8 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. * + * Copyright (C) 2012 Cavium, Inc. + * * Copyright (C) 2009 Wind River Systems, * written by Ralf Baechle */ @@ -19,32 +21,124 @@ #define EDAC_MOD_STR "octeon-l2c" -static void co_l2c_poll(struct edac_device_ctl_info *l2c) +static void octeon_l2c_poll_oct1(struct edac_device_ctl_info *l2c) { - union cvmx_l2t_err l2t_err; + union cvmx_l2t_err l2t_err, l2t_err_reset; + union cvmx_l2d_err l2d_err, l2d_err_reset; + l2t_err_reset.u64 = 0; l2t_err.u64 = cvmx_read_csr(CVMX_L2T_ERR); if (l2t_err.s.sec_err) { edac_device_handle_ce(l2c, 0, 0, - "Single bit error (corrected)"); - l2t_err.s.sec_err = 1; /* Reset */ - cvmx_write_csr(CVMX_L2T_ERR, l2t_err.u64); + "Tag Single bit error (corrected)"); + l2t_err_reset.s.sec_err = 1; } if (l2t_err.s.ded_err) { edac_device_handle_ue(l2c, 0, 0, - "Double bit error (corrected)"); - l2t_err.s.ded_err = 1; /* Reset */ - cvmx_write_csr(CVMX_L2T_ERR, l2t_err.u64); + "Tag Double bit error (detected)"); + l2t_err_reset.s.ded_err = 1; + } + if (l2t_err_reset.u64) + cvmx_write_csr(CVMX_L2T_ERR, l2t_err_reset.u64); + + l2d_err_reset.u64 = 0; + l2d_err.u64 = cvmx_read_csr(CVMX_L2D_ERR); + if (l2d_err.s.sec_err) { + edac_device_handle_ce(l2c, 0, 1, + "Data Single bit error (corrected)"); + l2d_err_reset.s.sec_err = 1; + } + if (l2d_err.s.ded_err) { + edac_device_handle_ue(l2c, 0, 1, + "Data Double bit error (detected)"); + l2d_err_reset.s.ded_err = 1; + } + if (l2d_err_reset.u64) + cvmx_write_csr(CVMX_L2D_ERR, l2d_err_reset.u64); + +} + +static void _octeon_l2c_poll_oct2(struct edac_device_ctl_info *l2c, int tad) +{ + union cvmx_l2c_err_tdtx err_tdtx, err_tdtx_reset; + union cvmx_l2c_err_ttgx err_ttgx, err_ttgx_reset; + char buf1[64]; + char buf2[80]; + + err_tdtx_reset.u64 = 0; + err_tdtx.u64 = cvmx_read_csr(CVMX_L2C_ERR_TDTX(tad)); + if (err_tdtx.s.dbe || err_tdtx.s.sbe || + err_tdtx.s.vdbe || err_tdtx.s.vsbe) + snprintf(buf1, sizeof(buf1), + "type:%d, syn:0x%x, way:%d", + err_tdtx.s.type, err_tdtx.s.syn, err_tdtx.s.wayidx); + + if (err_tdtx.s.dbe) { + snprintf(buf2, sizeof(buf2), + "L2D Double bit error (detected):%s", buf1); + err_tdtx_reset.s.dbe = 1; + edac_device_handle_ue(l2c, tad, 1, buf2); + } + if (err_tdtx.s.sbe) { + snprintf(buf2, sizeof(buf2), + "L2D Single bit error (corrected):%s", buf1); + err_tdtx_reset.s.sbe = 1; + edac_device_handle_ce(l2c, tad, 1, buf2); + } + if (err_tdtx.s.vdbe) { + snprintf(buf2, sizeof(buf2), + "VBF Double bit error (detected):%s", buf1); + err_tdtx_reset.s.vdbe = 1; + edac_device_handle_ue(l2c, tad, 1, buf2); + } + if (err_tdtx.s.vsbe) { + snprintf(buf2, sizeof(buf2), + "VBF Single bit error (corrected):%s", buf1); + err_tdtx_reset.s.vsbe = 1; + edac_device_handle_ce(l2c, tad, 1, buf2); + } + if (err_tdtx_reset.u64) + cvmx_write_csr(CVMX_L2C_ERR_TDTX(tad), err_tdtx_reset.u64); + + err_ttgx_reset.u64 = 0; + err_ttgx.u64 = cvmx_read_csr(CVMX_L2C_ERR_TTGX(tad)); + + if (err_ttgx.s.dbe || err_ttgx.s.sbe) + snprintf(buf1, sizeof(buf1), + "type:%d, syn:0x%x, way:%d", + err_ttgx.s.type, err_ttgx.s.syn, err_ttgx.s.wayidx); + + if (err_ttgx.s.dbe) { + snprintf(buf2, sizeof(buf2), + "Tag Double bit error (detected):%s", buf1); + err_ttgx_reset.s.dbe = 1; + edac_device_handle_ue(l2c, tad, 0, buf2); } + if (err_ttgx.s.sbe) { + snprintf(buf2, sizeof(buf2), + "Tag Single bit error (corrected):%s", buf1); + err_ttgx_reset.s.sbe = 1; + edac_device_handle_ce(l2c, tad, 0, buf2); + } + if (err_ttgx_reset.u64) + cvmx_write_csr(CVMX_L2C_ERR_TTGX(tad), err_ttgx_reset.u64); +} + +static void octeon_l2c_poll_oct2(struct edac_device_ctl_info *l2c) +{ + int i; + for (i = 0; i < l2c->nr_instances; i++) + _octeon_l2c_poll_oct2(l2c, i); } -static int __devinit co_l2c_probe(struct platform_device *pdev) +static int __devinit octeon_l2c_probe(struct platform_device *pdev) { struct edac_device_ctl_info *l2c; - union cvmx_l2t_err l2t_err; - int res = 0; - l2c = edac_device_alloc_ctl_info(0, "l2c", 1, NULL, 0, 0, + int num_tads = OCTEON_IS_MODEL(OCTEON_CN68XX) ? 4 : 1; + + /* 'Tags' are block 0, 'Data' is block 1*/ + l2c = edac_device_alloc_ctl_info(0, "l2c", num_tads, "l2c", 2, 0, NULL, 0, edac_device_alloc_index()); if (!l2c) return -ENOMEM; @@ -55,29 +149,43 @@ static int __devinit co_l2c_probe(struct platform_device *pdev) l2c->mod_name = "octeon-l2c"; l2c->ctl_name = "octeon_l2c_err"; - l2c->edac_check = co_l2c_poll; + + + if (OCTEON_IS_MODEL(OCTEON_FAM_1_PLUS)) { + union cvmx_l2t_err l2t_err; + union cvmx_l2d_err l2d_err; + + l2t_err.u64 = cvmx_read_csr(CVMX_L2T_ERR); + l2t_err.s.sec_intena = 0; /* We poll */ + l2t_err.s.ded_intena = 0; + cvmx_write_csr(CVMX_L2T_ERR, l2t_err.u64); + + l2d_err.u64 = cvmx_read_csr(CVMX_L2D_ERR); + l2d_err.s.sec_intena = 0; /* We poll */ + l2d_err.s.ded_intena = 0; + cvmx_write_csr(CVMX_L2T_ERR, l2d_err.u64); + + l2c->edac_check = octeon_l2c_poll_oct1; + } else { + /* OCTEON II */ + l2c->edac_check = octeon_l2c_poll_oct2; + } if (edac_device_add_device(l2c) > 0) { pr_err("%s: edac_device_add_device() failed\n", __func__); goto err; } - l2t_err.u64 = cvmx_read_csr(CVMX_L2T_ERR); - l2t_err.s.sec_intena = 0; /* We poll */ - l2t_err.s.ded_intena = 0; - l2t_err.s.sec_err = 1; /* Clear, just in case */ - l2t_err.s.ded_err = 1; - cvmx_write_csr(CVMX_L2T_ERR, l2t_err.u64); return 0; err: edac_device_free_ctl_info(l2c); - return res; + return -ENXIO; } -static int co_l2c_remove(struct platform_device *pdev) +static int octeon_l2c_remove(struct platform_device *pdev) { struct edac_device_ctl_info *l2c = platform_get_drvdata(pdev); @@ -87,32 +195,14 @@ static int co_l2c_remove(struct platform_device *pdev) return 0; } -static struct platform_driver co_l2c_driver = { - .probe = co_l2c_probe, - .remove = co_l2c_remove, +static struct platform_driver octeon_l2c_driver = { + .probe = octeon_l2c_probe, + .remove = octeon_l2c_remove, .driver = { - .name = "co_l2c_edac", + .name = "octeon_l2c_edac", } }; - -static int __init co_edac_init(void) -{ - int ret; - - ret = platform_driver_register(&co_l2c_driver); - if (ret) - pr_warning(EDAC_MOD_STR " EDAC failed to register\n"); - - return ret; -} - -static void __exit co_edac_exit(void) -{ - platform_driver_unregister(&co_l2c_driver); -} - -module_init(co_edac_init); -module_exit(co_edac_exit); +module_platform_driver(octeon_l2c_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ralf Baechle "); diff --git a/drivers/edac/octeon_edac-lmc.c b/drivers/edac/octeon_edac-lmc.c index e0c1e44187b..33bca766e37 100644 --- a/drivers/edac/octeon_edac-lmc.c +++ b/drivers/edac/octeon_edac-lmc.c @@ -12,139 +12,175 @@ #include #include -#include +#include +#include #include "edac_core.h" #include "edac_module.h" -#include "octeon_edac-lmc.h" -#define EDAC_MOD_STR "octeon" +#define OCTEON_MAX_MC 4 -static struct mem_ctl_info *mc_cavium; -static void *lmc_base; - -static void co_lmc_poll(struct mem_ctl_info *mci) +static void octeon_lmc_edac_poll(struct mem_ctl_info *mci) { - union lmc_mem_cfg0 cfg0; - union lmc_fadr fadr; + union cvmx_lmcx_mem_cfg0 cfg0; + bool do_clear = false; char msg[64]; - fadr.u64 = readq(lmc_base + LMC_FADR); - cfg0.u64 = readq(lmc_base + LMC_MEM_CFG0); - snprintf(msg, sizeof(msg), "DIMM %d rank %d bank %d row %d col %d", - fadr.fdimm, fadr.fbunk, fadr.fbank, fadr.frow, fadr.fcol); - - if (cfg0.sec_err) { - edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 0, 0, 0, -1, -1, -1, - msg, ""); - - cfg0.intr_sec_ena = -1; /* Done, re-arm */ + cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mci->mc_idx)); + if (cfg0.s.sec_err || cfg0.s.ded_err) { + union cvmx_lmcx_fadr fadr; + fadr.u64 = cvmx_read_csr(CVMX_LMCX_FADR(mci->mc_idx)); + snprintf(msg, sizeof(msg), + "DIMM %d rank %d bank %d row %d col %d", + fadr.cn30xx.fdimm, fadr.cn30xx.fbunk, + fadr.cn30xx.fbank, fadr.cn30xx.frow, fadr.cn30xx.fcol); } - if (cfg0.ded_err) { - edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 0, 0, 0, -1, -1, -1, - msg, ""); - cfg0.intr_ded_ena = -1; /* Done, re-arm */ + if (cfg0.s.sec_err) { + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 0, 0, 0, + -1, -1, -1, msg, ""); + cfg0.s.sec_err = -1; /* Done, re-arm */ + do_clear = true; } - writeq(cfg0.u64, lmc_base + LMC_MEM_CFG0); + if (cfg0.s.ded_err) { + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 0, 0, 0, + -1, -1, -1, msg, ""); + cfg0.s.ded_err = -1; /* Done, re-arm */ + do_clear = true; + } + if (do_clear) + cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mci->mc_idx), cfg0.u64); } -static int __devinit co_lmc_probe(struct platform_device *pdev) +static void octeon_lmc_edac_poll_o2(struct mem_ctl_info *mci) { - struct mem_ctl_info *mci; - union lmc_mem_cfg0 cfg0; - int res = 0; - - mci = edac_mc_alloc(0, 0, 0, 0); - if (!mci) - return -ENOMEM; - - mci->pdev = &pdev->dev; - platform_set_drvdata(pdev, mci); - mci->dev_name = dev_name(&pdev->dev); + union cvmx_lmcx_int int_reg; + bool do_clear = false; + char msg[64]; - mci->mod_name = "octeon-lmc"; - mci->ctl_name = "co_lmc_err"; - mci->edac_check = co_lmc_poll; + int_reg.u64 = cvmx_read_csr(CVMX_LMCX_INT(mci->mc_idx)); + if (int_reg.s.sec_err || int_reg.s.ded_err) { + union cvmx_lmcx_fadr fadr; + fadr.u64 = cvmx_read_csr(CVMX_LMCX_FADR(mci->mc_idx)); + snprintf(msg, sizeof(msg), + "DIMM %d rank %d bank %d row %d col %d", + fadr.cn61xx.fdimm, fadr.cn61xx.fbunk, + fadr.cn61xx.fbank, fadr.cn61xx.frow, fadr.cn61xx.fcol); + } - if (edac_mc_add_mc(mci) > 0) { - pr_err("%s: edac_mc_add_mc() failed\n", __func__); - goto err; + if (int_reg.s.sec_err) { + edac_mc_handle_error(HW_EVENT_ERR_CORRECTED, mci, 1, 0, 0, 0, + -1, -1, -1, msg, ""); + int_reg.s.sec_err = -1; /* Done, re-arm */ + do_clear = true; } - cfg0.u64 = readq(lmc_base + LMC_MEM_CFG0); /* We poll */ - cfg0.intr_ded_ena = 0; - cfg0.intr_sec_ena = 0; - writeq(cfg0.u64, lmc_base + LMC_MEM_CFG0); + if (int_reg.s.ded_err) { + edac_mc_handle_error(HW_EVENT_ERR_UNCORRECTED, mci, 1, 0, 0, 0, + -1, -1, -1, msg, ""); + int_reg.s.ded_err = -1; /* Done, re-arm */ + do_clear = true; + } + if (do_clear) + cvmx_write_csr(CVMX_LMCX_INT(mci->mc_idx), int_reg.u64); +} - mc_cavium = mci; +static int __devinit octeon_lmc_edac_probe(struct platform_device *pdev) +{ + struct mem_ctl_info *mci; + struct edac_mc_layer layers[1]; + int mc = pdev->id; + + layers[0].type = EDAC_MC_LAYER_CHANNEL; + layers[0].size = 1; + layers[0].is_virt_csrow = false; + + if (OCTEON_IS_MODEL(OCTEON_FAM_1_PLUS)) { + union cvmx_lmcx_mem_cfg0 cfg0; + + cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(0)); + if (!cfg0.s.ecc_ena) { + dev_info(&pdev->dev, "Disabled (ECC not enabled)\n"); + return 0; + } + + mci = edac_mc_alloc(mc, ARRAY_SIZE(layers), layers, 0); + if (!mci) + return -ENXIO; + + mci->pdev = &pdev->dev; + mci->dev_name = dev_name(&pdev->dev); + + mci->mod_name = "octeon-lmc"; + mci->ctl_name = "octeon-lmc-err"; + mci->edac_check = octeon_lmc_edac_poll; + + if (edac_mc_add_mc(mci)) { + dev_err(&pdev->dev, "edac_mc_add_mc() failed\n"); + edac_mc_free(mci); + return -ENXIO; + } + + cfg0.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mc)); + cfg0.s.intr_ded_ena = 0; /* We poll */ + cfg0.s.intr_sec_ena = 0; + cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mc), cfg0.u64); + } else { + /* OCTEON II */ + union cvmx_lmcx_int_en en; + union cvmx_lmcx_config config; + + config.u64 = cvmx_read_csr(CVMX_LMCX_CONFIG(0)); + if (!config.s.ecc_ena) { + dev_info(&pdev->dev, "Disabled (ECC not enabled)\n"); + return 0; + } + + mci = edac_mc_alloc(mc, ARRAY_SIZE(layers), layers, 0); + if (!mci) + return -ENXIO; + + mci->pdev = &pdev->dev; + mci->dev_name = dev_name(&pdev->dev); + + mci->mod_name = "octeon-lmc"; + mci->ctl_name = "co_lmc_err"; + mci->edac_check = octeon_lmc_edac_poll_o2; + + if (edac_mc_add_mc(mci)) { + dev_err(&pdev->dev, "edac_mc_add_mc() failed\n"); + edac_mc_free(mci); + return -ENXIO; + } + + en.u64 = cvmx_read_csr(CVMX_LMCX_MEM_CFG0(mc)); + en.s.intr_ded_ena = 0; /* We poll */ + en.s.intr_sec_ena = 0; + cvmx_write_csr(CVMX_LMCX_MEM_CFG0(mc), en.u64); + } + platform_set_drvdata(pdev, mci); return 0; - -err: - edac_mc_free(mci); - - return res; } -static int co_lmc_remove(struct platform_device *pdev) +static int octeon_lmc_edac_remove(struct platform_device *pdev) { struct mem_ctl_info *mci = platform_get_drvdata(pdev); - mc_cavium = NULL; edac_mc_del_mc(&pdev->dev); edac_mc_free(mci); - return 0; } -static struct platform_driver co_lmc_driver = { - .probe = co_lmc_probe, - .remove = co_lmc_remove, +static struct platform_driver octeon_lmc_edac_driver = { + .probe = octeon_lmc_edac_probe, + .remove = octeon_lmc_edac_remove, .driver = { - .name = "co_lmc_edac", + .name = "octeon_lmc_edac", } }; - -static int __init co_edac_init(void) -{ - union lmc_mem_cfg0 cfg0; - int ret; - - lmc_base = ioremap_nocache(LMC_BASE, LMC_SIZE); - if (!lmc_base) - return -ENOMEM; - - cfg0.u64 = readq(lmc_base + LMC_MEM_CFG0); - if (!cfg0.ecc_ena) { - pr_info(EDAC_MOD_STR " LMC EDAC: ECC disabled, good bye\n"); - ret = -ENODEV; - goto out; - } - - ret = platform_driver_register(&co_lmc_driver); - if (ret) { - pr_warning(EDAC_MOD_STR " LMC EDAC failed to register\n"); - goto out; - } - - return ret; - -out: - iounmap(lmc_base); - - return ret; -} - -static void __exit co_edac_exit(void) -{ - platform_driver_unregister(&co_lmc_driver); - iounmap(lmc_base); -} - -module_init(co_edac_init); -module_exit(co_edac_exit); +module_platform_driver(octeon_lmc_edac_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ralf Baechle "); diff --git a/drivers/edac/octeon_edac-lmc.h b/drivers/edac/octeon_edac-lmc.h deleted file mode 100644 index 246dc525bc1..00000000000 --- a/drivers/edac/octeon_edac-lmc.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * LMC Registers, see chapter 2.5 - * - * These are RSL Type registers and are accessed indirectly across the - * I/O bus, so accesses are slowish. Not that it matters. Any size load is - * ok but stores must be 64-bit. - */ -#define LMC_BASE 0x0001180088000000 -#define LMC_SIZE 0xb8 - -#define LMC_MEM_CFG0 0x0000000000000000 -#define LMC_MEM_CFG1 0x0000000000000008 -#define LMC_CTL 0x0000000000000010 -#define LMC_DDR2_CTL 0x0000000000000018 -#define LMC_FADR 0x0000000000000020 -#define LMC_FADR_FDIMM -#define LMC_FADR_FBUNK -#define LMC_FADR_FBANK -#define LMC_FADR_FROW -#define LMC_FADR_FCOL -#define LMC_COMP_CTL 0x0000000000000028 -#define LMC_WODT_CTL 0x0000000000000030 -#define LMC_ECC_SYND 0x0000000000000038 -#define LMC_IFB_CNT_LO 0x0000000000000048 -#define LMC_IFB_CNT_HI 0x0000000000000050 -#define LMC_OPS_CNT_LO 0x0000000000000058 -#define LMC_OPS_CNT_HI 0x0000000000000060 -#define LMC_DCLK_CNT_LO 0x0000000000000068 -#define LMC_DCLK_CNT_HI 0x0000000000000070 -#define LMC_DELAY_CFG 0x0000000000000088 -#define LMC_CTL1 0x0000000000000090 -#define LMC_DUAL_MEM_CONFIG 0x0000000000000098 -#define LMC_RODT_COMP_CTL 0x00000000000000A0 -#define LMC_PLL_CTL 0x00000000000000A8 -#define LMC_PLL_STATUS 0x00000000000000B0 - -union lmc_mem_cfg0 { - uint64_t u64; - struct { - uint64_t reserved_32_63:32; - uint64_t reset:1; - uint64_t silo_qc:1; - uint64_t bunk_ena:1; - uint64_t ded_err:4; - uint64_t sec_err:4; - uint64_t intr_ded_ena:1; - uint64_t intr_sec_ena:1; - uint64_t reserved_15_18:4; - uint64_t ref_int:5; - uint64_t pbank_lsb:4; - uint64_t row_lsb:3; - uint64_t ecc_ena:1; - uint64_t init_start:1; - }; -}; - -union lmc_fadr { - uint64_t u64; - struct { - uint64_t reserved_32_63:32; - uint64_t fdimm:2; - uint64_t fbunk:1; - uint64_t fbank:3; - uint64_t frow:14; - uint64_t fcol:12; - }; -}; - -union lmc_ecc_synd { - uint64_t u64; - struct { - uint64_t reserved_32_63:32; - uint64_t mrdsyn3:8; - uint64_t mrdsyn2:8; - uint64_t mrdsyn1:8; - uint64_t mrdsyn0:8; - }; -}; diff --git a/drivers/edac/octeon_edac-pc.c b/drivers/edac/octeon_edac-pc.c index 9d13061744e..14a5e57f2b3 100644 --- a/drivers/edac/octeon_edac-pc.c +++ b/drivers/edac/octeon_edac-pc.c @@ -3,6 +3,8 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. * + * Copyright (C) 2012 Cavium, Inc. + * * Copyright (C) 2009 Wind River Systems, * written by Ralf Baechle */ @@ -19,93 +21,112 @@ #include #include -#define EDAC_MOD_STR "octeon" - extern int register_co_cache_error_notifier(struct notifier_block *nb); extern int unregister_co_cache_error_notifier(struct notifier_block *nb); extern unsigned long long cache_err_dcache[NR_CPUS]; -static struct edac_device_ctl_info *ed_cavium; +struct co_cache_error { + struct notifier_block notifier; + struct edac_device_ctl_info *ed; +}; -/* +/** * EDAC CPU cache error callback * + * @event: non-zero if unrecoverable. */ - static int co_cache_error_event(struct notifier_block *this, unsigned long event, void *ptr) { + struct co_cache_error *p = container_of(this, struct co_cache_error, + notifier); + unsigned int core = cvmx_get_core_num(); unsigned int cpu = smp_processor_id(); - uint64_t icache_err = read_octeon_c0_icacheerr(); - struct edac_device_ctl_info *ed = ed_cavium; - - edac_device_printk(ed, KERN_ERR, - "Cache error exception on core %d / processor %d:\n", - core, cpu); - edac_device_printk(ed, KERN_ERR, - "cp0_errorepc == %lx\n", read_c0_errorepc()); + u64 icache_err = read_octeon_c0_icacheerr(); + u64 dcache_err; + + if (event) { + dcache_err = cache_err_dcache[core]; + cache_err_dcache[core] = 0; + } else { + dcache_err = read_octeon_c0_dcacheerr(); + } + if (icache_err & 1) { - edac_device_printk(ed, KERN_ERR, "CacheErr (Icache) == %llx\n", - (unsigned long long)icache_err); + edac_device_printk(p->ed, KERN_ERR, + "CacheErr (Icache):%llx, core %d/cpu %d, cp0_errorepc == %lx\n", + (unsigned long long)icache_err, core, cpu, + read_c0_errorepc()); write_octeon_c0_icacheerr(0); - edac_device_handle_ce(ed, 0, 0, ed->ctl_name); + edac_device_handle_ce(p->ed, cpu, 1, "icache"); } - if (cache_err_dcache[core] & 1) { - edac_device_printk(ed, KERN_ERR, "CacheErr (Dcache) == %llx\n", - (unsigned long long)cache_err_dcache[core]); - cache_err_dcache[core] = 0; - edac_device_handle_ue(ed, 0, 0, ed->ctl_name); + if (dcache_err & 1) { + edac_device_printk(p->ed, KERN_ERR, + "CacheErr (Dcache):%llx, core %d/cpu %d, cp0_errorepc == %lx\n", + (unsigned long long)dcache_err, core, cpu, + read_c0_errorepc()); + if (event) + edac_device_handle_ue(p->ed, cpu, 0, "dcache"); + else + edac_device_handle_ce(p->ed, cpu, 0, "dcache"); + + /* Clear the error indication */ + if (OCTEON_IS_MODEL(OCTEON_FAM_2)) + write_octeon_c0_dcacheerr(1); + else + write_octeon_c0_dcacheerr(0); } - return NOTIFY_DONE; + return NOTIFY_STOP; } -static struct notifier_block co_cache_error_notifier = { - .notifier_call = co_cache_error_event, -}; - static int __devinit co_cache_error_probe(struct platform_device *pdev) { - struct edac_device_ctl_info *ed; - int res = 0; + struct co_cache_error *p = devm_kzalloc(&pdev->dev, sizeof(*p), + GFP_KERNEL); + if (!p) + return -ENOMEM; + + p->notifier.notifier_call = co_cache_error_event; + platform_set_drvdata(pdev, p); + + p->ed = edac_device_alloc_ctl_info(0, "cpu", num_possible_cpus(), + "cache", 2, 0, NULL, 0, + edac_device_alloc_index()); + if (!p->ed) + goto err; - ed = edac_device_alloc_ctl_info(0, "cpu", 1, NULL, 0, 0, NULL, 0, - edac_device_alloc_index()); + p->ed->dev = &pdev->dev; - ed->dev = &pdev->dev; - platform_set_drvdata(pdev, ed); - ed->dev_name = dev_name(&pdev->dev); + p->ed->dev_name = dev_name(&pdev->dev); - ed->mod_name = "octeon-cpu"; - ed->ctl_name = "co_cpu_err"; + p->ed->mod_name = "octeon-cpu"; + p->ed->ctl_name = "cache"; - if (edac_device_add_device(ed) > 0) { + if (edac_device_add_device(p->ed)) { pr_err("%s: edac_device_add_device() failed\n", __func__); - goto err; + goto err1; } - register_co_cache_error_notifier(&co_cache_error_notifier); - ed_cavium = ed; + register_co_cache_error_notifier(&p->notifier); return 0; +err1: + edac_device_free_ctl_info(p->ed); err: - edac_device_free_ctl_info(ed); - - return res; + return -ENXIO; } static int co_cache_error_remove(struct platform_device *pdev) { - struct edac_device_ctl_info *ed = platform_get_drvdata(pdev); + struct co_cache_error *p = platform_get_drvdata(pdev); - unregister_co_cache_error_notifier(&co_cache_error_notifier); - ed_cavium = NULL; + unregister_co_cache_error_notifier(&p->notifier); edac_device_del_device(&pdev->dev); - edac_device_free_ctl_info(ed); - + edac_device_free_ctl_info(p->ed); return 0; } @@ -113,28 +134,10 @@ static struct platform_driver co_cache_error_driver = { .probe = co_cache_error_probe, .remove = co_cache_error_remove, .driver = { - .name = "co_pc_edac", + .name = "octeon_pc_edac", } }; - -static int __init co_edac_init(void) -{ - int ret; - - ret = platform_driver_register(&co_cache_error_driver); - if (ret) - pr_warning(EDAC_MOD_STR "CPU err failed to register\n"); - - return ret; -} - -static void __exit co_edac_exit(void) -{ - platform_driver_unregister(&co_cache_error_driver); -} - -module_init(co_edac_init); -module_exit(co_edac_exit); +module_platform_driver(co_cache_error_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ralf Baechle "); diff --git a/drivers/edac/octeon_edac-pci.c b/drivers/edac/octeon_edac-pci.c index e72b96e3e4e..758c1ef5fc9 100644 --- a/drivers/edac/octeon_edac-pci.c +++ b/drivers/edac/octeon_edac-pci.c @@ -3,6 +3,7 @@ * License. See the file "COPYING" in the main directory of this archive * for more details. * + * Copyright (C) 2012 Cavium, Inc. * Copyright (C) 2009 Wind River Systems, * written by Ralf Baechle */ @@ -20,9 +21,7 @@ #include "edac_core.h" #include "edac_module.h" -#define EDAC_MOD_STR "octeon" - -static void co_pci_poll(struct edac_pci_ctl_info *pci) +static void octeon_pci_poll(struct edac_pci_ctl_info *pci) { union cvmx_pci_cfg01 cfg01; @@ -57,14 +56,9 @@ static void co_pci_poll(struct edac_pci_ctl_info *pci) cfg01.s.mdpe = 1; /* Reset */ octeon_npi_write32(CVMX_NPI_PCI_CFG01, cfg01.u32); } - if (cfg01.s.mdpe) { - edac_pci_handle_npe(pci, "Master Data Parity Error"); - cfg01.s.mdpe = 1; /* Reset */ - octeon_npi_write32(CVMX_NPI_PCI_CFG01, cfg01.u32); - } } -static int __devinit co_pci_probe(struct platform_device *pdev) +static int __devinit octeon_pci_probe(struct platform_device *pdev) { struct edac_pci_ctl_info *pci; int res = 0; @@ -79,7 +73,7 @@ static int __devinit co_pci_probe(struct platform_device *pdev) pci->mod_name = "octeon-pci"; pci->ctl_name = "octeon_pci_err"; - pci->edac_check = co_pci_poll; + pci->edac_check = octeon_pci_poll; if (edac_pci_add_device(pci, 0) > 0) { pr_err("%s: edac_pci_add_device() failed\n", __func__); @@ -94,7 +88,7 @@ err: return res; } -static int co_pci_remove(struct platform_device *pdev) +static int octeon_pci_remove(struct platform_device *pdev) { struct edac_pci_ctl_info *pci = platform_get_drvdata(pdev); @@ -104,32 +98,14 @@ static int co_pci_remove(struct platform_device *pdev) return 0; } -static struct platform_driver co_pci_driver = { - .probe = co_pci_probe, - .remove = co_pci_remove, +static struct platform_driver octeon_pci_driver = { + .probe = octeon_pci_probe, + .remove = octeon_pci_remove, .driver = { - .name = "co_pci_edac", + .name = "octeon_pci_edac", } }; - -static int __init co_edac_init(void) -{ - int ret; - - ret = platform_driver_register(&co_pci_driver); - if (ret) - pr_warning(EDAC_MOD_STR " PCI EDAC failed to register\n"); - - return ret; -} - -static void __exit co_edac_exit(void) -{ - platform_driver_unregister(&co_pci_driver); -} - -module_init(co_edac_init); -module_exit(co_edac_exit); +module_platform_driver(octeon_pci_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Ralf Baechle "); -- cgit v1.2.3-70-g09d2