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 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 *error = EINVAL; 116 return; 117 } 118 timeout = 1 << (cmd - WD_TO_1MS); 119 120 /* reset the timer to prevent timeout a timeout is about to occur */ 121 i6300esbwd_unlock_res(sc); 122 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); 123 124 if (!cmd) { 125 /* 126 * when the lock is enabled, we are unable to overwrite LOCK 127 * register 128 */ 129 if (sc->locked) 130 *error = EPERM; 131 else 132 i6300esbwd_lock_write(sc, 133 i6300esbwd_lock_read(sc) & ~WDT_ENABLE); 134 return; 135 } 136 137 i6300esbwd_unlock_res(sc); 138 bus_write_4(sc->res, WDT_PRELOAD_1_REG, timeout); 139 140 i6300esbwd_unlock_res(sc); 141 bus_write_4(sc->res, WDT_PRELOAD_2_REG, timeout); 142 143 i6300esbwd_unlock_res(sc); 144 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD); 145 146 if (!sc->locked) { 147 i6300esbwd_lock_write(sc, WDT_ENABLE); 148 regval = i6300esbwd_lock_read(sc); 149 sc->locked = regval & WDT_LOCK; 150 } 151 } 152 153 static int 154 i6300esbwd_probe(device_t dev) 155 { 156 const struct i6300esbwd_pci_id *pci_id; 157 uint16_t pci_dev_id; 158 int err = ENXIO; 159 160 if (pci_get_vendor(dev) != VENDORID_INTEL) 161 goto end; 162 163 pci_dev_id = pci_get_device(dev); 164 for (pci_id = i6300esbwd_pci_devices; 165 pci_id < i6300esbwd_pci_devices + nitems(i6300esbwd_pci_devices); 166 ++pci_id) { 167 if (pci_id->id == pci_dev_id) { 168 device_set_desc(dev, pci_id->name); 169 err = BUS_PROBE_DEFAULT; 170 break; 171 } 172 } 173 174 end: 175 return (err); 176 } 177 178 static int 179 i6300esbwd_attach(device_t dev) 180 { 181 struct i6300esbwd_softc *sc = device_get_softc(dev); 182 uint16_t regval; 183 184 sc->dev = dev; 185 sc->res_id = PCIR_BAR(0); 186 sc->res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->res_id, 187 RF_ACTIVE); 188 if (sc->res == NULL) { 189 device_printf(dev, "unable to map memory region\n"); 190 return (ENXIO); 191 } 192 193 i6300esbwd_cfg_write(sc, WDT_INT_TYPE_DISABLED_VAL); 194 regval = i6300esbwd_lock_read(sc); 195 if (regval & WDT_LOCK) 196 sc->locked = true; 197 else { 198 sc->locked = false; 199 i6300esbwd_lock_write(sc, WDT_TOUT_CNF_WT_MODE); 200 } 201 202 i6300esbwd_unlock_res(sc); 203 bus_write_2(sc->res, WDT_RELOAD_REG, WDT_RELOAD | WDT_TIMEOUT); 204 205 sc->ev_tag = EVENTHANDLER_REGISTER(watchdog_list, i6300esbwd_event, sc, 206 0); 207 208 SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), 209 SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "locked", 210 CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_NEEDGIANT, sc, 0, 211 i6300esbwd_sysctl_locked, "I", 212 "Lock the timer so that we cannot disable it"); 213 214 return (0); 215 } 216 217 static int 218 i6300esbwd_detach(device_t dev) 219 { 220 struct i6300esbwd_softc *sc = device_get_softc(dev); 221 222 if (sc->ev_tag) 223 EVENTHANDLER_DEREGISTER(watchdog_list, sc->ev_tag); 224 225 if (sc->res) 226 bus_release_resource(dev, SYS_RES_MEMORY, sc->res_id, sc->res); 227 228 return (0); 229 } 230 231 static device_method_t i6300esbwd_methods[] = { 232 DEVMETHOD(device_probe, i6300esbwd_probe), 233 DEVMETHOD(device_attach, i6300esbwd_attach), 234 DEVMETHOD(device_detach, i6300esbwd_detach), 235 DEVMETHOD(device_shutdown, i6300esbwd_detach), 236 DEVMETHOD_END 237 }; 238 239 static driver_t i6300esbwd_driver = { 240 "i6300esbwd", 241 i6300esbwd_methods, 242 sizeof(struct i6300esbwd_softc), 243 }; 244 245 DRIVER_MODULE(i6300esbwd, pci, i6300esbwd_driver, NULL, NULL); 246