xref: /freebsd/sys/dev/dcons/dcons.c (revision dc08ffec)
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 #include <sys/kernel.h>
40 #include <sys/systm.h>
41 #include <sys/types.h>
42 #include <sys/conf.h>
43 #include <sys/cons.h>
44 #include <sys/consio.h>
45 #include <sys/tty.h>
46 #include <sys/malloc.h>
47 #include <sys/proc.h>
48 #include <sys/ucred.h>
49 
50 #include <machine/bus.h>
51 
52 #include <dev/dcons/dcons.h>
53 
54 #include <ddb/ddb.h>
55 #include <sys/reboot.h>
56 
57 #include <sys/sysctl.h>
58 
59 #include "opt_ddb.h"
60 #include "opt_comconsole.h"
61 #include "opt_dcons.h"
62 
63 #ifndef DCONS_POLL_HZ
64 #define DCONS_POLL_HZ	100
65 #endif
66 
67 #ifndef DCONS_BUF_SIZE
68 #define DCONS_BUF_SIZE (16*1024)
69 #endif
70 
71 #ifndef DCONS_FORCE_CONSOLE
72 #define DCONS_FORCE_CONSOLE	0	/* mostly for FreeBSD-4 */
73 #endif
74 
75 #ifndef DCONS_FORCE_GDB
76 #define DCONS_FORCE_GDB	1
77 #endif
78 
79 #if __FreeBSD_version >= 500101
80 #define CONS_NODEV	1	/* for latest current */
81 static struct consdev gdbconsdev;
82 #endif
83 
84 
85 static d_open_t		dcons_open;
86 static d_close_t	dcons_close;
87 static d_ioctl_t	dcons_ioctl;
88 
89 static struct cdevsw dcons_cdevsw = {
90 #if __FreeBSD_version >= 500104
91 	.d_version =	D_VERSION,
92 	.d_open =	dcons_open,
93 	.d_close =	dcons_close,
94 	.d_ioctl =	dcons_ioctl,
95 	.d_name =	"dcons",
96 	.d_flags =	D_TTY | D_NEEDGIANT,
97 #else
98 	/* open */	dcons_open,
99 	/* close */	dcons_close,
100 	/* read */	ttyread,
101 	/* write */	ttywrite,
102 	/* ioctl */	dcons_ioctl,
103 	/* poll */	ttypoll,
104 	/* mmap */	nommap,
105 	/* strategy */	nostrategy,
106 	/* name */	"dcons",
107 	/* major */	CDEV_MAJOR,
108 	/* dump */	nodump,
109 	/* psize */	nopsize,
110 	/* flags */	0,
111 #endif
112 };
113 
114 #ifndef KLD_MODULE
115 static char bssbuf[DCONS_BUF_SIZE];	/* buf in bss */
116 #endif
117 
118 /* global data */
119 static struct dcons_global dg;
120 struct dcons_global *dcons_conf;
121 static int poll_hz = DCONS_POLL_HZ;
122 
123 SYSCTL_NODE(_kern, OID_AUTO, dcons, CTLFLAG_RD, 0, "Dumb Console");
124 SYSCTL_INT(_kern_dcons, OID_AUTO, poll_hz, CTLFLAG_RW, &poll_hz, 0,
125 				"dcons polling rate");
126 
127 static int drv_init = 0;
128 static struct callout dcons_callout;
129 struct dcons_buf *dcons_buf;		/* for local dconschat */
130 
131 /* per device data */
132 static struct dcons_softc {
133 	dev_t dev;
134 	struct dcons_ch	o, i;
135 	int brk_state;
136 #define DC_GDB	1
137 	int flags;
138 } sc[DCONS_NPORT];
139 static void	dcons_tty_start(struct tty *);
140 static int	dcons_tty_param(struct tty *, struct termios *);
141 static void	dcons_timeout(void *);
142 static int	dcons_drv_init(int);
143 static int	dcons_getc(struct dcons_softc *);
144 static int	dcons_checkc(struct dcons_softc *);
145 static void	dcons_putc(struct dcons_softc *, int);
146 
147 static cn_probe_t	dcons_cnprobe;
148 static cn_init_t	dcons_cninit;
149 static cn_getc_t	dcons_cngetc;
150 static cn_checkc_t 	dcons_cncheckc;
151 static cn_putc_t	dcons_cnputc;
152 
153 CONS_DRIVER(dcons, dcons_cnprobe, dcons_cninit, NULL, dcons_cngetc,
154     dcons_cncheckc, dcons_cnputc, NULL);
155 
156 #if __FreeBSD_version < 500000
157 #define THREAD	proc
158 #else
159 #define THREAD	thread
160 #endif
161 
162 static int
163 dcons_open(dev_t dev, int flag, int mode, struct THREAD *td)
164 {
165 	struct tty *tp;
166 	int unit, error, s;
167 
168 	unit = minor(dev);
169 	if (unit != 0)
170 		return (ENXIO);
171 
172 	tp = dev->si_tty = ttymalloc(dev->si_tty);
173 	tp->t_oproc = dcons_tty_start;
174 	tp->t_param = dcons_tty_param;
175 	tp->t_stop = nottystop;
176 	tp->t_dev = dev;
177 
178 	error = 0;
179 
180 	s = spltty();
181 	if ((tp->t_state & TS_ISOPEN) == 0) {
182 		tp->t_state |= TS_CARR_ON;
183 		ttychars(tp);
184 		tp->t_iflag = TTYDEF_IFLAG;
185 		tp->t_oflag = TTYDEF_OFLAG;
186 		tp->t_cflag = TTYDEF_CFLAG|CLOCAL;
187 		tp->t_lflag = TTYDEF_LFLAG;
188 		tp->t_ispeed = tp->t_ospeed = TTYDEF_SPEED;
189 		ttsetwater(tp);
190 	} else if ((tp->t_state & TS_XCLUDE) && suser(td)) {
191 		splx(s);
192 		return (EBUSY);
193 	}
194 	splx(s);
195 
196 	error = (*linesw[tp->t_line].l_open)(dev, tp);
197 
198 	return (error);
199 }
200 
201 static int
202 dcons_close(dev_t dev, int flag, int mode, struct THREAD *td)
203 {
204 	int	unit;
205 	struct	tty *tp;
206 
207 	unit = minor(dev);
208 	if (unit != 0)
209 		return (ENXIO);
210 
211 	tp = dev->si_tty;
212 	if (tp->t_state & TS_ISOPEN) {
213 		(*linesw[tp->t_line].l_close)(tp, flag);
214 		ttyclose(tp);
215 	}
216 
217 	return (0);
218 }
219 
220 static int
221 dcons_ioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct THREAD *td)
222 {
223 	int	unit;
224 	struct	tty *tp;
225 	int	error;
226 
227 	unit = minor(dev);
228 	if (unit != 0)
229 		return (ENXIO);
230 
231 	tp = dev->si_tty;
232 	error = (*linesw[tp->t_line].l_ioctl)(tp, cmd, data, flag, td);
233 	if (error != ENOIOCTL)
234 		return (error);
235 
236 	error = ttioctl(tp, cmd, data, flag);
237 	if (error != ENOIOCTL)
238 		return (error);
239 
240 	return (ENOTTY);
241 }
242 
243 static int
244 dcons_tty_param(struct tty *tp, struct termios *t)
245 {
246 	tp->t_ispeed = t->c_ispeed;
247 	tp->t_ospeed = t->c_ospeed;
248 	tp->t_cflag = t->c_cflag;
249 	return 0;
250 }
251 
252 static void
253 dcons_tty_start(struct tty *tp)
254 {
255 	struct dcons_softc *dc;
256 	int s;
257 
258 	dc = (struct dcons_softc *)tp->t_dev->si_drv1;
259 	s = spltty();
260 	if (tp->t_state & (TS_TIMEOUT | TS_TTSTOP)) {
261 		ttwwakeup(tp);
262 		return;
263 	}
264 
265 	tp->t_state |= TS_BUSY;
266 	while (tp->t_outq.c_cc != 0)
267 		dcons_putc(dc, getc(&tp->t_outq));
268 	tp->t_state &= ~TS_BUSY;
269 
270 	ttwwakeup(tp);
271 	splx(s);
272 }
273 
274 static void
275 dcons_timeout(void *v)
276 {
277 	struct	tty *tp;
278 	struct dcons_softc *dc;
279 	int i, c, polltime;
280 
281 	for (i = 0; i < DCONS_NPORT; i ++) {
282 		dc = &sc[i];
283 		tp = dc->dev->si_tty;
284 		while ((c = dcons_checkc(dc)) != -1)
285 			if (tp->t_state & TS_ISOPEN)
286 				(*linesw[tp->t_line].l_rint)(c, tp);
287 	}
288 	polltime = hz / poll_hz;
289 	if (polltime < 1)
290 		polltime = 1;
291 	callout_reset(&dcons_callout, polltime, dcons_timeout, tp);
292 }
293 
294 static void
295 dcons_cnprobe(struct consdev *cp)
296 {
297 #if __FreeBSD_version >= 501109
298 	sprintf(cp->cn_name, "dcons");
299 #else
300 	cp->cn_dev = makedev(CDEV_MAJOR, DCONS_CON);
301 #endif
302 #if DCONS_FORCE_CONSOLE
303 	cp->cn_pri = CN_REMOTE;
304 #else
305 	cp->cn_pri = CN_NORMAL;
306 #endif
307 }
308 
309 static void
310 dcons_cninit(struct consdev *cp)
311 {
312 	dcons_drv_init(0);
313 #if CONS_NODEV
314 	cp->cn_arg
315 #else
316 	cp->cn_dev->si_drv1
317 #endif
318 		= (void *)&sc[DCONS_CON]; /* share port0 with unit0 */
319 }
320 
321 #if CONS_NODEV
322 static int
323 dcons_cngetc(struct consdev *cp)
324 {
325 	return(dcons_getc((struct dcons_softc *)cp->cn_arg));
326 }
327 static int
328 dcons_cncheckc(struct consdev *cp)
329 {
330 	return(dcons_checkc((struct dcons_softc *)cp->cn_arg));
331 }
332 static void
333 dcons_cnputc(struct consdev *cp, int c)
334 {
335 	dcons_putc((struct dcons_softc *)cp->cn_arg, c);
336 }
337 #else
338 static int
339 dcons_cngetc(dev_t dev)
340 {
341 	return(dcons_getc((struct dcons_softc *)dev->si_drv1));
342 }
343 static int
344 dcons_cncheckc(dev_t dev)
345 {
346 	return(dcons_checkc((struct dcons_softc *)dev->si_drv1));
347 }
348 static void
349 dcons_cnputc(dev_t dev, int c)
350 {
351 	dcons_putc((struct dcons_softc *)dev->si_drv1, c);
352 }
353 #endif
354 
355 static int
356 dcons_getc(struct dcons_softc *dc)
357 {
358 	int c;
359 
360 	while ((c = dcons_checkc(dc)) == -1);
361 
362 	return (c & 0xff);
363 }
364 
365 static int
366 dcons_checkc(struct dcons_softc *dc)
367 {
368 	unsigned char c;
369 	u_int32_t ptr, pos, gen, next_gen;
370 	struct dcons_ch *ch;
371 
372 	ch = &dc->i;
373 
374 	if (dg.dma_tag != NULL)
375 		bus_dmamap_sync(dg.dma_tag, dg.dma_map, BUS_DMASYNC_POSTREAD);
376 	ptr = ntohl(*ch->ptr);
377 	gen = ptr >> DCONS_GEN_SHIFT;
378 	pos = ptr & DCONS_POS_MASK;
379 	if (gen == ch->gen && pos == ch->pos)
380 		return (-1);
381 
382 	next_gen = DCONS_NEXT_GEN(ch->gen);
383 	/* XXX sanity check */
384 	if ((gen != ch->gen && gen != next_gen)
385 			|| (gen == ch->gen && pos < ch->pos)) {
386 		/* generation skipped !! */
387 		/* XXX discard */
388 		ch->gen = gen;
389 		ch->pos = pos;
390 		return (-1);
391 	}
392 
393 	c = ch->buf[ch->pos];
394 	ch->pos ++;
395 	if (ch->pos >= ch->size) {
396 		ch->gen = next_gen;
397 		ch->pos = 0;
398 	}
399 
400 #if DDB && ALT_BREAK_TO_DEBUGGER
401 	switch (dc->brk_state) {
402 	case STATE1:
403 		if (c == KEY_TILDE)
404 			dc->brk_state = STATE2;
405 		else
406 			dc->brk_state = STATE0;
407 		break;
408 	case STATE2:
409 		dc->brk_state = STATE0;
410 		if (c == KEY_CTRLB) {
411 #if DCONS_FORCE_GDB
412 			if (dc->flags & DC_GDB)
413 				boothowto |= RB_GDB;
414 #endif
415 			breakpoint();
416 		}
417 	}
418 	if (c == KEY_CR)
419 		dc->brk_state = STATE1;
420 #endif
421 	return (c);
422 }
423 
424 static void
425 dcons_putc(struct dcons_softc *dc, int c)
426 {
427 	struct dcons_ch *ch;
428 
429 	ch = &dc->o;
430 
431 	ch->buf[ch->pos] = c;
432 	ch->pos ++;
433 	if (ch->pos >= ch->size) {
434 		ch->gen = DCONS_NEXT_GEN(ch->gen);
435 		ch->pos = 0;
436 	}
437 	*ch->ptr = DCONS_MAKE_PTR(ch);
438 	if (dg.dma_tag != NULL)
439 		bus_dmamap_sync(dg.dma_tag, dg.dma_map, BUS_DMASYNC_PREWRITE);
440 }
441 
442 static int
443 dcons_init_port(int port, int offset, int size)
444 {
445 	int osize;
446 	struct dcons_softc *dc;
447 
448 	dc = &sc[port];
449 
450 	osize = size * 3 / 4;
451 
452 	dc->o.size = osize;
453 	dc->i.size = size - osize;
454 	dc->o.buf = (char *)dg.buf + offset;
455 	dc->i.buf = dc->o.buf + osize;
456 	dc->o.gen = dc->i.gen = 0;
457 	dc->o.pos = dc->i.pos = 0;
458 	dc->o.ptr = &dg.buf->optr[port];
459 	dc->i.ptr = &dg.buf->iptr[port];
460 	dc->brk_state = STATE0;
461 	dg.buf->osize[port] = htonl(osize);
462 	dg.buf->isize[port] = htonl(size - osize);
463 	dg.buf->ooffset[port] = htonl(offset);
464 	dg.buf->ioffset[port] = htonl(offset + osize);
465 	dg.buf->optr[port] = DCONS_MAKE_PTR(&dc->o);
466 	dg.buf->iptr[port] = DCONS_MAKE_PTR(&dc->i);
467 
468 	return(0);
469 }
470 
471 static int
472 dcons_drv_init(int stage)
473 {
474 	int size, size0, offset;
475 
476 	if (drv_init)
477 		return(drv_init);
478 
479 	drv_init = -1;
480 
481 	bzero(&dg, sizeof(dg));
482 	dcons_conf = &dg;
483 	dg.cdev = &dcons_consdev;
484 	dg.size = DCONS_BUF_SIZE;
485 
486 #ifndef KLD_MODULE
487 	if (stage == 0) /* XXX or cold */
488 		/*
489 		 * DCONS_FORCE_CONSOLE == 1 and statically linked.
490 		 * called from cninit(). can't use contigmalloc yet .
491 		 */
492 		dg.buf = (struct dcons_buf *) bssbuf;
493 	else
494 #endif
495 		/*
496 		 * DCONS_FORCE_CONSOLE == 0 or kernel module case.
497 		 * if the module is loaded after boot,
498 		 * bssbuf could be non-continuous.
499 		 */
500 		dg.buf = (struct dcons_buf *) contigmalloc(dg.size,
501 			M_DEVBUF, 0, 0x10000, 0xffffffff, PAGE_SIZE, 0ul);
502 
503 	dcons_buf = dg.buf;
504 	offset = DCONS_HEADER_SIZE;
505 	size = (dg.size - offset);
506 	size0 = size * 3 / 4;
507 
508 	dcons_init_port(0, offset, size0);
509 	offset += size0;
510 	dcons_init_port(1, offset, size - size0);
511 	dg.buf->version = htonl(DCONS_VERSION);
512 	dg.buf->magic = ntohl(DCONS_MAGIC);
513 
514 #if DDB && DCONS_FORCE_GDB
515 #if CONS_NODEV
516 	gdbconsdev.cn_arg = (void *)&sc[DCONS_GDB];
517 #if __FreeBSD_version >= 501109
518 	sprintf(gdbconsdev.cn_name, "dgdb");
519 #endif
520 	gdb_arg = &gdbconsdev;
521 #else
522 	gdbdev = makedev(CDEV_MAJOR, DCONS_GDB);
523 #endif
524 	gdb_getc = dcons_cngetc;
525 	gdb_putc = dcons_cnputc;
526 #endif
527 	drv_init = 1;
528 
529 	return 0;
530 }
531 
532 
533 static int
534 dcons_attach_port(int port, char *name, int flags)
535 {
536 	struct dcons_softc *dc;
537 	struct tty *tp;
538 
539 	dc = &sc[port];
540 	dc->flags = flags;
541 	dc->dev = make_dev(&dcons_cdevsw, port,
542 			UID_ROOT, GID_WHEEL, 0600, name);
543 	tp = ttymalloc(NULL);
544 
545 	dc->dev->si_drv1 = (void *)dc;
546 	dc->dev->si_tty = tp;
547 
548 	tp->t_oproc = dcons_tty_start;
549 	tp->t_param = dcons_tty_param;
550 	tp->t_stop = nottystop;
551 	tp->t_dev = dc->dev;
552 
553 	return(0);
554 }
555 
556 static int
557 dcons_attach(void)
558 {
559 	int polltime;
560 
561 	dcons_attach_port(DCONS_CON, "dcons", 0);
562 	dcons_attach_port(DCONS_GDB, "dgdb", DC_GDB);
563 #if __FreeBSD_version < 500000
564 	callout_init(&dcons_callout);
565 #else
566 	callout_init(&dcons_callout, 0);
567 #endif
568 	polltime = hz / poll_hz;
569 	if (polltime < 1)
570 		polltime = 1;
571 	callout_reset(&dcons_callout, polltime, dcons_timeout, NULL);
572 	return(0);
573 }
574 
575 static int
576 dcons_detach(int port)
577 {
578 	struct	tty *tp;
579 	struct dcons_softc *dc;
580 
581 	dc = &sc[port];
582 
583 	tp = dc->dev->si_tty;
584 
585 	if (tp->t_state & TS_ISOPEN) {
586 		printf("dcons: still opened\n");
587 		(*linesw[tp->t_line].l_close)(tp, 0);
588 		tp->t_gen++;
589 		ttyclose(tp);
590 		ttwakeup(tp);
591 		ttwwakeup(tp);
592 	}
593 	/* XXX
594 	 * must wait until all device are closed.
595 	 */
596 	tsleep((void *)dc, PWAIT, "dcodtc", hz/4);
597 	destroy_dev(dc->dev);
598 
599 	return(0);
600 }
601 
602 
603 /* cnXXX works only for FreeBSD-5 */
604 static int
605 dcons_modevent(module_t mode, int type, void *data)
606 {
607 	int err = 0, ret;
608 
609 	switch (type) {
610 	case MOD_LOAD:
611 		ret = dcons_drv_init(1);
612 		dcons_attach();
613 #if __FreeBSD_version >= 500000
614 		if (ret == 0) {
615 			dcons_cnprobe(&dcons_consdev);
616 			dcons_cninit(&dcons_consdev);
617 			cnadd(&dcons_consdev);
618 		}
619 #endif
620 		break;
621 	case MOD_UNLOAD:
622 		printf("dcons: unload\n");
623 		callout_stop(&dcons_callout);
624 #if DDB && DCONS_FORCE_GDB
625 #if CONS_NODEV
626 		gdb_arg = NULL;
627 #else
628 		gdbdev = NODEV;
629 #endif
630 #endif
631 #if __FreeBSD_version >= 500000
632 		cnremove(&dcons_consdev);
633 #endif
634 		dcons_detach(DCONS_CON);
635 		dcons_detach(DCONS_GDB);
636 		dg.buf->magic = 0;
637 
638 		contigfree(dg.buf, DCONS_BUF_SIZE, M_DEVBUF);
639 
640 		break;
641 	case MOD_SHUTDOWN:
642 		break;
643 	}
644 	return(err);
645 }
646 
647 DEV_MODULE(dcons, dcons_modevent, NULL);
648 MODULE_VERSION(dcons, DCONS_VERSION);
649