phyp_console.c revision 266019
1129198Scognet/*-
2129198Scognet * Copyright (C) 2011 by Nathan Whitehorn. All rights reserved.
3139735Simp *
4129198Scognet * Redistribution and use in source and binary forms, with or without
5129198Scognet * modification, are permitted provided that the following conditions
6129198Scognet * are met:
7129198Scognet * 1. Redistributions of source code must retain the above copyright
8129198Scognet *    notice, this list of conditions and the following disclaimer.
9129198Scognet * 2. Redistributions in binary form must reproduce the above copyright
10129198Scognet *    notice, this list of conditions and the following disclaimer in the
11129198Scognet *    documentation and/or other materials provided with the distribution.
12129198Scognet *
13129198Scognet * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14129198Scognet * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15129198Scognet * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16129198Scognet * IN NO EVENT SHALL TOOLS GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
17129198Scognet * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
18129198Scognet * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
19129198Scognet * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20129198Scognet * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
21129198Scognet * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
22129198Scognet * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23129198Scognet */
24129198Scognet
25129198Scognet#include <sys/cdefs.h>
26129198Scognet__FBSDID("$FreeBSD: stable/10/sys/powerpc/pseries/phyp_console.c 266019 2014-05-14 14:08:45Z ian $");
27129198Scognet
28129198Scognet#include <sys/param.h>
29129198Scognet#include <sys/kdb.h>
30129198Scognet#include <sys/kernel.h>
31129198Scognet#include <sys/priv.h>
32129198Scognet#include <sys/systm.h>
33129198Scognet#include <sys/module.h>
34129198Scognet#include <sys/types.h>
35129198Scognet#include <sys/conf.h>
36129198Scognet#include <sys/cons.h>
37129198Scognet#include <sys/tty.h>
38129198Scognet#include <machine/bus.h>
39129198Scognet
40129198Scognet#include <dev/ofw/openfirm.h>
41129198Scognet#include <dev/ofw/ofw_bus.h>
42129198Scognet#include <dev/ofw/ofw_bus_subr.h>
43129198Scognet#include <dev/uart/uart.h>
44129198Scognet#include <dev/uart/uart_cpu.h>
45129198Scognet#include <dev/uart/uart_bus.h>
46129198Scognet
47129198Scognet#include "phyp-hvcall.h"
48129198Scognet#include "uart_if.h"
49129198Scognet
50129198Scognetstruct uart_phyp_softc {
51129198Scognet	device_t dev;
52135652Scognet	phandle_t node;
53135652Scognet	int vtermid;
54129198Scognet
55129198Scognet	struct tty *tp;
56129198Scognet	struct resource *irqres;
57129198Scognet	int irqrid;
58129198Scognet	struct callout callout;
59129198Scognet	void *sc_icookie;
60135652Scognet	int polltime;
61135652Scognet
62129198Scognet	struct mtx sc_mtx;
63129198Scognet	int protocol;
64129198Scognet
65129198Scognet	union {
66129198Scognet		uint64_t u64[2];
67129198Scognet		char str[16];
68129198Scognet	} phyp_inbuf;
69129198Scognet	uint64_t inbuflen;
70129198Scognet	uint8_t outseqno;
71129198Scognet};
72129198Scognet
73129198Scognetstatic struct uart_phyp_softc	*console_sc = NULL;
74129198Scognet#if defined(KDB)
75129198Scognetstatic int			alt_break_state;
76129198Scognet#endif
77129198Scognet
78129198Scognetenum {
79129198Scognet	HVTERM1, HVTERMPROT
80129198Scognet};
81129198Scognet
82129198Scognet#define VS_DATA_PACKET_HEADER		0xff
83129198Scognet#define VS_CONTROL_PACKET_HEADER	0xfe
84129198Scognet#define  VSV_SET_MODEM_CTL		0x01
85129198Scognet#define  VSV_MODEM_CTL_UPDATE		0x02
86129198Scognet#define  VSV_RENEGOTIATE_CONNECTION	0x03
87129198Scognet#define VS_QUERY_PACKET_HEADER		0xfd
88129198Scognet#define  VSV_SEND_VERSION_NUMBER	0x01
89129198Scognet#define  VSV_SEND_MODEM_CTL_STATUS	0x02
90129198Scognet#define VS_QUERY_RESPONSE_PACKET_HEADER	0xfc
91129198Scognet
92129198Scognetstatic int uart_phyp_probe(device_t dev);
93129198Scognetstatic int uart_phyp_attach(device_t dev);
94129198Scognetstatic void uart_phyp_intr(void *v);
95129198Scognet
96129198Scognetstatic device_method_t uart_phyp_methods[] = {
97129198Scognet	/* Device interface */
98129198Scognet	DEVMETHOD(device_probe,		uart_phyp_probe),
99129198Scognet	DEVMETHOD(device_attach,	uart_phyp_attach),
100129198Scognet
101129198Scognet	DEVMETHOD_END
102129198Scognet};
103129198Scognet
104129198Scognetstatic driver_t uart_phyp_driver = {
105129198Scognet	"uart",
106129198Scognet	uart_phyp_methods,
107129198Scognet	sizeof(struct uart_phyp_softc),
108129198Scognet};
109129198Scognet
110129198ScognetDRIVER_MODULE(uart_phyp, vdevice, uart_phyp_driver, uart_devclass, 0, 0);
111129198Scognet
112129198Scognetstatic cn_probe_t uart_phyp_cnprobe;
113129198Scognetstatic cn_init_t uart_phyp_cninit;
114129198Scognetstatic cn_term_t uart_phyp_cnterm;
115129198Scognetstatic cn_getc_t uart_phyp_cngetc;
116129198Scognetstatic cn_putc_t uart_phyp_cnputc;
117129198Scognetstatic cn_grab_t uart_phyp_cngrab;
118129198Scognetstatic cn_ungrab_t uart_phyp_cnungrab;
119129198Scognet
120129198ScognetCONSOLE_DRIVER(uart_phyp);
121129198Scognet
122129198Scognetstatic void uart_phyp_ttyoutwakeup(struct tty *tp);
123129198Scognet
124129198Scognetstatic struct ttydevsw uart_phyp_tty_class = {
125129198Scognet	.tsw_flags	= TF_INITLOCK|TF_CALLOUT,
126129198Scognet	.tsw_outwakeup	= uart_phyp_ttyoutwakeup,
127129198Scognet};
128129198Scognet
129164080Scognetstatic int
130164080Scognetuart_phyp_probe_node(struct uart_phyp_softc *sc)
131164080Scognet{
132164080Scognet	phandle_t node = sc->node;
133164080Scognet	uint32_t reg;
134164080Scognet	char buf[64];
135164080Scognet
136129198Scognet	sc->inbuflen = 0;
137129198Scognet	sc->outseqno = 0;
138129198Scognet
139129198Scognet	if (OF_getprop(node, "name", buf, sizeof(buf)) <= 0)
140129198Scognet		return (ENXIO);
141129198Scognet	if (strcmp(buf, "vty") != 0)
142129198Scognet		return (ENXIO);
143129198Scognet
144164423Ssam	if (OF_getprop(node, "device_type", buf, sizeof(buf)) <= 0)
145129198Scognet		return (ENXIO);
146129198Scognet	if (strcmp(buf, "serial") != 0)
147129198Scognet		return (ENXIO);
148129198Scognet
149129198Scognet	reg = -1;
150129198Scognet	OF_getprop(node, "reg", &reg, sizeof(reg));
151129198Scognet	if (reg == -1)
152129198Scognet		return (ENXIO);
153129198Scognet	sc->vtermid = reg;
154129198Scognet	sc->node = node;
155129198Scognet
156129198Scognet	if (OF_getprop(node, "compatible", buf, sizeof(buf)) <= 0)
157129198Scognet		return (ENXIO);
158129198Scognet	if (strcmp(buf, "hvterm1") == 0) {
159129198Scognet		sc->protocol = HVTERM1;
160129198Scognet		return (0);
161129198Scognet	} else if (strcmp(buf, "hvterm-protocol") == 0) {
162129198Scognet		sc->protocol = HVTERMPROT;
163129198Scognet		return (0);
164129198Scognet	}
165129198Scognet
166129198Scognet	return (ENXIO);
167129198Scognet}
168129198Scognet
169129198Scognetstatic int
170129198Scognetuart_phyp_probe(device_t dev)
171129198Scognet{
172129198Scognet	const char *name;
173129198Scognet	struct uart_phyp_softc sc;
174129198Scognet	int err;
175129198Scognet
176129198Scognet	name = ofw_bus_get_name(dev);
177129198Scognet	if (name == NULL || strcmp(name, "vty") != 0)
178129198Scognet		return (ENXIO);
179129198Scognet
180129198Scognet	sc.node = ofw_bus_get_node(dev);
181129198Scognet	err = uart_phyp_probe_node(&sc);
182129198Scognet	if (err != 0)
183129198Scognet		return (err);
184129198Scognet
185129198Scognet	device_set_desc(dev, "POWER Hypervisor Virtual Serial Port");
186129198Scognet
187129198Scognet	return (err);
188129198Scognet}
189129198Scognet
190129198Scognetstatic void
191129198Scognetuart_phyp_cnprobe(struct consdev *cp)
192129198Scognet{
193129198Scognet	char buf[64];
194129198Scognet	ihandle_t stdout;
195129198Scognet	phandle_t input, chosen;
196129198Scognet	static struct uart_phyp_softc sc;
197152653Scognet
198152653Scognet	if ((chosen = OF_finddevice("/chosen")) == -1)
199129198Scognet		goto fail;
200129198Scognet
201129198Scognet	/* Check if OF has an active stdin/stdout */
202129198Scognet	input = -1;
203129198Scognet	if (OF_getprop(chosen, "stdout", &stdout,
204129198Scognet	    sizeof(stdout)) == sizeof(stdout) && stdout != 0)
205129198Scognet		input = OF_instance_to_package(stdout);
206129198Scognet	if (input == -1)
207129198Scognet		goto fail;
208129198Scognet
209129198Scognet	if (OF_getprop(input, "device_type", buf, sizeof(buf)) == -1)
210129198Scognet		goto fail;
211129198Scognet	if (strcmp(buf, "serial") != 0)
212129198Scognet		goto fail;
213129198Scognet
214129198Scognet	sc.node = input;
215129198Scognet	if (uart_phyp_probe_node(&sc) != 0)
216129198Scognet		goto fail;
217129198Scognet	mtx_init(&sc.sc_mtx, "uart_phyp", NULL, MTX_SPIN | MTX_QUIET |
218129198Scognet	    MTX_NOWITNESS);
219129198Scognet
220129198Scognet	cp->cn_pri = CN_NORMAL;
221129198Scognet	console_sc = &sc;
222129198Scognet	return;
223129198Scognet
224129198Scognetfail:
225129198Scognet	cp->cn_pri = CN_DEAD;
226129198Scognet	return;
227129198Scognet}
228129198Scognet
229129198Scognetstatic int
230129198Scognetuart_phyp_attach(device_t dev)
231129198Scognet{
232129198Scognet	struct uart_phyp_softc *sc;
233129198Scognet	int unit;
234129198Scognet
235129198Scognet	sc = device_get_softc(dev);
236129198Scognet	sc->dev = dev;
237129198Scognet	sc->node = ofw_bus_get_node(dev);
238129198Scognet	uart_phyp_probe_node(sc);
239164080Scognet
240164080Scognet	unit = device_get_unit(dev);
241164080Scognet	sc->tp = tty_alloc(&uart_phyp_tty_class, sc);
242161592Scognet	mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL,
243161592Scognet	    MTX_SPIN | MTX_QUIET | MTX_NOWITNESS);
244161592Scognet
245161592Scognet	if (console_sc != NULL && console_sc->vtermid == sc->vtermid) {
246161592Scognet		sc->outseqno = console_sc->outseqno;
247161592Scognet		console_sc = sc;
248129198Scognet		sprintf(uart_phyp_consdev.cn_name, "ttyu%r", unit);
249129198Scognet		tty_init_console(sc->tp, 0);
250129198Scognet	}
251129198Scognet
252129198Scognet	sc->irqrid = 0;
253129198Scognet	sc->irqres = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->irqrid,
254129198Scognet	    RF_ACTIVE | RF_SHAREABLE);
255129198Scognet	if (sc->irqres != NULL) {
256129198Scognet		bus_setup_intr(dev, sc->irqres, INTR_TYPE_TTY | INTR_MPSAFE,
257129198Scognet		    NULL, uart_phyp_intr, sc, &sc->sc_icookie);
258129198Scognet	} else {
259129198Scognet		callout_init(&sc->callout, CALLOUT_MPSAFE);
260129198Scognet		sc->polltime = hz / 20;
261129198Scognet		if (sc->polltime < 1)
262129198Scognet			sc->polltime = 1;
263129198Scognet		callout_reset(&sc->callout, sc->polltime, uart_phyp_intr, sc);
264129198Scognet	}
265129198Scognet
266129198Scognet	tty_makedev(sc->tp, NULL, "u%r", unit);
267129198Scognet
268129198Scognet	return (0);
269129198Scognet}
270129198Scognet
271129198Scognetstatic void
272129198Scognetuart_phyp_cninit(struct consdev *cp)
273129198Scognet{
274129198Scognet
275129198Scognet	strcpy(cp->cn_name, "phypcons");
276129198Scognet}
277129198Scognet
278129198Scognetstatic void
279129198Scognetuart_phyp_cnterm(struct consdev *cp)
280129198Scognet{
281129198Scognet}
282129198Scognet
283129198Scognetstatic int
284129198Scognetuart_phyp_get(struct uart_phyp_softc *sc, void *buffer, size_t bufsize)
285155242Simp{
286129198Scognet	int err;
287129198Scognet	int hdr = 0;
288129198Scognet
289129198Scognet	uart_lock(&sc->sc_mtx);
290129198Scognet	if (sc->inbuflen == 0) {
291129198Scognet		err = phyp_pft_hcall(H_GET_TERM_CHAR, sc->vtermid,
292129198Scognet		    0, 0, 0, &sc->inbuflen, &sc->phyp_inbuf.u64[0],
293129198Scognet		    &sc->phyp_inbuf.u64[1]);
294129198Scognet		if (err != H_SUCCESS) {
295129198Scognet			uart_unlock(&sc->sc_mtx);
296129198Scognet			return (-1);
297129198Scognet		}
298129198Scognet		hdr = 1;
299129198Scognet	}
300129198Scognet
301129198Scognet	if (sc->inbuflen == 0) {
302129198Scognet		uart_unlock(&sc->sc_mtx);
303129198Scognet		return (0);
304129198Scognet	}
305129198Scognet
306129198Scognet	if (bufsize > sc->inbuflen)
307129198Scognet		bufsize = sc->inbuflen;
308129198Scognet
309129198Scognet	if ((sc->protocol == HVTERMPROT) && (hdr == 1)) {
310129198Scognet		sc->inbuflen = sc->inbuflen - 4;
311129198Scognet		/* The VTERM protocol has a 4 byte header, skip it here. */
312129198Scognet		memmove(&sc->phyp_inbuf.str[0], &sc->phyp_inbuf.str[4],
313129198Scognet		    sc->inbuflen);
314129198Scognet	}
315129198Scognet
316129198Scognet	memcpy(buffer, sc->phyp_inbuf.str, bufsize);
317153940Snetchild	sc->inbuflen -= bufsize;
318153940Snetchild	if (sc->inbuflen > 0)
319153940Snetchild		memmove(&sc->phyp_inbuf.str[0], &sc->phyp_inbuf.str[bufsize],
320153940Snetchild		    sc->inbuflen);
321153940Snetchild
322153940Snetchild	uart_unlock(&sc->sc_mtx);
323153940Snetchild	return (bufsize);
324153940Snetchild}
325153940Snetchild
326129198Scognetstatic int
327129198Scognetuart_phyp_put(struct uart_phyp_softc *sc, void *buffer, size_t bufsize)
328129198Scognet{
329129198Scognet	uint16_t seqno;
330129198Scognet	uint64_t len = 0;
331129198Scognet	int	err;
332129198Scognet
333129198Scognet	union {
334129198Scognet		uint64_t u64[2];
335129198Scognet		char bytes[16];
336129198Scognet	} cbuf;
337129198Scognet
338129198Scognet	uart_lock(&sc->sc_mtx);
339129198Scognet	switch (sc->protocol) {
340129198Scognet	case HVTERM1:
341129198Scognet		if (bufsize > 16)
342129198Scognet			bufsize = 16;
343129198Scognet		memcpy(&cbuf, buffer, bufsize);
344155242Simp		len = bufsize;
345129198Scognet		break;
346129198Scognet	case HVTERMPROT:
347129198Scognet		if (bufsize > 12)
348129198Scognet			bufsize = 12;
349129198Scognet		seqno = sc->outseqno++;
350129198Scognet		cbuf.bytes[0] = VS_DATA_PACKET_HEADER;
351129198Scognet		cbuf.bytes[1] = 4 + bufsize; /* total length, max 16 bytes */
352129198Scognet		cbuf.bytes[2] = (seqno >> 8) & 0xff;
353129198Scognet		cbuf.bytes[3] = seqno & 0xff;
354155242Simp		memcpy(&cbuf.bytes[4], buffer, bufsize);
355129198Scognet		len = 4 + bufsize;
356129198Scognet		break;
357129198Scognet	}
358129198Scognet
359129198Scognet	do {
360129198Scognet	    err = phyp_hcall(H_PUT_TERM_CHAR, sc->vtermid, len, cbuf.u64[0],
361129198Scognet			    cbuf.u64[1]);
362129198Scognet		DELAY(100);
363129198Scognet	} while (err == H_BUSY);
364129198Scognet
365129198Scognet	uart_unlock(&sc->sc_mtx);
366129198Scognet
367129198Scognet	return (bufsize);
368129198Scognet}
369129198Scognet
370129198Scognetstatic int
371129198Scognetuart_phyp_cngetc(struct consdev *cp)
372129198Scognet{
373129198Scognet	unsigned char c;
374129198Scognet	int retval;
375129198Scognet
376129198Scognet	retval = uart_phyp_get(console_sc, &c, 1);
377171625Scognet	if (retval != 1)
378171625Scognet		return (-1);
379171625Scognet#if defined(KDB)
380171625Scognet	kdb_alt_break(c, &alt_break_state);
381171625Scognet#endif
382171625Scognet
383129198Scognet	return (c);
384129198Scognet}
385129198Scognet
386129198Scognetstatic void
387129198Scognetuart_phyp_cnputc(struct consdev *cp, int c)
388129198Scognet{
389129198Scognet	unsigned char ch = c;
390129198Scognet	uart_phyp_put(console_sc, &ch, 1);
391129198Scognet}
392129198Scognet
393129198Scognetstatic void
394129198Scognetuart_phyp_cngrab(struct consdev *cp)
395129198Scognet{
396129198Scognet}
397129198Scognet
398129198Scognetstatic void
399129198Scognetuart_phyp_cnungrab(struct consdev *cp)
400155242Simp{
401137272Scognet}
402137272Scognet
403137272Scognetstatic void
404137272Scognetuart_phyp_ttyoutwakeup(struct tty *tp)
405137272Scognet{
406155242Simp	struct uart_phyp_softc *sc;
407137272Scognet	char buffer[8];
408137272Scognet	int len;
409137272Scognet
410137272Scognet	sc = tty_softc(tp);
411155242Simp
412137272Scognet	while ((len = ttydisc_getc(tp, buffer, sizeof(buffer))) != 0)
413137272Scognet		uart_phyp_put(sc, buffer, len);
414155242Simp}
415137272Scognet
416137272Scognetstatic void
417137272Scognetuart_phyp_intr(void *v)
418137272Scognet{
419129198Scognet	struct uart_phyp_softc *sc = v;
420129198Scognet	struct tty *tp = sc->tp;
421	unsigned char c;
422	int len;
423
424	tty_lock(tp);
425	while ((len = uart_phyp_get(sc, &c, 1)) > 0)
426		ttydisc_rint(tp, c, 0);
427	ttydisc_rint_done(tp);
428	tty_unlock(tp);
429
430	if (sc->irqres == NULL)
431		callout_reset(&sc->callout, sc->polltime, uart_phyp_intr, sc);
432}
433
434