1*b2562c56Srin /* $NetBSD: swwdog.c,v 1.24 2024/11/07 09:47:40 rin Exp $ */ 2ddd2ade2Ssmb 3ddd2ade2Ssmb /* 4ddd2ade2Ssmb * Copyright (c) 2004, 2005 Steven M. Bellovin 5ddd2ade2Ssmb * All rights reserved. 6ddd2ade2Ssmb * 7ddd2ade2Ssmb * Redistribution and use in source and binary forms, with or without 8ddd2ade2Ssmb * modification, are permitted provided that the following conditions 9ddd2ade2Ssmb * are met: 10ddd2ade2Ssmb * 1. Redistributions of source code must retain the above copyright 11ddd2ade2Ssmb * notice, this list of conditions and the following disclaimer. 12ddd2ade2Ssmb * 2. Redistributions in binary form must reproduce the above copyright 13ddd2ade2Ssmb * notice, this list of conditions and the following disclaimer in the 14ddd2ade2Ssmb * documentation and/or other materials provided with the distribution. 15ddd2ade2Ssmb * 3. All advertising materials mentioning features or use of this software 16ddd2ade2Ssmb * must display the following acknowledgement: 17ddd2ade2Ssmb * This product includes software developed by Steven M. Bellovin 18ddd2ade2Ssmb * 4. The name of the author contributors may not be used to endorse or 19ddd2ade2Ssmb * promote products derived from this software without specific prior 20ddd2ade2Ssmb * written permission. 21ddd2ade2Ssmb * 22ddd2ade2Ssmb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS 23ddd2ade2Ssmb * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 24ddd2ade2Ssmb * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 25ddd2ade2Ssmb * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS 26ddd2ade2Ssmb * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27ddd2ade2Ssmb * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28ddd2ade2Ssmb * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29ddd2ade2Ssmb * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30ddd2ade2Ssmb * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31ddd2ade2Ssmb * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32ddd2ade2Ssmb * POSSIBILITY OF SUCH DAMAGE. 33ddd2ade2Ssmb */ 34ddd2ade2Ssmb 35ddd2ade2Ssmb #include <sys/cdefs.h> 36*b2562c56Srin __KERNEL_RCSID(0, "$NetBSD: swwdog.c,v 1.24 2024/11/07 09:47:40 rin Exp $"); 37ddd2ade2Ssmb 38ddd2ade2Ssmb /* 39ddd2ade2Ssmb * 40ddd2ade2Ssmb * Software watchdog timer 41ddd2ade2Ssmb * 42ddd2ade2Ssmb */ 43ddd2ade2Ssmb #include <sys/param.h> 44ddd2ade2Ssmb #include <sys/device.h> 45a8c699bdSpgoyette #include <sys/callout.h> 46ddd2ade2Ssmb #include <sys/kernel.h> 4701497b95Spgoyette #include <sys/kmem.h> 48ddd2ade2Ssmb #include <sys/reboot.h> 49ddd2ade2Ssmb #include <sys/systm.h> 5001497b95Spgoyette #include <sys/sysctl.h> 51ddd2ade2Ssmb #include <sys/wdog.h> 5213aff34bSpooka #include <sys/workqueue.h> 53a8c699bdSpgoyette #include <sys/module.h> 54ddd2ade2Ssmb #include <dev/sysmon/sysmonvar.h> 55ddd2ade2Ssmb 56a8c699bdSpgoyette #ifndef _MODULE 57a8c699bdSpgoyette #include "opt_modular.h" 58a8c699bdSpgoyette #endif 5901497b95Spgoyette 60ddd2ade2Ssmb struct swwdog_softc { 6101497b95Spgoyette device_t sc_dev; 62ddd2ade2Ssmb struct sysmon_wdog sc_smw; 63ddd2ade2Ssmb struct callout sc_c; 64a8c699bdSpgoyette int sc_armed; 6501497b95Spgoyette }; 66ddd2ade2Ssmb 67a8c699bdSpgoyette bool swwdog_reboot = false; /* false --> panic , true --> reboot */ 68a8c699bdSpgoyette 69a8c699bdSpgoyette static struct workqueue *wq; 70a8c699bdSpgoyette static device_t swwdog_dev; 71a8c699bdSpgoyette 7276a2f91aSpgoyette MODULE(MODULE_CLASS_DRIVER, swwdog, "sysmon_wdog"); 73a8c699bdSpgoyette 7432027a8eSchristos #ifdef _MODULE 75a8c699bdSpgoyette CFDRIVER_DECL(swwdog, DV_DULL, NULL); 7632027a8eSchristos #endif 77a8c699bdSpgoyette 78a8c699bdSpgoyette int swwdogattach(int); 79a8c699bdSpgoyette 80a8c699bdSpgoyette static int swwdog_setmode(struct sysmon_wdog *); 81a8c699bdSpgoyette static int swwdog_tickle(struct sysmon_wdog *); 82a8c699bdSpgoyette static bool swwdog_suspend(device_t, const pmf_qual_t *); 83a8c699bdSpgoyette static int swwdog_arm(struct swwdog_softc *); 84a8c699bdSpgoyette static int swwdog_disarm(struct swwdog_softc *); 85a8c699bdSpgoyette 86a8c699bdSpgoyette static void swwdog_panic(void *); 87a8c699bdSpgoyette 8801497b95Spgoyette static int swwdog_match(device_t, cfdata_t, void *); 8901497b95Spgoyette static void swwdog_attach(device_t, device_t, void *); 9001497b95Spgoyette static int swwdog_detach(device_t, int); 9101497b95Spgoyette static bool swwdog_suspend(device_t, const pmf_qual_t *); 9201497b95Spgoyette 93a8c699bdSpgoyette static int swwdog_init(void *); 94a8c699bdSpgoyette static int swwdog_fini(void *); 95a8c699bdSpgoyette static int swwdog_modcmd(modcmd_t, void *); 96ddd2ade2Ssmb 9701497b95Spgoyette CFATTACH_DECL_NEW(swwdog, sizeof(struct swwdog_softc), 9801497b95Spgoyette swwdog_match, swwdog_attach, swwdog_detach, NULL); 99a8c699bdSpgoyette extern struct cfdriver swwdog_cd; 10001497b95Spgoyette 101a8c699bdSpgoyette #define SWDOG_DEFAULT 60 /* 60-second default period */ 102c3884c5cSpooka 10313aff34bSpooka static void 10413aff34bSpooka doreboot(struct work *wrkwrkwrk, void *p) 10513aff34bSpooka { 10613aff34bSpooka 107599c2405Sthorpej kern_reboot(0, NULL); 10813aff34bSpooka } 10913aff34bSpooka 110a8c699bdSpgoyette int 11101497b95Spgoyette swwdogattach(int n __unused) 112ddd2ade2Ssmb { 113a8c699bdSpgoyette int error; 11401497b95Spgoyette static struct cfdata cf; 115ddd2ade2Ssmb 11613aff34bSpooka if (workqueue_create(&wq, "swwreboot", doreboot, NULL, 11713aff34bSpooka PRI_NONE, IPL_NONE, 0) != 0) { 11813aff34bSpooka aprint_error("failed to create swwdog reboot wq"); 119a8c699bdSpgoyette return 1; 12013aff34bSpooka } 12113aff34bSpooka 122a8c699bdSpgoyette error = config_cfattach_attach(swwdog_cd.cd_name, &swwdog_ca); 123a8c699bdSpgoyette if (error) { 124a8c699bdSpgoyette aprint_error("%s: unable to attach cfattach\n", 125a8c699bdSpgoyette swwdog_cd.cd_name); 12613aff34bSpooka workqueue_destroy(wq); 127a8c699bdSpgoyette return error; 12801497b95Spgoyette } 129ddd2ade2Ssmb 13001497b95Spgoyette cf.cf_name = swwdog_cd.cd_name; 13101497b95Spgoyette cf.cf_atname = swwdog_cd.cd_name; 13201497b95Spgoyette cf.cf_unit = 0; 13301497b95Spgoyette cf.cf_fstate = FSTATE_STAR; 134a8c699bdSpgoyette cf.cf_pspec = NULL; 135a8c699bdSpgoyette cf.cf_loc = NULL; 136a8c699bdSpgoyette cf.cf_flags = 0; 13701497b95Spgoyette 138a8c699bdSpgoyette swwdog_dev = config_attach_pseudo(&cf); 13901497b95Spgoyette 140a8c699bdSpgoyette if (swwdog_dev == NULL) { 141a8c699bdSpgoyette config_cfattach_detach(swwdog_cd.cd_name, &swwdog_ca); 142a8c699bdSpgoyette workqueue_destroy(wq); 143a8c699bdSpgoyette return 1; 144a8c699bdSpgoyette } 145a8c699bdSpgoyette return 0; 14601497b95Spgoyette } 14701497b95Spgoyette 14801497b95Spgoyette static int 14901497b95Spgoyette swwdog_match(device_t parent, cfdata_t data, void *aux) 15001497b95Spgoyette { 151a8c699bdSpgoyette 15201497b95Spgoyette return 1; 15301497b95Spgoyette } 15401497b95Spgoyette 15501497b95Spgoyette static void 15601497b95Spgoyette swwdog_attach(device_t parent, device_t self, void *aux) 15701497b95Spgoyette { 15801497b95Spgoyette struct swwdog_softc *sc = device_private(self); 15901497b95Spgoyette 16001497b95Spgoyette sc->sc_dev = self; 16101497b95Spgoyette sc->sc_smw.smw_name = device_xname(self); 162ddd2ade2Ssmb sc->sc_smw.smw_cookie = sc; 163ddd2ade2Ssmb sc->sc_smw.smw_setmode = swwdog_setmode; 164ddd2ade2Ssmb sc->sc_smw.smw_tickle = swwdog_tickle; 165ddd2ade2Ssmb sc->sc_smw.smw_period = SWDOG_DEFAULT; 166a8c699bdSpgoyette 16788ab7da9Sad callout_init(&sc->sc_c, 0); 168ddd2ade2Ssmb callout_setfunc(&sc->sc_c, swwdog_panic, sc); 169ddd2ade2Ssmb 170ddd2ade2Ssmb if (sysmon_wdog_register(&sc->sc_smw) == 0) 17101497b95Spgoyette aprint_normal_dev(self, "software watchdog initialized\n"); 172a8c699bdSpgoyette else { 17301497b95Spgoyette aprint_error_dev(self, "unable to register software " 17401497b95Spgoyette "watchdog with sysmon\n"); 175a8c699bdSpgoyette callout_destroy(&sc->sc_c); 176a8c699bdSpgoyette return; 177a8c699bdSpgoyette } 17801497b95Spgoyette 17901497b95Spgoyette if (!pmf_device_register(self, swwdog_suspend, NULL)) 18001497b95Spgoyette aprint_error_dev(self, "couldn't establish power handler\n"); 181ddd2ade2Ssmb } 18201497b95Spgoyette 18301497b95Spgoyette static int 18401497b95Spgoyette swwdog_detach(device_t self, int flags) 18501497b95Spgoyette { 18601497b95Spgoyette struct swwdog_softc *sc = device_private(self); 18701497b95Spgoyette 188aa1b1c5bSpgoyette pmf_device_deregister(self); 18901497b95Spgoyette swwdog_disarm(sc); 190a8c699bdSpgoyette sysmon_wdog_unregister(&sc->sc_smw); 191a8c699bdSpgoyette callout_destroy(&sc->sc_c); 19213aff34bSpooka workqueue_destroy(wq); 19301497b95Spgoyette 194a8c699bdSpgoyette return 0; 19501497b95Spgoyette } 19601497b95Spgoyette 19701497b95Spgoyette static bool 19801497b95Spgoyette swwdog_suspend(device_t dev, const pmf_qual_t *qual) 19901497b95Spgoyette { 20001497b95Spgoyette struct swwdog_softc *sc = device_private(dev); 20101497b95Spgoyette 20201497b95Spgoyette /* Don't allow suspend if watchdog is armed */ 20301497b95Spgoyette if ((sc->sc_smw.smw_mode & WDOG_MODE_MASK) != WDOG_MODE_DISARMED) 20401497b95Spgoyette return false; 20501497b95Spgoyette return true; 206ddd2ade2Ssmb } 207ddd2ade2Ssmb 208ddd2ade2Ssmb static int 209ddd2ade2Ssmb swwdog_setmode(struct sysmon_wdog *smw) 210ddd2ade2Ssmb { 211ddd2ade2Ssmb struct swwdog_softc *sc = smw->smw_cookie; 212ddd2ade2Ssmb int error = 0; 213ddd2ade2Ssmb 214ddd2ade2Ssmb if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { 215ddd2ade2Ssmb error = swwdog_disarm(sc); 216ddd2ade2Ssmb } else { 217ddd2ade2Ssmb if (smw->smw_period == 0) 218ddd2ade2Ssmb return EINVAL; 219ddd2ade2Ssmb else if (smw->smw_period == WDOG_PERIOD_DEFAULT) 220ddd2ade2Ssmb sc->sc_smw.smw_period = SWDOG_DEFAULT; 22194e3e9f7Ssimonb else 22294e3e9f7Ssimonb sc->sc_smw.smw_period = smw->smw_period; 223ddd2ade2Ssmb error = swwdog_arm(sc); 224ddd2ade2Ssmb } 225ddd2ade2Ssmb return error; 226ddd2ade2Ssmb } 227ddd2ade2Ssmb 228ddd2ade2Ssmb static int 229ddd2ade2Ssmb swwdog_tickle(struct sysmon_wdog *smw) 230ddd2ade2Ssmb { 231ddd2ade2Ssmb 232ddd2ade2Ssmb return swwdog_arm(smw->smw_cookie); 233ddd2ade2Ssmb } 234ddd2ade2Ssmb 235ddd2ade2Ssmb static int 236ddd2ade2Ssmb swwdog_arm(struct swwdog_softc *sc) 237ddd2ade2Ssmb { 238ddd2ade2Ssmb 239ddd2ade2Ssmb callout_schedule(&sc->sc_c, sc->sc_smw.smw_period * hz); 240ddd2ade2Ssmb return 0; 241ddd2ade2Ssmb } 242ddd2ade2Ssmb 243ddd2ade2Ssmb static int 244ddd2ade2Ssmb swwdog_disarm(struct swwdog_softc *sc) 245ddd2ade2Ssmb { 246ddd2ade2Ssmb 247ddd2ade2Ssmb callout_stop(&sc->sc_c); 248ddd2ade2Ssmb return 0; 249ddd2ade2Ssmb } 250ddd2ade2Ssmb 251ddd2ade2Ssmb static void 252ddd2ade2Ssmb swwdog_panic(void *vsc) 253ddd2ade2Ssmb { 254ddd2ade2Ssmb struct swwdog_softc *sc = vsc; 25513aff34bSpooka static struct work wk; /* we'll need it max once */ 25601497b95Spgoyette bool do_panic; 257ddd2ade2Ssmb 258c3884c5cSpooka do_panic = !swwdog_reboot; 259c3884c5cSpooka swwdog_reboot = false; 260ddd2ade2Ssmb callout_schedule(&sc->sc_c, 60 * hz); /* deliberate double-panic */ 261ddd2ade2Ssmb 262a8c699bdSpgoyette printf("%s: %d second timer expired\n", "swwdog", 263ddd2ade2Ssmb sc->sc_smw.smw_period); 264ddd2ade2Ssmb 265ddd2ade2Ssmb if (do_panic) 266ddd2ade2Ssmb panic("watchdog timer expired"); 26794e3e9f7Ssimonb else 26813aff34bSpooka workqueue_enqueue(wq, &wk, NULL); 269ddd2ade2Ssmb } 27001497b95Spgoyette 2719120d451Spgoyette SYSCTL_SETUP(swwdog_sysctl_setup, "swwdog sysctl") 27201497b95Spgoyette { 27301497b95Spgoyette const struct sysctlnode *me; 27401497b95Spgoyette 2759120d451Spgoyette sysctl_createv(clog, 0, NULL, &me, CTLFLAG_READWRITE, 27601497b95Spgoyette CTLTYPE_NODE, "swwdog", NULL, 27701497b95Spgoyette NULL, 0, NULL, 0, 27801497b95Spgoyette CTL_HW, CTL_CREATE, CTL_EOL); 2799120d451Spgoyette sysctl_createv(clog, 0, NULL, NULL, CTLFLAG_READWRITE, 280*b2562c56Srin CTLTYPE_BOOL, "reboot", SYSCTL_DESCR("reboot if timer expires"), 28101497b95Spgoyette NULL, 0, &swwdog_reboot, sizeof(bool), 28201497b95Spgoyette CTL_HW, me->sysctl_num, CTL_CREATE, CTL_EOL); 28301497b95Spgoyette } 284a8c699bdSpgoyette 285a8c699bdSpgoyette /* 286a8c699bdSpgoyette * Module management 287a8c699bdSpgoyette */ 288a8c699bdSpgoyette 28954853d3dSriastradh static int 290a8c699bdSpgoyette swwdog_init(void *arg) 291a8c699bdSpgoyette { 292a8c699bdSpgoyette /* 293a8c699bdSpgoyette * Merge the driver info into the kernel tables and attach the 294a8c699bdSpgoyette * pseudo-device 295a8c699bdSpgoyette */ 296a7268a97Spgoyette int error = 0; 297a7268a97Spgoyette 298a8c699bdSpgoyette 29988e94fddSpgoyette #ifdef _MODULE 300a8c699bdSpgoyette error = config_cfdriver_attach(&swwdog_cd); 301a8c699bdSpgoyette if (error) { 302a8c699bdSpgoyette aprint_error("%s: unable to attach cfdriver\n", 303a8c699bdSpgoyette swwdog_cd.cd_name); 304a8c699bdSpgoyette return error; 305a8c699bdSpgoyette } 306a8c699bdSpgoyette error = swwdogattach(1); 307a8c699bdSpgoyette if (error) { 308a8c699bdSpgoyette aprint_error("%s: device attach failed\n", swwdog_cd.cd_name); 309a8c699bdSpgoyette config_cfdriver_detach(&swwdog_cd); 310a8c699bdSpgoyette } 31188e94fddSpgoyette #endif 312a8c699bdSpgoyette 313a8c699bdSpgoyette return error; 314a8c699bdSpgoyette } 315a8c699bdSpgoyette 31654853d3dSriastradh static int 317a8c699bdSpgoyette swwdog_fini(void *arg) 318a8c699bdSpgoyette { 319a8c699bdSpgoyette int error; 320a8c699bdSpgoyette 321a8c699bdSpgoyette error = config_detach(swwdog_dev, 0); 322a8c699bdSpgoyette 32388e94fddSpgoyette #ifdef _MODULE 324a8c699bdSpgoyette error = config_cfattach_detach(swwdog_cd.cd_name, &swwdog_ca); 325a8c699bdSpgoyette if (error) 326a8c699bdSpgoyette aprint_error("%s: error detaching cfattach: %d\n", 327a8c699bdSpgoyette swwdog_cd.cd_name, error); 328a8c699bdSpgoyette 329a8c699bdSpgoyette error = config_cfdriver_detach(&swwdog_cd); 330a8c699bdSpgoyette if (error) 331a8c699bdSpgoyette aprint_error("%s: error detaching cfdriver: %d\n", 332a8c699bdSpgoyette swwdog_cd.cd_name, error); 33388e94fddSpgoyette #endif 334a8c699bdSpgoyette 335a8c699bdSpgoyette return error; 336a8c699bdSpgoyette } 337a8c699bdSpgoyette 33854853d3dSriastradh static int 339a8c699bdSpgoyette swwdog_modcmd(modcmd_t cmd, void *arg) 340a8c699bdSpgoyette { 341a8c699bdSpgoyette int ret; 342a8c699bdSpgoyette 343a8c699bdSpgoyette switch (cmd) { 344a8c699bdSpgoyette case MODULE_CMD_INIT: 345a8c699bdSpgoyette ret = swwdog_init(arg); 346a8c699bdSpgoyette break; 347a8c699bdSpgoyette case MODULE_CMD_FINI: 348a8c699bdSpgoyette ret = swwdog_fini(arg); 349a8c699bdSpgoyette break; 350a8c699bdSpgoyette case MODULE_CMD_STAT: 351a8c699bdSpgoyette default: 352a8c699bdSpgoyette ret = ENOTTY; 353a8c699bdSpgoyette } 354a8c699bdSpgoyette 355a8c699bdSpgoyette return ret; 356a8c699bdSpgoyette } 357