diff options
Diffstat (limited to 'drivers/watchdog/octeon-wdt-main.c')
| -rw-r--r-- | drivers/watchdog/octeon-wdt-main.c | 595 |
1 files changed, 227 insertions, 368 deletions
diff --git a/drivers/watchdog/octeon-wdt-main.c b/drivers/watchdog/octeon-wdt-main.c index 461208831428..0615bb816082 100644 --- a/drivers/watchdog/octeon-wdt-main.c +++ b/drivers/watchdog/octeon-wdt-main.c @@ -1,29 +1,22 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * Octeon Watchdog driver * - * Copyright (C) 2007, 2008, 2009, 2010 Cavium Networks + * Copyright (C) 2007-2017 Cavium, Inc. + * + * Converted to use WATCHDOG_CORE by Aaro Koskinen <aaro.koskinen@iki.fi>. * * Some parts derived from wdt.c * * (c) Copyright 1996-1997 Alan Cox <alan@lxorguk.ukuu.org.uk>, * All Rights Reserved. * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version - * 2 of the License, or (at your option) any later version. - * * Neither Alan Cox nor CymruNet Ltd. admit liability nor provide * warranty for any of this software. This material is provided * "AS-IS" and at no charge. * * (c) Copyright 1995 Alan Cox <alan@lxorguk.ukuu.org.uk> * - * This file is subject to the terms and conditions of the GNU General Public - * License. See the file "COPYING" in the main directory of this archive - * for more details. - * - * * The OCTEON watchdog has a maximum timeout of 2^32 * io_clock. * For most systems this is less than 10 seconds, so to allow for * software to request longer watchdog heartbeats, we maintain software @@ -54,24 +47,27 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt -#include <linux/miscdevice.h> #include <linux/interrupt.h> #include <linux/watchdog.h> #include <linux/cpumask.h> -#include <linux/bitops.h> -#include <linux/kernel.h> #include <linux/module.h> -#include <linux/string.h> #include <linux/delay.h> #include <linux/cpu.h> -#include <linux/smp.h> -#include <linux/fs.h> #include <linux/irq.h> +#include <linux/irqdomain.h> #include <asm/mipsregs.h> #include <asm/uasm.h> #include <asm/octeon/octeon.h> +#include <asm/octeon/cvmx-boot-vector.h> +#include <asm/octeon/cvmx-ciu2-defs.h> +#include <asm/octeon/cvmx-rst-defs.h> + +/* Watchdog interrupt major block number (8 MSBs of intsn) */ +#define WD_BLOCK_NUMBER 0x01 + +static int divisor; /* The count needed to achieve timeout_sec. */ static unsigned int timeout_cnt; @@ -83,7 +79,7 @@ static unsigned int max_timeout_sec; static unsigned int timeout_sec; /* Set to non-zero when userspace countdown mode active */ -static int do_coundown; +static bool do_countdown; static unsigned int countdown_reset; static unsigned int per_cpu_countdown[NR_CPUS]; @@ -91,150 +87,40 @@ static cpumask_t irq_enabled_cpus; #define WD_TIMO 60 /* Default heartbeat = 60 seconds */ +#define CVMX_GSERX_SCRATCH(offset) (CVMX_ADD_IO_SEG(0x0001180090000020ull) + ((offset) & 15) * 0x1000000ull) + static int heartbeat = WD_TIMO; -module_param(heartbeat, int, S_IRUGO); +module_param(heartbeat, int, 0444); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0 < heartbeat, default=" __MODULE_STRING(WD_TIMO) ")"); static bool nowayout = WATCHDOG_NOWAYOUT; -module_param(nowayout, bool, S_IRUGO); +module_param(nowayout, bool, 0444); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -static unsigned long octeon_wdt_is_open; -static char expect_close; +static int disable; +module_param(disable, int, 0444); +MODULE_PARM_DESC(disable, + "Disable the watchdog entirely (default=0)"); -static u32 __initdata nmi_stage1_insns[64]; -/* We need one branch and therefore one relocation per target label. */ -static struct uasm_label __initdata labels[5]; -static struct uasm_reloc __initdata relocs[5]; - -enum lable_id { - label_enter_bootloader = 1 -}; - -/* Some CP0 registers */ -#define K0 26 -#define C0_CVMMEMCTL 11, 7 -#define C0_STATUS 12, 0 -#define C0_EBASE 15, 1 -#define C0_DESAVE 31, 0 +static struct cvmx_boot_vector_element *octeon_wdt_bootvector; void octeon_wdt_nmi_stage2(void); -static void __init octeon_wdt_build_stage1(void) -{ - int i; - int len; - u32 *p = nmi_stage1_insns; -#ifdef CONFIG_HOTPLUG_CPU - struct uasm_label *l = labels; - struct uasm_reloc *r = relocs; -#endif - - /* - * For the next few instructions running the debugger may - * cause corruption of k0 in the saved registers. Since we're - * about to crash, nobody probably cares. - * - * Save K0 into the debug scratch register - */ - uasm_i_dmtc0(&p, K0, C0_DESAVE); - - uasm_i_mfc0(&p, K0, C0_STATUS); -#ifdef CONFIG_HOTPLUG_CPU - uasm_il_bbit0(&p, &r, K0, ilog2(ST0_NMI), label_enter_bootloader); -#endif - /* Force 64-bit addressing enabled */ - uasm_i_ori(&p, K0, K0, ST0_UX | ST0_SX | ST0_KX); - uasm_i_mtc0(&p, K0, C0_STATUS); - -#ifdef CONFIG_HOTPLUG_CPU - uasm_i_mfc0(&p, K0, C0_EBASE); - /* Coreid number in K0 */ - uasm_i_andi(&p, K0, K0, 0xf); - /* 8 * coreid in bits 16-31 */ - uasm_i_dsll_safe(&p, K0, K0, 3 + 16); - uasm_i_ori(&p, K0, K0, 0x8001); - uasm_i_dsll_safe(&p, K0, K0, 16); - uasm_i_ori(&p, K0, K0, 0x0700); - uasm_i_drotr_safe(&p, K0, K0, 32); - /* - * Should result in: 0x8001,0700,0000,8*coreid which is - * CVMX_CIU_WDOGX(coreid) - 0x0500 - * - * Now ld K0, CVMX_CIU_WDOGX(coreid) - */ - uasm_i_ld(&p, K0, 0x500, K0); - /* - * If bit one set handle the NMI as a watchdog event. - * otherwise transfer control to bootloader. - */ - uasm_il_bbit0(&p, &r, K0, 1, label_enter_bootloader); - uasm_i_nop(&p); -#endif - - /* Clear Dcache so cvmseg works right. */ - uasm_i_cache(&p, 1, 0, 0); - - /* Use K0 to do a read/modify/write of CVMMEMCTL */ - uasm_i_dmfc0(&p, K0, C0_CVMMEMCTL); - /* Clear out the size of CVMSEG */ - uasm_i_dins(&p, K0, 0, 0, 6); - /* Set CVMSEG to its largest value */ - uasm_i_ori(&p, K0, K0, 0x1c0 | 54); - /* Store the CVMMEMCTL value */ - uasm_i_dmtc0(&p, K0, C0_CVMMEMCTL); - - /* Load the address of the second stage handler */ - UASM_i_LA(&p, K0, (long)octeon_wdt_nmi_stage2); - uasm_i_jr(&p, K0); - uasm_i_dmfc0(&p, K0, C0_DESAVE); - -#ifdef CONFIG_HOTPLUG_CPU - uasm_build_label(&l, p, label_enter_bootloader); - /* Jump to the bootloader and restore K0 */ - UASM_i_LA(&p, K0, (long)octeon_bootloader_entry_addr); - uasm_i_jr(&p, K0); - uasm_i_dmfc0(&p, K0, C0_DESAVE); -#endif - uasm_resolve_relocs(relocs, labels); - - len = (int)(p - nmi_stage1_insns); - pr_debug("Synthesized NMI stage 1 handler (%d instructions)\n", len); - - pr_debug("\t.set push\n"); - pr_debug("\t.set noreorder\n"); - for (i = 0; i < len; i++) - pr_debug("\t.word 0x%08x\n", nmi_stage1_insns[i]); - pr_debug("\t.set pop\n"); - - if (len > 32) - panic("NMI stage 1 handler exceeds 32 instructions, was %d\n", len); -} - static int cpu2core(int cpu) { #ifdef CONFIG_SMP - return cpu_logical_map(cpu); + return cpu_logical_map(cpu) & 0x3f; #else return cvmx_get_core_num(); #endif } -static int core2cpu(int coreid) -{ -#ifdef CONFIG_SMP - return cpu_number_map(coreid); -#else - return 0; -#endif -} - /** - * Poke the watchdog when an interrupt is received + * octeon_wdt_poke_irq - Poke the watchdog when an interrupt is received * * @cpl: * @dev_id: @@ -243,13 +129,14 @@ static int core2cpu(int coreid) */ static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id) { - unsigned int core = cvmx_get_core_num(); - int cpu = core2cpu(core); + int cpu = raw_smp_processor_id(); + unsigned int core = cpu2core(cpu); + int node = cpu_to_node(cpu); - if (do_coundown) { + if (do_countdown) { if (per_cpu_countdown[cpu] > 0) { /* We're alive, poke the watchdog */ - cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1); per_cpu_countdown[cpu]--; } else { /* Bad news, you are about to reboot. */ @@ -258,7 +145,7 @@ static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id) } } else { /* Not open, just ping away... */ - cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1); } return IRQ_HANDLED; } @@ -267,7 +154,7 @@ static irqreturn_t octeon_wdt_poke_irq(int cpl, void *dev_id) extern int prom_putchar(char c); /** - * Write a string to the uart + * octeon_wdt_write_string - Write a string to the uart * * @str: String to write */ @@ -279,7 +166,7 @@ static void octeon_wdt_write_string(const char *str) } /** - * Write a hex number out of the uart + * octeon_wdt_write_hex() - Write a hex number out of the uart * * @value: Number to display * @digits: Number of digits to print (1 to 16) @@ -288,6 +175,7 @@ static void octeon_wdt_write_hex(u64 value, int digits) { int d; int v; + for (d = 0; d < digits; d++) { v = (value >> ((digits - d - 1) * 4)) & 0xf; if (v >= 10) @@ -297,7 +185,7 @@ static void octeon_wdt_write_hex(u64 value, int digits) } } -const char *reg_name[] = { +static const char reg_name[][3] = { "$0", "at", "v0", "v1", "a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "t0", "t1", "t2", "t3", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", @@ -305,6 +193,8 @@ const char *reg_name[] = { }; /** + * octeon_wdt_nmi_stage3: + * * NMI stage 3 handler. NMIs are handled in the following manner: * 1) The first NMI handler enables CVMSEG and transfers from * the bootbus region into normal memory. It is careful to not @@ -332,10 +222,10 @@ void octeon_wdt_nmi_stage3(u64 reg[32]) u64 cp0_epc = read_c0_epc(); /* Delay so output from all cores output is not jumbled together. */ - __delay(100000000ull * coreid); + udelay(85000 * coreid); octeon_wdt_write_string("\r\n*** NMI Watchdog interrupt on Core 0x"); - octeon_wdt_write_hex(coreid, 1); + octeon_wdt_write_hex(coreid, 2); octeon_wdt_write_string(" ***\r\n"); for (i = 0; i < 32; i++) { octeon_wdt_write_string("\t"); @@ -358,103 +248,178 @@ void octeon_wdt_nmi_stage3(u64 reg[32]) octeon_wdt_write_hex(cp0_cause, 16); octeon_wdt_write_string("\r\n"); - octeon_wdt_write_string("\tsum0\t0x"); - octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_SUM0(coreid * 2)), 16); - octeon_wdt_write_string("\ten0\t0x"); - octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_EN0(coreid * 2)), 16); - octeon_wdt_write_string("\r\n"); + /* The CIU register is different for each Octeon model. */ + if (OCTEON_IS_MODEL(OCTEON_CN68XX)) { + octeon_wdt_write_string("\tsrc_wd\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SRC_PPX_IP2_WDOG(coreid)), 16); + octeon_wdt_write_string("\ten_wd\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_EN_PPX_IP2_WDOG(coreid)), 16); + octeon_wdt_write_string("\r\n"); + octeon_wdt_write_string("\tsrc_rml\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SRC_PPX_IP2_RML(coreid)), 16); + octeon_wdt_write_string("\ten_rml\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_EN_PPX_IP2_RML(coreid)), 16); + octeon_wdt_write_string("\r\n"); + octeon_wdt_write_string("\tsum\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU2_SUM_PPX_IP2(coreid)), 16); + octeon_wdt_write_string("\r\n"); + } else if (!octeon_has_feature(OCTEON_FEATURE_CIU3)) { + octeon_wdt_write_string("\tsum0\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_SUM0(coreid * 2)), 16); + octeon_wdt_write_string("\ten0\t0x"); + octeon_wdt_write_hex(cvmx_read_csr(CVMX_CIU_INTX_EN0(coreid * 2)), 16); + octeon_wdt_write_string("\r\n"); + } octeon_wdt_write_string("*** Chip soft reset soon ***\r\n"); + + /* + * G-30204: We must trigger a soft reset before watchdog + * does an incomplete job of doing it. + */ + if (OCTEON_IS_OCTEON3() && !OCTEON_IS_MODEL(OCTEON_CN70XX)) { + u64 scr; + unsigned int node = cvmx_get_node_num(); + unsigned int lcore = cvmx_get_local_core_num(); + union cvmx_ciu_wdogx ciu_wdog; + + /* + * Wait for other cores to print out information, but + * not too long. Do the soft reset before watchdog + * can trigger it. + */ + do { + ciu_wdog.u64 = cvmx_read_csr_node(node, CVMX_CIU_WDOGX(lcore)); + } while (ciu_wdog.s.cnt > 0x10000); + + scr = cvmx_read_csr_node(0, CVMX_GSERX_SCRATCH(0)); + scr |= 1 << 11; /* Indicate watchdog in bit 11 */ + cvmx_write_csr_node(0, CVMX_GSERX_SCRATCH(0), scr); + cvmx_write_csr_node(0, CVMX_RST_SOFT_RST, 1); + } +} + +static int octeon_wdt_cpu_to_irq(int cpu) +{ + unsigned int coreid; + int node; + int irq; + + coreid = cpu2core(cpu); + node = cpu_to_node(cpu); + + if (octeon_has_feature(OCTEON_FEATURE_CIU3)) { + struct irq_domain *domain; + int hwirq; + + domain = octeon_irq_get_block_domain(node, + WD_BLOCK_NUMBER); + hwirq = WD_BLOCK_NUMBER << 12 | 0x200 | coreid; + irq = irq_find_mapping(domain, hwirq); + } else { + irq = OCTEON_IRQ_WDOG0 + coreid; + } + return irq; } -static void octeon_wdt_disable_interrupt(int cpu) +static int octeon_wdt_cpu_pre_down(unsigned int cpu) { unsigned int core; - unsigned int irq; + int node; union cvmx_ciu_wdogx ciu_wdog; core = cpu2core(cpu); - irq = OCTEON_IRQ_WDOG0 + core; + node = cpu_to_node(cpu); /* Poke the watchdog to clear out its state */ - cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1); /* Disable the hardware. */ ciu_wdog.u64 = 0; - cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64); - free_irq(irq, octeon_wdt_poke_irq); + free_irq(octeon_wdt_cpu_to_irq(cpu), octeon_wdt_poke_irq); + return 0; } -static void octeon_wdt_setup_interrupt(int cpu) +static int octeon_wdt_cpu_online(unsigned int cpu) { unsigned int core; unsigned int irq; union cvmx_ciu_wdogx ciu_wdog; + int node; + struct irq_domain *domain; + int hwirq; core = cpu2core(cpu); + node = cpu_to_node(cpu); + + octeon_wdt_bootvector[core].target_ptr = (u64)octeon_wdt_nmi_stage2; /* Disable it before doing anything with the interrupts. */ ciu_wdog.u64 = 0; - cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); + cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64); per_cpu_countdown[cpu] = countdown_reset; - irq = OCTEON_IRQ_WDOG0 + core; + if (octeon_has_feature(OCTEON_FEATURE_CIU3)) { + /* Must get the domain for the watchdog block */ + domain = octeon_irq_get_block_domain(node, WD_BLOCK_NUMBER); + + /* Get a irq for the wd intsn (hardware interrupt) */ + hwirq = WD_BLOCK_NUMBER << 12 | 0x200 | core; + irq = irq_create_mapping(domain, hwirq); + irqd_set_trigger_type(irq_get_irq_data(irq), + IRQ_TYPE_EDGE_RISING); + } else + irq = OCTEON_IRQ_WDOG0 + core; if (request_irq(irq, octeon_wdt_poke_irq, IRQF_NO_THREAD, "octeon_wdt", octeon_wdt_poke_irq)) panic("octeon_wdt: Couldn't obtain irq %d", irq); + /* Must set the irq affinity here */ + if (octeon_has_feature(OCTEON_FEATURE_CIU3)) { + irq_set_affinity(irq, cpumask_of(cpu)); + } + cpumask_set_cpu(cpu, &irq_enabled_cpus); /* Poke the watchdog to clear out its state */ - cvmx_write_csr(CVMX_CIU_PP_POKEX(core), 1); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(core), 1); /* Finally enable the watchdog now that all handlers are installed */ ciu_wdog.u64 = 0; ciu_wdog.s.len = timeout_cnt; ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */ - cvmx_write_csr(CVMX_CIU_WDOGX(core), ciu_wdog.u64); -} + cvmx_write_csr_node(node, CVMX_CIU_WDOGX(core), ciu_wdog.u64); -static int octeon_wdt_cpu_callback(struct notifier_block *nfb, - unsigned long action, void *hcpu) -{ - unsigned int cpu = (unsigned long)hcpu; - - switch (action) { - case CPU_DOWN_PREPARE: - octeon_wdt_disable_interrupt(cpu); - break; - case CPU_ONLINE: - case CPU_DOWN_FAILED: - octeon_wdt_setup_interrupt(cpu); - break; - default: - break; - } - return NOTIFY_OK; + return 0; } -static void octeon_wdt_ping(void) +static int octeon_wdt_ping(struct watchdog_device __always_unused *wdog) { int cpu; int coreid; + int node; + + if (disable) + return 0; for_each_online_cpu(cpu) { coreid = cpu2core(cpu); - cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + node = cpu_to_node(cpu); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1); per_cpu_countdown[cpu] = countdown_reset; - if ((countdown_reset || !do_coundown) && + if ((countdown_reset || !do_countdown) && !cpumask_test_cpu(cpu, &irq_enabled_cpus)) { /* We have to enable the irq */ - int irq = OCTEON_IRQ_WDOG0 + coreid; - enable_irq(irq); + enable_irq(octeon_wdt_cpu_to_irq(cpu)); cpumask_set_cpu(cpu, &irq_enabled_cpus); } } + return 0; } static void octeon_wdt_calc_parameters(int t) @@ -480,198 +445,98 @@ static void octeon_wdt_calc_parameters(int t) countdown_reset = periods > 2 ? periods - 2 : 0; heartbeat = t; - timeout_cnt = ((octeon_get_io_clock_rate() >> 8) * timeout_sec) >> 8; + timeout_cnt = ((octeon_get_io_clock_rate() / divisor) * timeout_sec) >> 8; } -static int octeon_wdt_set_heartbeat(int t) +static int octeon_wdt_set_timeout(struct watchdog_device *wdog, + unsigned int t) { int cpu; int coreid; union cvmx_ciu_wdogx ciu_wdog; + int node; if (t <= 0) return -1; octeon_wdt_calc_parameters(t); + if (disable) + return 0; + for_each_online_cpu(cpu) { coreid = cpu2core(cpu); - cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + node = cpu_to_node(cpu); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1); ciu_wdog.u64 = 0; ciu_wdog.s.len = timeout_cnt; ciu_wdog.s.mode = 3; /* 3 = Interrupt + NMI + Soft-Reset */ - cvmx_write_csr(CVMX_CIU_WDOGX(coreid), ciu_wdog.u64); - cvmx_write_csr(CVMX_CIU_PP_POKEX(coreid), 1); + cvmx_write_csr_node(node, CVMX_CIU_WDOGX(coreid), ciu_wdog.u64); + cvmx_write_csr_node(node, CVMX_CIU_PP_POKEX(coreid), 1); } - octeon_wdt_ping(); /* Get the irqs back on. */ + octeon_wdt_ping(wdog); /* Get the irqs back on. */ return 0; } -/** - * octeon_wdt_write: - * @file: file handle to the watchdog - * @buf: buffer to write (unused as data does not matter here - * @count: count of bytes - * @ppos: pointer to the position to write. No seeks allowed - * - * A write to a watchdog device is defined as a keepalive signal. Any - * write of data will do, as we we don't define content meaning. - */ - -static ssize_t octeon_wdt_write(struct file *file, const char __user *buf, - size_t count, loff_t *ppos) -{ - if (count) { - if (!nowayout) { - size_t i; - - /* In case it was set long ago */ - expect_close = 0; - - for (i = 0; i != count; i++) { - char c; - if (get_user(c, buf + i)) - return -EFAULT; - if (c == 'V') - expect_close = 1; - } - } - octeon_wdt_ping(); - } - return count; -} - -/** - * octeon_wdt_ioctl: - * @file: file handle to the device - * @cmd: watchdog command - * @arg: argument pointer - * - * The watchdog API defines a common set of functions for all - * watchdogs according to their available features. We only - * actually usefully support querying capabilities and setting - * the timeout. - */ - -static long octeon_wdt_ioctl(struct file *file, unsigned int cmd, - unsigned long arg) -{ - void __user *argp = (void __user *)arg; - int __user *p = argp; - int new_heartbeat; - - static struct watchdog_info ident = { - .options = WDIOF_SETTIMEOUT| - WDIOF_MAGICCLOSE| - WDIOF_KEEPALIVEPING, - .firmware_version = 1, - .identity = "OCTEON", - }; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, p); - case WDIOC_KEEPALIVE: - octeon_wdt_ping(); - return 0; - case WDIOC_SETTIMEOUT: - if (get_user(new_heartbeat, p)) - return -EFAULT; - if (octeon_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - /* Fall through. */ - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - default: - return -ENOTTY; - } -} - -/** - * octeon_wdt_open: - * @inode: inode of device - * @file: file handle to device - * - * The watchdog device has been opened. The watchdog device is single - * open and on opening we do a ping to reset the counters. - */ - -static int octeon_wdt_open(struct inode *inode, struct file *file) +static int octeon_wdt_start(struct watchdog_device *wdog) { - if (test_and_set_bit(0, &octeon_wdt_is_open)) - return -EBUSY; - /* - * Activate - */ - octeon_wdt_ping(); - do_coundown = 1; - return nonseekable_open(inode, file); + octeon_wdt_ping(wdog); + do_countdown = 1; + return 0; } -/** - * octeon_wdt_release: - * @inode: inode to board - * @file: file handle to board - * - * The watchdog has a configurable API. There is a religious dispute - * between people who want their watchdog to be able to shut down and - * those who want to be sure if the watchdog manager dies the machine - * reboots. In the former case we disable the counters, in the latter - * case you have to open it again very soon. - */ - -static int octeon_wdt_release(struct inode *inode, struct file *file) +static int octeon_wdt_stop(struct watchdog_device *wdog) { - if (expect_close) { - do_coundown = 0; - octeon_wdt_ping(); - } else { - pr_crit("WDT device closed unexpectedly. WDT will not stop!\n"); - } - clear_bit(0, &octeon_wdt_is_open); - expect_close = 0; + do_countdown = 0; + octeon_wdt_ping(wdog); return 0; } -static const struct file_operations octeon_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = octeon_wdt_write, - .unlocked_ioctl = octeon_wdt_ioctl, - .open = octeon_wdt_open, - .release = octeon_wdt_release, +static const struct watchdog_info octeon_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | WDIOF_KEEPALIVEPING, + .identity = "OCTEON", }; -static struct miscdevice octeon_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &octeon_wdt_fops, +static const struct watchdog_ops octeon_wdt_ops = { + .owner = THIS_MODULE, + .start = octeon_wdt_start, + .stop = octeon_wdt_stop, + .ping = octeon_wdt_ping, + .set_timeout = octeon_wdt_set_timeout, }; -static struct notifier_block octeon_wdt_cpu_notifier = { - .notifier_call = octeon_wdt_cpu_callback, +static struct watchdog_device octeon_wdt = { + .info = &octeon_wdt_info, + .ops = &octeon_wdt_ops, }; - +static enum cpuhp_state octeon_wdt_online; /** - * Module/ driver initialization. + * octeon_wdt_init - Module/ driver initialization. * * Returns Zero on success */ static int __init octeon_wdt_init(void) { - int i; int ret; - int cpu; - u64 *ptr; + + octeon_wdt_bootvector = cvmx_boot_vector_get(); + if (!octeon_wdt_bootvector) { + pr_err("Error: Cannot allocate boot vector.\n"); + return -ENOMEM; + } + + if (OCTEON_IS_MODEL(OCTEON_CN68XX)) + divisor = 0x200; + else if (OCTEON_IS_MODEL(OCTEON_CN78XX)) + divisor = 0x400; + else + divisor = 0x100; /* * Watchdog time expiration length = The 16 bits of LEN * represent the most significant bits of a 24 bit decrementer - * that decrements every 256 cycles. + * that decrements every divisor cycle. * * Try for a timeout of 5 sec, if that fails a smaller number * of even seconds, @@ -679,7 +544,7 @@ static int __init octeon_wdt_init(void) max_timeout_sec = 6; do { max_timeout_sec--; - timeout_cnt = ((octeon_get_io_clock_rate() >> 8) * max_timeout_sec) >> 8; + timeout_cnt = ((octeon_get_io_clock_rate() / divisor) * max_timeout_sec) >> 8; } while (timeout_cnt > 65535); BUG_ON(timeout_cnt == 0); @@ -688,52 +553,46 @@ static int __init octeon_wdt_init(void) pr_info("Initial granularity %d Sec\n", timeout_sec); - ret = misc_register(&octeon_wdt_miscdev); - if (ret) { - pr_err("cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); - goto out; - } + octeon_wdt.timeout = timeout_sec; + octeon_wdt.max_timeout = UINT_MAX; + + watchdog_set_nowayout(&octeon_wdt, nowayout); - /* Build the NMI handler ... */ - octeon_wdt_build_stage1(); + ret = watchdog_register_device(&octeon_wdt); + if (ret) + return ret; - /* ... and install it. */ - ptr = (u64 *) nmi_stage1_insns; - for (i = 0; i < 16; i++) { - cvmx_write_csr(CVMX_MIO_BOOT_LOC_ADR, i * 8); - cvmx_write_csr(CVMX_MIO_BOOT_LOC_DAT, ptr[i]); + if (disable) { + pr_notice("disabled\n"); + return 0; } - cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0x81fc0000); cpumask_clear(&irq_enabled_cpus); - for_each_online_cpu(cpu) - octeon_wdt_setup_interrupt(cpu); - - register_hotcpu_notifier(&octeon_wdt_cpu_notifier); -out: + ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "watchdog/octeon:online", + octeon_wdt_cpu_online, octeon_wdt_cpu_pre_down); + if (ret < 0) + goto err; + octeon_wdt_online = ret; + return 0; +err: + cvmx_write_csr(CVMX_MIO_BOOT_LOC_CFGX(0), 0); + watchdog_unregister_device(&octeon_wdt); return ret; } /** - * Module / driver shutdown + * octeon_wdt_cleanup - Module / driver shutdown */ static void __exit octeon_wdt_cleanup(void) { - int cpu; + watchdog_unregister_device(&octeon_wdt); - misc_deregister(&octeon_wdt_miscdev); + if (disable) + return; - unregister_hotcpu_notifier(&octeon_wdt_cpu_notifier); + cpuhp_remove_state(octeon_wdt_online); - for_each_online_cpu(cpu) { - int core = cpu2core(cpu); - /* Disable the watchdog */ - cvmx_write_csr(CVMX_CIU_WDOGX(core), 0); - /* Free the interrupt handler */ - free_irq(OCTEON_IRQ_WDOG0 + core, octeon_wdt_poke_irq); - } /* * Disable the boot-bus memory, the code it points to is soon * to go missing. @@ -742,7 +601,7 @@ static void __exit octeon_wdt_cleanup(void) } MODULE_LICENSE("GPL"); -MODULE_AUTHOR("Cavium Networks <support@caviumnetworks.com>"); -MODULE_DESCRIPTION("Cavium Networks Octeon Watchdog driver."); +MODULE_AUTHOR("Cavium Inc. <support@cavium.com>"); +MODULE_DESCRIPTION("Cavium Inc. OCTEON Watchdog driver."); module_init(octeon_wdt_init); module_exit(octeon_wdt_cleanup); |
