1 /* $OpenBSD: berkwdt.c,v 1.9 2017/09/08 05:36:52 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2009 Wim Van Sebroeck <wim@iguana.be> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* 20 * Berkshire PCI-PC Watchdog Card Driver 21 * http://www.pcwatchdog.com/ 22 */ 23 24 #include <sys/param.h> 25 #include <sys/device.h> 26 #include <sys/kernel.h> 27 #include <sys/systm.h> 28 29 #include <machine/bus.h> 30 31 #include <dev/pci/pcivar.h> 32 #include <dev/pci/pcireg.h> 33 #include <dev/pci/pcidevs.h> 34 35 struct berkwdt_softc { 36 struct device sc_dev; 37 38 /* device access through bus space */ 39 bus_space_tag_t sc_iot; 40 bus_space_handle_t sc_ioh; 41 42 /* the timeout period */ 43 int sc_period; 44 }; 45 46 int berkwdt_match(struct device *, void *, void *); 47 void berkwdt_attach(struct device *, struct device *, void *); 48 int berkwdt_activate(struct device *, int); 49 50 void berkwdt_start(struct berkwdt_softc *sc); 51 void berkwdt_stop(struct berkwdt_softc *sc); 52 void berkwdt_reload(struct berkwdt_softc *sc); 53 int berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val); 54 55 int berkwdt_set_timeout(void *, int); 56 57 struct cfattach berkwdt_ca = { 58 sizeof(struct berkwdt_softc), berkwdt_match, berkwdt_attach, 59 NULL, berkwdt_activate 60 }; 61 62 struct cfdriver berkwdt_cd = { 63 NULL, "berkwdt", DV_DULL 64 }; 65 66 const struct pci_matchid berkwdt_devices[] = { 67 { PCI_VENDOR_PIJNENBURG, PCI_PRODUCT_PIJNENBURG_PCWD_PCI } 68 }; 69 70 /* PCWD-PCI I/O Port definitions */ 71 #define PCWD_PCI_RELOAD 0x00 /* Re-trigger */ 72 #define PCWD_PCI_CS1 0x01 /* Control Status 1 */ 73 #define PCWD_PCI_CS2 0x02 /* Control Status 2 */ 74 #define PCWD_PCI_WDT_DIS 0x03 /* Watchdog Disable */ 75 #define PCWD_PCI_LSB 0x04 /* Command / Response */ 76 #define PCWD_PCI_MSB 0x05 /* Command/Response LSB */ 77 #define PCWD_PCI_CMD 0x06 /* Command/Response MSB */ 78 79 /* Port 1 : Control Status #1 */ 80 #define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ 81 #define WD_PCI_TTRP 0x04 /* Temperature Trip status */ 82 #define WD_PCI_R2DS 0x40 /* Relay 2 Disable Temp-trip reset */ 83 84 /* Port 2 : Control Status #2 */ 85 #define WD_PCI_WDIS 0x10 /* Watchdog Disable */ 86 #define WD_PCI_WRSP 0x40 /* Watchdog wrote response */ 87 88 /* 89 * According to documentation max. time to process a command for the pci 90 * watchdog card is 100 ms, so we give it 150 ms to do its job. 91 */ 92 #define PCI_CMD_TIMEOUT 150 93 94 /* Watchdog's internal commands */ 95 #define CMD_WRITE_WD_TIMEOUT 0x19 96 97 int 98 berkwdt_send_command(struct berkwdt_softc *sc, u_int8_t cmd, int *val) 99 { 100 u_int8_t msb; 101 u_int8_t lsb; 102 u_int8_t got_response; 103 int count; 104 105 msb = *val / 256; 106 lsb = *val % 256; 107 108 /* Send command with data (data first!) */ 109 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB, lsb); 110 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB, msb); 111 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD, cmd); 112 113 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 114 got_response &= WD_PCI_WRSP; 115 for (count = 0; count < PCI_CMD_TIMEOUT && !got_response; count++) { 116 delay(1000); 117 got_response = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 118 got_response &= WD_PCI_WRSP; 119 } 120 121 if (got_response) { 122 /* read back response */ 123 lsb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_LSB); 124 msb = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_MSB); 125 *val = (msb << 8) + lsb; 126 127 /* clear WRSP bit */ 128 bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CMD); 129 return 1; 130 } 131 132 return 0; 133 } 134 135 void 136 berkwdt_start(struct berkwdt_softc *sc) 137 { 138 u_int8_t reg; 139 140 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0x00); 141 delay(1000); 142 143 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 144 if (reg & WD_PCI_WDIS) { 145 printf("%s: unable to enable\n", sc->sc_dev.dv_xname); 146 } 147 } 148 149 void 150 berkwdt_stop(struct berkwdt_softc *sc) 151 { 152 u_int8_t reg; 153 154 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 155 delay(1000); 156 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_WDT_DIS, 0xa5); 157 delay(1000); 158 159 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS2); 160 if (!(reg & WD_PCI_WDIS)) { 161 printf("%s: unable to disable\n", sc->sc_dev.dv_xname); 162 } 163 } 164 165 void 166 berkwdt_reload(struct berkwdt_softc *sc) 167 { 168 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_RELOAD, 0x42); 169 } 170 171 int 172 berkwdt_match(struct device *parent, void *match, void *aux) 173 { 174 return (pci_matchbyid((struct pci_attach_args *)aux, berkwdt_devices, 175 sizeof(berkwdt_devices) / sizeof(berkwdt_devices[0]))); 176 } 177 178 void 179 berkwdt_attach(struct device *parent, struct device *self, void *aux) 180 { 181 struct berkwdt_softc *sc = (struct berkwdt_softc *)self; 182 struct pci_attach_args *const pa = (struct pci_attach_args *)aux; 183 bus_size_t iosize; 184 u_int8_t reg; 185 186 /* retrieve the I/O region (BAR 0) */ 187 if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_IO, 0, 188 &sc->sc_iot, &sc->sc_ioh, NULL, &iosize, 0) != 0) { 189 printf(": couldn't find PCI I/O region\n"); 190 return; 191 } 192 193 /* Check for reboot by the card */ 194 reg = bus_space_read_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1); 195 if (reg & WD_PCI_WTRP) { 196 printf(", warning: watchdog triggered"); 197 198 if (reg & WD_PCI_TTRP) 199 printf(", overheat detected"); 200 201 /* clear trip status & LED and keep mode of relay 2 */ 202 reg &= WD_PCI_R2DS; 203 reg |= WD_PCI_WTRP; 204 bus_space_write_1(sc->sc_iot, sc->sc_ioh, PCWD_PCI_CS1, reg); 205 } 206 207 printf("\n"); 208 209 /* ensure that the watchdog is disabled */ 210 berkwdt_stop(sc); 211 sc->sc_period = 0; 212 213 /* register with the watchdog framework */ 214 wdog_register(berkwdt_set_timeout, sc); 215 } 216 217 int 218 berkwdt_activate(struct device *self, int act) 219 { 220 switch (act) { 221 case DVACT_POWERDOWN: 222 wdog_shutdown(self); 223 break; 224 } 225 226 return (0); 227 } 228 229 int 230 berkwdt_set_timeout(void *self, int timeout) 231 { 232 struct berkwdt_softc *sc = self; 233 int new_timeout = timeout; 234 235 if (timeout == 0) { 236 /* Disable watchdog */ 237 berkwdt_stop(sc); 238 } else { 239 if (sc->sc_period != timeout) { 240 /* Set new timeout */ 241 berkwdt_send_command(sc, CMD_WRITE_WD_TIMEOUT, 242 &new_timeout); 243 } 244 if (sc->sc_period == 0) { 245 /* Enable watchdog */ 246 berkwdt_start(sc); 247 berkwdt_reload(sc); 248 } else { 249 /* Reset timer */ 250 berkwdt_reload(sc); 251 } 252 } 253 sc->sc_period = timeout; 254 255 return (timeout); 256 } 257 258