1 /* $NetBSD: swwdog.c,v 1.25 2025/12/31 14:11:16 nia Exp $ */ 2 3 /* 4 * Copyright (c) 2004, 2005 Steven M. Bellovin 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. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. The name of the author contributors may not be used to endorse or 16 * promote products derived from this software without specific prior 17 * written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 #include <sys/cdefs.h> 33 __KERNEL_RCSID(0, "$NetBSD: swwdog.c,v 1.25 2025/12/31 14:11:16 nia Exp $"); 34 35 /* 36 * 37 * Software watchdog timer 38 * 39 */ 40 #include <sys/param.h> 41 #include <sys/device.h> 42 #include <sys/callout.h> 43 #include <sys/kernel.h> 44 #include <sys/kmem.h> 45 #include <sys/reboot.h> 46 #include <sys/systm.h> 47 #include <sys/sysctl.h> 48 #include <sys/wdog.h> 49 #include <sys/workqueue.h> 50 #include <sys/module.h> 51 #include <dev/sysmon/sysmonvar.h> 52 53 #ifndef _MODULE 54 #include "opt_modular.h" 55 #endif 56 57 struct swwdog_softc { 58 device_t sc_dev; 59 struct sysmon_wdog sc_smw; 60 struct callout sc_c; 61 int sc_armed; 62 }; 63 64 bool swwdog_reboot = false; /* false --> panic , true --> reboot */ 65 66 static struct workqueue *wq; 67 static device_t swwdog_dev; 68 69 MODULE(MODULE_CLASS_DRIVER, swwdog, "sysmon_wdog"); 70 71 #ifdef _MODULE 72 CFDRIVER_DECL(swwdog, DV_DULL, NULL); 73 #endif 74 75 int swwdogattach(int); 76 77 static int swwdog_setmode(struct sysmon_wdog *); 78 static int swwdog_tickle(struct sysmon_wdog *); 79 static bool swwdog_suspend(device_t, const pmf_qual_t *); 80 static int swwdog_arm(struct swwdog_softc *); 81 static int swwdog_disarm(struct swwdog_softc *); 82 83 static void swwdog_panic(void *); 84 85 static int swwdog_match(device_t, cfdata_t, void *); 86 static void swwdog_attach(device_t, device_t, void *); 87 static int swwdog_detach(device_t, int); 88 static bool swwdog_suspend(device_t, const pmf_qual_t *); 89 90 static int swwdog_init(void *); 91 static int swwdog_fini(void *); 92 static int swwdog_modcmd(modcmd_t, void *); 93 94 CFATTACH_DECL_NEW(swwdog, sizeof(struct swwdog_softc), 95 swwdog_match, swwdog_attach, swwdog_detach, NULL); 96 extern struct cfdriver swwdog_cd; 97 98 #define SWDOG_DEFAULT 60 /* 60-second default period */ 99 100 static void 101 doreboot(struct work *wrkwrkwrk, void *p) 102 { 103 104 kern_reboot(0, NULL); 105 } 106 107 int 108 swwdogattach(int n __unused) 109 { 110 int error; 111 static struct cfdata cf; 112 113 if (workqueue_create(&wq, "swwreboot", doreboot, NULL, 114 PRI_NONE, IPL_NONE, 0) != 0) { 115 aprint_error("failed to create swwdog reboot wq"); 116 return 1; 117 } 118 119 error = config_cfattach_attach(swwdog_cd.cd_name, &swwdog_ca); 120 if (error) { 121 aprint_error("%s: unable to attach cfattach\n", 122 swwdog_cd.cd_name); 123 workqueue_destroy(wq); 124 return error; 125 } 126 127 cf.cf_name = swwdog_cd.cd_name; 128 cf.cf_atname = swwdog_cd.cd_name; 129 cf.cf_unit = 0; 130 cf.cf_fstate = FSTATE_STAR; 131 cf.cf_pspec = NULL; 132 cf.cf_loc = NULL; 133 cf.cf_flags = 0; 134 135 swwdog_dev = config_attach_pseudo(&cf); 136 137 if (swwdog_dev == NULL) { 138 config_cfattach_detach(swwdog_cd.cd_name, &swwdog_ca); 139 workqueue_destroy(wq); 140 return 1; 141 } 142 return 0; 143 } 144 145 static int 146 swwdog_match(device_t parent, cfdata_t data, void *aux) 147 { 148 149 return 1; 150 } 151 152 static void 153 swwdog_attach(device_t parent, device_t self, void *aux) 154 { 155 struct swwdog_softc *sc = device_private(self); 156 157 sc->sc_dev = self; 158 sc->sc_smw.smw_name = device_xname(self); 159 sc->sc_smw.smw_cookie = sc; 160 sc->sc_smw.smw_setmode = swwdog_setmode; 161 sc->sc_smw.smw_tickle = swwdog_tickle; 162 sc->sc_smw.smw_period = SWDOG_DEFAULT; 163 164 callout_init(&sc->sc_c, 0); 165 callout_setfunc(&sc->sc_c, swwdog_panic, sc); 166 167 if (sysmon_wdog_register(&sc->sc_smw) == 0) 168 aprint_normal_dev(self, "software watchdog initialized\n"); 169 else { 170 aprint_error_dev(self, "unable to register software " 171 "watchdog with sysmon\n"); 172 callout_destroy(&sc->sc_c); 173 return; 174 } 175 176 if (!pmf_device_register(self, swwdog_suspend, NULL)) 177 aprint_error_dev(self, "couldn't establish power handler\n"); 178 } 179 180 static int 181 swwdog_detach(device_t self, int flags) 182 { 183 struct swwdog_softc *sc = device_private(self); 184 185 pmf_device_deregister(self); 186 swwdog_disarm(sc); 187 sysmon_wdog_unregister(&sc->sc_smw); 188 callout_destroy(&sc->sc_c); 189 workqueue_destroy(wq); 190 191 return 0; 192 } 193 194 static bool 195 swwdog_suspend(device_t dev, const pmf_qual_t *qual) 196 { 197 struct swwdog_softc *sc = device_private(dev); 198 199 /* Don't allow suspend if watchdog is armed */ 200 if ((sc->sc_smw.smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED) 201 return false; 202 return true; 203 } 204 205 static int 206 swwdog_setmode(struct sysmon_wdog *smw) 207 { 208 struct swwdog_softc *sc = smw->smw_cookie; 209 int error = 0; 210 211 if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 212 error = swwdog_disarm(sc); 213 } else { 214 if (smw->smw_period == 0) 215 return EINVAL; 216 else if (smw->smw_period == WDOG_PERIOD_DEFAULT) 217 sc->sc_smw.smw_period = SWDOG_DEFAULT; 218 else 219 sc->sc_smw.smw_period = smw->smw_period; 220 error = swwdog_arm(sc); 221 } 222 return error; 223 } 224 225 static int 226 swwdog_tickle(struct sysmon_wdog *smw) 227 { 228 229 return swwdog_arm(smw->smw_cookie); 230 } 231 232 static int 233 swwdog_arm(struct swwdog_softc *sc) 234 { 235 236 callout_schedule(&sc->sc_c, sc->sc_smw.smw_period * hz); 237 return 0; 238 } 239 240 static int 241 swwdog_disarm(struct swwdog_softc *sc) 242 { 243 244 callout_stop(&sc->sc_c); 245 return 0; 246 } 247 248 static void 249 swwdog_panic(void *vsc) 250 { 251 struct swwdog_softc *sc = vsc; 252 static struct work wk; /* we'll need it max once */ 253 bool do_panic; 254 255 do_panic = !swwdog_reboot; 256 swwdog_reboot = false; 257 callout_schedule(&sc->sc_c, 60 * hz); /* deliberate double-panic */ 258 259 printf("%s: %d second timer expired\n", "swwdog", 260 sc->sc_smw.smw_period); 261 262 if (do_panic) 263 panic("watchdog timer expired"); 264 else 265 workqueue_enqueue(wq, &wk, NULL); 266 } 267 268 SYSCTL_SETUP(swwdog_sysctl_setup, "swwdog sysctl") 269 { 270 const struct sysctlnode *me; 271 272 sysctl_createv(clog, 0, NULL, &me, CTLFLAG_READWRITE, 273 CTLTYPE_NODE, "swwdog", NULL, 274 NULL, 0, NULL, 0, 275 CTL_HW, CTL_CREATE, CTL_EOL); 276 sysctl_createv(clog, 0, NULL, NULL, CTLFLAG_READWRITE, 277 CTLTYPE_BOOL, "reboot", SYSCTL_DESCR("reboot if timer expires"), 278 NULL, 0, &swwdog_reboot, sizeof(bool), 279 CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 280 } 281 282 /* 283 * Module management 284 */ 285 286 static int 287 swwdog_init(void *arg) 288 { 289 /* 290 * Merge the driver info into the kernel tables and attach the 291 * pseudo-device 292 */ 293 int error = 0; 294 295 296 #ifdef _MODULE 297 error = config_cfdriver_attach(&swwdog_cd); 298 if (error) { 299 aprint_error("%s: unable to attach cfdriver\n", 300 swwdog_cd.cd_name); 301 return error; 302 } 303 error = swwdogattach(1); 304 if (error) { 305 aprint_error("%s: device attach failed\n", swwdog_cd.cd_name); 306 config_cfdriver_detach(&swwdog_cd); 307 } 308 #endif 309 310 return error; 311 } 312 313 static int 314 swwdog_fini(void *arg) 315 { 316 int error; 317 318 error = config_detach(swwdog_dev, 0); 319 320 #ifdef _MODULE 321 error = config_cfattach_detach(swwdog_cd.cd_name, &swwdog_ca); 322 if (error) 323 aprint_error("%s: error detaching cfattach: %d\n", 324 swwdog_cd.cd_name, error); 325 326 error = config_cfdriver_detach(&swwdog_cd); 327 if (error) 328 aprint_error("%s: error detaching cfdriver: %d\n", 329 swwdog_cd.cd_name, error); 330 #endif 331 332 return error; 333 } 334 335 static int 336 swwdog_modcmd(modcmd_t cmd, void *arg) 337 { 338 int ret; 339 340 switch (cmd) { 341 case MODULE_CMD_INIT: 342 ret = swwdog_init(arg); 343 break; 344 case MODULE_CMD_FINI: 345 ret = swwdog_fini(arg); 346 break; 347 case MODULE_CMD_STAT: 348 default: 349 ret = ENOTTY; 350 } 351 352 return ret; 353 } 354