xref: /netbsd-src/sys/dev/pci/weasel_pci.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /*	$NetBSD: weasel_pci.c,v 1.15 2013/10/17 21:06:15 christos 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.15 2013/10/17 21:06:15 christos 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, "unexpected PING response from Weasel: 0x%02x\n", v);
128 		return;
129 	}
130 
131 	/* Read the config block. */
132 	if (weasel_issue_command(sc, OS_CMD_SHOW_CONFIG)) {
133 		aprint_error_dev(self, "Weasel didn't respond to SHOW_CONFIG\n");
134 		return;
135 	}
136 	cfg_size = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
137 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
138 
139 	if (++cfg_size != sizeof(cfg)) {
140 		aprint_error_dev(self, "weird config block size from Weasel: 0x%03x\n", cfg_size);
141 		return;
142 	}
143 
144 	for (cp = (uint8_t *) &cfg; cfg_size != 0; cfg_size--) {
145 		if (weasel_wait_response(sc)) {
146 			aprint_error_dev(self, "Weasel stopped providing config block(%d)\n", cfg_size);
147 			return;
148 		}
149 		*cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
150 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
151 	}
152 
153 	switch (cfg.cfg_version) {
154 	case CFG_VERSION_2:
155 		vers="2";
156 		switch (cfg.enable_duart_switching) {
157 		case 0:
158 			mode = "emulation";
159 			break;
160 		case 1:
161 			mode = "serial";
162 			break;
163 		case 2:
164 			mode = "autoswitch";
165 			break;
166 		default:
167 			mode = "unknown";
168 		}
169 		break;
170 
171 	default:
172 		vers = mode = NULL;
173 	}
174 
175 	if (vers != NULL)
176 		printf("%s: %s mode\n", device_xname(self), mode);
177 	else
178 		printf("%s: unknown config version 0x%02x\n", device_xname(self),
179 		    cfg.cfg_version);
180 
181 	/*
182 	 * Fetch sw version.
183 	 */
184 	if (weasel_issue_command(sc, OS_CMD_QUERY_SW_VER)) {
185 		aprint_error_dev(self, "didn't reply to software version query.\n");
186 	}
187 	else {
188 		v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
189 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
190 		if (v>7)
191 			printf("%s: weird length for version string(%d).\n",
192 			    device_xname(self), v);
193 		memset(buf, 0, sizeof(buf));
194 		for (cp = buf; v != 0; v--) {
195 			if (weasel_wait_response(sc)) {
196 				printf("%s: Weasel stopped providing version\n",
197 				    device_xname(self));
198 			}
199 			*cp++ = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
200 			bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
201 		}
202 		printf("%s: sw: %s", device_xname(self), buf);
203 	}
204 	/*
205 	 * Fetch logic version.
206 	 */
207 	if (weasel_issue_command(sc, OS_CMD_QUERY_L_VER)) {
208 		aprint_normal("\n");
209 		aprint_error_dev(self, "didn't reply to logic version query.\n");
210 	}
211 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
212 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
213 	printf(" logic: %03d", v);
214 	/*
215 	 * Fetch vga bios version.
216 	 */
217 	if (weasel_issue_command(sc, OS_CMD_QUERY_VB_VER)) {
218 		aprint_normal("\n");
219 		aprint_error_dev(self, "didn't reply to vga bios version query.\n");
220 	}
221 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
222 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
223 	printf(" vga bios: %d.%d", (v>>4), (v&0x0f));
224 	/*
225 	 * Fetch hw version.
226 	 */
227 	if (weasel_issue_command(sc, OS_CMD_QUERY_HW_VER)) {
228 		aprint_normal("\n");
229 		aprint_error_dev(self, "didn't reply to hardware 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(" hw: %d.%d", (v>>4), (v&0x0f));
234 
235 	printf("\n%s: break passthrough %s", device_xname(self),
236 	    cfg.break_passthru ? "enabled" : "disabled");
237 
238 	if ((sc->sc_wdog_armed = weasel_pci_wdog_query_state(sc)) == -1)
239 		sc->sc_wdog_armed = 0;
240 
241 	/* Weasel is big-endian */
242 	sc->sc_wdog_period = be16toh(cfg.wdt_msec) / 1000;
243 
244 	printf(", watchdog timer %d sec.\n", sc->sc_wdog_period);
245 	sc->sc_smw.smw_name = "weasel";
246 	sc->sc_smw.smw_cookie = sc;
247 	sc->sc_smw.smw_setmode = weasel_pci_wdog_setmode;
248 	sc->sc_smw.smw_tickle = weasel_pci_wdog_tickle;
249 	sc->sc_smw.smw_period = sc->sc_wdog_period;
250 
251 	if (sysmon_wdog_register(&sc->sc_smw) != 0)
252 		aprint_error_dev(self, "unable to register PC-Weasel watchdog "
253 		    "with sysmon\n");
254 }
255 
256 CFATTACH_DECL_NEW(weasel_pci, sizeof(struct weasel_softc),
257     weasel_pci_match, weasel_pci_attach, NULL, NULL);
258 
259 static int
260 weasel_wait_response(struct weasel_softc *sc)
261 {
262 	int i;
263 
264 	for (i = 10000; i ; i--) {
265 		delay(100);
266 		if (bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS) ==
267 		    OS_WS_HOST_READ)
268 			return(0);
269 	}
270 	return (1);
271 }
272 
273 static int
274 weasel_issue_command(struct weasel_softc *sc, uint8_t cmd)
275 {
276 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_WR, cmd);
277 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_HOST_STATUS, OS_HS_WEASEL_READ);
278 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
279 	return (weasel_wait_response(sc));
280 }
281 
282 static int
283 weasel_pci_wdog_setmode(struct sysmon_wdog *smw)
284 {
285 	struct weasel_softc *sc = smw->smw_cookie;
286 	int error = 0;
287 
288 	if ((smw->smw_mode & WDOG_MODE_MASK) == WDOG_MODE_DISARMED) {
289 		error = weasel_pci_wdog_disarm(sc);
290 	} else {
291 		if (smw->smw_period == WDOG_PERIOD_DEFAULT)
292 			smw->smw_period = sc->sc_wdog_period;
293 		else if (smw->smw_period != sc->sc_wdog_period) {
294 			/* Can't change the period on the Weasel. */
295 			return (EINVAL);
296 		}
297 		error = weasel_pci_wdog_arm(sc);
298 		weasel_pci_wdog_tickle(smw);
299 	}
300 
301 	return (error);
302 }
303 
304 static int
305 weasel_pci_wdog_tickle(struct sysmon_wdog *smw)
306 {
307 	struct weasel_softc *sc = smw->smw_cookie;
308 	u_int8_t reg;
309 	int x;
310 	int s;
311 	int error = 0;
312 
313 	s = splhigh();
314 	/*
315 	 * first we tickle the watchdog
316 	 */
317 	reg = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_CHALLENGE);
318 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_RESPONSE, ~reg);
319 
320 	/*
321 	 * then we check to make sure the weasel is still armed. If someone
322 	 * has rebooted the weasel for whatever reason (firmware update),
323 	 * then the watchdog timer would no longer be armed and we'd be
324 	 * servicing nothing. Let the user know that the machine is no
325 	 * longer being monitored by the weasel.
326 	 */
327 	if((x = weasel_pci_wdog_query_state(sc)) == -1)
328 		error = EIO;
329 	if (x == 1) {
330 		error = 0;
331 	} else {
332 		printf("%s: Watchdog timer disabled on PC/Weasel! Disarming wdog.\n",
333 			device_xname(sc->sc_dev));
334 		sc->sc_wdog_armed = 0;
335 		sysmon_wdog_setmode(smw, WDOG_MODE_DISARMED, 0);
336 		error = 1;
337 	}
338 	splx(s);
339 
340 	return (error);
341 }
342 
343 static int
344 weasel_pci_wdog_arm(struct weasel_softc *sc)
345 {
346 	int x;
347 	int s;
348 	int error = 0;
349 
350 	s = splhigh();
351 	if (weasel_issue_command(sc, OS_CMD_WDT_ENABLE)) {
352 		printf("%s: no reply to watchdog enable. Check Weasel \"Allow Watchdog\" setting.\n",
353 			device_xname(sc->sc_dev));
354 		error = EIO;
355 	}
356 	(void)bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
357 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
358 
359 	/*
360 	 * Ensure that the Weasel thinks it's in the same mode we want it to
361 	 * be in.   EIO if not.
362 	 */
363 	x = weasel_pci_wdog_query_state(sc);
364 	switch (x) {
365 		case -1:
366 			error = EIO;
367 			break;
368 		case 0:
369 			sc->sc_wdog_armed = 0;
370 			error = EIO;
371 			break;
372 		case 1:
373 			sc->sc_wdog_armed = 1;
374 			error = 0;
375 			break;
376 	}
377 
378 	splx(s);
379 	return(error);
380 }
381 
382 
383 static int
384 weasel_pci_wdog_disarm(struct weasel_softc *sc)
385 {
386 	int x;
387 	int s;
388 	int error = 0;
389 
390 	s = splhigh();
391 
392 	if (weasel_issue_command(sc, OS_CMD_WDT_DISABLE)) {
393 		printf("%s: didn't reply to watchdog disable.\n",
394 			device_xname(sc->sc_dev));
395 		error = EIO;
396 	}
397 	(void)bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
398 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
399 
400 	/*
401 	 * Ensure that the Weasel thinks it's in the same mode we want it to
402 	 * be in.   EIO if not.
403 	 */
404 	x = weasel_pci_wdog_query_state(sc);
405 	switch (x) {
406 		case -1:
407 			error = EIO;
408 			break;
409 		case 0:
410 			sc->sc_wdog_armed = 0;
411 			error = 0;
412 			break;
413 		case 1:
414 			sc->sc_wdog_armed = 1;
415 			error = EIO;
416 			break;
417 	}
418 
419 	splx(s);
420 	return(error);
421 }
422 
423 static int
424 weasel_pci_wdog_query_state(struct weasel_softc *sc)
425 {
426 
427 	u_int8_t v;
428 
429 	if (weasel_issue_command(sc, OS_CMD_WDT_QUERY)) {
430 		printf("%s: didn't reply to watchdog state query.\n",
431 			device_xname(sc->sc_dev));
432 		bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
433 		return(-1);
434 	}
435 	v = bus_space_read_1(sc->sc_st, sc->sc_sh, WEASEL_DATA_RD);
436 	bus_space_write_1(sc->sc_st, sc->sc_sh, WEASEL_STATUS, 0);
437 	return(v);
438 }
439