tco.c revision 1.9 1 /* $NetBSD: tco.c,v 1.9 2022/09/22 14:43:04 riastradh Exp $ */
2
3 /*-
4 * Copyright (c) 2015 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Minoura Makoto and Matthew R. Green.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31
32 /*
33 * Intel I/O Controller Hub (ICHn) watchdog timer
34 */
35
36 #include <sys/cdefs.h>
37 __KERNEL_RCSID(0, "$NetBSD: tco.c,v 1.9 2022/09/22 14:43:04 riastradh Exp $");
38
39 #include <sys/types.h>
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/device.h>
43 #include <sys/timetc.h>
44 #include <sys/module.h>
45
46 #include <dev/pci/pcivar.h>
47 #include <dev/pci/pcireg.h>
48 #include <dev/ic/i82801lpcreg.h>
49
50 #include <dev/sysmon/sysmonvar.h>
51
52 #include <arch/x86/pci/tco.h>
53
54 #include "pcibvar.h"
55
56 struct tco_softc {
57 struct sysmon_wdog sc_smw;
58 bus_space_tag_t sc_pmt;
59 bus_space_handle_t sc_pmh;
60 bus_space_tag_t sc_rcbat;
61 bus_space_handle_t sc_rcbah;
62 struct pcib_softc * sc_pcib;
63 bus_space_tag_t sc_tcot;
64 bus_space_handle_t sc_tcoh;
65 int sc_armed;
66 unsigned int sc_min_t;
67 unsigned int sc_max_t;
68 int sc_version;
69 bool sc_attached;
70 };
71
72 static int tco_match(device_t, cfdata_t, void *);
73 static void tco_attach(device_t, device_t, void *);
74 static int tco_detach(device_t, int);
75
76 static bool tco_suspend(device_t, const pmf_qual_t *);
77
78 static int tcotimer_setmode(struct sysmon_wdog *);
79 static int tcotimer_tickle(struct sysmon_wdog *);
80 static void tcotimer_stop(struct tco_softc *);
81 static void tcotimer_start(struct tco_softc *);
82 static void tcotimer_status_reset(struct tco_softc *);
83 static int tcotimer_disable_noreboot(device_t);
84
85 CFATTACH_DECL3_NEW(tco, sizeof(struct tco_softc),
86 tco_match, tco_attach, tco_detach, NULL, NULL, NULL, 0);
87
88 /*
89 * Autoconf callbacks.
90 */
91 static int
92 tco_match(device_t parent, cfdata_t match, void *aux)
93 {
94 struct tco_attach_args *ta = aux;
95
96 if (ta->ta_pmt == 0)
97 return 0;
98
99 switch (ta->ta_version) {
100 case TCO_VERSION_RCBA:
101 case TCO_VERSION_PCIB:
102 break;
103 default:
104 return 0;
105 }
106
107 return 1;
108 }
109
110 static void
111 tco_attach(device_t parent, device_t self, void *aux)
112 {
113 struct tco_softc *sc = device_private(self);
114 struct tco_attach_args *ta = aux;
115 uint32_t ioreg;
116
117 /* Retrieve bus info shared with parent/siblings */
118 sc->sc_version = ta->ta_version;
119 sc->sc_pmt = ta->ta_pmt;
120 sc->sc_pmh = ta->ta_pmh;
121 sc->sc_rcbat = ta->ta_rcbat;
122 sc->sc_rcbah = ta->ta_rcbah;
123 sc->sc_pcib = ta->ta_pcib;
124
125 aprint_normal(": TCO (watchdog) timer configured.\n");
126 aprint_naive("\n");
127
128 sc->sc_tcot = sc->sc_pmt;
129 if (bus_space_subregion(sc->sc_pmt, sc->sc_pmh, PMC_TCO_BASE,
130 TCO_REGSIZE, &sc->sc_tcoh)) {
131 aprint_error_dev(self, "failed to map TCO registers\n");
132 return;
133 }
134
135 /* Explicitly stop the TCO timer. */
136 tcotimer_stop(sc);
137
138 /*
139 * Enable TCO timeout SMI only if the hardware reset does not
140 * work. We don't know what the SMBIOS does.
141 */
142 ioreg = bus_space_read_4(sc->sc_pmt, sc->sc_pmh, PMC_SMI_EN);
143 ioreg &= ~PMC_SMI_EN_TCO_EN;
144
145 /*
146 * Clear the No Reboot (NR) bit. If this fails, enabling the TCO_EN bit
147 * in the SMI_EN register is the last chance.
148 */
149 if (tcotimer_disable_noreboot(self)) {
150 ioreg |= PMC_SMI_EN_TCO_EN;
151 }
152 if ((ioreg & PMC_SMI_EN_GBL_SMI_EN) != 0) {
153 bus_space_write_4(sc->sc_pmt, sc->sc_pmh, PMC_SMI_EN, ioreg);
154 }
155
156 /* Reset the watchdog status registers. */
157 tcotimer_status_reset(sc);
158
159 /*
160 * Register the driver with the sysmon watchdog framework.
161 */
162 sc->sc_smw.smw_name = device_xname(self);
163 sc->sc_smw.smw_cookie = sc;
164 sc->sc_smw.smw_setmode = tcotimer_setmode;
165 sc->sc_smw.smw_tickle = tcotimer_tickle;
166
167 /*
168 * ICH6 or newer are limited to 2ticks min and 613ticks max.
169 * 1sec 367secs
170 *
171 * ICH5 or older are limited to 4ticks min and 39ticks max.
172 * 2secs 23secs
173 */
174 switch (sc->sc_version) {
175 case TCO_VERSION_RCBA:
176 sc->sc_max_t = TCOTIMER2_MAX_TICK;
177 sc->sc_min_t = TCOTIMER2_MIN_TICK;
178 break;
179 case TCO_VERSION_PCIB:
180 sc->sc_max_t = TCOTIMER_MAX_TICK;
181 sc->sc_min_t = TCOTIMER_MIN_TICK;
182 break;
183 }
184 sc->sc_smw.smw_period = tcotimer_tick_to_second(sc->sc_max_t);
185
186 aprint_verbose_dev(self, "Min/Max interval %u/%u seconds\n",
187 tcotimer_tick_to_second(sc->sc_min_t),
188 tcotimer_tick_to_second(sc->sc_max_t));
189
190 if (sysmon_wdog_register(&sc->sc_smw))
191 aprint_error_dev(self, "unable to register TCO timer"
192 "as a sysmon watchdog device.\n");
193
194 if (!pmf_device_register(self, tco_suspend, NULL))
195 aprint_error_dev(self, "unable to register with pmf\n");
196
197 sc->sc_attached = true;
198 }
199
200 static int
201 tco_detach(device_t self, int flags)
202 {
203 struct tco_softc *sc = device_private(self);
204 int rc;
205
206 if (!sc->sc_attached)
207 return 0;
208
209 if ((rc = sysmon_wdog_unregister(&sc->sc_smw)) != 0) {
210 if (rc == ERESTART)
211 rc = EINTR;
212 return rc;
213 }
214
215 /* Explicitly stop the TCO timer. */
216 tcotimer_stop(sc);
217
218 /* XXX Set No Reboot? */
219
220 pmf_device_deregister(self);
221
222 return 0;
223 }
224
225 static bool
226 tco_suspend(device_t self, const pmf_qual_t *quals)
227 {
228 struct tco_softc *sc = device_private(self);
229
230 /* Allow suspend only if watchdog is not armed */
231
232 return ((sc->sc_smw.smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED);
233 }
234
235 /*
236 * Sysmon watchdog callbacks.
237 */
238 static int
239 tcotimer_setmode(struct sysmon_wdog *smw)
240 {
241 struct tco_softc *sc = smw->smw_cookie;
242 unsigned int period;
243 uint16_t ich6period = 0;
244 uint8_t ich5period = 0;
245
246 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
247 /* Stop the TCO timer. */
248 tcotimer_stop(sc);
249 } else {
250 period = tcotimer_second_to_tick(smw->smw_period);
251 if (period < sc->sc_min_t || period > sc->sc_max_t)
252 return EINVAL;
253
254 /* Stop the TCO timer, */
255 tcotimer_stop(sc);
256
257 /* set the timeout, */
258 switch (sc->sc_version) {
259 case TCO_VERSION_RCBA:
260 /* ICH6 or newer */
261 ich6period = bus_space_read_2(sc->sc_tcot, sc->sc_tcoh,
262 TCO_TMR2);
263 ich6period &= 0xfc00;
264 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh,
265 TCO_TMR2, ich6period | period);
266 break;
267 case TCO_VERSION_PCIB:
268 /* ICH5 or older */
269 ich5period = bus_space_read_1(sc->sc_tcot, sc->sc_tcoh,
270 TCO_TMR);
271 ich5period &= 0xc0;
272 bus_space_write_1(sc->sc_tcot, sc->sc_tcoh,
273 TCO_TMR, ich5period | period);
274 break;
275 }
276
277 /* and start/reload the timer. */
278 tcotimer_start(sc);
279 tcotimer_tickle(smw);
280 }
281
282 return 0;
283 }
284
285 static int
286 tcotimer_tickle(struct sysmon_wdog *smw)
287 {
288 struct tco_softc *sc = smw->smw_cookie;
289
290 /* any value is allowed */
291 switch (sc->sc_version) {
292 case TCO_VERSION_RCBA:
293 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO_RLD, 1);
294 break;
295 case TCO_VERSION_PCIB:
296 bus_space_write_1(sc->sc_tcot, sc->sc_tcoh, TCO_RLD, 1);
297 break;
298 }
299
300 return 0;
301 }
302
303 static void
304 tcotimer_stop(struct tco_softc *sc)
305 {
306 uint16_t ioreg;
307
308 ioreg = bus_space_read_2(sc->sc_tcot, sc->sc_tcoh, TCO1_CNT);
309 ioreg |= TCO1_CNT_TCO_TMR_HLT;
310 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO1_CNT, ioreg);
311 }
312
313 static void
314 tcotimer_start(struct tco_softc *sc)
315 {
316 uint16_t ioreg;
317
318 ioreg = bus_space_read_2(sc->sc_tcot, sc->sc_tcoh, TCO1_CNT);
319 ioreg &= ~TCO1_CNT_TCO_TMR_HLT;
320 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO1_CNT, ioreg);
321 }
322
323 static void
324 tcotimer_status_reset(struct tco_softc *sc)
325 {
326 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO1_STS,
327 TCO1_STS_TIMEOUT);
328 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO2_STS,
329 TCO2_STS_BOOT_STS);
330 bus_space_write_2(sc->sc_tcot, sc->sc_tcoh, TCO2_STS,
331 TCO2_STS_SECONDS_TO_STS);
332 }
333
334 /*
335 * Clear the No Reboot (NR) bit, this enables reboots when the timer
336 * reaches the timeout for the second time.
337 */
338 static int
339 tcotimer_disable_noreboot(device_t self)
340 {
341 struct tco_softc *sc = device_private(self);
342
343 switch (sc->sc_version) {
344 case TCO_VERSION_RCBA: {
345 uint32_t status;
346
347 status = bus_space_read_4(sc->sc_rcbat, sc->sc_rcbah,
348 LPCIB_GCS_OFFSET);
349 status &= ~LPCIB_GCS_NO_REBOOT;
350 bus_space_write_4(sc->sc_rcbat, sc->sc_rcbah,
351 LPCIB_GCS_OFFSET, status);
352 status = bus_space_read_4(sc->sc_rcbat, sc->sc_rcbah,
353 LPCIB_GCS_OFFSET);
354 if (status & LPCIB_GCS_NO_REBOOT)
355 goto error;
356 break;
357 }
358 case TCO_VERSION_PCIB: {
359 pcireg_t pcireg;
360
361 pcireg = pci_conf_read(sc->sc_pcib->sc_pc, sc->sc_pcib->sc_tag,
362 LPCIB_PCI_GEN_STA);
363 if (pcireg & LPCIB_PCI_GEN_STA_NO_REBOOT) {
364 /* TCO timeout reset is disabled; try to enable it */
365 pcireg &= ~LPCIB_PCI_GEN_STA_NO_REBOOT;
366 pci_conf_write(sc->sc_pcib->sc_pc, sc->sc_pcib->sc_tag,
367 LPCIB_PCI_GEN_STA, pcireg);
368 if (pcireg & LPCIB_PCI_GEN_STA_NO_REBOOT)
369 goto error;
370 }
371 break;
372 }
373 }
374
375 return 0;
376 error:
377 aprint_error_dev(self, "TCO timer reboot disabled by hardware; "
378 "hope SMBIOS properly handles it.\n");
379 return EINVAL;
380 }
381
382 MODULE(MODULE_CLASS_DRIVER, tco, "sysmon_wdog");
383
384 #ifdef _MODULE
385 #include "ioconf.c"
386 #endif
387
388 static int
389 tco_modcmd(modcmd_t cmd, void *arg)
390 {
391 int ret = 0;
392
393 switch (cmd) {
394 case MODULE_CMD_INIT:
395 #ifdef _MODULE
396 ret = config_init_component(cfdriver_ioconf_tco,
397 cfattach_ioconf_tco,
398 cfdata_ioconf_tco);
399 #endif
400 break;
401 case MODULE_CMD_FINI:
402 #ifdef _MODULE
403 ret = config_fini_component(cfdriver_ioconf_tco,
404 cfattach_ioconf_tco,
405 cfdata_ioconf_tco);
406 #endif
407 break;
408 default:
409 ret = ENOTTY;
410 }
411
412 return ret;
413 }
414