From 33f74b893a46448670f11375427f0084753c23b6 Mon Sep 17 00:00:00 2001 From: Rob Kramer Date: Mon, 8 Feb 2016 18:09:49 +0800 Subject: [PATCH 01/28] watchdog: w83627hf: Added NCT6102D support. As used in (and tested on) the ASRock IMB-150 board. Implementation is identical to other NCT chips, just with different registers. Signed-off-by: Rob Kramer Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 1 + drivers/watchdog/w83627hf_wdt.c | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0f6d8515ba4f..b5f39b7e853d 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1142,6 +1142,7 @@ config W83627HF_WDT NCT6779 NCT6791 NCT6792 + NCT6102D/04D/06D This watchdog simply watches your kernel to make sure it doesn't freeze, and if it does, it reboots your computer after a certain diff --git a/drivers/watchdog/w83627hf_wdt.c b/drivers/watchdog/w83627hf_wdt.c index cab14bc9106c..09e8003039dc 100644 --- a/drivers/watchdog/w83627hf_wdt.c +++ b/drivers/watchdog/w83627hf_wdt.c @@ -45,10 +45,11 @@ static int wdt_io; static int cr_wdt_timeout; /* WDT timeout register */ static int cr_wdt_control; /* WDT control register */ +static int cr_wdt_csr; /* WDT control & status register */ enum chips { w83627hf, w83627s, w83697hf, w83697ug, w83637hf, w83627thf, w83687thf, w83627ehf, w83627dhg, w83627uhg, w83667hg, w83627dhg_p, - w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792 }; + w83667hg_b, nct6775, nct6776, nct6779, nct6791, nct6792, nct6102 }; static int timeout; /* in seconds */ module_param(timeout, int, 0); @@ -92,15 +93,21 @@ MODULE_PARM_DESC(early_disable, "Disable watchdog at boot time (default=0)"); #define W83667HG_B_ID 0xb3 #define NCT6775_ID 0xb4 #define NCT6776_ID 0xc3 +#define NCT6102_ID 0xc4 #define NCT6779_ID 0xc5 #define NCT6791_ID 0xc8 #define NCT6792_ID 0xc9 #define W83627HF_WDT_TIMEOUT 0xf6 #define W83697HF_WDT_TIMEOUT 0xf4 +#define NCT6102D_WDT_TIMEOUT 0xf1 #define W83627HF_WDT_CONTROL 0xf5 #define W83697HF_WDT_CONTROL 0xf3 +#define NCT6102D_WDT_CONTROL 0xf0 + +#define W836X7HF_WDT_CSR 0xf7 +#define NCT6102D_WDT_CSR 0xf2 static void superio_outb(int reg, int val) { @@ -197,6 +204,7 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip) case nct6779: case nct6791: case nct6792: + case nct6102: /* * These chips have a fixed WDTO# output pin (W83627UHG), * or support more than one WDTO# output pin. @@ -229,8 +237,8 @@ static int w83627hf_init(struct watchdog_device *wdog, enum chips chip) superio_outb(cr_wdt_control, t); /* reset trigger, disable keyboard & mouse turning off watchdog */ - t = superio_inb(0xF7) & ~0xD0; - superio_outb(0xF7, t); + t = superio_inb(cr_wdt_csr) & ~0xD0; + superio_outb(cr_wdt_csr, t); superio_exit(); @@ -322,6 +330,7 @@ static int wdt_find(int addr) cr_wdt_timeout = W83627HF_WDT_TIMEOUT; cr_wdt_control = W83627HF_WDT_CONTROL; + cr_wdt_csr = W836X7HF_WDT_CSR; ret = superio_enter(); if (ret) @@ -387,6 +396,12 @@ static int wdt_find(int addr) case NCT6792_ID: ret = nct6792; break; + case NCT6102_ID: + ret = nct6102; + cr_wdt_timeout = NCT6102D_WDT_TIMEOUT; + cr_wdt_control = NCT6102D_WDT_CONTROL; + cr_wdt_csr = NCT6102D_WDT_CSR; + break; case 0xff: ret = -ENODEV; break; @@ -422,6 +437,7 @@ static int __init wdt_init(void) "NCT6779", "NCT6791", "NCT6792", + "NCT6102", }; wdt_io = 0x2e; From 0519e91d80148086997aa5f3ab5d15e95151b6b1 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Tue, 9 Feb 2016 11:17:48 +0100 Subject: [PATCH 02/28] watchdog: ts4800: add hardware dependency The Technologic Systems TS-4800 is an i.MX515 board, so its drivers are useless unless building a SOC_IMX51 kernel, except for build testing purposes. Signed-off-by: Jean Delvare Cc: Damien Riegel Cc: Rob Herring Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index b5f39b7e853d..d8108d8ad747 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -468,6 +468,7 @@ config NUC900_WATCHDOG config TS4800_WATCHDOG tristate "TS-4800 Watchdog" depends on HAS_IOMEM && OF + depends on SOC_IMX51 || COMPILE_TEST select WATCHDOG_CORE select MFD_SYSCON help From c3525e3f09526b3ea2b59bb8f9b78cb2ff19a094 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 24 Feb 2016 15:22:06 +0100 Subject: [PATCH 03/28] watchdog: orion: Depend on 32-bit ARM The driver uses the atomic_io_modify() function to update registers, but that function is only available on 32-bit ARM. Recent changes have added ARCH_MVEBU support to 64-bit ARM and hence allowed this driver to build on 64-bit ARM where this function isn't available and thereby causing allmodconfig builds to break. Signed-off-by: Thierry Reding Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index d8108d8ad747..fc1b2959c0b3 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -399,6 +399,7 @@ config DAVINCI_WATCHDOG config ORION_WATCHDOG tristate "Orion watchdog" depends on ARCH_ORION5X || ARCH_DOVE || MACH_DOVE || ARCH_MVEBU + depends on ARM select WATCHDOG_CORE help Say Y here if to include support for the watchdog timer From 972ec3510330c9d639c0dd72960e9aa02915855c Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 24 Feb 2016 16:17:26 +0100 Subject: [PATCH 04/28] watchdog: Add hardware dependency to BCM7038 driver The BCM7038 watchdog driver is specific to Broadcom ARM and MIPS SoCs so do not present it on other architectures, unless build-testing. Signed-off-by: Jean Delvare Reviewed-by: Justin Chen Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index fc1b2959c0b3..5f29f72f4691 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1380,10 +1380,12 @@ config BCM7038_WDT tristate "BCM7038 Watchdog" select WATCHDOG_CORE depends on HAS_IOMEM + depends on ARCH_BRCMSTB || BMIPS_GENERIC || COMPILE_TEST help - Watchdog driver for the built-in hardware in Broadcom 7038 SoCs. - - Say 'Y or 'M' here to enable the driver. + Watchdog driver for the built-in hardware in Broadcom 7038 and + later SoCs used in set-top boxes. BCM7038 was made public + during the 2004 CES, and since then, many Broadcom chips use this + watchdog block, including some cable modem chips. config IMGPDC_WDT tristate "Imagination Technologies PDC Watchdog Timer" From 10e7ac22cdd4d211cef99afcb9371b70cb175be6 Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Sun, 28 Feb 2016 17:44:09 +0200 Subject: [PATCH 05/28] watchdog: rc32434_wdt: fix ioctl error handling Calling return copy_to_user(...) in an ioctl will not do the right thing if there's a pagefault: copy_to_user returns the number of bytes not copied in this case. Fix up watchdog/rc32434_wdt to do return copy_to_user(...)) ? -EFAULT : 0; instead. Cc: stable@vger.kernel.org Signed-off-by: Michael S. Tsirkin Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/rc32434_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/rc32434_wdt.c b/drivers/watchdog/rc32434_wdt.c index 71e78ef4b736..3a75f3b53452 100644 --- a/drivers/watchdog/rc32434_wdt.c +++ b/drivers/watchdog/rc32434_wdt.c @@ -237,7 +237,7 @@ static long rc32434_wdt_ioctl(struct file *file, unsigned int cmd, return -EINVAL; /* Fall through */ case WDIOC_GETTIMEOUT: - return copy_to_user(argp, &timeout, sizeof(int)); + return copy_to_user(argp, &timeout, sizeof(int)) ? -EFAULT : 0; default: return -ENOTTY; } From 0ce72f354482cfa40fb0a6619511ab94d6daa5e9 Mon Sep 17 00:00:00 2001 From: Martyn Welch Date: Fri, 26 Feb 2016 16:05:12 +0000 Subject: [PATCH 06/28] watchdog: ziirave_wdt: Rename "trigger" reset reason "hw watchdog" The Zodiac watchdog is implemented on a microcontoller. The reset reason currently labelled "trigger" is not to detect when the watchdog has triggered (as had been initially understood and suggested by the naming), but to inform the reader that the watchdog, which in fact has it's own hardware watchdog, has been reset because the hardware watchdog has triggered. Renaming to "hw watchdog". Signed-off-by: Martyn Welch Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/ziirave_wdt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/watchdog/ziirave_wdt.c b/drivers/watchdog/ziirave_wdt.c index 0c7cb7302cf0..cbe373de3659 100644 --- a/drivers/watchdog/ziirave_wdt.c +++ b/drivers/watchdog/ziirave_wdt.c @@ -36,7 +36,7 @@ #define ZIIRAVE_STATE_OFF 0x1 #define ZIIRAVE_STATE_ON 0x2 -static char *ziirave_reasons[] = {"power cycle", "triggered", NULL, NULL, +static char *ziirave_reasons[] = {"power cycle", "hw watchdog", NULL, NULL, "host request", NULL, "illegal configuration", "illegal instruction", "illegal trap", "unknown"}; From 4d8b229d5ea610affe672e919021e9d02cd877da Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 26 Feb 2016 17:32:49 -0800 Subject: [PATCH 07/28] watchdog: Add 'action' and 'data' parameters to restart handler callback The 'action' (or restart mode) and data parameters may be used by restart handlers, so they should be passed to the restart callback functions. Cc: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/bcm47xx_wdt.c | 3 ++- drivers/watchdog/da9063_wdt.c | 3 ++- drivers/watchdog/digicolor_wdt.c | 3 ++- drivers/watchdog/imgpdc_wdt.c | 3 ++- drivers/watchdog/imx2_wdt.c | 3 ++- drivers/watchdog/lpc18xx_wdt.c | 3 ++- drivers/watchdog/meson_wdt.c | 3 ++- drivers/watchdog/moxart_wdt.c | 3 ++- drivers/watchdog/mtk_wdt.c | 3 ++- drivers/watchdog/qcom-wdt.c | 3 ++- drivers/watchdog/s3c2410_wdt.c | 3 ++- drivers/watchdog/sunxi_wdt.c | 3 ++- drivers/watchdog/watchdog_core.c | 2 +- include/linux/watchdog.h | 2 +- 14 files changed, 26 insertions(+), 14 deletions(-) diff --git a/drivers/watchdog/bcm47xx_wdt.c b/drivers/watchdog/bcm47xx_wdt.c index df1c2a4b0165..a1900b9ab6c4 100644 --- a/drivers/watchdog/bcm47xx_wdt.c +++ b/drivers/watchdog/bcm47xx_wdt.c @@ -87,7 +87,8 @@ static int bcm47xx_wdt_hard_set_timeout(struct watchdog_device *wdd, return 0; } -static int bcm47xx_wdt_restart(struct watchdog_device *wdd) +static int bcm47xx_wdt_restart(struct watchdog_device *wdd, + unsigned long action, void *data) { struct bcm47xx_wdt *wdt = bcm47xx_wdt_get(wdd); diff --git a/drivers/watchdog/da9063_wdt.c b/drivers/watchdog/da9063_wdt.c index 11e887572649..a100f648880d 100644 --- a/drivers/watchdog/da9063_wdt.c +++ b/drivers/watchdog/da9063_wdt.c @@ -119,7 +119,8 @@ static int da9063_wdt_set_timeout(struct watchdog_device *wdd, return ret; } -static int da9063_wdt_restart(struct watchdog_device *wdd) +static int da9063_wdt_restart(struct watchdog_device *wdd, unsigned long action, + void *data) { struct da9063_watchdog *wdt = watchdog_get_drvdata(wdd); int ret; diff --git a/drivers/watchdog/digicolor_wdt.c b/drivers/watchdog/digicolor_wdt.c index 1ccb0b239348..77df772406b0 100644 --- a/drivers/watchdog/digicolor_wdt.c +++ b/drivers/watchdog/digicolor_wdt.c @@ -48,7 +48,8 @@ static void dc_wdt_set(struct dc_wdt *wdt, u32 ticks) spin_unlock_irqrestore(&wdt->lock, flags); } -static int dc_wdt_restart(struct watchdog_device *wdog) +static int dc_wdt_restart(struct watchdog_device *wdog, unsigned long action, + void *data) { struct dc_wdt *wdt = watchdog_get_drvdata(wdog); diff --git a/drivers/watchdog/imgpdc_wdt.c b/drivers/watchdog/imgpdc_wdt.c index 3679f2e1922f..516fbef00856 100644 --- a/drivers/watchdog/imgpdc_wdt.c +++ b/drivers/watchdog/imgpdc_wdt.c @@ -150,7 +150,8 @@ static int pdc_wdt_start(struct watchdog_device *wdt_dev) return 0; } -static int pdc_wdt_restart(struct watchdog_device *wdt_dev) +static int pdc_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index e47966aa2db0..4cb59a23aab0 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -80,7 +80,8 @@ static const struct watchdog_info imx2_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, }; -static int imx2_wdt_restart(struct watchdog_device *wdog) +static int imx2_wdt_restart(struct watchdog_device *wdog, unsigned long action, + void *data) { struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); unsigned int wcr_enable = IMX2_WDT_WCR_WDE; diff --git a/drivers/watchdog/lpc18xx_wdt.c b/drivers/watchdog/lpc18xx_wdt.c index 6914c83aa6d9..fd171e6caa16 100644 --- a/drivers/watchdog/lpc18xx_wdt.c +++ b/drivers/watchdog/lpc18xx_wdt.c @@ -153,7 +153,8 @@ static int lpc18xx_wdt_start(struct watchdog_device *wdt_dev) return 0; } -static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev) +static int lpc18xx_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct lpc18xx_wdt_dev *lpc18xx_wdt = watchdog_get_drvdata(wdt_dev); unsigned long flags; diff --git a/drivers/watchdog/meson_wdt.c b/drivers/watchdog/meson_wdt.c index aea5d2f44ad7..56ea1caf71c3 100644 --- a/drivers/watchdog/meson_wdt.c +++ b/drivers/watchdog/meson_wdt.c @@ -62,7 +62,8 @@ struct meson_wdt_dev { const struct meson_wdt_data *data; }; -static int meson_wdt_restart(struct watchdog_device *wdt_dev) +static int meson_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct meson_wdt_dev *meson_wdt = watchdog_get_drvdata(wdt_dev); u32 tc_reboot = MESON_WDT_DC_RESET; diff --git a/drivers/watchdog/moxart_wdt.c b/drivers/watchdog/moxart_wdt.c index 885c81bc4210..2c4a73d1e214 100644 --- a/drivers/watchdog/moxart_wdt.c +++ b/drivers/watchdog/moxart_wdt.c @@ -31,7 +31,8 @@ struct moxart_wdt_dev { static int heartbeat; -static int moxart_wdt_restart(struct watchdog_device *wdt_dev) +static int moxart_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct moxart_wdt_dev *moxart_wdt = watchdog_get_drvdata(wdt_dev); diff --git a/drivers/watchdog/mtk_wdt.c b/drivers/watchdog/mtk_wdt.c index b78776c05554..7ed417a765c7 100644 --- a/drivers/watchdog/mtk_wdt.c +++ b/drivers/watchdog/mtk_wdt.c @@ -64,7 +64,8 @@ struct mtk_wdt_dev { void __iomem *wdt_base; }; -static int mtk_wdt_restart(struct watchdog_device *wdt_dev) +static int mtk_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct mtk_wdt_dev *mtk_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base; diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c index 424f9a952fee..20563ccb7be0 100644 --- a/drivers/watchdog/qcom-wdt.c +++ b/drivers/watchdog/qcom-wdt.c @@ -70,7 +70,8 @@ static int qcom_wdt_set_timeout(struct watchdog_device *wdd, return qcom_wdt_start(wdd); } -static int qcom_wdt_restart(struct watchdog_device *wdd) +static int qcom_wdt_restart(struct watchdog_device *wdd, unsigned long action, + void *data) { struct qcom_wdt *wdt = to_qcom_wdt(wdd); u32 timeout; diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index 0093450441fe..d57f19b5077f 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -349,7 +349,8 @@ static int s3c2410wdt_set_heartbeat(struct watchdog_device *wdd, unsigned timeou return 0; } -static int s3c2410wdt_restart(struct watchdog_device *wdd) +static int s3c2410wdt_restart(struct watchdog_device *wdd, unsigned long action, + void *data) { struct s3c2410_wdt *wdt = watchdog_get_drvdata(wdd); void __iomem *wdt_base = wdt->reg_base; diff --git a/drivers/watchdog/sunxi_wdt.c b/drivers/watchdog/sunxi_wdt.c index e027deb54740..953bb7b7446f 100644 --- a/drivers/watchdog/sunxi_wdt.c +++ b/drivers/watchdog/sunxi_wdt.c @@ -83,7 +83,8 @@ static const int wdt_timeout_map[] = { }; -static int sunxi_wdt_restart(struct watchdog_device *wdt_dev) +static int sunxi_wdt_restart(struct watchdog_device *wdt_dev, + unsigned long action, void *data) { struct sunxi_wdt_dev *sunxi_wdt = watchdog_get_drvdata(wdt_dev); void __iomem *wdt_base = sunxi_wdt->wdt_base; diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index e600fd93b7de..d9b3c9c023c4 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -164,7 +164,7 @@ static int watchdog_restart_notifier(struct notifier_block *nb, int ret; - ret = wdd->ops->restart(wdd); + ret = wdd->ops->restart(wdd, action, data); if (ret) return NOTIFY_BAD; diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index b585fa2507ee..0b565f2ad242 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -46,7 +46,7 @@ struct watchdog_ops { unsigned int (*status)(struct watchdog_device *); int (*set_timeout)(struct watchdog_device *, unsigned int); unsigned int (*get_timeleft)(struct watchdog_device *); - int (*restart)(struct watchdog_device *); + int (*restart)(struct watchdog_device *, unsigned long, void *); long (*ioctl)(struct watchdog_device *, unsigned int, unsigned long); }; From 70f3997667fb127333862977ba4fd3e855fbf617 Mon Sep 17 00:00:00 2001 From: Kyle Roeschley Date: Thu, 25 Feb 2016 11:28:00 -0600 Subject: [PATCH 08/28] watchdog: ni903x_wdt: Add NI 903x/913x watchdog driver Add support for the watchdog timer on NI cRIO-903x and cDAQ-913x real- time controllers. Signed-off-by: Jeff Westfahl Signed-off-by: Kyle Roeschley Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../watchdog/watchdog-parameters.txt | 5 + drivers/watchdog/Kconfig | 11 + drivers/watchdog/Makefile | 1 + drivers/watchdog/ni903x_wdt.c | 270 ++++++++++++++++++ 4 files changed, 287 insertions(+) create mode 100644 drivers/watchdog/ni903x_wdt.c diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 9f9ec9f76039..53dfc73e0171 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt @@ -200,6 +200,11 @@ mv64x60_wdt: nowayout: Watchdog cannot be stopped once started (default=kernel config parameter) ------------------------------------------------- +ni903x_wdt: +timeout: Initial watchdog timeout in seconds (0 +#include +#include +#include +#include +#include + +#define NIWD_CONTROL 0x01 +#define NIWD_COUNTER2 0x02 +#define NIWD_COUNTER1 0x03 +#define NIWD_COUNTER0 0x04 +#define NIWD_SEED2 0x05 +#define NIWD_SEED1 0x06 +#define NIWD_SEED0 0x07 + +#define NIWD_IO_SIZE 0x08 + +#define NIWD_CONTROL_MODE 0x80 +#define NIWD_CONTROL_PROC_RESET 0x20 +#define NIWD_CONTROL_PET 0x10 +#define NIWD_CONTROL_RUNNING 0x08 +#define NIWD_CONTROL_CAPTURECOUNTER 0x04 +#define NIWD_CONTROL_RESET 0x02 +#define NIWD_CONTROL_ALARM 0x01 + +#define NIWD_PERIOD_NS 30720 +#define NIWD_MIN_TIMEOUT 1 +#define NIWD_MAX_TIMEOUT 515 +#define NIWD_DEFAULT_TIMEOUT 60 + +#define NIWD_NAME "ni903x_wdt" + +struct ni903x_wdt { + struct device *dev; + u16 io_base; + struct watchdog_device wdd; +}; + +static unsigned int timeout; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (default=" + __MODULE_STRING(NIWD_DEFAULT_TIMEOUT) ")"); + +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, S_IRUGO); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static void ni903x_start(struct ni903x_wdt *wdt) +{ + u8 control = inb(wdt->io_base + NIWD_CONTROL); + + outb(control | NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL); + outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL); +} + +static int ni903x_wdd_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); + u32 counter = timeout * (1000000000 / NIWD_PERIOD_NS); + + outb(((0x00FF0000 & counter) >> 16), wdt->io_base + NIWD_SEED2); + outb(((0x0000FF00 & counter) >> 8), wdt->io_base + NIWD_SEED1); + outb((0x000000FF & counter), wdt->io_base + NIWD_SEED0); + + wdd->timeout = timeout; + + return 0; +} + +static unsigned int ni903x_wdd_get_timeleft(struct watchdog_device *wdd) +{ + struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); + u8 control, counter0, counter1, counter2; + u32 counter; + + control = inb(wdt->io_base + NIWD_CONTROL); + control |= NIWD_CONTROL_CAPTURECOUNTER; + outb(control, wdt->io_base + NIWD_CONTROL); + + counter2 = inb(wdt->io_base + NIWD_COUNTER2); + counter1 = inb(wdt->io_base + NIWD_COUNTER1); + counter0 = inb(wdt->io_base + NIWD_COUNTER0); + + counter = (counter2 << 16) | (counter1 << 8) | counter0; + + return counter / (1000000000 / NIWD_PERIOD_NS); +} + +static int ni903x_wdd_ping(struct watchdog_device *wdd) +{ + struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); + u8 control; + + control = inb(wdt->io_base + NIWD_CONTROL); + outb(control | NIWD_CONTROL_PET, wdt->io_base + NIWD_CONTROL); + + return 0; +} + +static int ni903x_wdd_start(struct watchdog_device *wdd) +{ + struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); + + outb(NIWD_CONTROL_RESET | NIWD_CONTROL_PROC_RESET, + wdt->io_base + NIWD_CONTROL); + + ni903x_wdd_set_timeout(wdd, wdd->timeout); + ni903x_start(wdt); + + return 0; +} + +static int ni903x_wdd_stop(struct watchdog_device *wdd) +{ + struct ni903x_wdt *wdt = watchdog_get_drvdata(wdd); + + outb(NIWD_CONTROL_RESET, wdt->io_base + NIWD_CONTROL); + + return 0; +} + +static acpi_status ni903x_resources(struct acpi_resource *res, void *data) +{ + struct ni903x_wdt *wdt = data; + u16 io_size; + + switch (res->type) { + case ACPI_RESOURCE_TYPE_IO: + if (wdt->io_base != 0) { + dev_err(wdt->dev, "too many IO resources\n"); + return AE_ERROR; + } + + wdt->io_base = res->data.io.minimum; + io_size = res->data.io.address_length; + + if (io_size < NIWD_IO_SIZE) { + dev_err(wdt->dev, "memory region too small\n"); + return AE_ERROR; + } + + if (!devm_request_region(wdt->dev, wdt->io_base, io_size, + NIWD_NAME)) { + dev_err(wdt->dev, "failed to get memory region\n"); + return AE_ERROR; + } + + return AE_OK; + + case ACPI_RESOURCE_TYPE_END_TAG: + default: + /* Ignore unsupported resources, e.g. IRQ */ + return AE_OK; + } +} + +static const struct watchdog_info ni903x_wdd_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "NI Watchdog", +}; + +static const struct watchdog_ops ni903x_wdd_ops = { + .owner = THIS_MODULE, + .start = ni903x_wdd_start, + .stop = ni903x_wdd_stop, + .ping = ni903x_wdd_ping, + .set_timeout = ni903x_wdd_set_timeout, + .get_timeleft = ni903x_wdd_get_timeleft, +}; + +static int ni903x_acpi_add(struct acpi_device *device) +{ + struct device *dev = &device->dev; + struct watchdog_device *wdd; + struct ni903x_wdt *wdt; + acpi_status status; + int ret; + + wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); + if (!wdt) + return -ENOMEM; + + device->driver_data = wdt; + wdt->dev = dev; + + status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + ni903x_resources, wdt); + if (ACPI_FAILURE(status) || wdt->io_base == 0) { + dev_err(dev, "failed to get resources\n"); + return -ENODEV; + } + + wdd = &wdt->wdd; + wdd->info = &ni903x_wdd_info; + wdd->ops = &ni903x_wdd_ops; + wdd->min_timeout = NIWD_MIN_TIMEOUT; + wdd->max_timeout = NIWD_MAX_TIMEOUT; + wdd->timeout = NIWD_DEFAULT_TIMEOUT; + wdd->parent = dev; + watchdog_set_drvdata(wdd, wdt); + watchdog_set_nowayout(wdd, nowayout); + ret = watchdog_init_timeout(wdd, timeout, dev); + if (ret) + dev_err(dev, "unable to set timeout value, using default\n"); + + ret = watchdog_register_device(wdd); + if (ret) { + dev_err(dev, "failed to register watchdog\n"); + return ret; + } + + /* Switch from boot mode to user mode */ + outb(NIWD_CONTROL_RESET | NIWD_CONTROL_MODE, + wdt->io_base + NIWD_CONTROL); + + dev_dbg(dev, "io_base=0x%04X, timeout=%d, nowayout=%d\n", + wdt->io_base, timeout, nowayout); + + return 0; +} + +static int ni903x_acpi_remove(struct acpi_device *device) +{ + struct ni903x_wdt *wdt = acpi_driver_data(device); + + ni903x_wdd_stop(&wdt->wdd); + watchdog_unregister_device(&wdt->wdd); + + return 0; +} + +static const struct acpi_device_id ni903x_device_ids[] = { + {"NIC775C", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, ni903x_device_ids); + +static struct acpi_driver ni903x_acpi_driver = { + .name = NIWD_NAME, + .ids = ni903x_device_ids, + .ops = { + .add = ni903x_acpi_add, + .remove = ni903x_acpi_remove, + }, +}; + +module_acpi_driver(ni903x_acpi_driver); + +MODULE_DESCRIPTION("NI 903x Watchdog"); +MODULE_AUTHOR("Jeff Westfahl "); +MODULE_AUTHOR("Kyle Roeschley "); +MODULE_LICENSE("GPL"); From c36a483d80072f7efdd09e94dd733120dc8ecfb2 Mon Sep 17 00:00:00 2001 From: William Breathitt Gray Date: Sun, 28 Feb 2016 23:54:46 -0500 Subject: [PATCH 09/28] watchdog: Add watchdog timer support for the WinSystems EBC-C384 The WinSystems EBC-C384 has an onboard watchdog timer. The timeout range supported by the watchdog timer is 1 second to 255 minutes. Timeouts under 256 seconds have a 1 second granularity, while the rest have a 1 minute granularity. This driver adds watchdog timer support for this onboard watchdog timer. The timeout may be configured via the timeout module parameter. Signed-off-by: William Breathitt Gray Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- MAINTAINERS | 6 + drivers/watchdog/Kconfig | 9 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/ebc-c384_wdt.c | 188 ++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) create mode 100644 drivers/watchdog/ebc-c384_wdt.c diff --git a/MAINTAINERS b/MAINTAINERS index da3e4d8016d0..88b769a0edde 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -11870,6 +11870,12 @@ M: David Härdeman S: Maintained F: drivers/media/rc/winbond-cir.c +WINSYSTEMS EBC-C384 WATCHDOG DRIVER +M: William Breathitt Gray +L: linux-watchdog@vger.kernel.org +S: Maintained +F: drivers/watchdog/ebc-c384_wdt.c + WIMAX STACK M: Inaky Perez-Gonzalez M: linux-wimax@intel.com diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 6f9530318e14..938f9205f828 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -715,6 +715,15 @@ config ALIM7101_WDT Most people will say N. +config EBC_C384_WDT + tristate "WinSystems EBC-C384 Watchdog Timer" + depends on X86 + select WATCHDOG_CORE + help + Enables watchdog timer support for the watchdog timer on the + WinSystems EBC-C384 motherboard. The timeout may be configured via + the timeout module parameter. + config F71808E_WDT tristate "Fintek F71808E, F71862FG, F71869, F71882FG and F71889FG Watchdog" depends on X86 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 5a23529091a0..d34e1b66b77f 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o +obj-$(CONFIG_EBC_C384_WDT) += ebc-c384_wdt.o obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o obj-$(CONFIG_GEODE_WDT) += geodewdt.o diff --git a/drivers/watchdog/ebc-c384_wdt.c b/drivers/watchdog/ebc-c384_wdt.c new file mode 100644 index 000000000000..77fda0b4b90e --- /dev/null +++ b/drivers/watchdog/ebc-c384_wdt.c @@ -0,0 +1,188 @@ +/* + * Watchdog timer driver for the WinSystems EBC-C384 + * Copyright (C) 2016 William Breathitt Gray + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MODULE_NAME "ebc-c384_wdt" +#define WATCHDOG_TIMEOUT 60 +/* + * The timeout value in minutes must fit in a single byte when sent to the + * watchdog timer; the maximum timeout possible is 15300 (255 * 60) seconds. + */ +#define WATCHDOG_MAX_TIMEOUT 15300 +#define BASE_ADDR 0x564 +#define ADDR_EXTENT 5 +#define CFG_ADDR (BASE_ADDR + 1) +#define PET_ADDR (BASE_ADDR + 2) + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static unsigned timeout; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static int ebc_c384_wdt_start(struct watchdog_device *wdev) +{ + unsigned t = wdev->timeout; + + /* resolution is in minutes for timeouts greater than 255 seconds */ + if (t > 255) + t = DIV_ROUND_UP(t, 60); + + outb(t, PET_ADDR); + + return 0; +} + +static int ebc_c384_wdt_stop(struct watchdog_device *wdev) +{ + outb(0x00, PET_ADDR); + + return 0; +} + +static int ebc_c384_wdt_set_timeout(struct watchdog_device *wdev, unsigned t) +{ + /* resolution is in minutes for timeouts greater than 255 seconds */ + if (t > 255) { + /* round second resolution up to minute granularity */ + wdev->timeout = roundup(t, 60); + + /* set watchdog timer for minutes */ + outb(0x00, CFG_ADDR); + } else { + wdev->timeout = t; + + /* set watchdog timer for seconds */ + outb(0x80, CFG_ADDR); + } + + return 0; +} + +static const struct watchdog_ops ebc_c384_wdt_ops = { + .start = ebc_c384_wdt_start, + .stop = ebc_c384_wdt_stop, + .set_timeout = ebc_c384_wdt_set_timeout +}; + +static const struct watchdog_info ebc_c384_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT, + .identity = MODULE_NAME +}; + +static int __init ebc_c384_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct watchdog_device *wdd; + + if (!devm_request_region(dev, BASE_ADDR, ADDR_EXTENT, dev_name(dev))) { + dev_err(dev, "Unable to lock port addresses (0x%X-0x%X)\n", + BASE_ADDR, BASE_ADDR + ADDR_EXTENT); + return -EBUSY; + } + + wdd = devm_kzalloc(dev, sizeof(*wdd), GFP_KERNEL); + if (!wdd) + return -ENOMEM; + + wdd->info = &ebc_c384_wdt_info; + wdd->ops = &ebc_c384_wdt_ops; + wdd->timeout = WATCHDOG_TIMEOUT; + wdd->min_timeout = 1; + wdd->max_timeout = WATCHDOG_MAX_TIMEOUT; + + watchdog_set_nowayout(wdd, nowayout); + + if (watchdog_init_timeout(wdd, timeout, dev)) + dev_warn(dev, "Invalid timeout (%u seconds), using default (%u seconds)\n", + timeout, WATCHDOG_TIMEOUT); + + platform_set_drvdata(pdev, wdd); + + return watchdog_register_device(wdd); +} + +static int ebc_c384_wdt_remove(struct platform_device *pdev) +{ + struct watchdog_device *wdd = platform_get_drvdata(pdev); + + watchdog_unregister_device(wdd); + + return 0; +} + +static struct platform_driver ebc_c384_wdt_driver = { + .driver = { + .name = MODULE_NAME + }, + .remove = ebc_c384_wdt_remove +}; + +static struct platform_device *ebc_c384_wdt_device; + +static int __init ebc_c384_wdt_init(void) +{ + int err; + + if (!dmi_match(DMI_BOARD_NAME, "EBC-C384 SBC")) + return -ENODEV; + + ebc_c384_wdt_device = platform_device_alloc(MODULE_NAME, -1); + if (!ebc_c384_wdt_device) + return -ENOMEM; + + err = platform_device_add(ebc_c384_wdt_device); + if (err) + goto err_platform_device; + + err = platform_driver_probe(&ebc_c384_wdt_driver, ebc_c384_wdt_probe); + if (err) + goto err_platform_driver; + + return 0; + +err_platform_driver: + platform_device_del(ebc_c384_wdt_device); +err_platform_device: + platform_device_put(ebc_c384_wdt_device); + return err; +} + +static void __exit ebc_c384_wdt_exit(void) +{ + platform_device_unregister(ebc_c384_wdt_device); + platform_driver_unregister(&ebc_c384_wdt_driver); +} + +module_init(ebc_c384_wdt_init); +module_exit(ebc_c384_wdt_exit); + +MODULE_AUTHOR("William Breathitt Gray "); +MODULE_DESCRIPTION("WinSystems EBC-C384 watchdog timer driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" MODULE_NAME); From 2fc94b5f229a5d9d1db7fd91d0a4808cfeb915f5 Mon Sep 17 00:00:00 2001 From: Fu Wei Date: Mon, 29 Feb 2016 16:46:47 +0800 Subject: [PATCH 10/28] Documentation: add sbsa-gwdt driver documentation The sbsa-gwdt.txt documentation in devicetree/bindings/watchdog is for introducing SBSA(Server Base System Architecture) Generic Watchdog device node info into FDT. Also add sbsa-gwdt introduction in watchdog-parameters.txt Acked-by: Arnd Bergmann Acked-by: Rob Herring Signed-off-by: Fu Wei Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../bindings/watchdog/sbsa-gwdt.txt | 31 +++++++++++++++++++ .../watchdog/watchdog-parameters.txt | 7 +++++ 2 files changed, 38 insertions(+) create mode 100644 Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt diff --git a/Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt b/Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt new file mode 100644 index 000000000000..6f2d5f91964d --- /dev/null +++ b/Documentation/devicetree/bindings/watchdog/sbsa-gwdt.txt @@ -0,0 +1,31 @@ +* SBSA (Server Base System Architecture) Generic Watchdog + +The SBSA Generic Watchdog Timer is used to force a reset of the system +after two stages of timeout have elapsed. A detailed definition of the +watchdog timer can be found in the ARM document: ARM-DEN-0029 - Server +Base System Architecture (SBSA) + +Required properties: +- compatible: Should at least contain "arm,sbsa-gwdt". + +- reg: Each entry specifies the base physical address of a register frame + and the length of that frame; currently, two frames must be defined, + in this order: + 1: Watchdog control frame; + 2: Refresh frame. + +- interrupts: Should contain the Watchdog Signal 0 (WS0) SPI (Shared + Peripheral Interrupt) number of SBSA Generic Watchdog. + +Optional properties +- timeout-sec: Watchdog timeout values (in seconds). + +Example for FVP Foundation Model v8: + +watchdog@2a440000 { + compatible = "arm,sbsa-gwdt"; + reg = <0x0 0x2a440000 0 0x1000>, + <0x0 0x2a450000 0 0x1000>; + interrupts = <0 27 4>; + timeout-sec = <30>; +}; diff --git a/Documentation/watchdog/watchdog-parameters.txt b/Documentation/watchdog/watchdog-parameters.txt index 53dfc73e0171..beb0ae7bb3e6 100644 --- a/Documentation/watchdog/watchdog-parameters.txt +++ b/Documentation/watchdog/watchdog-parameters.txt @@ -289,6 +289,13 @@ sbc_fitpc2_wdt: margin: Watchdog margin in seconds (default 60s) nowayout: Watchdog cannot be stopped once started ------------------------------------------------- +sbsa_gwdt: +timeout: Watchdog timeout in seconds. (default 10s) +action: Watchdog action at the first stage timeout, + set to 0 to ignore, 1 to panic. (default=0) +nowayout: Watchdog cannot be stopped once started + (default=kernel config parameter) +------------------------------------------------- sc1200wdt: isapnp: When set to 0 driver ISA PnP support will be disabled (default=1) io: io port From 57d2caaabfc744692e36ff83b508d7c52c70e36f Mon Sep 17 00:00:00 2001 From: Fu Wei Date: Mon, 29 Feb 2016 16:46:50 +0800 Subject: [PATCH 11/28] Watchdog: introduce ARM SBSA watchdog driver According to Server Base System Architecture (SBSA) specification, the SBSA Generic Watchdog has two stage timeouts: the first signal (WS0) is for alerting the system by interrupt, the second one (WS1) is a real hardware reset. More details about the hardware specification of this device: ARM DEN0029B - Server Base System Architecture (SBSA) This driver can operate ARM SBSA Generic Watchdog as a single stage watchdog or a two stages watchdog, it's set up by the module parameter "action". In the single stage mode, when the timeout is reached, your system will be reset by WS1. The first signal (WS0) is ignored. In the two stages mode, when the timeout is reached, the first signal (WS0) will trigger panic. If the system is getting into trouble and cannot be reset by panic or restart properly by the kdump kernel(if supported), then the second stage (as long as the first stage) will be reached, system will be reset by WS1. This function can help administrator to backup the system context info by panic console output or kdump. This driver bases on linux kernel watchdog framework, so it can get timeout from module parameter and FDT at the driver init stage. Signed-off-by: Fu Wei Reviewed-by: Graeme Gregory Tested-by: Pratyush Anand Acked-by: Timur Tabi Reviewed-by: Mathieu Poirier Tested-by: Suravee Suthikulpanit Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 20 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/sbsa_gwdt.c | 408 +++++++++++++++++++++++++++++++++++ 3 files changed, 429 insertions(+) create mode 100644 drivers/watchdog/sbsa_gwdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 938f9205f828..3fca65e418c9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -202,6 +202,26 @@ config ARM_SP805_WATCHDOG ARM Primecell SP805 Watchdog timer. This will reboot your system when the timeout is reached. +config ARM_SBSA_WATCHDOG + tristate "ARM SBSA Generic Watchdog" + depends on ARM64 + depends on ARM_ARCH_TIMER + select WATCHDOG_CORE + help + ARM SBSA Generic Watchdog has two stage timeouts: + the first signal (WS0) is for alerting the system by interrupt, + the second one (WS1) is a real hardware reset. + More details: ARM DEN0029B - Server Base System Architecture (SBSA) + + This driver can operate ARM SBSA Generic Watchdog as a single stage + or a two stages watchdog, it depends on the module parameter "action". + + Note: the maximum timeout in the two stages mode is half of that in + the single stage mode. + + To compile this driver as module, choose M here: The module + will be called sbsa_gwdt. + config ASM9260_WATCHDOG tristate "Alphascale ASM9260 watchdog" depends on MACH_ASM9260 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index d34e1b66b77f..9ae5db0acb1a 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -30,6 +30,7 @@ obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o # ARM Architecture obj-$(CONFIG_ARM_SP805_WATCHDOG) += sp805_wdt.o +obj-$(CONFIG_ARM_SBSA_WATCHDOG) += sbsa_gwdt.o obj-$(CONFIG_ASM9260_WATCHDOG) += asm9260_wdt.o obj-$(CONFIG_AT91RM9200_WATCHDOG) += at91rm9200_wdt.o obj-$(CONFIG_AT91SAM9X_WATCHDOG) += at91sam9_wdt.o diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c new file mode 100644 index 000000000000..ad383f6f15fc --- /dev/null +++ b/drivers/watchdog/sbsa_gwdt.c @@ -0,0 +1,408 @@ +/* + * SBSA(Server Base System Architecture) Generic Watchdog driver + * + * Copyright (c) 2015, Linaro Ltd. + * Author: Fu Wei + * Suravee Suthikulpanit + * Al Stone + * Timur Tabi + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * ARM SBSA Generic Watchdog has two stage timeouts: + * the first signal (WS0) is for alerting the system by interrupt, + * the second one (WS1) is a real hardware reset. + * More details about the hardware specification of this device: + * ARM DEN0029B - Server Base System Architecture (SBSA) + * + * This driver can operate ARM SBSA Generic Watchdog as a single stage watchdog + * or a two stages watchdog, it's set up by the module parameter "action". + * In the single stage mode, when the timeout is reached, your system + * will be reset by WS1. The first signal (WS0) is ignored. + * In the two stages mode, when the timeout is reached, the first signal (WS0) + * will trigger panic. If the system is getting into trouble and cannot be reset + * by panic or restart properly by the kdump kernel(if supported), then the + * second stage (as long as the first stage) will be reached, system will be + * reset by WS1. This function can help administrator to backup the system + * context info by panic console output or kdump. + * + * SBSA GWDT: + * if action is 1 (the two stages mode): + * |--------WOR-------WS0--------WOR-------WS1 + * |----timeout-----(panic)----timeout-----reset + * + * if action is 0 (the single stage mode): + * |------WOR-----WS0(ignored)-----WOR------WS1 + * |--------------timeout-------------------reset + * + * Note: Since this watchdog timer has two stages, and each stage is determined + * by WOR, in the single stage mode, the timeout is (WOR * 2); in the two + * stages mode, the timeout is WOR. The maximum timeout in the two stages mode + * is half of that in the single stage mode. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "sbsa-gwdt" +#define WATCHDOG_NAME "SBSA Generic Watchdog" + +/* SBSA Generic Watchdog register definitions */ +/* refresh frame */ +#define SBSA_GWDT_WRR 0x000 + +/* control frame */ +#define SBSA_GWDT_WCS 0x000 +#define SBSA_GWDT_WOR 0x008 +#define SBSA_GWDT_WCV 0x010 + +/* refresh/control frame */ +#define SBSA_GWDT_W_IIDR 0xfcc +#define SBSA_GWDT_IDR 0xfd0 + +/* Watchdog Control and Status Register */ +#define SBSA_GWDT_WCS_EN BIT(0) +#define SBSA_GWDT_WCS_WS0 BIT(1) +#define SBSA_GWDT_WCS_WS1 BIT(2) + +/** + * struct sbsa_gwdt - Internal representation of the SBSA GWDT + * @wdd: kernel watchdog_device structure + * @clk: store the System Counter clock frequency, in Hz. + * @refresh_base: Virtual address of the watchdog refresh frame + * @control_base: Virtual address of the watchdog control frame + */ +struct sbsa_gwdt { + struct watchdog_device wdd; + u32 clk; + void __iomem *refresh_base; + void __iomem *control_base; +}; + +#define DEFAULT_TIMEOUT 10 /* seconds */ + +static unsigned int timeout; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. (>=0, default=" + __MODULE_STRING(DEFAULT_TIMEOUT) ")"); + +/* + * action refers to action taken when watchdog gets WS0 + * 0 = skip + * 1 = panic + * defaults to skip (0) + */ +static int action; +module_param(action, int, 0); +MODULE_PARM_DESC(action, "after watchdog gets WS0 interrupt, do: " + "0 = skip(*) 1 = panic"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, S_IRUGO); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +/* + * watchdog operation functions + */ +static int sbsa_gwdt_set_timeout(struct watchdog_device *wdd, + unsigned int timeout) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + + wdd->timeout = timeout; + + if (action) + writel(gwdt->clk * timeout, + gwdt->control_base + SBSA_GWDT_WOR); + else + /* + * In the single stage mode, The first signal (WS0) is ignored, + * the timeout is (WOR * 2), so the WOR should be configured + * to half value of timeout. + */ + writel(gwdt->clk / 2 * timeout, + gwdt->control_base + SBSA_GWDT_WOR); + + return 0; +} + +static unsigned int sbsa_gwdt_get_timeleft(struct watchdog_device *wdd) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + u64 timeleft = 0; + + /* + * In the single stage mode, if WS0 is deasserted + * (watchdog is in the first stage), + * timeleft = WOR + (WCV - system counter) + */ + if (!action && + !(readl(gwdt->control_base + SBSA_GWDT_WCS) & SBSA_GWDT_WCS_WS0)) + timeleft += readl(gwdt->control_base + SBSA_GWDT_WOR); + + timeleft += readq(gwdt->control_base + SBSA_GWDT_WCV) - + arch_counter_get_cntvct(); + + do_div(timeleft, gwdt->clk); + + return timeleft; +} + +static int sbsa_gwdt_keepalive(struct watchdog_device *wdd) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + + /* + * Writing WRR for an explicit watchdog refresh. + * You can write anyting (like 0). + */ + writel(0, gwdt->refresh_base + SBSA_GWDT_WRR); + + return 0; +} + +static unsigned int sbsa_gwdt_status(struct watchdog_device *wdd) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + u32 status = readl(gwdt->control_base + SBSA_GWDT_WCS); + + /* is the watchdog timer running? */ + return (status & SBSA_GWDT_WCS_EN) << WDOG_ACTIVE; +} + +static int sbsa_gwdt_start(struct watchdog_device *wdd) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + + /* writing WCS will cause an explicit watchdog refresh */ + writel(SBSA_GWDT_WCS_EN, gwdt->control_base + SBSA_GWDT_WCS); + + return 0; +} + +static int sbsa_gwdt_stop(struct watchdog_device *wdd) +{ + struct sbsa_gwdt *gwdt = watchdog_get_drvdata(wdd); + + /* Simply write 0 to WCS to clean WCS_EN bit */ + writel(0, gwdt->control_base + SBSA_GWDT_WCS); + + return 0; +} + +static irqreturn_t sbsa_gwdt_interrupt(int irq, void *dev_id) +{ + panic(WATCHDOG_NAME " timeout"); + + return IRQ_HANDLED; +} + +static struct watchdog_info sbsa_gwdt_info = { + .identity = WATCHDOG_NAME, + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE | + WDIOF_CARDRESET, +}; + +static struct watchdog_ops sbsa_gwdt_ops = { + .owner = THIS_MODULE, + .start = sbsa_gwdt_start, + .stop = sbsa_gwdt_stop, + .status = sbsa_gwdt_status, + .ping = sbsa_gwdt_keepalive, + .set_timeout = sbsa_gwdt_set_timeout, + .get_timeleft = sbsa_gwdt_get_timeleft, +}; + +static int sbsa_gwdt_probe(struct platform_device *pdev) +{ + void __iomem *rf_base, *cf_base; + struct device *dev = &pdev->dev; + struct watchdog_device *wdd; + struct sbsa_gwdt *gwdt; + struct resource *res; + int ret, irq; + u32 status; + + gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL); + if (!gwdt) + return -ENOMEM; + platform_set_drvdata(pdev, gwdt); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + cf_base = devm_ioremap_resource(dev, res); + if (IS_ERR(cf_base)) + return PTR_ERR(cf_base); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + rf_base = devm_ioremap_resource(dev, res); + if (IS_ERR(rf_base)) + return PTR_ERR(rf_base); + + /* + * Get the frequency of system counter from the cp15 interface of ARM + * Generic timer. We don't need to check it, because if it returns "0", + * system would panic in very early stage. + */ + gwdt->clk = arch_timer_get_cntfrq(); + gwdt->refresh_base = rf_base; + gwdt->control_base = cf_base; + + wdd = &gwdt->wdd; + wdd->parent = dev; + wdd->info = &sbsa_gwdt_info; + wdd->ops = &sbsa_gwdt_ops; + wdd->min_timeout = 1; + wdd->max_timeout = U32_MAX / gwdt->clk; + wdd->timeout = DEFAULT_TIMEOUT; + watchdog_set_drvdata(wdd, gwdt); + watchdog_set_nowayout(wdd, nowayout); + + status = readl(cf_base + SBSA_GWDT_WCS); + if (status & SBSA_GWDT_WCS_WS1) { + dev_warn(dev, "System reset by WDT.\n"); + wdd->bootstatus |= WDIOF_CARDRESET; + } + + if (action) { + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + action = 0; + dev_warn(dev, "unable to get ws0 interrupt.\n"); + } else { + /* + * In case there is a pending ws0 interrupt, just ping + * the watchdog before registering the interrupt routine + */ + writel(0, rf_base + SBSA_GWDT_WRR); + if (devm_request_irq(dev, irq, sbsa_gwdt_interrupt, 0, + pdev->name, gwdt)) { + action = 0; + dev_warn(dev, "unable to request IRQ %d.\n", + irq); + } + } + if (!action) + dev_warn(dev, "falling back to single stage mode.\n"); + } + /* + * In the single stage mode, The first signal (WS0) is ignored, + * the timeout is (WOR * 2), so the maximum timeout should be doubled. + */ + if (!action) + wdd->max_timeout *= 2; + + watchdog_init_timeout(wdd, timeout, dev); + /* + * Update timeout to WOR. + * Because of the explicit watchdog refresh mechanism, + * it's also a ping, if watchdog is enabled. + */ + sbsa_gwdt_set_timeout(wdd, wdd->timeout); + + ret = watchdog_register_device(wdd); + if (ret) + return ret; + + dev_info(dev, "Initialized with %ds timeout @ %u Hz, action=%d.%s\n", + wdd->timeout, gwdt->clk, action, + status & SBSA_GWDT_WCS_EN ? " [enabled]" : ""); + + return 0; +} + +static void sbsa_gwdt_shutdown(struct platform_device *pdev) +{ + struct sbsa_gwdt *gwdt = platform_get_drvdata(pdev); + + sbsa_gwdt_stop(&gwdt->wdd); +} + +static int sbsa_gwdt_remove(struct platform_device *pdev) +{ + struct sbsa_gwdt *gwdt = platform_get_drvdata(pdev); + + watchdog_unregister_device(&gwdt->wdd); + + return 0; +} + +/* Disable watchdog if it is active during suspend */ +static int __maybe_unused sbsa_gwdt_suspend(struct device *dev) +{ + struct sbsa_gwdt *gwdt = dev_get_drvdata(dev); + + if (watchdog_active(&gwdt->wdd)) + sbsa_gwdt_stop(&gwdt->wdd); + + return 0; +} + +/* Enable watchdog if necessary */ +static int __maybe_unused sbsa_gwdt_resume(struct device *dev) +{ + struct sbsa_gwdt *gwdt = dev_get_drvdata(dev); + + if (watchdog_active(&gwdt->wdd)) + sbsa_gwdt_start(&gwdt->wdd); + + return 0; +} + +static const struct dev_pm_ops sbsa_gwdt_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(sbsa_gwdt_suspend, sbsa_gwdt_resume) +}; + +static const struct of_device_id sbsa_gwdt_of_match[] = { + { .compatible = "arm,sbsa-gwdt", }, + {}, +}; +MODULE_DEVICE_TABLE(of, sbsa_gwdt_of_match); + +static const struct platform_device_id sbsa_gwdt_pdev_match[] = { + { .name = DRV_NAME, }, + {}, +}; +MODULE_DEVICE_TABLE(platform, sbsa_gwdt_pdev_match); + +static struct platform_driver sbsa_gwdt_driver = { + .driver = { + .name = DRV_NAME, + .pm = &sbsa_gwdt_pm_ops, + .of_match_table = sbsa_gwdt_of_match, + }, + .probe = sbsa_gwdt_probe, + .remove = sbsa_gwdt_remove, + .shutdown = sbsa_gwdt_shutdown, + .id_table = sbsa_gwdt_pdev_match, +}; + +module_platform_driver(sbsa_gwdt_driver); + +MODULE_DESCRIPTION("SBSA Generic Watchdog Driver"); +MODULE_AUTHOR("Fu Wei "); +MODULE_AUTHOR("Suravee Suthikulpanit "); +MODULE_AUTHOR("Al Stone "); +MODULE_AUTHOR("Timur Tabi "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); From 882dec1ff125e9020157d27672ec11c2fdfb82c2 Mon Sep 17 00:00:00 2001 From: Javier Martinez Canillas Date: Tue, 1 Mar 2016 13:45:17 -0300 Subject: [PATCH 12/28] watchdog: s3c2410_wdt: Add max and min timeout values The watchdog maximum timeout value is determined by the number of bits for the interval timer counter, its source clock frequency, the number of bits of the prescaler and maximum divider value. This can be calculated with the following equation: max_timeout = counter / (freq / (max_prescale + 1) / max_divider) Setting a maximum timeout value will allow the watchdog core to refuse user-space calls to the WDIOC_SETTIMEOUT ioctl that sets not supported timeout values. For example, systemd tries to set a timeout of 10 minutes on reboot to ensure that the machine will be rebooted even if a reboot failed. This leads to the following error message on an Exynos5422 Odroid XU4 board: [ 147.986045] s3c2410-wdt 101d0000.watchdog: timeout 600 too big Reported-by: Krzysztof Kozlowski Signed-off-by: Javier Martinez Canillas Reviewed-by: Krzysztof Kozlowski Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/s3c2410_wdt.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/watchdog/s3c2410_wdt.c b/drivers/watchdog/s3c2410_wdt.c index d57f19b5077f..59e95762a6de 100644 --- a/drivers/watchdog/s3c2410_wdt.c +++ b/drivers/watchdog/s3c2410_wdt.c @@ -47,6 +47,8 @@ #define S3C2410_WTDAT 0x04 #define S3C2410_WTCNT 0x08 +#define S3C2410_WTCNT_MAXCNT 0xffff + #define S3C2410_WTCON_RSTEN (1 << 0) #define S3C2410_WTCON_INTEN (1 << 2) #define S3C2410_WTCON_ENABLE (1 << 5) @@ -56,8 +58,11 @@ #define S3C2410_WTCON_DIV64 (2 << 3) #define S3C2410_WTCON_DIV128 (3 << 3) +#define S3C2410_WTCON_MAXDIV 0x80 + #define S3C2410_WTCON_PRESCALE(x) ((x) << 8) #define S3C2410_WTCON_PRESCALE_MASK (0xff << 8) +#define S3C2410_WTCON_PRESCALE_MAX 0xff #define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) #define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) @@ -198,6 +203,14 @@ do { \ /* functions */ +static inline unsigned int s3c2410wdt_max_timeout(struct clk *clock) +{ + unsigned long freq = clk_get_rate(clock); + + return S3C2410_WTCNT_MAXCNT / (freq / (S3C2410_WTCON_PRESCALE_MAX + 1) + / S3C2410_WTCON_MAXDIV); +} + static inline struct s3c2410_wdt *freq_to_wdt(struct notifier_block *nb) { return container_of(nb, struct s3c2410_wdt, freq_transition); @@ -568,6 +581,9 @@ static int s3c2410wdt_probe(struct platform_device *pdev) return ret; } + wdt->wdt_device.min_timeout = 1; + wdt->wdt_device.max_timeout = s3c2410wdt_max_timeout(wdt->clock); + ret = s3c2410wdt_cpufreq_register(wdt); if (ret < 0) { dev_err(dev, "failed to register cpufreq\n"); From ccc8208d0855a5b02a4fc49c141b6f1dbf282304 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 2 Mar 2016 23:33:36 +0100 Subject: [PATCH 13/28] watchdog: atlas7_wdt: test clock rate to avoid division by 0 The clk API may return 0 on clk_get_rate, so we should check the result before using it as a divisor. Signed-off-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/atlas7_wdt.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/watchdog/atlas7_wdt.c b/drivers/watchdog/atlas7_wdt.c index df6d9242a319..ed80734befae 100644 --- a/drivers/watchdog/atlas7_wdt.c +++ b/drivers/watchdog/atlas7_wdt.c @@ -154,6 +154,11 @@ static int atlas7_wdt_probe(struct platform_device *pdev) writel(0, wdt->base + ATLAS7_WDT_CNT_CTRL); wdt->tick_rate = clk_get_rate(clk); + if (!wdt->tick_rate) { + ret = -EINVAL; + goto err1; + } + wdt->clk = clk; atlas7_wdd.min_timeout = 1; atlas7_wdd.max_timeout = UINT_MAX / wdt->tick_rate; From 84b84bcf4f718b7b9f5296d4068025fea801a0e1 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 3 Mar 2016 09:24:12 +0100 Subject: [PATCH 14/28] watchdog: tangox_wdt: test clock rate to avoid division by 0 The clk API may return 0 on clk_get_rate, so we should check the result before using it as a divisor. For this, refactor the code to use a central error path. Signed-off-by: Wolfram Sang Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/tangox_wdt.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/watchdog/tangox_wdt.c b/drivers/watchdog/tangox_wdt.c index 709c1ed6fd79..cfbed7e051b6 100644 --- a/drivers/watchdog/tangox_wdt.c +++ b/drivers/watchdog/tangox_wdt.c @@ -139,6 +139,10 @@ static int tangox_wdt_probe(struct platform_device *pdev) return err; dev->clk_rate = clk_get_rate(dev->clk); + if (!dev->clk_rate) { + err = -EINVAL; + goto err; + } dev->wdt.parent = &pdev->dev; dev->wdt.info = &tangox_wdt_info; @@ -171,10 +175,8 @@ static int tangox_wdt_probe(struct platform_device *pdev) } err = watchdog_register_device(&dev->wdt); - if (err) { - clk_disable_unprepare(dev->clk); - return err; - } + if (err) + goto err; platform_set_drvdata(pdev, dev); @@ -187,6 +189,10 @@ static int tangox_wdt_probe(struct platform_device *pdev) dev_info(&pdev->dev, "SMP86xx/SMP87xx watchdog registered\n"); return 0; + + err: + clk_disable_unprepare(dev->clk); + return err; } static int tangox_wdt_remove(struct platform_device *pdev) From 43eec2f50c65bd469989a4dab8c1b5e14d87db84 Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:05 -0500 Subject: [PATCH 15/28] watchdog: pnx4008: update logging during power-on There is no need to add the driver name in the text to display on the console during the power-on: pnx4008-watchdog 4003c000.watchdog: PNX4008 Watchdog Timer: heartbeat 19 sec Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pnx4008_wdt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index 313cd1c6fda0..88264a21017b 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -178,8 +178,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev) goto disable_clk; } - dev_info(&pdev->dev, "PNX4008 Watchdog Timer: heartbeat %d sec\n", - pnx4008_wdd.timeout); + dev_info(&pdev->dev, "heartbeat %d sec\n", pnx4008_wdd.timeout); return 0; From 4ed5443d9175f4b6008ca18601672c812a1fe93b Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:06 -0500 Subject: [PATCH 16/28] watchdog: pnx4008: add restart handler Add restart handler capability to the driver; the restart handler implementation was taken from "mach-lpc32xx" ("lpc23xx_restart" function). Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pnx4008_wdt.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index 88264a21017b..f3be522986b6 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include /* WatchDog Timer - Chapter 23 Page 207 */ @@ -124,6 +126,19 @@ static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, return 0; } +static int pnx4008_restart_handler(struct watchdog_device *wdd, + unsigned long mode, void *cmd) +{ + /* Instant assert of RESETOUT_N with pulse length 1mS */ + writel(13000, WDTIM_PULSE(wdt_base)); + writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base)); + + /* Wait for watchdog to reset system */ + mdelay(1000); + + return NOTIFY_DONE; +} + static const struct watchdog_info pnx4008_wdt_ident = { .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, @@ -135,6 +150,7 @@ static const struct watchdog_ops pnx4008_wdt_ops = { .start = pnx4008_wdt_start, .stop = pnx4008_wdt_stop, .set_timeout = pnx4008_wdt_set_timeout, + .restart = pnx4008_restart_handler, }; static struct watchdog_device pnx4008_wdd = { @@ -169,6 +185,7 @@ static int pnx4008_wdt_probe(struct platform_device *pdev) WDIOF_CARDRESET : 0; pnx4008_wdd.parent = &pdev->dev; watchdog_set_nowayout(&pnx4008_wdd, nowayout); + watchdog_set_restart_priority(&pnx4008_wdd, 128); pnx4008_wdt_stop(&pnx4008_wdd); /* disable for now */ From 25b286c02defa26b136818b17909789102225aa8 Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:07 -0500 Subject: [PATCH 17/28] watchdog: pnx4008: add support for soft reset Add support for explicit soft reset using the reboot mode. The default reboot mode behavior is unchanged; you can overwrite the default reboot type in the board specific file "DT_MACHINE_START" definition using the "reboot_mode" parameter. Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pnx4008_wdt.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index f3be522986b6..51be66e8a473 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -129,9 +129,16 @@ static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, static int pnx4008_restart_handler(struct watchdog_device *wdd, unsigned long mode, void *cmd) { - /* Instant assert of RESETOUT_N with pulse length 1mS */ - writel(13000, WDTIM_PULSE(wdt_base)); - writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base)); + if (mode == REBOOT_SOFT) { + /* Force match output active */ + writel(EXT_MATCH0, WDTIM_EMR(wdt_base)); + /* Internal reset on match output (RESOUT_N not asserted) */ + writel(M_RES1, WDTIM_MCTRL(wdt_base)); + } else { + /* Instant assert of RESETOUT_N with pulse length 1mS */ + writel(13000, WDTIM_PULSE(wdt_base)); + writel(M_RES2 | RESFRC1 | RESFRC2, WDTIM_MCTRL(wdt_base)); + } /* Wait for watchdog to reset system */ mdelay(1000); From 247dcad5c0361fdae06903f3b10855ba0381802d Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:08 -0500 Subject: [PATCH 18/28] watchdog: pnx4008: restart: support "cmd" from userspace Added support to verify if a "cmd" is passed from the userspace program rebooting the system; - if a valid "cmd" is available, handle it; - If the received "cmd" is not supported, use the default reboot mode. Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/pnx4008_wdt.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/drivers/watchdog/pnx4008_wdt.c b/drivers/watchdog/pnx4008_wdt.c index 51be66e8a473..0529aed158a4 100644 --- a/drivers/watchdog/pnx4008_wdt.c +++ b/drivers/watchdog/pnx4008_wdt.c @@ -129,6 +129,21 @@ static int pnx4008_wdt_set_timeout(struct watchdog_device *wdd, static int pnx4008_restart_handler(struct watchdog_device *wdd, unsigned long mode, void *cmd) { + const char *boot_cmd = cmd; + + /* + * Verify if a "cmd" passed from the userspace program rebooting + * the system; if available, handle it. + * - For details, see the 'reboot' syscall in kernel/reboot.c + * - If the received "cmd" is not supported, use the default mode. + */ + if (boot_cmd) { + if (boot_cmd[0] == 'h') + mode = REBOOT_HARD; + else if (boot_cmd[0] == 's') + mode = REBOOT_SOFT; + } + if (mode == REBOOT_SOFT) { /* Force match output active */ writel(EXT_MATCH0, WDTIM_EMR(wdt_base)); From 0a25345743aeae19ae61bd28b588cd4cb9f0123c Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:09 -0500 Subject: [PATCH 19/28] arm: lpc32xx: phy3250 remove restart hook Remove the restart hook assignment from phy3250; this functionality is now managed by the pnx4008 watchdog driver. Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- arch/arm/mach-lpc32xx/phy3250.c | 1 - 1 file changed, 1 deletion(-) diff --git a/arch/arm/mach-lpc32xx/phy3250.c b/arch/arm/mach-lpc32xx/phy3250.c index 77d6b1bab278..942bc3fc9463 100644 --- a/arch/arm/mach-lpc32xx/phy3250.c +++ b/arch/arm/mach-lpc32xx/phy3250.c @@ -263,5 +263,4 @@ DT_MACHINE_START(LPC32XX_DT, "LPC32XX SoC (Flattened Device Tree)") .init_time = lpc32xx_timer_init, .init_machine = lpc3250_machine_init, .dt_compat = lpc32xx_dt_compat, - .restart = lpc23xx_restart, MACHINE_END From e21f56219bf636b4abd15a0b076a1c5cba381501 Mon Sep 17 00:00:00 2001 From: Sylvain Lemieux Date: Fri, 4 Mar 2016 13:44:10 -0500 Subject: [PATCH 20/28] arm: lpc32xx: remove restart handler Remove the restart handler from "mach-lpc32xx"; this functionality is now available in the pnx4008 watchdog driver. Signed-off-by: Sylvain Lemieux Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- arch/arm/mach-lpc32xx/common.c | 15 --------------- arch/arm/mach-lpc32xx/common.h | 1 - 2 files changed, 16 deletions(-) diff --git a/arch/arm/mach-lpc32xx/common.c b/arch/arm/mach-lpc32xx/common.c index 716e83eb1db8..5b7a1e78c3a5 100644 --- a/arch/arm/mach-lpc32xx/common.c +++ b/arch/arm/mach-lpc32xx/common.c @@ -194,21 +194,6 @@ void __init lpc32xx_map_io(void) iotable_init(lpc32xx_io_desc, ARRAY_SIZE(lpc32xx_io_desc)); } -void lpc23xx_restart(enum reboot_mode mode, const char *cmd) -{ - /* Make sure WDT clocks are enabled */ - __raw_writel(LPC32XX_CLKPWR_PWMCLK_WDOG_EN, - LPC32XX_CLKPWR_TIMER_CLK_CTRL); - - /* Instant assert of RESETOUT_N with pulse length 1mS */ - __raw_writel(13000, io_p2v(LPC32XX_WDTIM_BASE + 0x18)); - __raw_writel(0x70, io_p2v(LPC32XX_WDTIM_BASE + 0xC)); - - /* Wait for watchdog to reset system */ - while (1) - ; -} - static int __init lpc32xx_check_uid(void) { u32 uid[4]; diff --git a/arch/arm/mach-lpc32xx/common.h b/arch/arm/mach-lpc32xx/common.h index 1cd8853b2f9b..2d90801ed1e1 100644 --- a/arch/arm/mach-lpc32xx/common.h +++ b/arch/arm/mach-lpc32xx/common.h @@ -30,7 +30,6 @@ extern void lpc32xx_timer_init(void); extern void __init lpc32xx_init_irq(void); extern void __init lpc32xx_map_io(void); extern void __init lpc32xx_serial_init(void); -extern void lpc23xx_restart(enum reboot_mode, const char *); /* From fb32e9b9deeb5df2913deb7d2ae8c36f4f66ecf3 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:14 -0800 Subject: [PATCH 21/28] watchdog: Make set_timeout function optional For some watchdogs, the watchdog driver handles timeout changes without explicitly setting any registers. In this situation, the watchdog driver might only set the 'timeout' variable but do nothing else. This can as well be handled by the infrastructure, so make the set_timeout callback optional. If WDIOF_SETTIMEOUT is configured but the .set_timeout callback is not available, update the timeout variable in the infrastructure code. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 5 +++++ drivers/watchdog/watchdog_dev.c | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 55120a055a14..dd8f912c0576 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -156,6 +156,11 @@ they are supported. These optional routines/operations are: because the watchdog does not necessarily has a 1 second resolution). (Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the watchdog's info structure). + If the watchdog driver does not have to perform any action but setting the + watchdog_device.timeout, this callback can be omitted. + If set_timeout is not provided but, WDIOF_SETTIMEOUT is set, the watchdog + infrastructure updates the timeout value of the watchdog_device internally + to the requested value. * get_timeleft: this routines returns the time that's left before a reset. * restart: this routine restarts the machine. It returns 0 on success or a negative errno code for failure. diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index ba2ecce4aae6..b5e700186ae0 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -183,13 +183,20 @@ static unsigned int watchdog_get_status(struct watchdog_device *wdd) static int watchdog_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { - if (!wdd->ops->set_timeout || !(wdd->info->options & WDIOF_SETTIMEOUT)) + int err = 0; + + if (!(wdd->info->options & WDIOF_SETTIMEOUT)) return -EOPNOTSUPP; if (watchdog_timeout_invalid(wdd, timeout)) return -EINVAL; - return wdd->ops->set_timeout(wdd, timeout); + if (wdd->ops->set_timeout) + err = wdd->ops->set_timeout(wdd, timeout); + else + wdd->timeout = timeout; + + return err; } /* From 664a39236e718f9f03fa73fc01006da9ced04efc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:15 -0800 Subject: [PATCH 22/28] watchdog: Introduce hardware maximum heartbeat in watchdog core Introduce an optional hardware maximum heartbeat in the watchdog core. The hardware maximum heartbeat can be lower than the maximum timeout. Drivers can set the maximum hardware heartbeat value in the watchdog data structure. If the configured timeout exceeds the maximum hardware heartbeat, the watchdog core enables a timer function to assist sending keepalive requests to the watchdog driver. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../watchdog/watchdog-kernel-api.txt | 19 ++- drivers/watchdog/watchdog_dev.c | 129 ++++++++++++++++-- include/linux/watchdog.h | 28 ++-- 3 files changed, 158 insertions(+), 18 deletions(-) diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index dd8f912c0576..15a02595ade1 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -52,6 +52,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + unsigned int max_hw_heartbeat_ms; struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; @@ -73,8 +74,18 @@ It contains following fields: additional information about the watchdog timer itself. (Like it's unique name) * ops: a pointer to the list of watchdog operations that the watchdog supports. * timeout: the watchdog timer's timeout value (in seconds). + This is the time after which the system will reboot if user space does + not send a heartbeat request if WDOG_ACTIVE is set. * min_timeout: the watchdog timer's minimum timeout value (in seconds). -* max_timeout: the watchdog timer's maximum timeout value (in seconds). + If set, the minimum configurable value for 'timeout'. +* max_timeout: the watchdog timer's maximum timeout value (in seconds), + as seen from userspace. If set, the maximum configurable value for + 'timeout'. Not used if max_hw_heartbeat_ms is non-zero. +* max_hw_heartbeat_ms: Maximum hardware heartbeat, in milli-seconds. + If set, the infrastructure will send heartbeats to the watchdog driver + if 'timeout' is larger than max_hw_heartbeat_ms, unless WDOG_ACTIVE + is set and userspace failed to send a heartbeat for at least 'timeout' + seconds. * reboot_nb: notifier block that is registered for reboot notifications, for internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core will stop the watchdog on such notifications. @@ -153,7 +164,11 @@ they are supported. These optional routines/operations are: and -EIO for "could not write value to the watchdog". On success this routine should set the timeout value of the watchdog_device to the achieved timeout value (which may be different from the requested one - because the watchdog does not necessarily has a 1 second resolution). + because the watchdog does not necessarily have a 1 second resolution). + Drivers implementing max_hw_heartbeat_ms set the hardware watchdog heartbeat + to the minimum of timeout and max_hw_heartbeat_ms. Those drivers set the + timeout value of the watchdog_device either to the requested timeout value + (if it is larger than max_hw_heartbeat_ms), or to the achieved timeout value. (Note: the WDIOF_SETTIMEOUT needs to be set in the options field of the watchdog's info structure). If the watchdog driver does not have to perform any action but setting the diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index b5e700186ae0..e668a9e8b648 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -36,6 +36,7 @@ #include /* For the -ENODEV/... values */ #include /* For file operations */ #include /* For __init/__exit/... */ +#include /* For timeout functions */ #include /* For printk/panic/... */ #include /* For data references */ #include /* For handling misc devices */ @@ -44,6 +45,7 @@ #include /* For memory functions */ #include /* For standard types (like size_t) */ #include /* For watchdog specific items */ +#include /* For workqueue */ #include /* For copy_to_user/put_user/... */ #include "watchdog_core.h" @@ -61,6 +63,8 @@ struct watchdog_core_data { struct cdev cdev; struct watchdog_device *wdd; struct mutex lock; + unsigned long last_keepalive; + struct delayed_work work; unsigned long status; /* Internal status bits */ #define _WDOG_DEV_OPEN 0 /* Opened ? */ #define _WDOG_ALLOW_RELEASE 1 /* Did we receive the magic char ? */ @@ -71,6 +75,76 @@ static dev_t watchdog_devt; /* Reference to watchdog device behind /dev/watchdog */ static struct watchdog_core_data *old_wd_data; +static struct workqueue_struct *watchdog_wq; + +static inline bool watchdog_need_worker(struct watchdog_device *wdd) +{ + /* All variables in milli-seconds */ + unsigned int hm = wdd->max_hw_heartbeat_ms; + unsigned int t = wdd->timeout * 1000; + + /* + * A worker to generate heartbeat requests is needed if all of the + * following conditions are true. + * - Userspace activated the watchdog. + * - The driver provided a value for the maximum hardware timeout, and + * thus is aware that the framework supports generating heartbeat + * requests. + * - Userspace requests a longer timeout than the hardware can handle. + */ + return watchdog_active(wdd) && hm && t > hm; +} + +static long watchdog_next_keepalive(struct watchdog_device *wdd) +{ + struct watchdog_core_data *wd_data = wdd->wd_data; + unsigned int timeout_ms = wdd->timeout * 1000; + unsigned long keepalive_interval; + unsigned long last_heartbeat; + unsigned long virt_timeout; + unsigned int hw_heartbeat_ms; + + virt_timeout = wd_data->last_keepalive + msecs_to_jiffies(timeout_ms); + hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms); + keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); + + /* + * To ensure that the watchdog times out wdd->timeout seconds + * after the most recent ping from userspace, the last + * worker ping has to come in hw_heartbeat_ms before this timeout. + */ + last_heartbeat = virt_timeout - msecs_to_jiffies(hw_heartbeat_ms); + return min_t(long, last_heartbeat - jiffies, keepalive_interval); +} + +static inline void watchdog_update_worker(struct watchdog_device *wdd) +{ + struct watchdog_core_data *wd_data = wdd->wd_data; + + if (watchdog_need_worker(wdd)) { + long t = watchdog_next_keepalive(wdd); + + if (t > 0) + mod_delayed_work(watchdog_wq, &wd_data->work, t); + } else { + cancel_delayed_work(&wd_data->work); + } +} + +static int __watchdog_ping(struct watchdog_device *wdd) +{ + int err; + + if (wdd->ops->ping) + err = wdd->ops->ping(wdd); /* ping the watchdog */ + else + err = wdd->ops->start(wdd); /* restart watchdog */ + + watchdog_update_worker(wdd); + + return err; +} + /* * watchdog_ping: ping the watchdog. * @wdd: the watchdog device to ping @@ -85,17 +159,28 @@ static struct watchdog_core_data *old_wd_data; static int watchdog_ping(struct watchdog_device *wdd) { - int err; + struct watchdog_core_data *wd_data = wdd->wd_data; if (!watchdog_active(wdd)) return 0; - if (wdd->ops->ping) - err = wdd->ops->ping(wdd); /* ping the watchdog */ - else - err = wdd->ops->start(wdd); /* restart watchdog */ + wd_data->last_keepalive = jiffies; + return __watchdog_ping(wdd); +} - return err; +static void watchdog_ping_work(struct work_struct *work) +{ + struct watchdog_core_data *wd_data; + struct watchdog_device *wdd; + + wd_data = container_of(to_delayed_work(work), struct watchdog_core_data, + work); + + mutex_lock(&wd_data->lock); + wdd = wd_data->wdd; + if (wdd && watchdog_active(wdd)) + __watchdog_ping(wdd); + mutex_unlock(&wd_data->lock); } /* @@ -111,14 +196,20 @@ static int watchdog_ping(struct watchdog_device *wdd) static int watchdog_start(struct watchdog_device *wdd) { + struct watchdog_core_data *wd_data = wdd->wd_data; + unsigned long started_at; int err; if (watchdog_active(wdd)) return 0; + started_at = jiffies; err = wdd->ops->start(wdd); - if (err == 0) + if (err == 0) { set_bit(WDOG_ACTIVE, &wdd->status); + wd_data->last_keepalive = started_at; + watchdog_update_worker(wdd); + } return err; } @@ -137,6 +228,7 @@ static int watchdog_start(struct watchdog_device *wdd) static int watchdog_stop(struct watchdog_device *wdd) { + struct watchdog_core_data *wd_data = wdd->wd_data; int err; if (!watchdog_active(wdd)) @@ -149,8 +241,10 @@ static int watchdog_stop(struct watchdog_device *wdd) } err = wdd->ops->stop(wdd); - if (err == 0) + if (err == 0) { clear_bit(WDOG_ACTIVE, &wdd->status); + cancel_delayed_work(&wd_data->work); + } return err; } @@ -196,6 +290,8 @@ static int watchdog_set_timeout(struct watchdog_device *wdd, else wdd->timeout = timeout; + watchdog_update_worker(wdd); + return err; } @@ -616,6 +712,8 @@ static int watchdog_release(struct inode *inode, struct file *file) watchdog_ping(wdd); } + cancel_delayed_work_sync(&wd_data->work); + /* make sure that /dev/watchdog can be re-opened */ clear_bit(_WDOG_DEV_OPEN, &wd_data->status); @@ -665,6 +763,11 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) wd_data->wdd = wdd; wdd->wd_data = wd_data; + if (!watchdog_wq) + return -ENODEV; + + INIT_DELAYED_WORK(&wd_data->work, watchdog_ping_work); + if (wdd->id == 0) { old_wd_data = wd_data; watchdog_miscdev.parent = wdd->parent; @@ -722,6 +825,8 @@ static void watchdog_cdev_unregister(struct watchdog_device *wdd) wdd->wd_data = NULL; mutex_unlock(&wd_data->lock); + cancel_delayed_work_sync(&wd_data->work); + kref_put(&wd_data->kref, watchdog_core_data_release); } @@ -787,6 +892,13 @@ int __init watchdog_dev_init(void) { int err; + watchdog_wq = alloc_workqueue("watchdogd", + WQ_HIGHPRI | WQ_MEM_RECLAIM, 0); + if (!watchdog_wq) { + pr_err("Failed to create watchdog workqueue\n"); + return -ENOMEM; + } + err = class_register(&watchdog_class); if (err < 0) { pr_err("couldn't register class\n"); @@ -813,4 +925,5 @@ void __exit watchdog_dev_exit(void) { unregister_chrdev_region(watchdog_devt, MAX_DOGS); class_unregister(&watchdog_class); + destroy_workqueue(watchdog_wq); } diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 0b565f2ad242..8e82daecb7d3 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -10,8 +10,9 @@ #include -#include #include +#include +#include #include #include @@ -61,14 +62,19 @@ struct watchdog_ops { * @bootstatus: Status of the watchdog device at boot. * @timeout: The watchdog devices timeout value (in seconds). * @min_timeout:The watchdog devices minimum timeout value (in seconds). - * @max_timeout:The watchdog devices maximum timeout value (in seconds). + * @max_timeout:The watchdog devices maximum timeout value (in seconds) + * as configurable from user space. Only relevant if + * max_hw_heartbeat_ms is not provided. + * @max_hw_heartbeat_ms: + * Hardware limit for maximum timeout, in milli-seconds. + * Replaces max_timeout if specified. * @reboot_nb: The notifier block to stop watchdog on reboot. * @restart_nb: The notifier block to register a restart function. * @driver_data:Pointer to the drivers private data. * @wd_data: Pointer to watchdog core internal data. * @status: Field that contains the devices internal status bits. - * @deferred: entry in wtd_deferred_reg_list which is used to - * register early initialized watchdogs. + * @deferred: Entry in wtd_deferred_reg_list which is used to + * register early initialized watchdogs. * * The watchdog_device structure contains all information about a * watchdog timer device. @@ -89,6 +95,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + unsigned int max_hw_heartbeat_ms; struct notifier_block reboot_nb; struct notifier_block restart_nb; void *driver_data; @@ -128,13 +135,18 @@ static inline bool watchdog_timeout_invalid(struct watchdog_device *wdd, unsigne { /* * The timeout is invalid if + * - the requested value is larger than UINT_MAX / 1000 + * (since internal calculations are done in milli-seconds), + * or * - the requested value is smaller than the configured minimum timeout, * or - * - a maximum timeout is configured, and the requested value is larger - * than the maximum timeout. + * - a maximum hardware timeout is not configured, a maximum timeout + * is configured, and the requested value is larger than the + * configured maximum timeout. */ - return t < wdd->min_timeout || - (wdd->max_timeout && t > wdd->max_timeout); + return t > UINT_MAX / 1000 || t < wdd->min_timeout || + (!wdd->max_hw_heartbeat_ms && wdd->max_timeout && + t > wdd->max_timeout); } /* Use the following functions to manipulate watchdog driver specific data */ From ee142889e32f564f9b5e57b68b06693ec5473074 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:16 -0800 Subject: [PATCH 23/28] watchdog: Introduce WDOG_HW_RUNNING flag The WDOG_HW_RUNNING flag is expected to be set by watchdog drivers if the hardware watchdog is running. If the flag is set, the watchdog subsystem will ping the watchdog even if the watchdog device is closed. The watchdog driver stop function is now optional and may be omitted if the watchdog can not be stopped. If stopping the watchdog is not possible but the driver implements a stop function, it is responsible to set the WDOG_HW_RUNNING flag in its stop function. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../watchdog/watchdog-kernel-api.txt | 22 +++++--- drivers/watchdog/watchdog_dev.c | 52 ++++++++++++++----- include/linux/watchdog.h | 10 ++++ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 15a02595ade1..954134a5c4a4 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -137,10 +137,10 @@ are: * stop: with this routine the watchdog timer device is being stopped. The routine needs a pointer to the watchdog timer device structure as a parameter. It returns zero on success or a negative errno code for failure. - Some watchdog timer hardware can only be started and not be stopped. The - driver supporting this hardware needs to make sure that a start and stop - routine is being provided. This can be done by using a timer in the driver - that regularly sends a keepalive ping to the watchdog timer hardware. + Some watchdog timer hardware can only be started and not be stopped. + If a watchdog can not be stopped, the watchdog driver must set the + WDOG_HW_RUNNING flag in its stop function to inform the watchdog core that + the watchdog is still running. Not all watchdog timer hardware supports the same functionality. That's why all other routines/operations are optional. They only need to be provided if @@ -189,11 +189,19 @@ The 'ref' and 'unref' operations are no longer used and deprecated. The status bits should (preferably) be set with the set_bit and clear_bit alike bit-operations. The status bits that are defined are: * WDOG_ACTIVE: this status bit indicates whether or not a watchdog timer device - is active or not. When the watchdog is active after booting, then you should - set this status bit (Note: when you register the watchdog timer device with - this bit set, then opening /dev/watchdog will skip the start operation) + is active or not from user perspective. User space is expected to send + heartbeat requests to the driver while this flag is set. * WDOG_NO_WAY_OUT: this bit stores the nowayout setting for the watchdog. If this bit is set then the watchdog timer will not be able to stop. +* WDOG_HW_RUNNING: Set by the watchdog driver if the hardware watchdog is + running. The bit must be set if the watchdog timer hardware can not be + stopped. The bit may also be set if the watchdog timer is running after + booting, before the watchdog device is opened. If set, the watchdog + infrastructure will send keepalives to the watchdog hardware while + WDOG_ACTIVE is not set. + Note: when you register the watchdog timer device with this bit set, + then opening /dev/watchdog will skip the start operation but send a keepalive + request instead. To set the WDOG_NO_WAY_OUT status bit (before registering your watchdog timer device) you can either: diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index e668a9e8b648..5d3a9fa4856e 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -92,7 +92,8 @@ static inline bool watchdog_need_worker(struct watchdog_device *wdd) * requests. * - Userspace requests a longer timeout than the hardware can handle. */ - return watchdog_active(wdd) && hm && t > hm; + return hm && ((watchdog_active(wdd) && t > hm) || + (t && !watchdog_active(wdd) && watchdog_hw_running(wdd))); } static long watchdog_next_keepalive(struct watchdog_device *wdd) @@ -108,6 +109,9 @@ static long watchdog_next_keepalive(struct watchdog_device *wdd) hw_heartbeat_ms = min(timeout_ms, wdd->max_hw_heartbeat_ms); keepalive_interval = msecs_to_jiffies(hw_heartbeat_ms / 2); + if (!watchdog_active(wdd)) + return keepalive_interval; + /* * To ensure that the watchdog times out wdd->timeout seconds * after the most recent ping from userspace, the last @@ -161,7 +165,7 @@ static int watchdog_ping(struct watchdog_device *wdd) { struct watchdog_core_data *wd_data = wdd->wd_data; - if (!watchdog_active(wdd)) + if (!watchdog_active(wdd) && !watchdog_hw_running(wdd)) return 0; wd_data->last_keepalive = jiffies; @@ -178,7 +182,7 @@ static void watchdog_ping_work(struct work_struct *work) mutex_lock(&wd_data->lock); wdd = wd_data->wdd; - if (wdd && watchdog_active(wdd)) + if (wdd && (watchdog_active(wdd) || watchdog_hw_running(wdd))) __watchdog_ping(wdd); mutex_unlock(&wd_data->lock); } @@ -204,7 +208,10 @@ static int watchdog_start(struct watchdog_device *wdd) return 0; started_at = jiffies; - err = wdd->ops->start(wdd); + if (watchdog_hw_running(wdd) && wdd->ops->ping) + err = wdd->ops->ping(wdd); + else + err = wdd->ops->start(wdd); if (err == 0) { set_bit(WDOG_ACTIVE, &wdd->status); wd_data->last_keepalive = started_at; @@ -228,8 +235,7 @@ static int watchdog_start(struct watchdog_device *wdd) static int watchdog_stop(struct watchdog_device *wdd) { - struct watchdog_core_data *wd_data = wdd->wd_data; - int err; + int err = 0; if (!watchdog_active(wdd)) return 0; @@ -243,7 +249,7 @@ static int watchdog_stop(struct watchdog_device *wdd) err = wdd->ops->stop(wdd); if (err == 0) { clear_bit(WDOG_ACTIVE, &wdd->status); - cancel_delayed_work(&wd_data->work); + watchdog_update_worker(wdd); } return err; @@ -641,7 +647,7 @@ static int watchdog_open(struct inode *inode, struct file *file) * If the /dev/watchdog device is open, we don't want the module * to be unloaded. */ - if (!try_module_get(wdd->ops->owner)) { + if (!watchdog_hw_running(wdd) && !try_module_get(wdd->ops->owner)) { err = -EBUSY; goto out_clear; } @@ -652,7 +658,8 @@ static int watchdog_open(struct inode *inode, struct file *file) file->private_data = wd_data; - kref_get(&wd_data->kref); + if (!watchdog_hw_running(wdd)) + kref_get(&wd_data->kref); /* dev/watchdog is a virtual (and thus non-seekable) filesystem */ return nonseekable_open(inode, file); @@ -713,15 +720,22 @@ static int watchdog_release(struct inode *inode, struct file *file) } cancel_delayed_work_sync(&wd_data->work); + watchdog_update_worker(wdd); /* make sure that /dev/watchdog can be re-opened */ clear_bit(_WDOG_DEV_OPEN, &wd_data->status); done: mutex_unlock(&wd_data->lock); - /* Allow the owner module to be unloaded again */ - module_put(wd_data->cdev.owner); - kref_put(&wd_data->kref, watchdog_core_data_release); + /* + * Allow the owner module to be unloaded again unless the watchdog + * is still running. If the watchdog is still running, it can not + * be stopped, and its driver must not be unloaded. + */ + if (!watchdog_hw_running(wdd)) { + module_put(wdd->ops->owner); + kref_put(&wd_data->kref, watchdog_core_data_release); + } return 0; } @@ -798,8 +812,20 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) old_wd_data = NULL; kref_put(&wd_data->kref, watchdog_core_data_release); } + return err; } - return err; + + /* + * If the watchdog is running, prevent its driver from being unloaded, + * and schedule an immediate ping. + */ + if (watchdog_hw_running(wdd)) { + __module_get(wdd->ops->owner); + kref_get(&wd_data->kref); + queue_delayed_work(watchdog_wq, &wd_data->work, 0); + } + + return 0; } /* diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 8e82daecb7d3..e2f45549b243 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -105,6 +105,7 @@ struct watchdog_device { #define WDOG_ACTIVE 0 /* Is the watchdog running/active */ #define WDOG_NO_WAY_OUT 1 /* Is 'nowayout' feature set ? */ #define WDOG_STOP_ON_REBOOT 2 /* Should be stopped on reboot */ +#define WDOG_HW_RUNNING 3 /* True if HW watchdog running */ struct list_head deferred; }; @@ -117,6 +118,15 @@ static inline bool watchdog_active(struct watchdog_device *wdd) return test_bit(WDOG_ACTIVE, &wdd->status); } +/* + * Use the following function to check whether or not the hardware watchdog + * is running + */ +static inline bool watchdog_hw_running(struct watchdog_device *wdd) +{ + return test_bit(WDOG_HW_RUNNING, &wdd->status); +} + /* Use the following function to set the nowayout feature */ static inline void watchdog_set_nowayout(struct watchdog_device *wdd, bool nowayout) { From d0684c8a9354953efdea214b437445c00743cf49 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:17 -0800 Subject: [PATCH 24/28] watchdog: Make stop function optional Not all hardware watchdogs can be stopped. The driver for such watchdogs would typically only set the WATCHDOG_HW_RUNNING flag in its stop function. Make the stop function optional and set WATCHDOG_HW_RUNNING in the watchdog core if it is not provided. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- .../watchdog/watchdog-kernel-api.txt | 20 +++++++++++-------- drivers/watchdog/watchdog_core.c | 2 +- drivers/watchdog/watchdog_dev.c | 6 +++++- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 954134a5c4a4..9eabca1d9355 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -85,7 +85,8 @@ It contains following fields: If set, the infrastructure will send heartbeats to the watchdog driver if 'timeout' is larger than max_hw_heartbeat_ms, unless WDOG_ACTIVE is set and userspace failed to send a heartbeat for at least 'timeout' - seconds. + seconds. max_hw_heartbeat_ms must be set if a driver does not implement + the stop function. * reboot_nb: notifier block that is registered for reboot notifications, for internal use only. If the driver calls watchdog_stop_on_reboot, watchdog core will stop the watchdog on such notifications. @@ -134,17 +135,20 @@ are: device. The routine needs a pointer to the watchdog timer device structure as a parameter. It returns zero on success or a negative errno code for failure. -* stop: with this routine the watchdog timer device is being stopped. - The routine needs a pointer to the watchdog timer device structure as a - parameter. It returns zero on success or a negative errno code for failure. - Some watchdog timer hardware can only be started and not be stopped. - If a watchdog can not be stopped, the watchdog driver must set the - WDOG_HW_RUNNING flag in its stop function to inform the watchdog core that - the watchdog is still running. Not all watchdog timer hardware supports the same functionality. That's why all other routines/operations are optional. They only need to be provided if they are supported. These optional routines/operations are: +* stop: with this routine the watchdog timer device is being stopped. + The routine needs a pointer to the watchdog timer device structure as a + parameter. It returns zero on success or a negative errno code for failure. + Some watchdog timer hardware can only be started and not be stopped. A + driver supporting such hardware does not have to implement the stop routine. + If a driver has no stop function, the watchdog core will set WDOG_HW_RUNNING + and start calling the driver's keepalive pings function after the watchdog + device is closed. + If a watchdog driver does not implement the stop function, it must set + max_hw_heartbeat_ms. * ping: this is the routine that sends a keepalive ping to the watchdog timer hardware. The routine needs a pointer to the watchdog timer device structure as a diff --git a/drivers/watchdog/watchdog_core.c b/drivers/watchdog/watchdog_core.c index d9b3c9c023c4..c1658fe73d58 100644 --- a/drivers/watchdog/watchdog_core.c +++ b/drivers/watchdog/watchdog_core.c @@ -199,7 +199,7 @@ static int __watchdog_register_device(struct watchdog_device *wdd) return -EINVAL; /* Mandatory operations need to be supported */ - if (wdd->ops->start == NULL || wdd->ops->stop == NULL) + if (!wdd->ops->start || (!wdd->ops->stop && !wdd->max_hw_heartbeat_ms)) return -EINVAL; watchdog_check_min_max_timeout(wdd); diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 5d3a9fa4856e..5163c3eb3428 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -246,7 +246,11 @@ static int watchdog_stop(struct watchdog_device *wdd) return -EBUSY; } - err = wdd->ops->stop(wdd); + if (wdd->ops->stop) + err = wdd->ops->stop(wdd); + else + set_bit(WDOG_HW_RUNNING, &wdd->status); + if (err == 0) { clear_bit(WDOG_ACTIVE, &wdd->status); watchdog_update_worker(wdd); From 15013ad813f6544be8e79afc23672745950d59bc Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:18 -0800 Subject: [PATCH 25/28] watchdog: Add support for minimum time between heartbeats Some watchdogs require a minimum time between heartbeats. Examples are the watchdogs in DA9062 and AT91SAM9x. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- Documentation/watchdog/watchdog-kernel-api.txt | 3 +++ drivers/watchdog/watchdog_dev.c | 15 +++++++++++++++ include/linux/watchdog.h | 3 +++ 3 files changed, 21 insertions(+) diff --git a/Documentation/watchdog/watchdog-kernel-api.txt b/Documentation/watchdog/watchdog-kernel-api.txt index 9eabca1d9355..917eeeabfa5e 100644 --- a/Documentation/watchdog/watchdog-kernel-api.txt +++ b/Documentation/watchdog/watchdog-kernel-api.txt @@ -52,6 +52,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + unsigned int min_hw_heartbeat_ms; unsigned int max_hw_heartbeat_ms; struct notifier_block reboot_nb; struct notifier_block restart_nb; @@ -81,6 +82,8 @@ It contains following fields: * max_timeout: the watchdog timer's maximum timeout value (in seconds), as seen from userspace. If set, the maximum configurable value for 'timeout'. Not used if max_hw_heartbeat_ms is non-zero. +* min_hw_heartbeat_ms: Minimum time between heartbeats sent to the chip, + in milli-seconds. * max_hw_heartbeat_ms: Maximum hardware heartbeat, in milli-seconds. If set, the infrastructure will send heartbeats to the watchdog driver if 'timeout' is larger than max_hw_heartbeat_ms, unless WDOG_ACTIVE diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 5163c3eb3428..2d6110278eac 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -64,6 +64,7 @@ struct watchdog_core_data { struct watchdog_device *wdd; struct mutex lock; unsigned long last_keepalive; + unsigned long last_hw_keepalive; struct delayed_work work; unsigned long status; /* Internal status bits */ #define _WDOG_DEV_OPEN 0 /* Opened ? */ @@ -137,8 +138,19 @@ static inline void watchdog_update_worker(struct watchdog_device *wdd) static int __watchdog_ping(struct watchdog_device *wdd) { + struct watchdog_core_data *wd_data = wdd->wd_data; + unsigned long earliest_keepalive = wd_data->last_hw_keepalive + + msecs_to_jiffies(wdd->min_hw_heartbeat_ms); int err; + if (time_is_after_jiffies(earliest_keepalive)) { + mod_delayed_work(watchdog_wq, &wd_data->work, + earliest_keepalive - jiffies); + return 0; + } + + wd_data->last_hw_keepalive = jiffies; + if (wdd->ops->ping) err = wdd->ops->ping(wdd); /* ping the watchdog */ else @@ -819,6 +831,9 @@ static int watchdog_cdev_register(struct watchdog_device *wdd, dev_t devno) return err; } + /* Record time of most recent heartbeat as 'just before now'. */ + wd_data->last_hw_keepalive = jiffies - 1; + /* * If the watchdog is running, prevent its driver from being unloaded, * and schedule an immediate ping. diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index e2f45549b243..51732d6c9555 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -65,6 +65,8 @@ struct watchdog_ops { * @max_timeout:The watchdog devices maximum timeout value (in seconds) * as configurable from user space. Only relevant if * max_hw_heartbeat_ms is not provided. + * @min_hw_heartbeat_ms: + * Minimum time between heartbeats, in milli-seconds. * @max_hw_heartbeat_ms: * Hardware limit for maximum timeout, in milli-seconds. * Replaces max_timeout if specified. @@ -95,6 +97,7 @@ struct watchdog_device { unsigned int timeout; unsigned int min_timeout; unsigned int max_timeout; + unsigned int min_hw_heartbeat_ms; unsigned int max_hw_heartbeat_ms; struct notifier_block reboot_nb; struct notifier_block restart_nb; From f29a72c24ad4927027e77e4eed431a61bc8335b2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:19 -0800 Subject: [PATCH 26/28] watchdog: dw_wdt: Convert to use watchdog infrastructure Convert driver to use watchdog infrastructure. This includes infrastructure support to handle watchdog keepalive if the watchdog is running while the watchdog device is closed. Signed-off-by: Guenter Roeck Tested-by: Douglas Anderson Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/Kconfig | 1 + drivers/watchdog/dw_wdt.c | 323 ++++++++++++++------------------------ 2 files changed, 117 insertions(+), 207 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 3fca65e418c9..302078e321dc 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -350,6 +350,7 @@ config SA1100_WATCHDOG config DW_WATCHDOG tristate "Synopsys DesignWare watchdog" depends on HAS_IOMEM + select WATCHDOG_CORE help Say Y here if to include support for the Synopsys DesignWare watchdog timer found in many chips. diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c index 8fefa4ad46d4..2acb51cf5504 100644 --- a/drivers/watchdog/dw_wdt.c +++ b/drivers/watchdog/dw_wdt.c @@ -12,9 +12,8 @@ * and these are a function of the input clock frequency. * * The DesignWare watchdog cannot be stopped once it has been started so we - * use a software timer to implement a ping that will keep the watchdog alive. - * If we receive an expected close for the watchdog then we keep the timer - * running, otherwise the timer is stopped and the watchdog will expire. + * do not implement a stop function. The watchdog core will continue to send + * heartbeat requests after the watchdog device has been closed. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -22,12 +21,9 @@ #include #include #include -#include #include -#include #include #include -#include #include #include #include @@ -35,8 +31,6 @@ #include #include #include -#include -#include #include #define WDOG_CONTROL_REG_OFFSET 0x00 @@ -57,53 +51,50 @@ module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); -#define WDT_TIMEOUT (HZ / 2) - -static struct { +struct dw_wdt { void __iomem *regs; struct clk *clk; - unsigned long in_use; - unsigned long next_heartbeat; - struct timer_list timer; - int expect_close; struct notifier_block restart_handler; -} dw_wdt; + struct watchdog_device wdd; +}; -static inline int dw_wdt_is_enabled(void) +#define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd) + +static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt) { - return readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET) & + return readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET) & WDOG_CONTROL_REG_WDT_EN_MASK; } -static inline int dw_wdt_top_in_seconds(unsigned top) +static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top) { /* * There are 16 possible timeout values in 0..15 where the number of * cycles is 2 ^ (16 + i) and the watchdog counts down. */ - return (1U << (16 + top)) / clk_get_rate(dw_wdt.clk); + return (1U << (16 + top)) / clk_get_rate(dw_wdt->clk); } -static int dw_wdt_get_top(void) +static int dw_wdt_get_top(struct dw_wdt *dw_wdt) { - int top = readl(dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; + int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF; - return dw_wdt_top_in_seconds(top); + return dw_wdt_top_in_seconds(dw_wdt, top); } -static inline void dw_wdt_set_next_heartbeat(void) +static int dw_wdt_ping(struct watchdog_device *wdd) { - dw_wdt.next_heartbeat = jiffies + dw_wdt_get_top() * HZ; -} + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); -static void dw_wdt_keepalive(void) -{ - writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + + writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); + + return 0; } -static int dw_wdt_set_top(unsigned top_s) +static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s) { + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); int i, top_val = DW_WDT_MAX_TOP; /* @@ -111,7 +102,7 @@ static int dw_wdt_set_top(unsigned top_s) * always look for >=. */ for (i = 0; i <= DW_WDT_MAX_TOP; ++i) - if (dw_wdt_top_in_seconds(i) >= top_s) { + if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) { top_val = i; break; } @@ -123,33 +114,43 @@ static int dw_wdt_set_top(unsigned top_s) * effectively get a pat of the watchdog right here. */ writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT, - dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); - /* - * Add an explicit pat to handle versions of the watchdog that - * don't have TOPINIT. This won't hurt on versions that have - * it. - */ - dw_wdt_keepalive(); + wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val); - dw_wdt_set_next_heartbeat(); + return 0; +} - return dw_wdt_top_in_seconds(top_val); +static int dw_wdt_start(struct watchdog_device *wdd) +{ + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); + + dw_wdt_set_timeout(wdd, wdd->timeout); + + set_bit(WDOG_HW_RUNNING, &wdd->status); + + writel(WDOG_CONTROL_REG_WDT_EN_MASK, + dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); + + return 0; } static int dw_wdt_restart_handle(struct notifier_block *this, - unsigned long mode, void *cmd) + unsigned long mode, void *cmd) { + struct dw_wdt *dw_wdt; u32 val; - writel(0, dw_wdt.regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); - val = readl(dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + dw_wdt = container_of(this, struct dw_wdt, restart_handler); + + writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET); + val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); if (val & WDOG_CONTROL_REG_WDT_EN_MASK) - writel(WDOG_COUNTER_RESTART_KICK_VALUE, dw_wdt.regs + - WDOG_COUNTER_RESTART_REG_OFFSET); + writel(WDOG_COUNTER_RESTART_KICK_VALUE, + dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET); else writel(WDOG_CONTROL_REG_WDT_EN_MASK, - dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); + dw_wdt->regs + WDOG_CONTROL_REG_OFFSET); /* wait for reset to assert... */ mdelay(500); @@ -157,74 +158,12 @@ static int dw_wdt_restart_handle(struct notifier_block *this, return NOTIFY_DONE; } -static void dw_wdt_ping(unsigned long data) +static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd) { - if (time_before(jiffies, dw_wdt.next_heartbeat) || - (!nowayout && !dw_wdt.in_use)) { - dw_wdt_keepalive(); - mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); - } else - pr_crit("keepalive missed, machine will reset\n"); -} + struct dw_wdt *dw_wdt = to_dw_wdt(wdd); -static int dw_wdt_open(struct inode *inode, struct file *filp) -{ - if (test_and_set_bit(0, &dw_wdt.in_use)) - return -EBUSY; - - /* Make sure we don't get unloaded. */ - __module_get(THIS_MODULE); - - if (!dw_wdt_is_enabled()) { - /* - * The watchdog is not currently enabled. Set the timeout to - * something reasonable and then start it. - */ - dw_wdt_set_top(DW_WDT_DEFAULT_SECONDS); - writel(WDOG_CONTROL_REG_WDT_EN_MASK, - dw_wdt.regs + WDOG_CONTROL_REG_OFFSET); - } - - dw_wdt_set_next_heartbeat(); - - return nonseekable_open(inode, filp); -} - -static ssize_t dw_wdt_write(struct file *filp, const char __user *buf, - size_t len, loff_t *offset) -{ - if (!len) - return 0; - - if (!nowayout) { - size_t i; - - dw_wdt.expect_close = 0; - - for (i = 0; i < len; ++i) { - char c; - - if (get_user(c, buf + i)) - return -EFAULT; - - if (c == 'V') { - dw_wdt.expect_close = 1; - break; - } - } - } - - dw_wdt_set_next_heartbeat(); - dw_wdt_keepalive(); - mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); - - return len; -} - -static u32 dw_wdt_time_left(void) -{ - return readl(dw_wdt.regs + WDOG_CURRENT_COUNT_REG_OFFSET) / - clk_get_rate(dw_wdt.clk); + return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) / + clk_get_rate(dw_wdt->clk); } static const struct watchdog_info dw_wdt_ident = { @@ -233,78 +172,33 @@ static const struct watchdog_info dw_wdt_ident = { .identity = "Synopsys DesignWare Watchdog", }; -static long dw_wdt_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) -{ - unsigned long val; - int timeout; - - switch (cmd) { - case WDIOC_GETSUPPORT: - return copy_to_user((void __user *)arg, &dw_wdt_ident, - sizeof(dw_wdt_ident)) ? -EFAULT : 0; - - case WDIOC_GETSTATUS: - case WDIOC_GETBOOTSTATUS: - return put_user(0, (int __user *)arg); - - case WDIOC_KEEPALIVE: - dw_wdt_set_next_heartbeat(); - return 0; - - case WDIOC_SETTIMEOUT: - if (get_user(val, (int __user *)arg)) - return -EFAULT; - timeout = dw_wdt_set_top(val); - return put_user(timeout , (int __user *)arg); - - case WDIOC_GETTIMEOUT: - return put_user(dw_wdt_get_top(), (int __user *)arg); - - case WDIOC_GETTIMELEFT: - /* Get the time left until expiry. */ - if (get_user(val, (int __user *)arg)) - return -EFAULT; - return put_user(dw_wdt_time_left(), (int __user *)arg); - - default: - return -ENOTTY; - } -} - -static int dw_wdt_release(struct inode *inode, struct file *filp) -{ - clear_bit(0, &dw_wdt.in_use); - - if (!dw_wdt.expect_close) { - del_timer(&dw_wdt.timer); - - if (!nowayout) - pr_crit("unexpected close, system will reboot soon\n"); - else - pr_crit("watchdog cannot be disabled, system will reboot soon\n"); - } - - dw_wdt.expect_close = 0; - - return 0; -} +static const struct watchdog_ops dw_wdt_ops = { + .owner = THIS_MODULE, + .start = dw_wdt_start, + .ping = dw_wdt_ping, + .set_timeout = dw_wdt_set_timeout, + .get_timeleft = dw_wdt_get_timeleft, +}; #ifdef CONFIG_PM_SLEEP static int dw_wdt_suspend(struct device *dev) { - clk_disable_unprepare(dw_wdt.clk); + struct dw_wdt *dw_wdt = dev_get_drvdata(dev); + + clk_disable_unprepare(dw_wdt->clk); return 0; } static int dw_wdt_resume(struct device *dev) { - int err = clk_prepare_enable(dw_wdt.clk); + struct dw_wdt *dw_wdt = dev_get_drvdata(dev); + int err = clk_prepare_enable(dw_wdt->clk); if (err) return err; - dw_wdt_keepalive(); + dw_wdt_ping(&dw_wdt->wdd); return 0; } @@ -312,67 +206,82 @@ static int dw_wdt_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume); -static const struct file_operations wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .open = dw_wdt_open, - .write = dw_wdt_write, - .unlocked_ioctl = dw_wdt_ioctl, - .release = dw_wdt_release -}; - -static struct miscdevice dw_wdt_miscdev = { - .fops = &wdt_fops, - .name = "watchdog", - .minor = WATCHDOG_MINOR, -}; - static int dw_wdt_drv_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; + struct watchdog_device *wdd; + struct dw_wdt *dw_wdt; + struct resource *mem; int ret; - struct resource *mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dw_wdt.regs = devm_ioremap_resource(&pdev->dev, mem); - if (IS_ERR(dw_wdt.regs)) - return PTR_ERR(dw_wdt.regs); + dw_wdt = devm_kzalloc(dev, sizeof(*dw_wdt), GFP_KERNEL); + if (!dw_wdt) + return -ENOMEM; - dw_wdt.clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(dw_wdt.clk)) - return PTR_ERR(dw_wdt.clk); + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dw_wdt->regs = devm_ioremap_resource(dev, mem); + if (IS_ERR(dw_wdt->regs)) + return PTR_ERR(dw_wdt->regs); - ret = clk_prepare_enable(dw_wdt.clk); + dw_wdt->clk = devm_clk_get(dev, NULL); + if (IS_ERR(dw_wdt->clk)) + return PTR_ERR(dw_wdt->clk); + + ret = clk_prepare_enable(dw_wdt->clk); if (ret) return ret; - ret = misc_register(&dw_wdt_miscdev); + wdd = &dw_wdt->wdd; + wdd->info = &dw_wdt_ident; + wdd->ops = &dw_wdt_ops; + wdd->min_timeout = 1; + wdd->max_hw_heartbeat_ms = + dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000; + wdd->parent = dev; + + watchdog_set_drvdata(wdd, dw_wdt); + watchdog_set_nowayout(wdd, nowayout); + watchdog_init_timeout(wdd, 0, dev); + + /* + * If the watchdog is already running, use its already configured + * timeout. Otherwise use the default or the value provided through + * devicetree. + */ + if (dw_wdt_is_enabled(dw_wdt)) { + wdd->timeout = dw_wdt_get_top(dw_wdt); + set_bit(WDOG_HW_RUNNING, &wdd->status); + } else { + wdd->timeout = DW_WDT_DEFAULT_SECONDS; + watchdog_init_timeout(wdd, 0, dev); + } + + platform_set_drvdata(pdev, dw_wdt); + + ret = watchdog_register_device(wdd); if (ret) goto out_disable_clk; - dw_wdt.restart_handler.notifier_call = dw_wdt_restart_handle; - dw_wdt.restart_handler.priority = 128; - ret = register_restart_handler(&dw_wdt.restart_handler); + dw_wdt->restart_handler.notifier_call = dw_wdt_restart_handle; + dw_wdt->restart_handler.priority = 128; + ret = register_restart_handler(&dw_wdt->restart_handler); if (ret) pr_warn("cannot register restart handler\n"); - dw_wdt_set_next_heartbeat(); - setup_timer(&dw_wdt.timer, dw_wdt_ping, 0); - mod_timer(&dw_wdt.timer, jiffies + WDT_TIMEOUT); - return 0; out_disable_clk: - clk_disable_unprepare(dw_wdt.clk); - + clk_disable_unprepare(dw_wdt->clk); return ret; } static int dw_wdt_drv_remove(struct platform_device *pdev) { - unregister_restart_handler(&dw_wdt.restart_handler); + struct dw_wdt *dw_wdt = platform_get_drvdata(pdev); - misc_deregister(&dw_wdt_miscdev); - - clk_disable_unprepare(dw_wdt.clk); + unregister_restart_handler(&dw_wdt->restart_handler); + watchdog_unregister_device(&dw_wdt->wdd); + clk_disable_unprepare(dw_wdt->clk); return 0; } From 11d7aba9ceb726d86aaaca3eb5f7d79de38989c5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 28 Feb 2016 13:12:20 -0800 Subject: [PATCH 27/28] watchdog: imx2: Convert to use infrastructure triggered keepalives The watchdog infrastructure now supports handling watchdog keepalive if the watchdog is running while the watchdog device is closed. Convert the driver to use this infrastructure. Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/imx2_wdt.c | 74 +++++++------------------------------ 1 file changed, 13 insertions(+), 61 deletions(-) diff --git a/drivers/watchdog/imx2_wdt.c b/drivers/watchdog/imx2_wdt.c index 4cb59a23aab0..331aed831dac 100644 --- a/drivers/watchdog/imx2_wdt.c +++ b/drivers/watchdog/imx2_wdt.c @@ -25,14 +25,12 @@ #include #include #include -#include #include #include #include #include #include #include -#include #include #define DRIVER_NAME "imx2-wdt" @@ -60,7 +58,6 @@ struct imx2_wdt_device { struct clk *clk; struct regmap *regmap; - struct timer_list timer; /* Pings the watchdog when closed */ struct watchdog_device wdog; }; @@ -147,16 +144,6 @@ static int imx2_wdt_ping(struct watchdog_device *wdog) return 0; } -static void imx2_wdt_timer_ping(unsigned long arg) -{ - struct watchdog_device *wdog = (struct watchdog_device *)arg; - struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); - - /* ping it every wdog->timeout / 2 seconds to prevent reboot */ - imx2_wdt_ping(wdog); - mod_timer(&wdev->timer, jiffies + wdog->timeout * HZ / 2); -} - static int imx2_wdt_set_timeout(struct watchdog_device *wdog, unsigned int new_timeout) { @@ -173,40 +160,19 @@ static int imx2_wdt_start(struct watchdog_device *wdog) { struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); - if (imx2_wdt_is_running(wdev)) { - /* delete the timer that pings the watchdog after close */ - del_timer_sync(&wdev->timer); + if (imx2_wdt_is_running(wdev)) imx2_wdt_set_timeout(wdog, wdog->timeout); - } else + else imx2_wdt_setup(wdog); + set_bit(WDOG_HW_RUNNING, &wdog->status); + return imx2_wdt_ping(wdog); } -static int imx2_wdt_stop(struct watchdog_device *wdog) -{ - /* - * We don't need a clk_disable, it cannot be disabled once started. - * We use a timer to ping the watchdog while /dev/watchdog is closed - */ - imx2_wdt_timer_ping((unsigned long)wdog); - return 0; -} - -static inline void imx2_wdt_ping_if_active(struct watchdog_device *wdog) -{ - struct imx2_wdt_device *wdev = watchdog_get_drvdata(wdog); - - if (imx2_wdt_is_running(wdev)) { - imx2_wdt_set_timeout(wdog, wdog->timeout); - imx2_wdt_timer_ping((unsigned long)wdog); - } -} - static const struct watchdog_ops imx2_wdt_ops = { .owner = THIS_MODULE, .start = imx2_wdt_start, - .stop = imx2_wdt_stop, .ping = imx2_wdt_ping, .set_timeout = imx2_wdt_set_timeout, .restart = imx2_wdt_restart, @@ -254,7 +220,7 @@ static int __init imx2_wdt_probe(struct platform_device *pdev) wdog->info = &imx2_wdt_info; wdog->ops = &imx2_wdt_ops; wdog->min_timeout = 1; - wdog->max_timeout = IMX2_WDT_MAX_TIME; + wdog->max_hw_heartbeat_ms = IMX2_WDT_MAX_TIME * 1000; wdog->parent = &pdev->dev; ret = clk_prepare_enable(wdev->clk); @@ -275,9 +241,10 @@ static int __init imx2_wdt_probe(struct platform_device *pdev) watchdog_set_restart_priority(wdog, 128); watchdog_init_timeout(wdog, timeout, &pdev->dev); - setup_timer(&wdev->timer, imx2_wdt_timer_ping, (unsigned long)wdog); - - imx2_wdt_ping_if_active(wdog); + if (imx2_wdt_is_running(wdev)) { + imx2_wdt_set_timeout(wdog, wdog->timeout); + set_bit(WDOG_HW_RUNNING, &wdog->status); + } /* * Disable the watchdog power down counter at boot. Otherwise the power @@ -310,7 +277,6 @@ static int __exit imx2_wdt_remove(struct platform_device *pdev) watchdog_unregister_device(wdog); if (imx2_wdt_is_running(wdev)) { - del_timer_sync(&wdev->timer); imx2_wdt_ping(wdog); dev_crit(&pdev->dev, "Device removed: Expect reboot!\n"); } @@ -324,10 +290,9 @@ static void imx2_wdt_shutdown(struct platform_device *pdev) if (imx2_wdt_is_running(wdev)) { /* - * We are running, we need to delete the timer but will - * give max timeout before reboot will take place + * We are running, configure max timeout before reboot + * will take place. */ - del_timer_sync(&wdev->timer); imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); imx2_wdt_ping(wdog); dev_crit(&pdev->dev, "Device shutdown: Expect reboot!\n"); @@ -345,10 +310,6 @@ static int imx2_wdt_suspend(struct device *dev) if (imx2_wdt_is_running(wdev)) { imx2_wdt_set_timeout(wdog, IMX2_WDT_MAX_TIME); imx2_wdt_ping(wdog); - - /* The watchdog is not active */ - if (!watchdog_active(wdog)) - del_timer_sync(&wdev->timer); } clk_disable_unprepare(wdev->clk); @@ -374,19 +335,10 @@ static int imx2_wdt_resume(struct device *dev) * watchdog again. */ imx2_wdt_setup(wdog); + } + if (imx2_wdt_is_running(wdev)) { imx2_wdt_set_timeout(wdog, wdog->timeout); imx2_wdt_ping(wdog); - } else if (imx2_wdt_is_running(wdev)) { - /* Resuming from non-deep sleep state. */ - imx2_wdt_set_timeout(wdog, wdog->timeout); - imx2_wdt_ping(wdog); - /* - * But the watchdog is not active, then start - * the timer again. - */ - if (!watchdog_active(wdog)) - mod_timer(&wdev->timer, - jiffies + wdog->timeout * HZ / 2); } return 0; From d1ed3ba4e3d76b4ebec239c64f990c26d7935700 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 8 Mar 2016 18:46:13 -0800 Subject: [PATCH 28/28] watchdog: Ensure that wdd is not dereferenced if NULL Smatch rightfully complains that wdd is dereferenced in the watchdog release function after being checked for NULL. Also make sure that it is not accessed outside mutex protection to avoid use-after-free problems. Fixes: e6c71e84e4c0 ("watchdog: Introduce WDOG_HW_RUNNING flag") Reported-by: Dan Carpenter Signed-off-by: Guenter Roeck Signed-off-by: Wim Van Sebroeck --- drivers/watchdog/watchdog_dev.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/watchdog/watchdog_dev.c b/drivers/watchdog/watchdog_dev.c index 2d6110278eac..e2c5abbb45ff 100644 --- a/drivers/watchdog/watchdog_dev.c +++ b/drivers/watchdog/watchdog_dev.c @@ -711,6 +711,7 @@ static int watchdog_release(struct inode *inode, struct file *file) struct watchdog_core_data *wd_data = file->private_data; struct watchdog_device *wdd; int err = -EBUSY; + bool running; mutex_lock(&wd_data->lock); @@ -742,14 +743,15 @@ static int watchdog_release(struct inode *inode, struct file *file) clear_bit(_WDOG_DEV_OPEN, &wd_data->status); done: + running = wdd && watchdog_hw_running(wdd); mutex_unlock(&wd_data->lock); /* * Allow the owner module to be unloaded again unless the watchdog * is still running. If the watchdog is still running, it can not * be stopped, and its driver must not be unloaded. */ - if (!watchdog_hw_running(wdd)) { - module_put(wdd->ops->owner); + if (!running) { + module_put(wd_data->cdev.owner); kref_put(&wd_data->kref, watchdog_core_data_release); } return 0;