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