Commit 058dfc76 authored by Mika Westerberg's avatar Mika Westerberg Committed by Rafael J. Wysocki

ACPI / watchdog: Add support for WDAT hardware watchdog

Starting from Intel Skylake the iTCO watchdog timer registers were moved to
reside in the same register space with SMBus host controller.  Not all
needed registers are available though and we need to unhide P2SB (Primary
to Sideband) device briefly to be able to read status of required NO_REBOOT
bit. The i2c-i801.c SMBus driver used to handle this and creation of the
iTCO watchdog platform device.

Windows, on the other hand, does not use the iTCO watchdog hardware
directly even if it is available. Instead it relies on ACPI Watchdog Action
Table (WDAT) table to describe the watchdog hardware to the OS. This table
contains necessary information about the the hardware and also set of
actions which are executed by a driver as needed.

This patch implements a new watchdog driver that takes advantage of the
ACPI WDAT table. We split the functionality into two parts: first part
enumerates the WDAT table and if found, populates resources and creates
platform device for the actual driver. The second part is the driver
itself.

The reason for the split is that this way we can make the driver itself to
be a module and loaded automatically if the WDAT table is found. Otherwise
the module is not loaded.
Signed-off-by: default avatarMika Westerberg <mika.westerberg@linux.intel.com>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarRafael J. Wysocki <rafael.j.wysocki@intel.com>
parent 3be79886
...@@ -462,6 +462,9 @@ source "drivers/acpi/nfit/Kconfig" ...@@ -462,6 +462,9 @@ source "drivers/acpi/nfit/Kconfig"
source "drivers/acpi/apei/Kconfig" source "drivers/acpi/apei/Kconfig"
source "drivers/acpi/dptf/Kconfig" source "drivers/acpi/dptf/Kconfig"
config ACPI_WATCHDOG
bool
config ACPI_EXTLOG config ACPI_EXTLOG
tristate "Extended Error Log support" tristate "Extended Error Log support"
depends on X86_MCE && X86_LOCAL_APIC depends on X86_MCE && X86_LOCAL_APIC
......
...@@ -56,6 +56,7 @@ acpi-$(CONFIG_ACPI_NUMA) += numa.o ...@@ -56,6 +56,7 @@ acpi-$(CONFIG_ACPI_NUMA) += numa.o
acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o acpi-$(CONFIG_ACPI_PROCFS_POWER) += cm_sbs.o
acpi-y += acpi_lpat.o acpi-y += acpi_lpat.o
acpi-$(CONFIG_ACPI_GENERIC_GSI) += gsi.o acpi-$(CONFIG_ACPI_GENERIC_GSI) += gsi.o
acpi-$(CONFIG_ACPI_WATCHDOG) += acpi_watchdog.o
# These are (potentially) separate modules # These are (potentially) separate modules
......
/*
* ACPI watchdog table parsing support.
*
* 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.
*/
#define pr_fmt(fmt) "ACPI: watchdog: " fmt
#include <linux/acpi.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include "internal.h"
/**
* Returns true if this system should prefer ACPI based watchdog instead of
* the native one (which are typically the same hardware).
*/
bool acpi_has_watchdog(void)
{
struct acpi_table_header hdr;
if (acpi_disabled)
return false;
return ACPI_SUCCESS(acpi_get_table_header(ACPI_SIG_WDAT, 0, &hdr));
}
EXPORT_SYMBOL_GPL(acpi_has_watchdog);
void __init acpi_watchdog_init(void)
{
const struct acpi_wdat_entry *entries;
const struct acpi_table_wdat *wdat;
struct list_head resource_list;
struct resource_entry *rentry;
struct platform_device *pdev;
struct resource *resources;
size_t nresources = 0;
acpi_status status;
int i;
status = acpi_get_table(ACPI_SIG_WDAT, 0,
(struct acpi_table_header **)&wdat);
if (ACPI_FAILURE(status)) {
/* It is fine if there is no WDAT */
return;
}
/* Watchdog disabled by BIOS */
if (!(wdat->flags & ACPI_WDAT_ENABLED))
return;
/* Skip legacy PCI WDT devices */
if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff ||
wdat->pci_device != 0xff || wdat->pci_function != 0xff)
return;
INIT_LIST_HEAD(&resource_list);
entries = (struct acpi_wdat_entry *)(wdat + 1);
for (i = 0; i < wdat->entries; i++) {
const struct acpi_generic_address *gas;
struct resource_entry *rentry;
struct resource res;
bool found;
gas = &entries[i].register_region;
res.start = gas->address;
if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
res.flags = IORESOURCE_MEM;
res.end = res.start + ALIGN(gas->access_width, 4);
} else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
res.flags = IORESOURCE_IO;
res.end = res.start + gas->access_width;
} else {
pr_warn("Unsupported address space: %u\n",
gas->space_id);
goto fail_free_resource_list;
}
found = false;
resource_list_for_each_entry(rentry, &resource_list) {
if (resource_contains(rentry->res, &res)) {
found = true;
break;
}
}
if (!found) {
rentry = resource_list_create_entry(NULL, 0);
if (!rentry)
goto fail_free_resource_list;
*rentry->res = res;
resource_list_add_tail(rentry, &resource_list);
nresources++;
}
}
resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL);
if (!resources)
goto fail_free_resource_list;
i = 0;
resource_list_for_each_entry(rentry, &resource_list)
resources[i++] = *rentry->res;
pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE,
resources, nresources);
if (IS_ERR(pdev))
pr_err("Failed to create platform device\n");
kfree(resources);
fail_free_resource_list:
resource_list_free(&resource_list);
}
...@@ -225,4 +225,14 @@ static inline void suspend_nvs_restore(void) {} ...@@ -225,4 +225,14 @@ static inline void suspend_nvs_restore(void) {}
void acpi_init_properties(struct acpi_device *adev); void acpi_init_properties(struct acpi_device *adev);
void acpi_free_properties(struct acpi_device *adev); void acpi_free_properties(struct acpi_device *adev);
/*--------------------------------------------------------------------------
Watchdog
-------------------------------------------------------------------------- */
#ifdef CONFIG_ACPI_WATCHDOG
void acpi_watchdog_init(void);
#else
static inline void acpi_watchdog_init(void) {}
#endif
#endif /* _ACPI_INTERNAL_H_ */ #endif /* _ACPI_INTERNAL_H_ */
...@@ -2002,6 +2002,7 @@ int __init acpi_scan_init(void) ...@@ -2002,6 +2002,7 @@ int __init acpi_scan_init(void)
acpi_pnp_init(); acpi_pnp_init();
acpi_int340x_thermal_init(); acpi_int340x_thermal_init();
acpi_amba_init(); acpi_amba_init();
acpi_watchdog_init();
acpi_scan_add_handler(&generic_device_handler); acpi_scan_add_handler(&generic_device_handler);
......
...@@ -152,6 +152,19 @@ config TANGOX_WATCHDOG ...@@ -152,6 +152,19 @@ config TANGOX_WATCHDOG
This driver can be built as a module. The module name is tangox_wdt. This driver can be built as a module. The module name is tangox_wdt.
config WDAT_WDT
tristate "ACPI Watchdog Action Table (WDAT)"
depends on ACPI
select ACPI_WATCHDOG
help
This driver adds support for systems with ACPI Watchdog Action
Table (WDAT) table. Servers typically have this but it can be
found on some desktop machines as well. This driver will take
over the native iTCO watchdog driver found on many Intel CPUs.
To compile this driver as module, choose M here: the module will
be called wdat_wdt.
config WM831X_WATCHDOG config WM831X_WATCHDOG
tristate "WM831x watchdog" tristate "WM831x watchdog"
depends on MFD_WM831X depends on MFD_WM831X
......
...@@ -202,6 +202,7 @@ obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o ...@@ -202,6 +202,7 @@ obj-$(CONFIG_DA9062_WATCHDOG) += da9062_wdt.o
obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o obj-$(CONFIG_DA9063_WATCHDOG) += da9063_wdt.o
obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o obj-$(CONFIG_GPIO_WATCHDOG) += gpio_wdt.o
obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o obj-$(CONFIG_TANGOX_WATCHDOG) += tangox_wdt.o
obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o obj-$(CONFIG_WM831X_WATCHDOG) += wm831x_wdt.o
obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o obj-$(CONFIG_WM8350_WATCHDOG) += wm8350_wdt.o
obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o obj-$(CONFIG_MAX63XX_WATCHDOG) += max63xx_wdt.o
......
This diff is collapsed.
...@@ -1074,4 +1074,10 @@ void acpi_table_upgrade(void); ...@@ -1074,4 +1074,10 @@ void acpi_table_upgrade(void);
static inline void acpi_table_upgrade(void) { } static inline void acpi_table_upgrade(void) { }
#endif #endif
#if defined(CONFIG_ACPI) && defined(CONFIG_ACPI_WATCHDOG)
extern bool acpi_has_watchdog(void);
#else
static inline bool acpi_has_watchdog(void) { return false; }
#endif
#endif /*_LINUX_ACPI_H*/ #endif /*_LINUX_ACPI_H*/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment