diff options
Diffstat (limited to 'drivers/watchdog/wdat_wdt.c')
| -rw-r--r-- | drivers/watchdog/wdat_wdt.c | 151 |
1 files changed, 97 insertions, 54 deletions
diff --git a/drivers/watchdog/wdat_wdt.c b/drivers/watchdog/wdat_wdt.c index 56ad19608a9b..dd3c2d69c9df 100644 --- a/drivers/watchdog/wdat_wdt.c +++ b/drivers/watchdog/wdat_wdt.c @@ -1,12 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * ACPI Hardware Watchdog (WDAT) driver. * * Copyright (C) 2016, Intel Corporation * Author: Mika Westerberg <mika.westerberg@linux.intel.com> - * - * 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. */ #include <linux/acpi.h> @@ -37,9 +34,9 @@ struct wdat_instruction { * @period: How long is one watchdog period in ms * @stopped_in_sleep: Is this watchdog stopped by the firmware in S1-S5 * @stopped: Was the watchdog stopped by the driver in suspend - * @actions: An array of instruction lists indexed by an action number from - * the WDAT table. There can be %NULL entries for not implemented - * actions. + * @instructions: An array of instruction lists indexed by an action number from + * the WDAT table. There can be %NULL entries for not implemented + * actions. */ struct wdat_wdt { struct platform_device *pdev; @@ -57,6 +54,13 @@ module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); +#define WDAT_DEFAULT_TIMEOUT 30 + +static int timeout = WDAT_DEFAULT_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(WDAT_DEFAULT_TIMEOUT) ")"); + static int wdat_wdt_read(struct wdat_wdt *wdat, const struct wdat_instruction *instr, u32 *value) { @@ -204,8 +208,8 @@ static int wdat_wdt_enable_reboot(struct wdat_wdt *wdat) /* * WDAT specification says that the watchdog is required to reboot * the system when it fires. However, it also states that it is - * recommeded to make it configurable through hardware register. We - * enable reboot now if it is configrable, just in case. + * recommended to make it configurable through hardware register. We + * enable reboot now if it is configurable, just in case. */ ret = wdat_wdt_run_action(wdat, ACPI_WDAT_SET_REBOOT, 0, NULL); if (ret && ret != -EOPNOTSUPP) { @@ -265,7 +269,7 @@ static int wdat_wdt_stop(struct watchdog_device *wdd) static int wdat_wdt_ping(struct watchdog_device *wdd) { - return wdat_wdt_run_action(to_wdat_wdt(wdd), ACPI_WDAT_RESET, 0, NULL); + return wdat_wdt_run_action(to_wdat_wdt(wdd), ACPI_WDAT_RESET, wdd->timeout, NULL); } static int wdat_wdt_set_timeout(struct watchdog_device *wdd, @@ -287,7 +291,7 @@ static unsigned int wdat_wdt_get_timeleft(struct watchdog_device *wdd) struct wdat_wdt *wdat = to_wdat_wdt(wdd); u32 periods = 0; - wdat_wdt_run_action(wdat, ACPI_WDAT_GET_COUNTDOWN, 0, &periods); + wdat_wdt_run_action(wdat, ACPI_WDAT_GET_CURRENT_COUNTDOWN, 0, &periods); return periods * wdat->period / 1000; } @@ -297,17 +301,17 @@ static const struct watchdog_info wdat_wdt_info = { .identity = "wdat_wdt", }; -static const struct watchdog_ops wdat_wdt_ops = { +static struct watchdog_ops wdat_wdt_ops = { .owner = THIS_MODULE, .start = wdat_wdt_start, .stop = wdat_wdt_stop, .ping = wdat_wdt_ping, .set_timeout = wdat_wdt_set_timeout, - .get_timeleft = wdat_wdt_get_timeleft, }; static int wdat_wdt_probe(struct platform_device *pdev) { + struct device *dev = &pdev->dev; const struct acpi_wdat_entry *entries; const struct acpi_table_wdat *tbl; struct wdat_wdt *wdat; @@ -321,24 +325,32 @@ static int wdat_wdt_probe(struct platform_device *pdev) if (ACPI_FAILURE(status)) return -ENODEV; - wdat = devm_kzalloc(&pdev->dev, sizeof(*wdat), GFP_KERNEL); - if (!wdat) - return -ENOMEM; + wdat = devm_kzalloc(dev, sizeof(*wdat), GFP_KERNEL); + if (!wdat) { + ret = -ENOMEM; + goto out_put_table; + } - regs = devm_kcalloc(&pdev->dev, pdev->num_resources, sizeof(*regs), + regs = devm_kcalloc(dev, pdev->num_resources, sizeof(*regs), GFP_KERNEL); - if (!regs) - return -ENOMEM; + if (!regs) { + ret = -ENOMEM; + goto out_put_table; + } /* WDAT specification wants to have >= 1ms period */ - if (tbl->timer_period < 1) - return -EINVAL; - if (tbl->min_count > tbl->max_count) - return -EINVAL; + if (tbl->timer_period < 1) { + ret = -EINVAL; + goto out_put_table; + } + if (tbl->min_count > tbl->max_count) { + ret = -EINVAL; + goto out_put_table; + } wdat->period = tbl->timer_period; - wdat->wdd.min_hw_heartbeat_ms = wdat->period * tbl->min_count; - wdat->wdd.max_hw_heartbeat_ms = wdat->period * tbl->max_count; + wdat->wdd.min_timeout = DIV_ROUND_UP(wdat->period * tbl->min_count, 1000); + wdat->wdd.max_timeout = wdat->period * tbl->max_count / 1000; wdat->stopped_in_sleep = tbl->flags & ACPI_WDAT_STOPPED; wdat->wdd.info = &wdat_wdt_info; wdat->wdd.ops = &wdat_wdt_ops; @@ -350,16 +362,21 @@ static int wdat_wdt_probe(struct platform_device *pdev) res = &pdev->resource[i]; if (resource_type(res) == IORESOURCE_MEM) { - reg = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(reg)) - return PTR_ERR(reg); + reg = devm_ioremap_resource(dev, res); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + goto out_put_table; + } } else if (resource_type(res) == IORESOURCE_IO) { - reg = devm_ioport_map(&pdev->dev, res->start, 1); - if (!reg) - return -ENOMEM; + reg = devm_ioport_map(dev, res->start, 1); + if (!reg) { + ret = -ENOMEM; + goto out_put_table; + } } else { - dev_err(&pdev->dev, "Unsupported resource\n"); - return -EINVAL; + dev_err(dev, "Unsupported resource\n"); + ret = -EINVAL; + goto out_put_table; } regs[i] = reg; @@ -376,14 +393,15 @@ static int wdat_wdt_probe(struct platform_device *pdev) action = entries[i].action; if (action >= MAX_WDAT_ACTIONS) { - dev_dbg(&pdev->dev, "Skipping unknown action: %u\n", - action); + dev_dbg(dev, "Skipping unknown action: %u\n", action); continue; } - instr = devm_kzalloc(&pdev->dev, sizeof(*instr), GFP_KERNEL); - if (!instr) - return -ENOMEM; + instr = devm_kzalloc(dev, sizeof(*instr), GFP_KERNEL); + if (!instr) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(&instr->node); instr->entry = entries[i]; @@ -392,13 +410,13 @@ static int wdat_wdt_probe(struct platform_device *pdev) memset(&r, 0, sizeof(r)); r.start = gas->address; - r.end = r.start + gas->access_width - 1; + r.end = r.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1; if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { r.flags = IORESOURCE_MEM; } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { r.flags = IORESOURCE_IO; } else { - dev_dbg(&pdev->dev, "Unsupported address space: %d\n", + dev_dbg(dev, "Unsupported address space: %d\n", gas->space_id); continue; } @@ -413,16 +431,20 @@ static int wdat_wdt_probe(struct platform_device *pdev) } if (!instr->reg) { - dev_err(&pdev->dev, "I/O resource not found\n"); - return -EINVAL; + dev_err(dev, "I/O resource not found\n"); + ret = -EINVAL; + goto out_put_table; } instructions = wdat->instructions[action]; if (!instructions) { - instructions = devm_kzalloc(&pdev->dev, - sizeof(*instructions), GFP_KERNEL); - if (!instructions) - return -ENOMEM; + instructions = devm_kzalloc(dev, + sizeof(*instructions), + GFP_KERNEL); + if (!instructions) { + ret = -ENOMEM; + goto out_put_table; + } INIT_LIST_HEAD(instructions); wdat->instructions[action] = instructions; @@ -431,20 +453,43 @@ static int wdat_wdt_probe(struct platform_device *pdev) list_add_tail(&instr->node, instructions); } + if (wdat->instructions[ACPI_WDAT_GET_CURRENT_COUNTDOWN]) + wdat_wdt_ops.get_timeleft = wdat_wdt_get_timeleft; + wdat_wdt_boot_status(wdat); wdat_wdt_set_running(wdat); ret = wdat_wdt_enable_reboot(wdat); if (ret) - return ret; + goto out_put_table; platform_set_drvdata(pdev, wdat); + /* + * Set initial timeout so that userspace has time to configure the + * watchdog properly after it has opened the device. In some cases + * the BIOS default is too short and causes immediate reboot. + */ + if (watchdog_timeout_invalid(&wdat->wdd, timeout)) { + dev_warn(dev, "Invalid timeout %d given, using %d\n", + timeout, WDAT_DEFAULT_TIMEOUT); + timeout = WDAT_DEFAULT_TIMEOUT; + } + + ret = wdat_wdt_set_timeout(&wdat->wdd, timeout); + if (ret) + goto out_put_table; + watchdog_set_nowayout(&wdat->wdd, nowayout); - return devm_watchdog_register_device(&pdev->dev, &wdat->wdd); + watchdog_stop_on_reboot(&wdat->wdd); + watchdog_stop_on_unregister(&wdat->wdd); + ret = devm_watchdog_register_device(dev, &wdat->wdd); + +out_put_table: + acpi_put_table((struct acpi_table_header *)tbl); + return ret; } -#ifdef CONFIG_PM_SLEEP static int wdat_wdt_suspend_noirq(struct device *dev) { struct wdat_wdt *wdat = dev_get_drvdata(dev); @@ -454,7 +499,7 @@ static int wdat_wdt_suspend_noirq(struct device *dev) return 0; /* - * We need to stop the watchdog if firmare is not doing it or if we + * We need to stop the watchdog if firmware is not doing it or if we * are going suspend to idle (where firmware is not involved). If * firmware is stopping the watchdog we kick it here one more time * to give it some time. @@ -505,18 +550,16 @@ static int wdat_wdt_resume_noirq(struct device *dev) return wdat_wdt_start(&wdat->wdd); } -#endif static const struct dev_pm_ops wdat_wdt_pm_ops = { - SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, - wdat_wdt_resume_noirq) + NOIRQ_SYSTEM_SLEEP_PM_OPS(wdat_wdt_suspend_noirq, wdat_wdt_resume_noirq) }; static struct platform_driver wdat_wdt_driver = { .probe = wdat_wdt_probe, .driver = { .name = "wdat_wdt", - .pm = &wdat_wdt_pm_ops, + .pm = pm_sleep_ptr(&wdat_wdt_pm_ops), }, }; |
