1 /* 2 * Copyright (c) 2025 The FreeBSD Foundation 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 */ 6 7 /* 8 * Reference: Intel 6300ESB Controller Hub Datasheet Section 16 9 */ 10 11 #include <sys/param.h> 12 #include <sys/eventhandler.h> 13 #include <sys/kernel.h> 14 #include <sys/module.h> 15 #include <sys/sysctl.h> 16 #include <sys/errno.h> 17 #include <sys/systm.h> 18 #include <sys/bus.h> 19 #include <machine/bus.h> 20 #include <sys/rman.h> 21 #include <machine/resource.h> 22 #include <sys/watchdog.h> 23 24 #include <dev/pci/pcireg.h> 25 26 #include <dev/ichwd/ichwd.h> 27 #include <dev/ichwd/i6300esbwd.h> 28 29 #include <x86/pci_cfgreg.h> 30 #include <dev/pci/pcivar.h> 31 #include <dev/pci/pci_private.h> 32 33 struct i6300esbwd_softc { 34 device_t dev; 35 int res_id; 36 struct resource *res; 37 eventhandler_tag ev_tag; 38 bool locked; 39 }; 40 41 static const struct i6300esbwd_pci_id { 42 uint16_t id; 43 const char *name; 44 } i6300esbwd_pci_devices[] = { 45 { DEVICEID_6300ESB_2, "6300ESB Watchdog Timer" }, 46 }; 47 48 static uint16_t __unused 49 i6300esbwd_cfg_read(struct i6300esbwd_softc *sc) 50 { 51 return (pci_read_config(sc->dev, WDT_CONFIG_REG, 2)); 52 } 53 54 static void 55 i6300esbwd_cfg_write(struct i6300esbwd_softc *sc, uint16_t val) 56 { 57 pci_write_config(sc->dev, WDT_CONFIG_REG, val, 2); 58 } 59 60 static uint8_t 61 i6300esbwd_lock_read(struct i6300esbwd_softc *sc) 62 { 63 return (pci_read_config(sc->dev, WDT_LOCK_REG, 1)); 64 } 65 66 static void 67 i6300esbwd_lock_write(struct i6300esbwd_softc *sc, uint8_t val) 68 { 69 pci_write_config(sc->dev, WDT_LOCK_REG, val, 1); 70 } 71 72 /* 73 * According to Intel 6300ESB I/O Controller Hub Datasheet 16.5.2, 74 * the resource should be unlocked before modifing any registers. 75 * The way to unlock is by write 0x80, 0x86 to the reload register. 76 */ 77 static void 78 i6300esbwd_unlock_res(struct i6300esbwd_softc *sc) 79 { 80 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_1_VAL); 81 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_UNLOCK_SEQ_2_VAL); 82 } 83 84 static int 85 i6300esbwd_sysctl_locked(SYSCTL_HANDLER_ARGS) 86 { 87 struct i6300esbwd_softc *sc = (struct i6300esbwd_softc *)arg1; 88 int error; 89 int result; 90 91 result = sc->locked; 92 error = sysctl_handle_int(oidp, &result, 0, req); 93 94 if (error || !req->newptr) 95 return (error); 96 97 if (result == 1 && !sc->locked) { 98 i6300esbwd_lock_write(sc, i6300esbwd_lock_read(sc) | WDT_LOCK); 99 sc->locked = true; 100 } 101 102 return (0); 103 } 104 105 static void 106 i6300esbwd_event(void *arg, unsigned int cmd, int *error) 107 { 108 struct i6300esbwd_softc *sc = arg; 109 uint32_t timeout; 110 uint16_t regval; 111 112 cmd &= WD_INTERVAL; 113 if (cmd != 0 && 114 (cmd < WD_TO_1MS || (cmd - WD_TO_1MS) >= WDT_PRELOAD_BIT)) { 115 return; 116 } 117 timeout = 1 << (cmd - WD_TO_1MS); 118 119 /* reset the timer to prevent timeout a timeout is about to occur */ 120 i6300esbwd_unlock_res(sc); 121 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); 122 123 if (!cmd) { 124 /* 125 * when the lock is enabled, we are unable to overwrite LOCK 126 * register 127 */ 128 if (sc->locked) 129 *error = EPERM; 130 else 131 i6300esbwd_lock_write(sc, 132 i6300esbwd_lock_read(sc) & ~WDT_ENABLE); 133 return; 134 } 135 136 i6300esbwd_unlock_res(sc); 137 bus_write_4(sc->res, WDT_PRELOAD_1_REG, timeout); 138 139 i6300esbwd_unlock_res(sc); 140 bus_write_4(sc->res, WDT_PRELOAD_2_REG, timeout); 141 142 i6300esbwd_unlock_res(sc); 143 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); 144 145 if (!sc->locked) { 146 i6300esbwd_lock_write(sc, WDT_ENABLE); 147 regval = i6300esbwd_lock_read(sc); 148 sc->locked = regval & WDT_LOCK; 149 } 150 /* Set error to 0 to indicate we did something. */ 151 *error = 0; 152 } 153 154 static int 155 i6300esbwd_probe(device_t dev) 156 { 157 const struct i6300esbwd_pci_id *pci_id; 158 uint16_t pci_dev_id; 159 int err = ENXIO; 160 161 if (pci_get_vendor(dev) != VENDORID_INTEL) 162 goto end; 163 164 pci_dev_id = pci_get_device(dev); 165 for (pci_id = i6300esbwd_pci_devices; 166 pci_id < i6300esbwd_pci_devices + nitems(i6300esbwd_pci_devices); 167 ++pci_id) { 168 if (pci_id->id == pci_dev_id) { 169 device_set_desc(dev, pci_id->name); 170 err = BUS_PROBE_DEFAULT; 171 break; 172 } 173 } 174 175 end: 176 return (err); 177 } 178 179 static int 180 i6300esbwd_attach(device_t dev) 181 { 182 struct i6300esbwd_softc *sc = device_get_softc(dev); 183 uint16_t regval; 184 185 sc->dev = dev; 186 sc->res_id = PCIR_BAR(0); 187 sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->res_id, 188 RF_ACTIVE); 189 if (sc->res == NULL) { 190 device_printf(dev, "unable to map memory region\n"); 191 return (ENXIO); 192 } 193 194 i6300esbwd_cfg_write(sc, WDT_INT_TYPE_DISABLED_VAL); 195 regval = i6300esbwd_lock_read(sc); 196 if (regval & WDT_LOCK) 197 sc->locked = true; 198 else { 199 sc->locked = false; 200 i6300esbwd_lock_write(sc, WDT_TOUT_CNF_WT_MODE); 201 } 202 203 i6300esbwd_unlock_res(sc); 204 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD | WDT_TIMEOUT); 205 206 sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, i6300esbwd_event, sc, 207 0); 208 209 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), 210 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "locked", 211 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, 212 i6300esbwd_sysctl_locked, "I", 213 "Lock the timer so that we cannot disable it"); 214 215 return (0); 216 } 217 218 static int 219 i6300esbwd_detach(device_t dev) 220 { 221 struct i6300esbwd_softc *sc = device_get_softc(dev); 222 223 if (sc->ev_tag) 224 EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); 225 226 if (sc->res) 227 bus_release_resource(dev, SYS_RES_MEMORY, sc->res_id, sc->res); 228 229 return (0); 230 } 231 232 static device_method_t i6300esbwd_methods[] = { 233 DEVMETHOD(device_probe, i6300esbwd_probe), 234 DEVMETHOD(device_attach, i6300esbwd_attach), 235 DEVMETHOD(device_detach, i6300esbwd_detach), 236 DEVMETHOD(device_shutdown, i6300esbwd_detach), 237 DEVMETHOD_END 238 }; 239 240 static driver_t i6300esbwd_driver = { 241 "i6300esbwd", 242 i6300esbwd_methods, 243 sizeof(struct i6300esbwd_softc), 244 }; 245 246 DRIVER_MODULE(i6300esbwd, pci, i6300esbwd_driver, NULL, NULL); 247