xref: /freebsd/sys/dev/dcons/dcons.c (revision 6b3455a7665208c366849f0b2b3bc916fb97516e)
1 /*
2  * Copyright (C) 2003
3  * 	Hidetoshi Shimokawa. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *
16  *	This product includes software developed by Hidetoshi Shimokawa.
17  *
18  * 4. Neither the name of the author nor the names of its contributors
19  *    may be used to endorse or promote products derived from this software
20  *    without specific prior written permission.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $Id: dcons.c,v 1.65 2003/10/24 03:24:55 simokawa Exp $
35  * $FreeBSD$
36  */
37 
38 #include <sys/param.h>
39 #if __FreeBSD_version >= 502122
40 #include <sys/kdb.h>
41 #include <gdb/gdb.h>
42 #endif
43 #include <sys/kernel.h>
44 #include <sys/module.h>
45 #include <sys/systm.h>
46 #include <sys/types.h>
47 #include <sys/conf.h>
48 #include <sys/cons.h>
49 #include <sys/consio.h>
50 #include <sys/tty.h>
51 #include <sys/malloc.h>
52 #include <sys/proc.h>
53 #include <sys/ucred.h>
54 
55 #include <machine/bus.h>
56 
57 #include <dev/dcons/dcons.h>
58 
59 #include <ddb/ddb.h>
60 #include <sys/reboot.h>
61 
62 #include <sys/sysctl.h>
63 
64 #include "opt_ddb.h"
65 #include "opt_comconsole.h"
66 #include "opt_dcons.h"
67 
68 #ifndef DCONS_POLL_HZ
69 #define DCONS_POLL_HZ	100
70 #endif
71 
72 #ifndef DCONS_BUF_SIZE
73 #define DCONS_BUF_SIZE (16*1024)
74 #endif
75 
76 #ifndef DCONS_FORCE_CONSOLE
77 #define DCONS_FORCE_CONSOLE	0	/* mostly for FreeBSD-4 */
78 #endif
79 
80 #ifndef DCONS_FORCE_GDB
81 #define DCONS_FORCE_GDB	1
82 #endif
83 
84 #if __FreeBSD_version >= 500101
85 #define CONS_NODEV	1
86 #if __FreeBSD_version < 502122
87 static struct consdev gdbconsdev;
88 #endif
89 #endif
90 
91 
92 static d_open_t		dcons_open;
93 static d_close_t	dcons_close;
94 
95 static struct cdevsw dcons_cdevsw = {
96 #if __FreeBSD_version >= 500104
97 	.d_version =	D_VERSION,
98 	.d_open =	dcons_open,
99 	.d_close =	dcons_close,
100 	.d_name =	"dcons",
101 	.d_flags =	D_TTY | D_NEEDGIANT,
102 #else
103 	/* open */	dcons_open,
104 	/* close */	dcons_close,
105 	/* read */	ttyread,
106 	/* write */	ttywrite,
107 	/* ioctl */	dcons_ioctl,
108 	/* poll */	ttypoll,
109 	/* mmap */	nommap,
110 	/* strategy */	nostrategy,
111 	/* name */	"dcons",
112 	/* major */	CDEV_MAJOR,
113 	/* dump */	nodump,
114 	/* psize */	nopsize,
115 	/* flags */	0,
116 #endif
117 };
118 
119 #ifndef KLD_MODULE
120 static char bssbuf[DCONS_BUF_SIZE];	/* buf in bss */
121 #endif
122 
123 /* global data */
124 static struct dcons_global dg;
125 struct dcons_global *dcons_conf;
126 static int poll_hz = DCONS_POLL_HZ;
127 
128 SYSCTL_NODE(_kern, OID_AUTO, dcons, CTLFLAG_RD, 0, "Dumb Console");
129 SYSCTL_INT(_kern_dcons, OID_AUTO, poll_hz, CTLFLAG_RW, &poll_hz, 0,
130 				"dcons polling rate");
131 
132 static int drv_init = 0;
133 static struct callout dcons_callout;
134 struct dcons_buf *dcons_buf;		/* for local dconschat */
135 
136 /* per device data */
137 static struct dcons_softc {
138 	struct cdev *dev;
139 	struct dcons_ch	o, i;
140 	int brk_state;
141 #define DC_GDB	1
142 	int flags;
143 } sc[DCONS_NPORT];
144 static void	dcons_tty_start(struct tty *);
145 static int	dcons_tty_param(struct tty *, struct termios *);
146 static void	dcons_timeout(void *);
147 static int	dcons_drv_init(int);
148 static int	dcons_getc(struct dcons_softc *);
149 static int	dcons_checkc(struct dcons_softc *);
150 static void	dcons_putc(struct dcons_softc *, int);
151 
152 static cn_probe_t	dcons_cnprobe;
153 static cn_init_t	dcons_cninit;
154 static cn_getc_t	dcons_cngetc;
155 static cn_checkc_t 	dcons_cncheckc;
156 static cn_putc_t	dcons_cnputc;
157 
158 CONS_DRIVER(dcons, dcons_cnprobe, dcons_cninit, NULL, dcons_cngetc,
159     dcons_cncheckc, dcons_cnputc, NULL);
160 
161 #if __FreeBSD_version >= 502122
162 static gdb_probe_f dcons_dbg_probe;
163 static gdb_init_f dcons_dbg_init;
164 static gdb_term_f dcons_dbg_term;
165 static gdb_getc_f dcons_dbg_getc;
166 static gdb_checkc_f dcons_dbg_checkc;
167 static gdb_putc_f dcons_dbg_putc;
168 
169 GDB_DBGPORT(dcons, dcons_dbg_probe, dcons_dbg_init, dcons_dbg_term,
170     dcons_dbg_checkc, dcons_dbg_getc, dcons_dbg_putc);
171 
172 extern struct gdb_dbgport *gdb_cur;
173 #endif
174 
175 #if __FreeBSD_version < 500000
176 #define THREAD	proc
177 #else
178 #define THREAD	thread
179 #endif
180 
181 static int
182 dcons_open(struct cdev *dev, int flag, int mode, struct THREAD *td)
183 {
184 	struct tty *tp;
185 	int unit, error, s;
186 
187 	unit = minor(dev);
188 	if (unit != 0)
189 		return (ENXIO);
190 
191 	tp = dev->si_tty = ttymalloc(dev->si_tty);
192 	tp->t_oproc = dcons_tty_start;
193 	tp->t_param = dcons_tty_param;
194 	tp->t_stop = nottystop;
195 	tp->t_dev = dev;
196 
197 	error = 0;
198 
199 	s = spltty();
200 	if ((tp->t_state & TS_ISOPEN) == 0) {
201 		tp->t_state |= TS_CARR_ON;
202 		ttychars(tp);
203 		tp->t_iflag = TTYDEF_IFLAG;
204 		tp->t_oflag = TTYDEF_OFLAG;
205 		tp->t_cflag = TTYDEF_CFLAG|CLOCAL;
206 		tp->t_lflag = TTYDEF_LFLAG;
207 		tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
208 		ttsetwater(tp);
209 	} else if ((tp->t_state & TS_XCLUDE) && suser(td)) {
210 		splx(s);
211 		return (EBUSY);
212 	}
213 	splx(s);
214 
215 	error = ttyld_open(tp, dev);
216 
217 	return (error);
218 }
219 
220 static int
221 dcons_close(struct cdev *dev, int flag, int mode, struct THREAD *td)
222 {
223 	int	unit;
224 	struct	tty *tp;
225 
226 	unit = minor(dev);
227 	if (unit != 0)
228 		return (ENXIO);
229 
230 	tp = dev->si_tty;
231 	if (tp->t_state & TS_ISOPEN) {
232 		ttyld_close(tp, flag);
233 		tty_close(tp);
234 	}
235 
236 	return (0);
237 }
238 
239 static int
240 dcons_tty_param(struct tty *tp, struct termios *t)
241 {
242 	tp->t_ispeed = t->c_ispeed;
243 	tp->t_ospeed = t->c_ospeed;
244 	tp->t_cflag = t->c_cflag;
245 	return 0;
246 }
247 
248 static void
249 dcons_tty_start(struct tty *tp)
250 {
251 	struct dcons_softc *dc;
252 	int s;
253 
254 	dc = (struct dcons_softc *)tp->t_dev->si_drv1;
255 	s = spltty();
256 	if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
257 		ttwwakeup(tp);
258 		return;
259 	}
260 
261 	tp->t_state |= TS_BUSY;
262 	while (tp->t_outq.c_cc != 0)
263 		dcons_putc(dc, getc(&tp->t_outq));
264 	tp->t_state &= ~TS_BUSY;
265 
266 	ttwwakeup(tp);
267 	splx(s);
268 }
269 
270 static void
271 dcons_timeout(void *v)
272 {
273 	struct	tty *tp;
274 	struct dcons_softc *dc;
275 	int i, c, polltime;
276 
277 	for (i = 0; i < DCONS_NPORT; i ++) {
278 		dc = &sc[i];
279 		tp = dc->dev->si_tty;
280 		while ((c = dcons_checkc(dc)) != -1)
281 			if (tp->t_state & TS_ISOPEN)
282 				ttyld_rint(tp, c);
283 	}
284 	polltime = hz / poll_hz;
285 	if (polltime < 1)
286 		polltime = 1;
287 	callout_reset(&dcons_callout, polltime, dcons_timeout, tp);
288 }
289 
290 static void
291 dcons_cnprobe(struct consdev *cp)
292 {
293 #if __FreeBSD_version >= 501109
294 	sprintf(cp->cn_name, "dcons");
295 #else
296 	cp->cn_dev = makedev(CDEV_MAJOR, DCONS_CON);
297 #endif
298 #if DCONS_FORCE_CONSOLE
299 	cp->cn_pri = CN_REMOTE;
300 #else
301 	cp->cn_pri = CN_NORMAL;
302 #endif
303 }
304 
305 static void
306 dcons_cninit(struct consdev *cp)
307 {
308 	dcons_drv_init(0);
309 #if CONS_NODEV
310 	cp->cn_arg
311 #else
312 	cp->cn_dev->si_drv1
313 #endif
314 		= (void *)&sc[DCONS_CON]; /* share port0 with unit0 */
315 }
316 
317 #if CONS_NODEV
318 static int
319 dcons_cngetc(struct consdev *cp)
320 {
321 	return(dcons_getc((struct dcons_softc *)cp->cn_arg));
322 }
323 static int
324 dcons_cncheckc(struct consdev *cp)
325 {
326 	return(dcons_checkc((struct dcons_softc *)cp->cn_arg));
327 }
328 static void
329 dcons_cnputc(struct consdev *cp, int c)
330 {
331 	dcons_putc((struct dcons_softc *)cp->cn_arg, c);
332 }
333 #else
334 static int
335 dcons_cngetc(struct cdev *dev)
336 {
337 	return(dcons_getc((struct dcons_softc *)dev->si_drv1));
338 }
339 static int
340 dcons_cncheckc(struct cdev *dev)
341 {
342 	return(dcons_checkc((struct dcons_softc *)dev->si_drv1));
343 }
344 static void
345 dcons_cnputc(struct cdev *dev, int c)
346 {
347 	dcons_putc((struct dcons_softc *)dev->si_drv1, c);
348 }
349 #endif
350 
351 static int
352 dcons_getc(struct dcons_softc *dc)
353 {
354 	int c;
355 
356 	while ((c = dcons_checkc(dc)) == -1);
357 
358 	return (c & 0xff);
359 }
360 
361 static int
362 dcons_checkc(struct dcons_softc *dc)
363 {
364 	unsigned char c;
365 	u_int32_t ptr, pos, gen, next_gen;
366 	struct dcons_ch *ch;
367 
368 	ch = &dc->i;
369 
370 	if (dg.dma_tag != NULL)
371 		bus_dmamap_sync(dg.dma_tag, dg.dma_map, BUS_DMASYNC_POSTREAD);
372 	ptr = ntohl(*ch->ptr);
373 	gen = ptr >> DCONS_GEN_SHIFT;
374 	pos = ptr & DCONS_POS_MASK;
375 	if (gen == ch->gen && pos == ch->pos)
376 		return (-1);
377 
378 	next_gen = DCONS_NEXT_GEN(ch->gen);
379 	/* XXX sanity check */
380 	if ((gen != ch->gen && gen != next_gen)
381 			|| (gen == ch->gen && pos < ch->pos)) {
382 		/* generation skipped !! */
383 		/* XXX discard */
384 		ch->gen = gen;
385 		ch->pos = pos;
386 		return (-1);
387 	}
388 
389 	c = ch->buf[ch->pos];
390 	ch->pos ++;
391 	if (ch->pos >= ch->size) {
392 		ch->gen = next_gen;
393 		ch->pos = 0;
394 	}
395 
396 #if __FreeBSD_version >= 502122
397 #if KDB && ALT_BREAK_TO_DEBUGGER
398 	if (kdb_alt_break(c, &dc->brk_state)) {
399 		if ((dc->flags & DC_GDB) != 0) {
400 			if (gdb_cur == &dcons_gdb_dbgport) {
401 				kdb_dbbe_select("gdb");
402 				breakpoint();
403 			}
404 		} else
405 			breakpoint();
406 	}
407 #endif
408 #else
409 #if DDB && ALT_BREAK_TO_DEBUGGER
410 	switch (dc->brk_state) {
411 	case STATE1:
412 		if (c == KEY_TILDE)
413 			dc->brk_state = STATE2;
414 		else
415 			dc->brk_state = STATE0;
416 		break;
417 	case STATE2:
418 		dc->brk_state = STATE0;
419 		if (c == KEY_CTRLB) {
420 #if DCONS_FORCE_GDB
421 			if (dc->flags & DC_GDB)
422 				boothowto |= RB_GDB;
423 #endif
424 			breakpoint();
425 		}
426 	}
427 	if (c == KEY_CR)
428 		dc->brk_state = STATE1;
429 #endif
430 #endif
431 	return (c);
432 }
433 
434 static void
435 dcons_putc(struct dcons_softc *dc, int c)
436 {
437 	struct dcons_ch *ch;
438 
439 	ch = &dc->o;
440 
441 	ch->buf[ch->pos] = c;
442 	ch->pos ++;
443 	if (ch->pos >= ch->size) {
444 		ch->gen = DCONS_NEXT_GEN(ch->gen);
445 		ch->pos = 0;
446 	}
447 	*ch->ptr = DCONS_MAKE_PTR(ch);
448 	if (dg.dma_tag != NULL)
449 		bus_dmamap_sync(dg.dma_tag, dg.dma_map, BUS_DMASYNC_PREWRITE);
450 }
451 
452 static int
453 dcons_init_port(int port, int offset, int size)
454 {
455 	int osize;
456 	struct dcons_softc *dc;
457 
458 	dc = &sc[port];
459 
460 	osize = size * 3 / 4;
461 
462 	dc->o.size = osize;
463 	dc->i.size = size - osize;
464 	dc->o.buf = (char *)dg.buf + offset;
465 	dc->i.buf = dc->o.buf + osize;
466 	dc->o.gen = dc->i.gen = 0;
467 	dc->o.pos = dc->i.pos = 0;
468 	dc->o.ptr = &dg.buf->optr[port];
469 	dc->i.ptr = &dg.buf->iptr[port];
470 	dc->brk_state = STATE0;
471 	dg.buf->osize[port] = htonl(osize);
472 	dg.buf->isize[port] = htonl(size - osize);
473 	dg.buf->ooffset[port] = htonl(offset);
474 	dg.buf->ioffset[port] = htonl(offset + osize);
475 	dg.buf->optr[port] = DCONS_MAKE_PTR(&dc->o);
476 	dg.buf->iptr[port] = DCONS_MAKE_PTR(&dc->i);
477 
478 	return(0);
479 }
480 
481 static int
482 dcons_drv_init(int stage)
483 {
484 	int size, size0, offset;
485 
486 	if (drv_init)
487 		return(drv_init);
488 
489 	drv_init = -1;
490 
491 	bzero(&dg, sizeof(dg));
492 	dcons_conf = &dg;
493 	dg.cdev = &dcons_consdev;
494 	dg.size = DCONS_BUF_SIZE;
495 
496 #ifndef KLD_MODULE
497 	if (stage == 0) /* XXX or cold */
498 		/*
499 		 * DCONS_FORCE_CONSOLE == 1 and statically linked.
500 		 * called from cninit(). can't use contigmalloc yet .
501 		 */
502 		dg.buf = (struct dcons_buf *) bssbuf;
503 	else
504 #endif
505 		/*
506 		 * DCONS_FORCE_CONSOLE == 0 or kernel module case.
507 		 * if the module is loaded after boot,
508 		 * bssbuf could be non-continuous.
509 		 */
510 		dg.buf = (struct dcons_buf *) contigmalloc(dg.size,
511 			M_DEVBUF, 0, 0x10000, 0xffffffff, PAGE_SIZE, 0ul);
512 
513 	dcons_buf = dg.buf;
514 	offset = DCONS_HEADER_SIZE;
515 	size = (dg.size - offset);
516 	size0 = size * 3 / 4;
517 
518 	dcons_init_port(0, offset, size0);
519 	offset += size0;
520 	dcons_init_port(1, offset, size - size0);
521 	dg.buf->version = htonl(DCONS_VERSION);
522 	dg.buf->magic = ntohl(DCONS_MAGIC);
523 
524 #if __FreeBSD_version < 502122
525 #if DDB && DCONS_FORCE_GDB
526 #if CONS_NODEV
527 	gdbconsdev.cn_arg = (void *)&sc[DCONS_GDB];
528 #if __FreeBSD_version >= 501109
529 	sprintf(gdbconsdev.cn_name, "dgdb");
530 #endif
531 	gdb_arg = &gdbconsdev;
532 #else
533 	gdbdev = makedev(CDEV_MAJOR, DCONS_GDB);
534 #endif
535 	gdb_getc = dcons_cngetc;
536 	gdb_putc = dcons_cnputc;
537 #endif
538 #endif
539 	drv_init = 1;
540 
541 	return 0;
542 }
543 
544 
545 static int
546 dcons_attach_port(int port, char *name, int flags)
547 {
548 	struct dcons_softc *dc;
549 	struct tty *tp;
550 
551 	dc = &sc[port];
552 	dc->flags = flags;
553 	dc->dev = make_dev(&dcons_cdevsw, port,
554 			UID_ROOT, GID_WHEEL, 0600, name);
555 	tp = ttymalloc(NULL);
556 
557 	dc->dev->si_drv1 = (void *)dc;
558 	dc->dev->si_tty = tp;
559 
560 	tp->t_oproc = dcons_tty_start;
561 	tp->t_param = dcons_tty_param;
562 	tp->t_stop = nottystop;
563 	tp->t_dev = dc->dev;
564 
565 	return(0);
566 }
567 
568 static int
569 dcons_attach(void)
570 {
571 	int polltime;
572 
573 	dcons_attach_port(DCONS_CON, "dcons", 0);
574 	dcons_attach_port(DCONS_GDB, "dgdb", DC_GDB);
575 #if __FreeBSD_version < 500000
576 	callout_init(&dcons_callout);
577 #else
578 	callout_init(&dcons_callout, 0);
579 #endif
580 	polltime = hz / poll_hz;
581 	if (polltime < 1)
582 		polltime = 1;
583 	callout_reset(&dcons_callout, polltime, dcons_timeout, NULL);
584 	return(0);
585 }
586 
587 static int
588 dcons_detach(int port)
589 {
590 	struct	tty *tp;
591 	struct dcons_softc *dc;
592 
593 	dc = &sc[port];
594 
595 	tp = dc->dev->si_tty;
596 
597 	if (tp->t_state & TS_ISOPEN) {
598 		printf("dcons: still opened\n");
599 		ttyld_close(tp, 0);
600 		tty_close(tp);
601 	}
602 	/* XXX
603 	 * must wait until all device are closed.
604 	 */
605 	tsleep((void *)dc, PWAIT, "dcodtc", hz/4);
606 	destroy_dev(dc->dev);
607 
608 	return(0);
609 }
610 
611 
612 /* cnXXX works only for FreeBSD-5 */
613 static int
614 dcons_modevent(module_t mode, int type, void *data)
615 {
616 	int err = 0, ret;
617 
618 	switch (type) {
619 	case MOD_LOAD:
620 		ret = dcons_drv_init(1);
621 		dcons_attach();
622 #if __FreeBSD_version >= 500000
623 		if (ret == 0) {
624 			dcons_cnprobe(&dcons_consdev);
625 			dcons_cninit(&dcons_consdev);
626 			cnadd(&dcons_consdev);
627 		}
628 #endif
629 		break;
630 	case MOD_UNLOAD:
631 		printf("dcons: unload\n");
632 		callout_stop(&dcons_callout);
633 #if __FreeBSD_version < 502122
634 #if DDB && DCONS_FORCE_GDB
635 #if CONS_NODEV
636 		gdb_arg = NULL;
637 #else
638 		gdbdev = NULL;
639 #endif
640 #endif
641 #endif
642 #if __FreeBSD_version >= 500000
643 		cnremove(&dcons_consdev);
644 #endif
645 		dcons_detach(DCONS_CON);
646 		dcons_detach(DCONS_GDB);
647 		dg.buf->magic = 0;
648 
649 		contigfree(dg.buf, DCONS_BUF_SIZE, M_DEVBUF);
650 
651 		break;
652 	case MOD_SHUTDOWN:
653 		break;
654 	default:
655 		err = EOPNOTSUPP;
656 		break;
657 	}
658 	return(err);
659 }
660 
661 #if __FreeBSD_version >= 502122
662 /* Debugger interface */
663 
664 static int
665 dcons_dbg_probe(void)
666 {
667 	return(DCONS_FORCE_GDB);
668 }
669 
670 static void
671 dcons_dbg_init(void)
672 {
673 }
674 
675 static void
676 dcons_dbg_term(void)
677 {
678 }
679 
680 static void
681 dcons_dbg_putc(int c)
682 {
683 	dcons_putc(&sc[DCONS_GDB], c);
684 }
685 
686 static int
687 dcons_dbg_checkc(void)
688 {
689 	return (dcons_checkc(&sc[DCONS_GDB]));
690 }
691 
692 static int
693 dcons_dbg_getc(void)
694 {
695 	return (dcons_getc(&sc[DCONS_GDB]));
696 }
697 #endif
698 
699 DEV_MODULE(dcons, dcons_modevent, NULL);
700 MODULE_VERSION(dcons, DCONS_VERSION);
701