1 | /* $NetBSD: weasel_pci.c,v 1.16 2016/07/07 06:55:41 msaitoh Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2001 The NetBSD Foundation, Inc. |
5 | * All rights reserved. |
6 | * |
7 | * This code is derived from software contributed to The NetBSD Foundation |
8 | * by Herb Peyerl and Jason Thorpe. |
9 | * |
10 | * Redistribution and use in source and binary forms, with or without |
11 | * modification, are permitted provided that the following conditions |
12 | * are met: |
13 | * 1. Redistributions of source code must retain the above copyright |
14 | * notice, this list of conditions and the following disclaimer. |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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 | /* |
33 | * Device driver for the control space on the Middle Digital, Inc. |
34 | * PCI-Weasel serial console board. |
35 | * |
36 | * Since the other functions of the PCI-Weasel already appear in |
37 | * PCI configuration space, we just need to hook up the watchdog |
38 | * timer. |
39 | */ |
40 | |
41 | #include <sys/cdefs.h> |
42 | __KERNEL_RCSID(0, "$NetBSD: weasel_pci.c,v 1.16 2016/07/07 06:55:41 msaitoh Exp $" ); |
43 | |
44 | #include <sys/param.h> |
45 | #include <sys/systm.h> |
46 | #include <sys/device.h> |
47 | #include <sys/wdog.h> |
48 | #include <sys/endian.h> |
49 | |
50 | #include <sys/bus.h> |
51 | |
52 | #include <dev/pci/pcireg.h> |
53 | #include <dev/pci/pcivar.h> |
54 | #include <dev/pci/pcidevs.h> |
55 | |
56 | #include <dev/pci/weaselreg.h> |
57 | |
58 | #include <dev/sysmon/sysmonvar.h> |
59 | |
60 | struct weasel_softc { |
61 | device_t sc_dev; |
62 | bus_space_tag_t sc_st; |
63 | bus_space_handle_t sc_sh; |
64 | |
65 | struct sysmon_wdog sc_smw; |
66 | |
67 | int sc_wdog_armed; |
68 | int sc_wdog_period; |
69 | }; |
70 | |
71 | /* XXX */ |
72 | extern int sysmon_wdog_setmode(struct sysmon_wdog *, int, u_int); |
73 | |
74 | static int weasel_pci_wdog_setmode(struct sysmon_wdog *); |
75 | static int weasel_pci_wdog_tickle(struct sysmon_wdog *); |
76 | |
77 | static int weasel_wait_response(struct weasel_softc *); |
78 | static int weasel_issue_command(struct weasel_softc *, uint8_t cmd); |
79 | |
80 | static int weasel_pci_wdog_arm(struct weasel_softc *); |
81 | static int weasel_pci_wdog_disarm(struct weasel_softc *); |
82 | |
83 | static int weasel_pci_wdog_query_state(struct weasel_softc *); |
84 | |
85 | static int |
86 | weasel_pci_match(device_t parent, cfdata_t cf, void *aux) |
87 | { |
88 | struct pci_attach_args *pa = aux; |
89 | |
90 | if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_MIDDLE_DIGITAL && |
91 | PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_MIDDLE_DIGITAL_WEASEL_CONTROL) |
92 | return (1); |
93 | |
94 | return (0); |
95 | } |
96 | |
97 | static void |
98 | weasel_pci_attach(device_t parent, device_t self, void *aux) |
99 | { |
100 | struct weasel_softc *sc = device_private(self); |
101 | struct pci_attach_args *pa = aux; |
102 | struct weasel_config_block cfg; |
103 | const char *vers, *mode; |
104 | uint8_t v, *cp; |
105 | uint16_t cfg_size; |
106 | uint8_t buf[8]; |
107 | |
108 | sc->sc_dev = self; |
109 | |
110 | printf(": PCI-Weasel watchdog timer\n" ); |
111 | |
112 | if (pci_mapreg_map(pa, PCI_MAPREG_START, |
113 | PCI_MAPREG_TYPE_MEM|PCI_MAPREG_MEM_TYPE_32BIT, 0, |
114 | &sc->sc_st, &sc->sc_sh, NULL, NULL) != 0) { |
115 | aprint_error_dev(self, "unable to map device registers\n" ); |
116 | return; |
117 | } |
118 | |
119 | /* Ping the Weasel to see if it's alive. */ |
120 | if (weasel_issue_command(sc, OS_CMD_PING)) { |
121 | aprint_error_dev(self, "Weasel didn't respond to PING\n" ); |
122 | return; |
123 | } |
124 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
125 | if ((v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD)) != |
126 | OS_RET_PONG) { |
127 | aprint_error_dev(self, |
128 | "unexpected PING response from Weasel: 0x%02x\n" , v); |
129 | return; |
130 | } |
131 | |
132 | /* Read the config block. */ |
133 | if (weasel_issue_command(sc, OS_CMD_SHOW_CONFIG)) { |
134 | aprint_error_dev(self, |
135 | "Weasel didn't respond to SHOW_CONFIG\n" ); |
136 | return; |
137 | } |
138 | cfg_size = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
139 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
140 | |
141 | if (++cfg_size != sizeof(cfg)) { |
142 | aprint_error_dev(self, |
143 | "weird config block size from Weasel: 0x%03x\n" , cfg_size); |
144 | return; |
145 | } |
146 | |
147 | for (cp = (uint8_t *) &cfg; cfg_size != 0; cfg_size--) { |
148 | if (weasel_wait_response(sc)) { |
149 | aprint_error_dev(self, |
150 | "Weasel stopped providing config block(%d)\n" , |
151 | cfg_size); |
152 | return; |
153 | } |
154 | *cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
155 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
156 | } |
157 | |
158 | switch (cfg.cfg_version) { |
159 | case CFG_VERSION_2: |
160 | vers="2" ; |
161 | switch (cfg.enable_duart_switching) { |
162 | case 0: |
163 | mode = "emulation" ; |
164 | break; |
165 | case 1: |
166 | mode = "serial" ; |
167 | break; |
168 | case 2: |
169 | mode = "autoswitch" ; |
170 | break; |
171 | default: |
172 | mode = "unknown" ; |
173 | } |
174 | break; |
175 | |
176 | default: |
177 | vers = mode = NULL; |
178 | } |
179 | |
180 | if (vers != NULL) |
181 | printf("%s: %s mode\n" , device_xname(self), mode); |
182 | else |
183 | printf("%s: unknown config version 0x%02x\n" , |
184 | device_xname(self), cfg.cfg_version); |
185 | |
186 | /* |
187 | * Fetch sw version. |
188 | */ |
189 | if (weasel_issue_command(sc, OS_CMD_QUERY_SW_VER)) { |
190 | aprint_error_dev(self, |
191 | "didn't reply to software version query.\n" ); |
192 | } |
193 | else { |
194 | v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
195 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
196 | if (v>7) |
197 | printf("%s: weird length for version string(%d).\n" , |
198 | device_xname(self), v); |
199 | memset(buf, 0, sizeof(buf)); |
200 | for (cp = buf; v != 0; v--) { |
201 | if (weasel_wait_response(sc)) { |
202 | printf("%s: Weasel stopped providing version\n" , |
203 | device_xname(self)); |
204 | } |
205 | *cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, |
206 | WEASEL_DATA_RD); |
207 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, |
208 | 0); |
209 | } |
210 | printf("%s: sw: %s" , device_xname(self), buf); |
211 | } |
212 | /* |
213 | * Fetch logic version. |
214 | */ |
215 | if (weasel_issue_command(sc, OS_CMD_QUERY_L_VER)) { |
216 | aprint_normal("\n" ); |
217 | aprint_error_dev(self, |
218 | "didn't reply to logic version query.\n" ); |
219 | } |
220 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
221 | v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
222 | printf(" logic: %03d" , v); |
223 | /* |
224 | * Fetch vga bios version. |
225 | */ |
226 | if (weasel_issue_command(sc, OS_CMD_QUERY_VB_VER)) { |
227 | aprint_normal("\n" ); |
228 | aprint_error_dev(self, |
229 | "didn't reply to vga bios version query.\n" ); |
230 | } |
231 | v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
232 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
233 | printf(" vga bios: %d.%d" , (v>>4), (v&0x0f)); |
234 | /* |
235 | * Fetch hw version. |
236 | */ |
237 | if (weasel_issue_command(sc, OS_CMD_QUERY_HW_VER)) { |
238 | aprint_normal("\n" ); |
239 | aprint_error_dev(self, |
240 | "didn't reply to hardware version query.\n" ); |
241 | } |
242 | v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
243 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
244 | printf(" hw: %d.%d" , (v>>4), (v&0x0f)); |
245 | |
246 | printf("\n%s: break passthrough %s" , device_xname(self), |
247 | cfg.break_passthru ? "enabled" : "disabled" ); |
248 | |
249 | if ((sc->sc_wdog_armed = weasel_pci_wdog_query_state(sc)) == -1) |
250 | sc->sc_wdog_armed = 0; |
251 | |
252 | /* Weasel is big-endian */ |
253 | sc->sc_wdog_period = be16toh(cfg.wdt_msec) / 1000; |
254 | |
255 | printf(", watchdog timer %d sec.\n" , sc->sc_wdog_period); |
256 | sc->sc_smw.smw_name = "weasel" ; |
257 | sc->sc_smw.smw_cookie = sc; |
258 | sc->sc_smw.smw_setmode = weasel_pci_wdog_setmode; |
259 | sc->sc_smw.smw_tickle = weasel_pci_wdog_tickle; |
260 | sc->sc_smw.smw_period = sc->sc_wdog_period; |
261 | |
262 | if (sysmon_wdog_register(&sc->sc_smw) != 0) |
263 | aprint_error_dev(self, "unable to register PC-Weasel watchdog " |
264 | "with sysmon\n" ); |
265 | } |
266 | |
267 | CFATTACH_DECL_NEW(weasel_pci, sizeof(struct weasel_softc), |
268 | weasel_pci_match, weasel_pci_attach, NULL, NULL); |
269 | |
270 | static int |
271 | weasel_wait_response(struct weasel_softc *sc) |
272 | { |
273 | int i; |
274 | |
275 | for (i = 10000; i ; i--) { |
276 | delay(100); |
277 | if (bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS) == |
278 | OS_WS_HOST_READ) |
279 | return(0); |
280 | } |
281 | return (1); |
282 | } |
283 | |
284 | static int |
285 | weasel_issue_command(struct weasel_softc *sc, uint8_t cmd) |
286 | { |
287 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_WR, cmd); |
288 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_HOST_STATUS, |
289 | OS_HS_WEASEL_READ); |
290 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
291 | return (weasel_wait_response(sc)); |
292 | } |
293 | |
294 | static int |
295 | weasel_pci_wdog_setmode(struct sysmon_wdog *smw) |
296 | { |
297 | struct weasel_softc *sc = smw->smw_cookie; |
298 | int error = 0; |
299 | |
300 | if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) { |
301 | error = weasel_pci_wdog_disarm(sc); |
302 | } else { |
303 | if (smw->smw_period == WDOG_PERIOD_DEFAULT) |
304 | smw->smw_period = sc->sc_wdog_period; |
305 | else if (smw->smw_period != sc->sc_wdog_period) { |
306 | /* Can't change the period on the Weasel. */ |
307 | return (EINVAL); |
308 | } |
309 | error = weasel_pci_wdog_arm(sc); |
310 | weasel_pci_wdog_tickle(smw); |
311 | } |
312 | |
313 | return (error); |
314 | } |
315 | |
316 | static int |
317 | weasel_pci_wdog_tickle(struct sysmon_wdog *smw) |
318 | { |
319 | struct weasel_softc *sc = smw->smw_cookie; |
320 | u_int8_t reg; |
321 | int x; |
322 | int s; |
323 | int error = 0; |
324 | |
325 | s = splhigh(); |
326 | /* |
327 | * first we tickle the watchdog |
328 | */ |
329 | reg = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_CHALLENGE); |
330 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_RESPONSE, ~reg); |
331 | |
332 | /* |
333 | * then we check to make sure the weasel is still armed. If someone |
334 | * has rebooted the weasel for whatever reason (firmware update), |
335 | * then the watchdog timer would no longer be armed and we'd be |
336 | * servicing nothing. Let the user know that the machine is no |
337 | * longer being monitored by the weasel. |
338 | */ |
339 | if((x = weasel_pci_wdog_query_state(sc)) == -1) |
340 | error = EIO; |
341 | if (x == 1) { |
342 | error = 0; |
343 | } else { |
344 | printf("%s: Watchdog timer disabled on PC/Weasel! Disarming wdog.\n" , |
345 | device_xname(sc->sc_dev)); |
346 | sc->sc_wdog_armed = 0; |
347 | sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 0); |
348 | error = 1; |
349 | } |
350 | splx(s); |
351 | |
352 | return (error); |
353 | } |
354 | |
355 | static int |
356 | weasel_pci_wdog_arm(struct weasel_softc *sc) |
357 | { |
358 | int x; |
359 | int s; |
360 | int error = 0; |
361 | |
362 | s = splhigh(); |
363 | if (weasel_issue_command(sc, OS_CMD_WDT_ENABLE)) { |
364 | printf("%s: no reply to watchdog enable. Check Weasel \"Allow Watchdog\" setting.\n" , |
365 | device_xname(sc->sc_dev)); |
366 | error = EIO; |
367 | } |
368 | (void)bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
369 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
370 | |
371 | /* |
372 | * Ensure that the Weasel thinks it's in the same mode we want it to |
373 | * be in. EIO if not. |
374 | */ |
375 | x = weasel_pci_wdog_query_state(sc); |
376 | switch (x) { |
377 | case -1: |
378 | error = EIO; |
379 | break; |
380 | case 0: |
381 | sc->sc_wdog_armed = 0; |
382 | error = EIO; |
383 | break; |
384 | case 1: |
385 | sc->sc_wdog_armed = 1; |
386 | error = 0; |
387 | break; |
388 | } |
389 | |
390 | splx(s); |
391 | return(error); |
392 | } |
393 | |
394 | |
395 | static int |
396 | weasel_pci_wdog_disarm(struct weasel_softc *sc) |
397 | { |
398 | int x; |
399 | int s; |
400 | int error = 0; |
401 | |
402 | s = splhigh(); |
403 | |
404 | if (weasel_issue_command(sc, OS_CMD_WDT_DISABLE)) { |
405 | printf("%s: didn't reply to watchdog disable.\n" , |
406 | device_xname(sc->sc_dev)); |
407 | error = EIO; |
408 | } |
409 | (void)bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
410 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
411 | |
412 | /* |
413 | * Ensure that the Weasel thinks it's in the same mode we want it to |
414 | * be in. EIO if not. |
415 | */ |
416 | x = weasel_pci_wdog_query_state(sc); |
417 | switch (x) { |
418 | case -1: |
419 | error = EIO; |
420 | break; |
421 | case 0: |
422 | sc->sc_wdog_armed = 0; |
423 | error = 0; |
424 | break; |
425 | case 1: |
426 | sc->sc_wdog_armed = 1; |
427 | error = EIO; |
428 | break; |
429 | } |
430 | |
431 | splx(s); |
432 | return(error); |
433 | } |
434 | |
435 | static int |
436 | weasel_pci_wdog_query_state(struct weasel_softc *sc) |
437 | { |
438 | |
439 | u_int8_t v; |
440 | |
441 | if (weasel_issue_command(sc, OS_CMD_WDT_QUERY)) { |
442 | printf("%s: didn't reply to watchdog state query.\n" , |
443 | device_xname(sc->sc_dev)); |
444 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
445 | return(-1); |
446 | } |
447 | v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD); |
448 | bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0); |
449 | return(v); |
450 | } |
451 | |