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