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: stable/10/sys/dev/gxemul/cons/gxemul_cons.c 314667 2017-03-04 13:03:31Z avg $");
33 
34 #include <sys/param.h>
35 #include <sys/cons.h>
36 #include <sys/endian.h>
37 #include <sys/kdb.h>
38 #include <sys/systm.h>
39 #include <sys/kernel.h>
40 #include <sys/reboot.h>
41 #include <sys/tty.h>
42 
43 #include <ddb/ddb.h>
44 
45 #include <machine/cpuregs.h>
46 
47 #define	GC_LOCK_INIT()		mtx_init(&gc_lock, "gc_lock", NULL, MTX_SPIN)
48 
49 #define	GC_LOCK() do {							\
50 	if (!kdb_active)						\
51 		mtx_lock_spin(&gc_lock);				\
52 } while (0)
53 
54 #define	GC_LOCK_ASSERT() do {						\
55 	if (!kdb_active)						\
56 		mtx_assert(&gc_lock, MA_OWNED);				\
57 } while (0)
58 
59 #define	GC_UNLOCK() do {						\
60 	if (!kdb_active)						\
61 		mtx_unlock_spin(&gc_lock);				\
62 } while (0)
63 
64 
65 static struct mtx	gc_lock;
66 
67 /*
68  * Low-level console driver functions.
69  */
70 static cn_probe_t	gxemul_cons_cnprobe;
71 static cn_init_t	gxemul_cons_cninit;
72 static cn_term_t	gxemul_cons_cnterm;
73 static cn_getc_t	gxemul_cons_cngetc;
74 static cn_putc_t	gxemul_cons_cnputc;
75 static cn_grab_t	gxemul_cons_cngrab;
76 static cn_ungrab_t	gxemul_cons_cnungrab;
77 
78 /*
79  * TTY-level fields.
80  */
81 static tsw_outwakeup_t	gxemul_cons_outwakeup;
82 
83 static struct ttydevsw gxemul_cons_ttydevsw = {
84 	.tsw_flags	= TF_NOPREFIX,
85 	.tsw_outwakeup	= gxemul_cons_outwakeup,
86 };
87 
88 static struct callout	gxemul_cons_callout;
89 static u_int		gxemul_cons_polltime = 10;
90 #ifdef KDB
91 static int		gxemul_cons_alt_break_state;
92 #endif
93 
94 static void		gxemul_cons_timeout(void *);
95 
96 /*
97  * I/O routines lifted from Deimos.
98  *
99  * XXXRW: Should be using FreeBSD's bus routines here, but they are not
100  * available until later in the boot.
101  */
102 
103 static inline vm_offset_t
mips_phys_to_uncached(vm_paddr_t phys)104 mips_phys_to_uncached(vm_paddr_t phys)
105 {
106 
107 	return (MIPS_PHYS_TO_DIRECT_UNCACHED(phys));
108 }
109 
110 static inline uint8_t
mips_ioread_uint8(vm_offset_t vaddr)111 mips_ioread_uint8(vm_offset_t vaddr)
112 {
113 	uint8_t v;
114 
115 	__asm__ __volatile__ ("lbu %0, 0(%1)" : "=r" (v) : "r" (vaddr));
116 	return (v);
117 }
118 
119 static inline void
mips_iowrite_uint8(vm_offset_t vaddr,uint8_t v)120 mips_iowrite_uint8(vm_offset_t vaddr, uint8_t v)
121 {
122 
123 	__asm__ __volatile__ ("sb %0, 0(%1)" : : "r" (v), "r" (vaddr));
124 }
125 
126 /*
127  * gxemul-specific constants.
128  */
129 #define	GXEMUL_CONS_BASE	0x10000000	/* gxemul console device. */
130 
131 /*
132  * Routines for interacting with the gxemul test console.  Programming details
133  * are a result of manually inspecting the source code for gxemul's
134  * dev_cons.cc and dev_cons.h.
135  *
136  * Offsets of I/O channels relative to the base.
137  */
138 #define	GXEMUL_PUTGETCHAR_OFF		0x00000000
139 #define	GXEMUL_CONS_HALT		0x00000010
140 
141 /*
142  * One-byte buffer as we can't check whether the console is readable without
143  * actually reading from it.
144  */
145 static char	buffer_data;
146 static int	buffer_valid;
147 
148 /*
149  * Low-level read and write routines.
150  */
151 static inline uint8_t
gxemul_cons_data_read(void)152 gxemul_cons_data_read(void)
153 {
154 
155 	return (mips_ioread_uint8(mips_phys_to_uncached(GXEMUL_CONS_BASE +
156 	    GXEMUL_PUTGETCHAR_OFF)));
157 }
158 
159 static inline void
gxemul_cons_data_write(uint8_t v)160 gxemul_cons_data_write(uint8_t v)
161 {
162 
163 	mips_iowrite_uint8(mips_phys_to_uncached(GXEMUL_CONS_BASE +
164 	    GXEMUL_PUTGETCHAR_OFF), v);
165 }
166 
167 static int
gxemul_cons_writable(void)168 gxemul_cons_writable(void)
169 {
170 
171 	return (1);
172 }
173 
174 static int
gxemul_cons_readable(void)175 gxemul_cons_readable(void)
176 {
177 	uint32_t v;
178 
179 	GC_LOCK_ASSERT();
180 
181 	if (buffer_valid)
182 		return (1);
183 	v = gxemul_cons_data_read();
184 	if (v != 0) {
185 		buffer_valid = 1;
186 		buffer_data = v;
187 		return (1);
188 	}
189 	return (0);
190 }
191 
192 static void
gxemul_cons_write(char ch)193 gxemul_cons_write(char ch)
194 {
195 
196 	GC_LOCK_ASSERT();
197 
198 	while (!gxemul_cons_writable());
199 	gxemul_cons_data_write(ch);
200 }
201 
202 static char
gxemul_cons_read(void)203 gxemul_cons_read(void)
204 {
205 
206 	GC_LOCK_ASSERT();
207 
208 	while (!gxemul_cons_readable());
209 	buffer_valid = 0;
210 	return (buffer_data);
211 }
212 
213 /*
214  * Implementation of a FreeBSD low-level, polled console driver.
215  */
216 static void
gxemul_cons_cnprobe(struct consdev * cp)217 gxemul_cons_cnprobe(struct consdev *cp)
218 {
219 
220 	sprintf(cp->cn_name, "ttyu0");
221 	cp->cn_pri = (boothowto & RB_SERIAL) ? CN_REMOTE : CN_NORMAL;
222 }
223 
224 static void
gxemul_cons_cninit(struct consdev * cp)225 gxemul_cons_cninit(struct consdev *cp)
226 {
227 
228 	GC_LOCK_INIT();
229 }
230 
231 static void
gxemul_cons_cnterm(struct consdev * cp)232 gxemul_cons_cnterm(struct consdev *cp)
233 {
234 
235 }
236 
237 static int
gxemul_cons_cngetc(struct consdev * cp)238 gxemul_cons_cngetc(struct consdev *cp)
239 {
240 	int ret;
241 
242 	GC_LOCK();
243 	ret = gxemul_cons_read();
244 	GC_UNLOCK();
245 	return (ret);
246 }
247 
248 static void
gxemul_cons_cnputc(struct consdev * cp,int c)249 gxemul_cons_cnputc(struct consdev *cp, int c)
250 {
251 
252 	GC_LOCK();
253 	gxemul_cons_write(c);
254 	GC_UNLOCK();
255 }
256 
257 static void
gxemul_cons_cngrab(struct consdev * cp)258 gxemul_cons_cngrab(struct consdev *cp)
259 {
260 
261 }
262 
263 static void
gxemul_cons_cnungrab(struct consdev * cp)264 gxemul_cons_cnungrab(struct consdev *cp)
265 {
266 
267 }
268 
269 CONSOLE_DRIVER(gxemul_cons);
270 
271 /*
272  * TTY-level functions for gxemul_cons.
273  */
274 static void
gxemul_cons_ttyinit(void * unused)275 gxemul_cons_ttyinit(void *unused)
276 {
277 	struct tty *tp;
278 
279 	tp = tty_alloc(&gxemul_cons_ttydevsw, NULL);
280 	tty_init_console(tp, 0);
281 	tty_makedev(tp, NULL, "%s", "ttyu0");
282 	callout_init(&gxemul_cons_callout, 1);
283 	callout_reset(&gxemul_cons_callout, gxemul_cons_polltime,
284 	    gxemul_cons_timeout, tp);
285 
286 }
287 SYSINIT(gxemul_cons_ttyinit, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE,
288     gxemul_cons_ttyinit, NULL);
289 
290 static void
gxemul_cons_outwakeup(struct tty * tp)291 gxemul_cons_outwakeup(struct tty *tp)
292 {
293 	int len;
294 	u_char ch;
295 
296 	/*
297 	 * XXXRW: Would be nice not to do blocking writes to the console here,
298 	 * rescheduling on our timer tick if work remains to be done..
299 	 */
300 	for (;;) {
301 		len = ttydisc_getc(tp, &ch, sizeof(ch));
302 		if (len == 0)
303 			break;
304 		GC_LOCK();
305 		gxemul_cons_write(ch);
306 		GC_UNLOCK();
307 	}
308 }
309 
310 static void
gxemul_cons_timeout(void * v)311 gxemul_cons_timeout(void *v)
312 {
313 	struct tty *tp;
314 	int c;
315 
316 	tp = v;
317 	tty_lock(tp);
318 	GC_LOCK();
319 	while (gxemul_cons_readable()) {
320 		c = gxemul_cons_read();
321 		GC_UNLOCK();
322 #ifdef KDB
323 		kdb_alt_break(c, &gxemul_cons_alt_break_state);
324 #endif
325 		ttydisc_rint(tp, c, 0);
326 		GC_LOCK();
327 	}
328 	GC_UNLOCK();
329 	ttydisc_rint_done(tp);
330 	tty_unlock(tp);
331 	callout_reset(&gxemul_cons_callout, gxemul_cons_polltime,
332 	    gxemul_cons_timeout, tp);
333 }
334