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