Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/acpi/acpi_watchdog.c
26278 views
1
// SPDX-License-Identifier: GPL-2.0-only
2
/*
3
* ACPI watchdog table parsing support.
4
*
5
* Copyright (C) 2016, Intel Corporation
6
* Author: Mika Westerberg <[email protected]>
7
*/
8
9
#define pr_fmt(fmt) "ACPI: watchdog: " fmt
10
11
#include <linux/acpi.h>
12
#include <linux/ioport.h>
13
#include <linux/platform_device.h>
14
15
#include "internal.h"
16
17
#ifdef CONFIG_RTC_MC146818_LIB
18
#include <linux/mc146818rtc.h>
19
20
/*
21
* There are several systems where the WDAT table is accessing RTC SRAM to
22
* store persistent information. This does not work well with the Linux RTC
23
* driver so on those systems we skip WDAT driver and prefer iTCO_wdt
24
* instead.
25
*
26
* See also https://bugzilla.kernel.org/show_bug.cgi?id=199033.
27
*/
28
static bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat)
29
{
30
const struct acpi_wdat_entry *entries;
31
int i;
32
33
entries = (struct acpi_wdat_entry *)(wdat + 1);
34
for (i = 0; i < wdat->entries; i++) {
35
const struct acpi_generic_address *gas;
36
37
gas = &entries[i].register_region;
38
if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
39
switch (gas->address) {
40
case RTC_PORT(0):
41
case RTC_PORT(1):
42
case RTC_PORT(2):
43
case RTC_PORT(3):
44
return true;
45
}
46
}
47
}
48
49
return false;
50
}
51
#else
52
static bool acpi_watchdog_uses_rtc(const struct acpi_table_wdat *wdat)
53
{
54
return false;
55
}
56
#endif
57
58
static bool acpi_no_watchdog;
59
60
static const struct acpi_table_wdat *acpi_watchdog_get_wdat(void)
61
{
62
const struct acpi_table_wdat *wdat = NULL;
63
acpi_status status;
64
65
if (acpi_disabled || acpi_no_watchdog)
66
return NULL;
67
68
status = acpi_get_table(ACPI_SIG_WDAT, 0,
69
(struct acpi_table_header **)&wdat);
70
if (ACPI_FAILURE(status)) {
71
/* It is fine if there is no WDAT */
72
return NULL;
73
}
74
75
if (acpi_watchdog_uses_rtc(wdat)) {
76
acpi_put_table((struct acpi_table_header *)wdat);
77
pr_info("Skipping WDAT on this system because it uses RTC SRAM\n");
78
return NULL;
79
}
80
81
return wdat;
82
}
83
84
/*
85
* Returns true if this system should prefer ACPI based watchdog instead of
86
* the native one (which are typically the same hardware).
87
*/
88
bool acpi_has_watchdog(void)
89
{
90
return !!acpi_watchdog_get_wdat();
91
}
92
EXPORT_SYMBOL_GPL(acpi_has_watchdog);
93
94
/* ACPI watchdog can be disabled on boot command line */
95
static int __init disable_acpi_watchdog(char *str)
96
{
97
acpi_no_watchdog = true;
98
return 1;
99
}
100
__setup("acpi_no_watchdog", disable_acpi_watchdog);
101
102
void __init acpi_watchdog_init(void)
103
{
104
const struct acpi_wdat_entry *entries;
105
const struct acpi_table_wdat *wdat;
106
struct list_head resource_list;
107
struct resource_entry *rentry;
108
struct platform_device *pdev;
109
struct resource *resources;
110
size_t nresources = 0;
111
int i;
112
113
wdat = acpi_watchdog_get_wdat();
114
if (!wdat) {
115
/* It is fine if there is no WDAT */
116
return;
117
}
118
119
/* Watchdog disabled by BIOS */
120
if (!(wdat->flags & ACPI_WDAT_ENABLED))
121
goto fail_put_wdat;
122
123
/* Skip legacy PCI WDT devices */
124
if (wdat->pci_segment != 0xff || wdat->pci_bus != 0xff ||
125
wdat->pci_device != 0xff || wdat->pci_function != 0xff)
126
goto fail_put_wdat;
127
128
INIT_LIST_HEAD(&resource_list);
129
130
entries = (struct acpi_wdat_entry *)(wdat + 1);
131
for (i = 0; i < wdat->entries; i++) {
132
const struct acpi_generic_address *gas;
133
struct resource_entry *rentry;
134
struct resource res = {};
135
bool found;
136
137
gas = &entries[i].register_region;
138
139
res.start = gas->address;
140
res.end = res.start + ACPI_ACCESS_BYTE_WIDTH(gas->access_width) - 1;
141
if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) {
142
res.flags = IORESOURCE_MEM;
143
} else if (gas->space_id == ACPI_ADR_SPACE_SYSTEM_IO) {
144
res.flags = IORESOURCE_IO;
145
} else {
146
pr_warn("Unsupported address space: %u\n",
147
gas->space_id);
148
goto fail_free_resource_list;
149
}
150
151
found = false;
152
resource_list_for_each_entry(rentry, &resource_list) {
153
if (rentry->res->flags == res.flags &&
154
resource_union(rentry->res, &res, rentry->res)) {
155
found = true;
156
break;
157
}
158
}
159
160
if (!found) {
161
rentry = resource_list_create_entry(NULL, 0);
162
if (!rentry)
163
goto fail_free_resource_list;
164
165
*rentry->res = res;
166
resource_list_add_tail(rentry, &resource_list);
167
nresources++;
168
}
169
}
170
171
resources = kcalloc(nresources, sizeof(*resources), GFP_KERNEL);
172
if (!resources)
173
goto fail_free_resource_list;
174
175
i = 0;
176
resource_list_for_each_entry(rentry, &resource_list)
177
resources[i++] = *rentry->res;
178
179
pdev = platform_device_register_simple("wdat_wdt", PLATFORM_DEVID_NONE,
180
resources, nresources);
181
if (IS_ERR(pdev))
182
pr_err("Device creation failed: %pe\n", pdev);
183
184
kfree(resources);
185
186
fail_free_resource_list:
187
resource_list_free(&resource_list);
188
fail_put_wdat:
189
acpi_put_table((struct acpi_table_header *)wdat);
190
}
191
192