Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-only
0002 /*
0003  * ACPI watchdog table parsing support.
0004  *
0005  * Copyright (C) 2016, Intel Corporation
0006  * Author: Mika Westerberg <mika.westerberg@linux.intel.com>
0007  */
0008 
0009 #define pr_fmt(fmt) "ACPI: watchdog: " fmt
0010 
0011 #include <linux/acpi.h>
0012 #include <linux/ioport.h>
0013 #include <linux/platform_device.h>
0014 
0015 #include "internal.h"
0016 
0017 #ifdef CONFIG_RTC_MC146818_LIB
0018 #include <linux/mc146818rtc.h>
0019 
0020 /*
0021  * There are several systems where the WDAT table is accessing RTC SRAM to
0022  * store persistent information. This does not work well with the Linux RTC
0023  * driver so on those systems we skip WDAT driver and prefer iTCO_wdt
0024  * instead.
0025  *
0026  * See also https://bugzilla.kernel.org/show_bug.cgi?id=199033.
0027  */
0028 static bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat)
0029 {
0030     const struct acpi_wdat_entry *entries;
0031     int i;
0032 
0033     entries = (struct acpi_wdat_entry *)(wdat + 1);
0034     for (i = 0; i < wdat->entries; i++) {
0035         const struct acpi_generic_address *gas;
0036 
0037         gas = &entries[i].register_region;
0038         if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
0039             switch (gas->address) {
0040             case RTC_PORT(0):
0041             case RTC_PORT(1):
0042             case RTC_PORT(2):
0043             case RTC_PORT(3):
0044                 return true;
0045             }
0046         }
0047     }
0048 
0049     return false;
0050 }
0051 #else
0052 static bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat)
0053 {
0054     return false;
0055 }
0056 #endif
0057 
0058 static bool acpi_no_watchdog;
0059 
0060 static const struct acpi_table_wdat *acpi_watchdog_get_wdat(void)
0061 {
0062     const struct acpi_table_wdat *wdat = NULL;
0063     acpi_status status;
0064 
0065     if (acpi_disabled || acpi_no_watchdog)
0066         return NULL;
0067 
0068     status = acpi_get_table(ACPI_SIG_WDAT, 0,
0069                 (struct acpi_table_header **)&wdat);
0070     if (ACPI_FAILURE(status)) {
0071         /* It is fine if there is no WDAT */
0072         return NULL;
0073     }
0074 
0075     if (acpi_watchdog_uses_rtc(wdat)) {
0076         acpi_put_table((struct acpi_table_header *)wdat);
0077         pr_info("Skipping WDAT on this system because it uses RTC SRAM\n");
0078         return NULL;
0079     }
0080 
0081     return wdat;
0082 }
0083 
0084 /**
0085  * Returns true if this system should prefer ACPI based watchdog instead of
0086  * the native one (which are typically the same hardware).
0087  */
0088 bool acpi_has_watchdog(void)
0089 {
0090     return !!acpi_watchdog_get_wdat();
0091 }
0092 EXPORT_SYMBOL_GPL(acpi_has_watchdog);
0093 
0094 /* ACPI watchdog can be disabled on boot command line */
0095 static int __init disable_acpi_watchdog(char *str)
0096 {
0097     acpi_no_watchdog = true;
0098     return 1;
0099 }
0100 __setup("acpi_no_watchdog", disable_acpi_watchdog);
0101 
0102 void __init acpi_watchdog_init(void)
0103 {
0104     const struct acpi_wdat_entry *entries;
0105     const struct acpi_table_wdat *wdat;
0106     struct list_head resource_list;
0107     struct resource_entry *rentry;
0108     struct platform_device *pdev;
0109     struct resource *resources;
0110     size_t nresources = 0;
0111     int i;
0112 
0113     wdat = acpi_watchdog_get_wdat();
0114     if (!wdat) {
0115         /* It is fine if there is no WDAT */
0116         return;
0117     }
0118 
0119     /* Watchdog disabled by BIOS */
0120     if (!(wdat->flags & ACPI_WDAT_ENABLED))
0121         goto fail_put_wdat;
0122 
0123     /* Skip legacy PCI WDT devices */
0124     if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff ||
0125         wdat->pci_device != 0xff || wdat->pci_function != 0xff)
0126         goto fail_put_wdat;
0127 
0128     INIT_LIST_HEAD(&resource_list);
0129 
0130     entries = (struct acpi_wdat_entry *)(wdat + 1);
0131     for (i = 0; i < wdat->entries; i++) {
0132         const struct acpi_generic_address *gas;
0133         struct resource_entry *rentry;
0134         struct resource res = {};
0135         bool found;
0136 
0137         gas = &entries[i].register_region;
0138 
0139         res.start = gas->address;
0140         res.end = res.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1;
0141         if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
0142             res.flags = IORESOURCE_MEM;
0143         } else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
0144             res.flags = IORESOURCE_IO;
0145         } else {
0146             pr_warn("Unsupported address space: %u\n",
0147                 gas->space_id);
0148             goto fail_free_resource_list;
0149         }
0150 
0151         found = false;
0152         resource_list_for_each_entry(rentry, &resource_list) {
0153             if (rentry->res->flags == res.flags &&
0154                 resource_union(rentry->res, &res, rentry->res)) {
0155                 found = true;
0156                 break;
0157             }
0158         }
0159 
0160         if (!found) {
0161             rentry = resource_list_create_entry(NULL, 0);
0162             if (!rentry)
0163                 goto fail_free_resource_list;
0164 
0165             *rentry->res = res;
0166             resource_list_add_tail(rentry, &resource_list);
0167             nresources++;
0168         }
0169     }
0170 
0171     resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL);
0172     if (!resources)
0173         goto fail_free_resource_list;
0174 
0175     i = 0;
0176     resource_list_for_each_entry(rentry, &resource_list)
0177         resources[i++] = *rentry->res;
0178 
0179     pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE,
0180                            resources, nresources);
0181     if (IS_ERR(pdev))
0182         pr_err("Device creation failed: %ld\n", PTR_ERR(pdev));
0183 
0184     kfree(resources);
0185 
0186 fail_free_resource_list:
0187     resource_list_free(&resource_list);
0188 fail_put_wdat:
0189     acpi_put_table((struct acpi_table_header *)wdat);
0190 }