1 | /* $NetBSD: acpi_wdrt.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $ */ |
2 | |
3 | /* |
4 | * Copyright (c) 2011 Jared D. McNeill <jmcneill@invisible.ca> |
5 | * All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. The name of the author may not be used to endorse or promote products |
13 | * derived from this software without specific prior written permission. |
14 | * |
15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
16 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
17 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
18 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
19 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
20 | * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
21 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
22 | * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
23 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
25 | * SUCH DAMAGE. |
26 | */ |
27 | |
28 | /* |
29 | * ACPI "WDRT" watchdog support, based on: |
30 | * |
31 | * Watchdog Timer Hardware Requirements for Windows Server 2003, version 1.01 |
32 | * http://www.microsoft.com/whdc/system/sysinternals/watchdog.mspx |
33 | */ |
34 | |
35 | /* #define ACPIWDRT_DEBUG */ |
36 | |
37 | #include <sys/cdefs.h> |
38 | __KERNEL_RCSID(0, "$NetBSD: acpi_wdrt.c,v 1.4 2015/04/23 23:23:00 pgoyette Exp $" ); |
39 | |
40 | #include <sys/param.h> |
41 | #include <sys/device.h> |
42 | #include <sys/systm.h> |
43 | #include <sys/module.h> |
44 | |
45 | #include <dev/acpi/acpireg.h> |
46 | #include <dev/acpi/acpivar.h> |
47 | |
48 | #include <dev/sysmon/sysmonvar.h> |
49 | |
50 | #define _COMPONENT ACPI_RESOURCE_COMPONENT |
51 | ACPI_MODULE_NAME ("acpi_wdrt" ) |
52 | |
53 | #ifdef ACPIWDRT_DEBUG |
54 | #define DPRINTF(x) printf x |
55 | #else |
56 | #define DPRINTF(x) do { } while (0) |
57 | #endif |
58 | |
59 | /* Watchdog Control/Status Register (32 bits) */ |
60 | #define ACPI_WDRT_CSR_RUNSTOP (1 << 0) /* rw */ |
61 | #define ACPI_WDRT_RUNSTOP_STOPPED (0 << 0) |
62 | #define ACPI_WDRT_RUNSTOP_RUNNING (1 << 0) |
63 | #define ACPI_WDRT_CSR_FIRED (1 << 1) /* rw */ |
64 | #define ACPI_WDRT_CSR_ACTION (1 << 2) /* rw */ |
65 | #define ACPI_WDRT_ACTION_RESET (0 << 2) |
66 | #define ACPI_WDRT_ACTION_POWEROFF (1 << 2) |
67 | #define ACPI_WDRT_CSR_WDOGEN (1 << 3) /* ro */ |
68 | #define ACPI_WDRT_WDOGEN_ENABLE (0 << 3) |
69 | #define ACPI_WDRT_WDOGEN_DISABLE (1 << 3) |
70 | #define ACPI_WDRT_CSR_TRIGGER (1 << 7) /* wo */ |
71 | /* Watchdog Count Register (32 bits) */ |
72 | #define ACPI_WDRT_COUNT_DATA_MASK 0xffff |
73 | |
74 | /* Watchdog Resource Table - Units */ |
75 | #define ACPI_WDRT_UNITS_1S 0x0 |
76 | #define ACPI_WDRT_UNITS_100MS 0x1 |
77 | #define ACPI_WDRT_UNITS_10MS 0x2 |
78 | |
79 | struct acpi_wdrt_softc { |
80 | device_t sc_dev; |
81 | |
82 | bus_space_tag_t sc_memt; |
83 | bus_space_handle_t sc_memh; |
84 | |
85 | struct sysmon_wdog sc_smw; |
86 | bool sc_smw_valid; |
87 | |
88 | uint32_t sc_max_period; |
89 | unsigned int sc_period_scale; |
90 | |
91 | ACPI_GENERIC_ADDRESS sc_control_reg; |
92 | ACPI_GENERIC_ADDRESS sc_count_reg; |
93 | }; |
94 | |
95 | static int acpi_wdrt_match(device_t, cfdata_t, void *); |
96 | static void acpi_wdrt_attach(device_t, device_t, void *); |
97 | static int acpi_wdrt_detach(device_t, int); |
98 | static bool acpi_wdrt_suspend(device_t, const pmf_qual_t *); |
99 | |
100 | static int acpi_wdrt_setmode(struct sysmon_wdog *); |
101 | static int acpi_wdrt_tickle(struct sysmon_wdog *); |
102 | |
103 | static ACPI_STATUS acpi_wdrt_read_control(struct acpi_wdrt_softc *, uint64_t *); |
104 | static ACPI_STATUS acpi_wdrt_write_control(struct acpi_wdrt_softc *, uint64_t); |
105 | #if 0 |
106 | static ACPI_STATUS acpi_wdrt_read_count(struct acpi_wdrt_softc *, uint32_t *); |
107 | #endif |
108 | static ACPI_STATUS acpi_wdrt_write_count(struct acpi_wdrt_softc *, uint32_t); |
109 | |
110 | CFATTACH_DECL_NEW( |
111 | acpiwdrt, |
112 | sizeof(struct acpi_wdrt_softc), |
113 | acpi_wdrt_match, |
114 | acpi_wdrt_attach, |
115 | acpi_wdrt_detach, |
116 | NULL |
117 | ); |
118 | |
119 | static int |
120 | acpi_wdrt_match(device_t parent, cfdata_t match, void *opaque) |
121 | { |
122 | ACPI_TABLE_WDRT *wdrt; |
123 | ACPI_STATUS rv; |
124 | uint64_t val; |
125 | |
126 | rv = AcpiGetTable(ACPI_SIG_WDRT, 1, (ACPI_TABLE_HEADER **)&wdrt); |
127 | if (ACPI_FAILURE(rv)) |
128 | return 0; |
129 | |
130 | /* Only system memory address spaces are allowed */ |
131 | if (wdrt->ControlRegister.SpaceId != ACPI_ADR_SPACE_SYSTEM_MEMORY || |
132 | wdrt->CountRegister.SpaceId != ACPI_ADR_SPACE_SYSTEM_MEMORY) { |
133 | return 0; |
134 | } |
135 | /* Sanity check control & count register addresses */ |
136 | if (wdrt->ControlRegister.Address == 0 || |
137 | wdrt->ControlRegister.Address == 0xffffffff || |
138 | wdrt->ControlRegister.Address == 0xffffffffffffffff || |
139 | wdrt->CountRegister.Address == 0 || |
140 | wdrt->CountRegister.Address == 0xffffffff || |
141 | wdrt->CountRegister.Address == 0xffffffffffffffff) { |
142 | return 0; |
143 | } |
144 | |
145 | /* Read control regster */ |
146 | rv = AcpiOsReadMemory(wdrt->ControlRegister.Address, &val, |
147 | wdrt->ControlRegister.BitWidth); |
148 | if (ACPI_FAILURE(rv)) |
149 | return 0; |
150 | |
151 | /* Make sure the hardware watchdog is enabled */ |
152 | if ((val & ACPI_WDRT_CSR_WDOGEN) == ACPI_WDRT_WDOGEN_DISABLE) |
153 | return 0; |
154 | |
155 | return 1; |
156 | } |
157 | |
158 | static void |
159 | acpi_wdrt_attach(device_t parent, device_t self, void *opaque) |
160 | { |
161 | struct acpi_wdrt_softc *sc = device_private(self); |
162 | ACPI_TABLE_WDRT *wdrt; |
163 | ACPI_STATUS rv; |
164 | |
165 | sc->sc_dev = self; |
166 | |
167 | pmf_device_register(self, acpi_wdrt_suspend, NULL); |
168 | |
169 | rv = AcpiGetTable(ACPI_SIG_WDRT, 1, (ACPI_TABLE_HEADER **)&wdrt); |
170 | if (ACPI_FAILURE(rv)) { |
171 | aprint_error(": couldn't get WDRT (%s)\n" , |
172 | AcpiFormatException(rv)); |
173 | return; |
174 | } |
175 | |
176 | /* Maximum counter value must be 511 - 65535 */ |
177 | if (wdrt->MaxCount < 511) { |
178 | aprint_error(": maximum counter value out of range (%d)\n" , |
179 | wdrt->MaxCount); |
180 | return; |
181 | } |
182 | /* Counter units can be 1s, 100ms, or 10ms */ |
183 | switch (wdrt->Units) { |
184 | case ACPI_WDRT_UNITS_1S: |
185 | case ACPI_WDRT_UNITS_100MS: |
186 | case ACPI_WDRT_UNITS_10MS: |
187 | break; |
188 | default: |
189 | aprint_error(": units not supported (0x%x)\n" , wdrt->Units); |
190 | return; |
191 | } |
192 | |
193 | sc->sc_control_reg = wdrt->ControlRegister; |
194 | sc->sc_count_reg = wdrt->CountRegister; |
195 | |
196 | aprint_naive("\n" ); |
197 | aprint_normal(": mem 0x%" PRIx64 ",0x%" PRIx64 "\n" , |
198 | sc->sc_control_reg.Address, sc->sc_count_reg.Address); |
199 | |
200 | if (wdrt->PciVendorId != 0xffff && wdrt->PciDeviceId != 0xffff) { |
201 | aprint_verbose_dev(sc->sc_dev, "PCI %u:%03u:%02u:%01u" , |
202 | wdrt->PciSegment, wdrt->PciBus, wdrt->PciDevice, |
203 | wdrt->PciFunction); |
204 | aprint_verbose(" vendor 0x%04x product 0x%04x\n" , |
205 | wdrt->PciVendorId, wdrt->PciDeviceId); |
206 | } |
207 | |
208 | sc->sc_max_period = wdrt->MaxCount; |
209 | sc->sc_period_scale = 1; |
210 | if (wdrt->Units == ACPI_WDRT_UNITS_100MS) |
211 | sc->sc_period_scale = 10; |
212 | if (wdrt->Units == ACPI_WDRT_UNITS_10MS) |
213 | sc->sc_period_scale = 100; |
214 | sc->sc_max_period /= sc->sc_period_scale; |
215 | aprint_normal_dev(self, "watchdog interval 1-%d sec.\n" , |
216 | sc->sc_max_period); |
217 | |
218 | sc->sc_smw.smw_name = device_xname(self); |
219 | sc->sc_smw.smw_cookie = sc; |
220 | sc->sc_smw.smw_setmode = acpi_wdrt_setmode; |
221 | sc->sc_smw.smw_tickle = acpi_wdrt_tickle; |
222 | sc->sc_smw.smw_period = sc->sc_max_period; |
223 | |
224 | if (sysmon_wdog_register(&sc->sc_smw)) |
225 | aprint_error_dev(self, "couldn't register with sysmon\n" ); |
226 | else |
227 | sc->sc_smw_valid = true; |
228 | } |
229 | |
230 | static int |
231 | acpi_wdrt_detach(device_t self, int flags) |
232 | { |
233 | struct acpi_wdrt_softc *sc = device_private(self); |
234 | |
235 | /* Don't allow detach if watchdog is armed */ |
236 | if (sc->sc_smw_valid && |
237 | (sc->sc_smw.smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED) |
238 | return EBUSY; |
239 | |
240 | if (sc->sc_smw_valid) { |
241 | sysmon_wdog_unregister(&sc->sc_smw); |
242 | sc->sc_smw_valid = false; |
243 | } |
244 | |
245 | pmf_device_deregister(self); |
246 | |
247 | return 0; |
248 | } |
249 | |
250 | static bool |
251 | acpi_wdrt_suspend(device_t self, const pmf_qual_t *qual) |
252 | { |
253 | struct acpi_wdrt_softc *sc = device_private(self); |
254 | |
255 | if (sc->sc_smw_valid == false) |
256 | return true; |
257 | |
258 | /* Don't allow suspend if watchdog is armed */ |
259 | if ((sc->sc_smw.smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED) |
260 | return false; |
261 | |
262 | return true; |
263 | } |
264 | |
265 | static int |
266 | acpi_wdrt_setmode(struct sysmon_wdog *smw) |
267 | { |
268 | struct acpi_wdrt_softc *sc = smw->smw_cookie; |
269 | uint64_t val; |
270 | |
271 | DPRINTF(("%s: %s mode 0x%x period %u\n" , device_xname(sc->sc_dev), |
272 | __func__, smw->smw_mode, smw->smw_period)); |
273 | |
274 | switch (smw->smw_mode & WDOG_MODE_MASK) { |
275 | case WDOG_MODE_DISARMED: |
276 | /* Disable watchdog timer */ |
277 | if (ACPI_FAILURE(acpi_wdrt_read_control(sc, &val))) |
278 | goto failed; |
279 | val &= ~ACPI_WDRT_CSR_RUNSTOP; |
280 | val |= ACPI_WDRT_RUNSTOP_STOPPED; |
281 | if (ACPI_FAILURE(acpi_wdrt_write_control(sc, val))) |
282 | goto failed; |
283 | break; |
284 | default: |
285 | if (smw->smw_period == 0) |
286 | return EINVAL; |
287 | if (smw->smw_period > sc->sc_max_period) |
288 | smw->smw_period = sc->sc_max_period; |
289 | |
290 | /* Enable watchdog timer */ |
291 | if (ACPI_FAILURE(acpi_wdrt_read_control(sc, &val))) |
292 | goto failed; |
293 | val &= ~ACPI_WDRT_CSR_RUNSTOP; |
294 | val &= ~ACPI_WDRT_CSR_ACTION; |
295 | val |= ACPI_WDRT_ACTION_RESET; |
296 | if (ACPI_FAILURE(acpi_wdrt_write_control(sc, val))) |
297 | goto failed; |
298 | /* Reset count register */ |
299 | if (acpi_wdrt_tickle(smw)) |
300 | goto failed; |
301 | val |= ACPI_WDRT_RUNSTOP_RUNNING; |
302 | if (ACPI_FAILURE(acpi_wdrt_write_control(sc, val))) |
303 | goto failed; |
304 | break; |
305 | } |
306 | |
307 | return 0; |
308 | |
309 | failed: |
310 | return EIO; |
311 | } |
312 | |
313 | static int |
314 | acpi_wdrt_tickle(struct sysmon_wdog *smw) |
315 | { |
316 | struct acpi_wdrt_softc *sc = smw->smw_cookie; |
317 | uint64_t val; |
318 | |
319 | DPRINTF(("%s: %s mode 0x%x period %u\n" , device_xname(sc->sc_dev), |
320 | __func__, smw->smw_mode, smw->smw_period)); |
321 | |
322 | /* Reset count register */ |
323 | val = smw->smw_period * sc->sc_period_scale; |
324 | if (ACPI_FAILURE(acpi_wdrt_write_count(sc, val))) |
325 | return EIO; |
326 | |
327 | /* Trigger new count interval */ |
328 | if (ACPI_FAILURE(acpi_wdrt_read_control(sc, &val))) |
329 | return EIO; |
330 | val |= ACPI_WDRT_CSR_TRIGGER; |
331 | if (ACPI_FAILURE(acpi_wdrt_write_control(sc, val))) |
332 | return EIO; |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static ACPI_STATUS |
338 | acpi_wdrt_read_control(struct acpi_wdrt_softc *sc, uint64_t *val) |
339 | { |
340 | ACPI_STATUS rv; |
341 | |
342 | KASSERT(sc->sc_smw_valid == true); |
343 | |
344 | rv = AcpiOsReadMemory(sc->sc_control_reg.Address, |
345 | val, sc->sc_control_reg.BitWidth); |
346 | |
347 | DPRINTF(("%s: %s 0x%" PRIx64 "/%u 0x%08x (%u)\n" , |
348 | device_xname(sc->sc_dev), |
349 | __func__, sc->sc_control_reg.Address, sc->sc_control_reg.BitWidth, |
350 | *val, rv)); |
351 | |
352 | return rv; |
353 | } |
354 | |
355 | static ACPI_STATUS |
356 | acpi_wdrt_write_control(struct acpi_wdrt_softc *sc, uint64_t val) |
357 | { |
358 | ACPI_STATUS rv; |
359 | |
360 | KASSERT(sc->sc_smw_valid == true); |
361 | |
362 | rv = AcpiOsWriteMemory(sc->sc_control_reg.Address, |
363 | val, sc->sc_control_reg.BitWidth); |
364 | |
365 | DPRINTF(("%s: %s 0x%" PRIx64 "/%u 0x%08x (%u)\n" , |
366 | device_xname(sc->sc_dev), |
367 | __func__, sc->sc_control_reg.Address, sc->sc_control_reg.BitWidth, |
368 | val, rv)); |
369 | |
370 | return rv; |
371 | } |
372 | |
373 | #if 0 |
374 | static ACPI_STATUS |
375 | acpi_wdrt_read_count(struct acpi_wdrt_softc *sc, uint32_t *val) |
376 | { |
377 | ACPI_STATUS rv; |
378 | |
379 | KASSERT(sc->sc_smw_valid == true); |
380 | |
381 | rv = AcpiOsReadMemory(sc->sc_count_reg.Address, |
382 | val, sc->sc_count_reg.BitWidth); |
383 | |
384 | DPRINTF(("%s: %s 0x%" PRIx64 "/%u 0x%08x (%u)\n" , |
385 | device_xname(sc->sc_dev), |
386 | __func__, sc->sc_count_reg.Address, sc->sc_count_reg.BitWidth, |
387 | *val, rv)); |
388 | |
389 | return rv; |
390 | } |
391 | #endif |
392 | |
393 | static ACPI_STATUS |
394 | acpi_wdrt_write_count(struct acpi_wdrt_softc *sc, uint32_t val) |
395 | { |
396 | ACPI_STATUS rv; |
397 | |
398 | KASSERT(sc->sc_smw_valid == true); |
399 | |
400 | rv = AcpiOsWriteMemory(sc->sc_count_reg.Address, |
401 | val, sc->sc_count_reg.BitWidth); |
402 | |
403 | DPRINTF(("%s: %s 0x%" PRIx64 "/%u 0x%08x (%u)\n" , |
404 | device_xname(sc->sc_dev), |
405 | __func__, sc->sc_count_reg.Address, sc->sc_count_reg.BitWidth, |
406 | val, rv)); |
407 | |
408 | return rv; |
409 | } |
410 | |
411 | MODULE(MODULE_CLASS_DRIVER, acpiwdrt, "sysmon_wdog" ); |
412 | |
413 | #ifdef _MODULE |
414 | #include "ioconf.c" |
415 | #endif |
416 | |
417 | static int |
418 | acpiwdrt_modcmd(modcmd_t cmd, void *opaque) |
419 | { |
420 | int error = 0; |
421 | |
422 | switch (cmd) { |
423 | case MODULE_CMD_INIT: |
424 | #ifdef _MODULE |
425 | error = config_init_component(cfdriver_ioconf_acpiwdrt, |
426 | cfattach_ioconf_acpiwdrt, cfdata_ioconf_acpiwdrt); |
427 | #endif |
428 | return error; |
429 | case MODULE_CMD_FINI: |
430 | #ifdef _MODULE |
431 | error = config_fini_component(cfdriver_ioconf_acpiwdrt, |
432 | cfattach_ioconf_acpiwdrt, cfdata_ioconf_acpiwdrt); |
433 | #endif |
434 | return error; |
435 | default: |
436 | return ENOTTY; |
437 | } |
438 | } |
439 | |