altera_jtag_uart_tty.c revision 239676
1/*-
2 * Copyright (c) 2011-2012 Robert N. M. Watson
3 * All rights reserved.
4 *
5 * This software was developed by SRI International and the University of
6 * Cambridge Computer Laboratory under DARPA/AFRL contract (FA8750-10-C-0237)
7 * ("CTSRD"), as part of the DARPA CRASH research programme.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD: head/sys/dev/altera/jtag_uart/altera_jtag_uart_tty.c 239676 2012-08-25 11:30:36Z rwatson $");
33
34#include <sys/param.h>
35#include <sys/bus.h>
36#include <sys/cons.h>
37#include <sys/endian.h>
38#include <sys/kdb.h>
39#include <sys/rman.h>
40#include <sys/systm.h>
41#include <sys/kernel.h>
42#include <sys/reboot.h>
43#include <sys/tty.h>
44
45#include <ddb/ddb.h>
46
47#include <machine/bus.h>
48
49#include <dev/altera/jtag_uart/altera_jtag_uart.h>
50
51/*
52 * If one of the Altera JTAG UARTs is currently the system console, register
53 * it here.
54 */
55static struct altera_jtag_uart_softc	*aju_cons_sc;
56
57static tsw_outwakeup_t	aju_outwakeup;
58static void		aju_ac_callout(void *);
59static void		aju_io_callout(void *);
60
61static struct ttydevsw aju_ttydevsw = {
62	.tsw_flags	= TF_NOPREFIX,
63	.tsw_outwakeup	= aju_outwakeup,
64};
65
66/*
67 * When polling for the AC bit, the number of times we have to not see it
68 * before assuming JTAG has disappeared on us.  By default, one second.
69 */
70#define	AJU_JTAG_MAXMISS		5
71
72/*
73 * Polling intervals for input/output and JTAG connection events.
74 */
75#define	AJU_IO_POLLINTERVAL		(hz/100)
76#define	AJU_AC_POLLINTERVAL		(hz/5)
77
78/*
79 * Low-level read and write register routines; the Altera UART is little
80 * endian, so we byte swap 32-bit reads and writes.
81 */
82static inline uint32_t
83aju_data_read(struct altera_jtag_uart_softc *sc)
84{
85
86	return (le32toh(bus_read_4(sc->ajus_mem_res,
87	    ALTERA_JTAG_UART_DATA_OFF)));
88}
89
90static inline void
91aju_data_write(struct altera_jtag_uart_softc *sc, uint32_t v)
92{
93
94	bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_DATA_OFF, htole32(v));
95}
96
97static inline uint32_t
98aju_control_read(struct altera_jtag_uart_softc *sc)
99{
100
101	return (le32toh(bus_read_4(sc->ajus_mem_res,
102	    ALTERA_JTAG_UART_CONTROL_OFF)));
103}
104
105static inline void
106aju_control_write(struct altera_jtag_uart_softc *sc, uint32_t v)
107{
108
109	bus_write_4(sc->ajus_mem_res, ALTERA_JTAG_UART_CONTROL_OFF,
110	    htole32(v));
111}
112
113/*
114 * Slightly higher-level routines aware of buffering and flow control.
115 */
116static inline int
117aju_writable(struct altera_jtag_uart_softc *sc)
118{
119
120	return ((aju_control_read(sc) &
121	    ALTERA_JTAG_UART_CONTROL_WSPACE) != 0);
122}
123
124static inline int
125aju_readable(struct altera_jtag_uart_softc *sc)
126{
127	uint32_t v;
128
129	AJU_LOCK_ASSERT(sc);
130
131	if (*sc->ajus_buffer_validp)
132		return (1);
133	v = aju_data_read(sc);
134	if ((v & ALTERA_JTAG_UART_DATA_RVALID) != 0) {
135		*sc->ajus_buffer_validp = 1;
136		*sc->ajus_buffer_datap = (v & ALTERA_JTAG_UART_DATA_DATA);
137		return (1);
138	}
139	return (0);
140}
141
142static char
143aju_read(struct altera_jtag_uart_softc *sc)
144{
145
146	AJU_LOCK_ASSERT(sc);
147
148	while (!aju_readable(sc));
149	*sc->ajus_buffer_validp = 0;
150	return (*sc->ajus_buffer_datap);
151}
152
153/*
154 * Routines for enabling and disabling interrupts for read and write.
155 */
156static void
157aju_intr_readable_enable(struct altera_jtag_uart_softc *sc)
158{
159	uint32_t v;
160
161	AJU_LOCK_ASSERT(sc);
162
163	v = aju_control_read(sc);
164	v |= ALTERA_JTAG_UART_CONTROL_RE;
165	aju_control_write(sc, v);
166}
167
168static void
169aju_intr_writable_enable(struct altera_jtag_uart_softc *sc)
170{
171	uint32_t v;
172
173	AJU_LOCK_ASSERT(sc);
174
175	v = aju_control_read(sc);
176	v |= ALTERA_JTAG_UART_CONTROL_WE;
177	aju_control_write(sc, v);
178}
179
180static void
181aju_intr_writable_disable(struct altera_jtag_uart_softc *sc)
182{
183	uint32_t v;
184
185	AJU_LOCK_ASSERT(sc);
186
187	v = aju_control_read(sc);
188	v &= ~ALTERA_JTAG_UART_CONTROL_WE;
189	aju_control_write(sc, v);
190}
191
192static void
193aju_intr_disable(struct altera_jtag_uart_softc *sc)
194{
195	uint32_t v;
196
197	AJU_LOCK_ASSERT(sc);
198
199	v = aju_control_read(sc);
200	v &= ~(ALTERA_JTAG_UART_CONTROL_RE | ALTERA_JTAG_UART_CONTROL_WE);
201	aju_control_write(sc, v);
202}
203
204/*
205 * The actual work of checking for, and handling, available reads.  This is
206 * used in both polled and interrupt-driven modes, as JTAG UARTs may be hooked
207 * up with, or without, IRQs allocated.
208 */
209static void
210aju_handle_input(struct altera_jtag_uart_softc *sc, struct tty *tp)
211{
212	int c;
213
214	tty_lock_assert(tp, MA_OWNED);
215	AJU_LOCK_ASSERT(sc);
216
217	while (aju_readable(sc)) {
218		c = aju_read(sc);
219		AJU_UNLOCK(sc);
220#ifdef KDB
221		if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE)
222			kdb_alt_break(c, &sc->ajus_alt_break_state);
223#endif
224		ttydisc_rint(tp, c, 0);
225		AJU_LOCK(sc);
226	}
227	AJU_UNLOCK(sc);
228	ttydisc_rint_done(tp);
229	AJU_LOCK(sc);
230}
231
232/*
233 * Send output to the UART until either there's none left to send, or we run
234 * out of room and need to await an interrupt so that we can start sending
235 * again.
236 *
237 * XXXRW: It would be nice to query WSPACE at the beginning and write to the
238 * FIFO in bugger chunks.
239 */
240static void
241aju_handle_output(struct altera_jtag_uart_softc *sc, struct tty *tp)
242{
243	uint32_t v;
244	uint8_t ch;
245
246	tty_lock_assert(tp, MA_OWNED);
247	AJU_LOCK_ASSERT(sc);
248
249	AJU_UNLOCK(sc);
250	while (ttydisc_getc_poll(tp) != 0) {
251		AJU_LOCK(sc);
252		v = aju_control_read(sc);
253		if ((v & ALTERA_JTAG_UART_CONTROL_WSPACE) != 0) {
254			AJU_UNLOCK(sc);
255			if (ttydisc_getc(tp, &ch, sizeof(ch)) != sizeof(ch))
256				panic("%s: ttydisc_getc", __func__);
257			AJU_LOCK(sc);
258			aju_data_write(sc, ch);
259		} else {
260			/*
261			 * If JTAG is not present, then we will drop this
262			 * character instead of perhaps scheduling an
263			 * interrupt to let us know when there is buffer
264			 * space.  Otherwise we might get a write interrupt
265			 * later even though we aren't interested in sending
266			 * anymore.  Loop to drain TTY-layer buffer.
267			 */
268			if (*sc->ajus_jtag_presentp == 0) {
269				if (ttydisc_getc(tp, &ch, sizeof(ch)) !=
270				    sizeof(ch))
271					panic("%s: ttydisc_getc 2", __func__);
272				AJU_UNLOCK(sc);
273				continue;
274			}
275			if (sc->ajus_irq_res != NULL)
276				aju_intr_writable_enable(sc);
277			return;
278		}
279		AJU_UNLOCK(sc);
280	}
281	AJU_LOCK(sc);
282	aju_intr_writable_disable(sc);
283}
284
285static void
286aju_outwakeup(struct tty *tp)
287{
288	struct altera_jtag_uart_softc *sc = tty_softc(tp);
289
290	tty_lock_assert(tp, MA_OWNED);
291
292	AJU_LOCK(sc);
293	aju_handle_output(sc, tp);
294	AJU_UNLOCK(sc);
295}
296
297static void
298aju_io_callout(void *arg)
299{
300	struct altera_jtag_uart_softc *sc = arg;
301	struct tty *tp = sc->ajus_ttyp;
302
303	tty_lock(tp);
304	AJU_LOCK(sc);
305
306	/*
307	 * It would be convenient if we could share code with aju_intr() here
308	 * by testing the control register for ALTERA_JTAG_UART_CONTROL_RI and
309	 * ALTERA_JTAG_UART_CONTROL_WI.  Unfortunately, it's not clear that
310	 * this is supported, so do all the work to poll for both input and
311	 * output.
312	 */
313	aju_handle_input(sc, tp);
314	aju_handle_output(sc, tp);
315
316	/*
317	 * Reschedule next poll attempt.  There's some argument that we should
318	 * do adaptive polling based on the expectation of I/O: is something
319	 * pending in the output buffer, or have we recently had input, but we
320	 * don't.
321	 */
322	callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL,
323	    aju_io_callout, sc);
324	AJU_UNLOCK(sc);
325	tty_unlock(tp);
326}
327
328static void
329aju_ac_callout(void *arg)
330{
331	struct altera_jtag_uart_softc *sc = arg;
332	struct tty *tp = sc->ajus_ttyp;
333	uint32_t v;
334
335	tty_lock(tp);
336	AJU_LOCK(sc);
337	v = aju_control_read(sc);
338	if (v & ALTERA_JTAG_UART_CONTROL_AC) {
339		v &= ~ALTERA_JTAG_UART_CONTROL_AC;
340		aju_control_write(sc, v);
341		if (*sc->ajus_jtag_presentp == 0) {
342			*sc->ajus_jtag_missedp = 0;
343			*sc->ajus_jtag_presentp = 1;
344			aju_handle_output(sc, tp);
345		}
346	} else if (*sc->ajus_jtag_presentp != 0) {
347		(*sc->ajus_jtag_missedp)++;
348		if (*sc->ajus_jtag_missedp >= AJU_JTAG_MAXMISS) {
349			*sc->ajus_jtag_presentp = 0;
350			aju_handle_output(sc, tp);
351		}
352	}
353	callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL,
354	    aju_ac_callout, sc);
355	AJU_UNLOCK(sc);
356	tty_unlock(tp);
357}
358
359static void
360aju_intr(void *arg)
361{
362	struct altera_jtag_uart_softc *sc = arg;
363	struct tty *tp = sc->ajus_ttyp;
364	uint32_t v;
365
366	tty_lock(tp);
367	AJU_LOCK(sc);
368	v = aju_control_read(sc);
369	if (v & ALTERA_JTAG_UART_CONTROL_RI)
370		aju_handle_input(sc, tp);
371	if (v & ALTERA_JTAG_UART_CONTROL_WI)
372		aju_handle_output(sc, tp);
373	AJU_UNLOCK(sc);
374	tty_unlock(tp);
375}
376
377int
378altera_jtag_uart_attach(struct altera_jtag_uart_softc *sc)
379{
380	struct tty *tp;
381	int error;
382
383	AJU_LOCK_INIT(sc);
384
385	/*
386	 * XXXRW: Currently, we detect the console solely based on it using a
387	 * reserved address, and borrow console-level locks and buffer if so.
388	 * Is there a better way?
389	 */
390	if (rman_get_start(sc->ajus_mem_res) == BERI_UART_BASE) {
391		sc->ajus_lockp = &aju_cons_lock;
392		sc->ajus_buffer_validp = &aju_cons_buffer_valid;
393		sc->ajus_buffer_datap = &aju_cons_buffer_data;
394		sc->ajus_jtag_presentp = &aju_cons_jtag_present;
395		sc->ajus_jtag_missedp = &aju_cons_jtag_missed;
396		sc->ajus_flags |= ALTERA_JTAG_UART_FLAG_CONSOLE;
397	} else {
398		sc->ajus_lockp = &sc->ajus_lock;
399		sc->ajus_buffer_validp = &sc->ajus_buffer_valid;
400		sc->ajus_buffer_datap = &sc->ajus_buffer_data;
401		sc->ajus_jtag_presentp = &sc->ajus_jtag_present;
402		sc->ajus_jtag_missedp = &sc->ajus_jtag_missed;
403	}
404
405	/*
406	 * Disable interrupts regardless of whether or not we plan to use
407	 * them.  We will register an interrupt handler now if they will be
408	 * used, but not re-enable intil later once the remainder of the tty
409	 * layer is properly initialised, as we're not ready for input yet.
410	 */
411	AJU_LOCK(sc);
412	aju_intr_disable(sc);
413	AJU_UNLOCK(sc);
414	if (sc->ajus_irq_res != NULL) {
415		error = bus_setup_intr(sc->ajus_dev, sc->ajus_irq_res,
416		    INTR_ENTROPY | INTR_TYPE_TTY | INTR_MPSAFE, NULL,
417		    aju_intr, sc, &sc->ajus_irq_cookie);
418		if (error) {
419			device_printf(sc->ajus_dev,
420			    "could not activate interrupt\n");
421			AJU_LOCK_DESTROY(sc);
422			return (error);
423		}
424	}
425	tp = sc->ajus_ttyp = tty_alloc(&aju_ttydevsw, sc);
426	if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE) {
427		aju_cons_sc = sc;
428		tty_init_console(tp, 0);
429	}
430	tty_makedev(tp, NULL, "%s%d", AJU_TTYNAME, sc->ajus_unit);
431
432	/*
433	 * If we will be using interrupts, enable them now; otherwise, start
434	 * polling.  From this point onwards, input can arrive.
435	 */
436	if (sc->ajus_irq_res != NULL) {
437		AJU_LOCK(sc);
438		aju_intr_readable_enable(sc);
439		AJU_UNLOCK(sc);
440	} else {
441		callout_init(&sc->ajus_io_callout, CALLOUT_MPSAFE);
442		callout_reset(&sc->ajus_io_callout, AJU_IO_POLLINTERVAL,
443		    aju_io_callout, sc);
444	}
445	callout_init(&sc->ajus_ac_callout, CALLOUT_MPSAFE);
446	callout_reset(&sc->ajus_ac_callout, AJU_AC_POLLINTERVAL,
447	    aju_ac_callout, sc);
448	return (0);
449}
450
451void
452altera_jtag_uart_detach(struct altera_jtag_uart_softc *sc)
453{
454	struct tty *tp = sc->ajus_ttyp;
455
456	/*
457	 * If we're using interrupts, disable and release the interrupt
458	 * handler now.  Otherwise drain the polling timeout.
459	 */
460	if (sc->ajus_irq_res != NULL) {
461		AJU_LOCK(sc);
462		aju_intr_disable(sc);
463		AJU_UNLOCK(sc);
464		bus_teardown_intr(sc->ajus_dev, sc->ajus_irq_res,
465		    sc->ajus_irq_cookie);
466	} else
467		callout_drain(&sc->ajus_io_callout);
468	callout_drain(&sc->ajus_ac_callout);
469	if (sc->ajus_flags & ALTERA_JTAG_UART_FLAG_CONSOLE)
470		aju_cons_sc = NULL;
471	tty_lock(tp);
472	tty_rel_gone(tp);
473	AJU_LOCK_DESTROY(sc);
474}
475