18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ACPI watchdog table parsing support. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2016, Intel Corporation 68c2ecf20Sopenharmony_ci * Author: Mika Westerberg <mika.westerberg@linux.intel.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "ACPI: watchdog: " fmt 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/acpi.h> 128c2ecf20Sopenharmony_ci#include <linux/ioport.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include "internal.h" 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci#ifdef CONFIG_RTC_MC146818_LIB 188c2ecf20Sopenharmony_ci#include <linux/mc146818rtc.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/* 218c2ecf20Sopenharmony_ci * There are several systems where the WDAT table is accessing RTC SRAM to 228c2ecf20Sopenharmony_ci * store persistent information. This does not work well with the Linux RTC 238c2ecf20Sopenharmony_ci * driver so on those systems we skip WDAT driver and prefer iTCO_wdt 248c2ecf20Sopenharmony_ci * instead. 258c2ecf20Sopenharmony_ci * 268c2ecf20Sopenharmony_ci * See also https://bugzilla.kernel.org/show_bug.cgi?id=199033. 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_cistatic bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat) 298c2ecf20Sopenharmony_ci{ 308c2ecf20Sopenharmony_ci const struct acpi_wdat_entry *entries; 318c2ecf20Sopenharmony_ci int i; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci entries = (struct acpi_wdat_entry *)(wdat + 1); 348c2ecf20Sopenharmony_ci for (i = 0; i < wdat->entries; i++) { 358c2ecf20Sopenharmony_ci const struct acpi_generic_address *gas; 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci gas = &entries[i].register_region; 388c2ecf20Sopenharmony_ci if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { 398c2ecf20Sopenharmony_ci switch (gas->address) { 408c2ecf20Sopenharmony_ci case RTC_PORT(0): 418c2ecf20Sopenharmony_ci case RTC_PORT(1): 428c2ecf20Sopenharmony_ci case RTC_PORT(2): 438c2ecf20Sopenharmony_ci case RTC_PORT(3): 448c2ecf20Sopenharmony_ci return true; 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci } 478c2ecf20Sopenharmony_ci } 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return false; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci#else 528c2ecf20Sopenharmony_cistatic bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci return false; 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci#endif 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic bool acpi_no_watchdog; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cistatic const struct acpi_table_wdat *acpi_watchdog_get_wdat(void) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci const struct acpi_table_wdat *wdat = NULL; 638c2ecf20Sopenharmony_ci acpi_status status; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (acpi_disabled || acpi_no_watchdog) 668c2ecf20Sopenharmony_ci return NULL; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci status = acpi_get_table(ACPI_SIG_WDAT, 0, 698c2ecf20Sopenharmony_ci (struct acpi_table_header **)&wdat); 708c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 718c2ecf20Sopenharmony_ci /* It is fine if there is no WDAT */ 728c2ecf20Sopenharmony_ci return NULL; 738c2ecf20Sopenharmony_ci } 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (acpi_watchdog_uses_rtc(wdat)) { 768c2ecf20Sopenharmony_ci acpi_put_table((struct acpi_table_header *)wdat); 778c2ecf20Sopenharmony_ci pr_info("Skipping WDAT on this system because it uses RTC SRAM\n"); 788c2ecf20Sopenharmony_ci return NULL; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci return wdat; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/** 858c2ecf20Sopenharmony_ci * Returns true if this system should prefer ACPI based watchdog instead of 868c2ecf20Sopenharmony_ci * the native one (which are typically the same hardware). 878c2ecf20Sopenharmony_ci */ 888c2ecf20Sopenharmony_cibool acpi_has_watchdog(void) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci return !!acpi_watchdog_get_wdat(); 918c2ecf20Sopenharmony_ci} 928c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(acpi_has_watchdog); 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci/* ACPI watchdog can be disabled on boot command line */ 958c2ecf20Sopenharmony_cistatic int __init disable_acpi_watchdog(char *str) 968c2ecf20Sopenharmony_ci{ 978c2ecf20Sopenharmony_ci acpi_no_watchdog = true; 988c2ecf20Sopenharmony_ci return 1; 998c2ecf20Sopenharmony_ci} 1008c2ecf20Sopenharmony_ci__setup("acpi_no_watchdog", disable_acpi_watchdog); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_civoid __init acpi_watchdog_init(void) 1038c2ecf20Sopenharmony_ci{ 1048c2ecf20Sopenharmony_ci const struct acpi_wdat_entry *entries; 1058c2ecf20Sopenharmony_ci const struct acpi_table_wdat *wdat; 1068c2ecf20Sopenharmony_ci struct list_head resource_list; 1078c2ecf20Sopenharmony_ci struct resource_entry *rentry; 1088c2ecf20Sopenharmony_ci struct platform_device *pdev; 1098c2ecf20Sopenharmony_ci struct resource *resources; 1108c2ecf20Sopenharmony_ci size_t nresources = 0; 1118c2ecf20Sopenharmony_ci int i; 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci wdat = acpi_watchdog_get_wdat(); 1148c2ecf20Sopenharmony_ci if (!wdat) { 1158c2ecf20Sopenharmony_ci /* It is fine if there is no WDAT */ 1168c2ecf20Sopenharmony_ci return; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci /* Watchdog disabled by BIOS */ 1208c2ecf20Sopenharmony_ci if (!(wdat->flags & ACPI_WDAT_ENABLED)) 1218c2ecf20Sopenharmony_ci goto fail_put_wdat; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci /* Skip legacy PCI WDT devices */ 1248c2ecf20Sopenharmony_ci if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff || 1258c2ecf20Sopenharmony_ci wdat->pci_device != 0xff || wdat->pci_function != 0xff) 1268c2ecf20Sopenharmony_ci goto fail_put_wdat; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&resource_list); 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci entries = (struct acpi_wdat_entry *)(wdat + 1); 1318c2ecf20Sopenharmony_ci for (i = 0; i < wdat->entries; i++) { 1328c2ecf20Sopenharmony_ci const struct acpi_generic_address *gas; 1338c2ecf20Sopenharmony_ci struct resource_entry *rentry; 1348c2ecf20Sopenharmony_ci struct resource res = {}; 1358c2ecf20Sopenharmony_ci bool found; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci gas = &entries[i].register_region; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci res.start = gas->address; 1408c2ecf20Sopenharmony_ci res.end = res.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1; 1418c2ecf20Sopenharmony_ci if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { 1428c2ecf20Sopenharmony_ci res.flags = IORESOURCE_MEM; 1438c2ecf20Sopenharmony_ci } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) { 1448c2ecf20Sopenharmony_ci res.flags = IORESOURCE_IO; 1458c2ecf20Sopenharmony_ci } else { 1468c2ecf20Sopenharmony_ci pr_warn("Unsupported address space: %u\n", 1478c2ecf20Sopenharmony_ci gas->space_id); 1488c2ecf20Sopenharmony_ci goto fail_free_resource_list; 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci found = false; 1528c2ecf20Sopenharmony_ci resource_list_for_each_entry(rentry, &resource_list) { 1538c2ecf20Sopenharmony_ci if (rentry->res->flags == res.flags && 1548c2ecf20Sopenharmony_ci resource_overlaps(rentry->res, &res)) { 1558c2ecf20Sopenharmony_ci if (res.start < rentry->res->start) 1568c2ecf20Sopenharmony_ci rentry->res->start = res.start; 1578c2ecf20Sopenharmony_ci if (res.end > rentry->res->end) 1588c2ecf20Sopenharmony_ci rentry->res->end = res.end; 1598c2ecf20Sopenharmony_ci found = true; 1608c2ecf20Sopenharmony_ci break; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci } 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci if (!found) { 1658c2ecf20Sopenharmony_ci rentry = resource_list_create_entry(NULL, 0); 1668c2ecf20Sopenharmony_ci if (!rentry) 1678c2ecf20Sopenharmony_ci goto fail_free_resource_list; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci *rentry->res = res; 1708c2ecf20Sopenharmony_ci resource_list_add_tail(rentry, &resource_list); 1718c2ecf20Sopenharmony_ci nresources++; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci } 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL); 1768c2ecf20Sopenharmony_ci if (!resources) 1778c2ecf20Sopenharmony_ci goto fail_free_resource_list; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci i = 0; 1808c2ecf20Sopenharmony_ci resource_list_for_each_entry(rentry, &resource_list) 1818c2ecf20Sopenharmony_ci resources[i++] = *rentry->res; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE, 1848c2ecf20Sopenharmony_ci resources, nresources); 1858c2ecf20Sopenharmony_ci if (IS_ERR(pdev)) 1868c2ecf20Sopenharmony_ci pr_err("Device creation failed: %ld\n", PTR_ERR(pdev)); 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci kfree(resources); 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_cifail_free_resource_list: 1918c2ecf20Sopenharmony_ci resource_list_free(&resource_list); 1928c2ecf20Sopenharmony_cifail_put_wdat: 1938c2ecf20Sopenharmony_ci acpi_put_table((struct acpi_table_header *)wdat); 1948c2ecf20Sopenharmony_ci} 195