kern_cons.c revision 85448
1/*
2 * Copyright (c) 1988 University of Utah.
3 * Copyright (c) 1991 The Regents of the University of California.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * the Systems Programming Group of the University of Utah Computer
8 * Science Department.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 *    must display the following acknowledgement:
20 *	This product includes software developed by the University of
21 *	California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 *    may be used to endorse or promote products derived from this software
24 *    without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 *
38 *	from: @(#)cons.c	7.2 (Berkeley) 5/9/91
39 * $FreeBSD: head/sys/kern/tty_cons.c 85448 2001-10-25 00:14:16Z jlemon $
40 */
41
42#include <sys/param.h>
43#include <sys/systm.h>
44#include <sys/conf.h>
45#include <sys/cons.h>
46#include <sys/fcntl.h>
47#include <sys/kernel.h>
48#include <sys/malloc.h>
49#include <sys/namei.h>
50#include <sys/proc.h>
51#include <sys/queue.h>
52#include <sys/reboot.h>
53#include <sys/sysctl.h>
54#include <sys/tty.h>
55#include <sys/uio.h>
56#include <sys/vnode.h>
57
58#include <machine/cpu.h>
59
60static	d_open_t	cnopen;
61static	d_close_t	cnclose;
62static	d_read_t	cnread;
63static	d_write_t	cnwrite;
64static	d_ioctl_t	cnioctl;
65static	d_poll_t	cnpoll;
66static	d_kqfilter_t	cnkqfilter;
67
68#define	CDEV_MAJOR	0
69static struct cdevsw cn_cdevsw = {
70	/* open */	cnopen,
71	/* close */	cnclose,
72	/* read */	cnread,
73	/* write */	cnwrite,
74	/* ioctl */	cnioctl,
75	/* poll */	cnpoll,
76	/* mmap */	nommap,
77	/* strategy */	nostrategy,
78	/* name */	"console",
79	/* maj */	CDEV_MAJOR,
80	/* dump */	nodump,
81	/* psize */	nopsize,
82	/* flags */	D_TTY | D_KQFILTER,
83	/* kqfilter */	cnkqfilter,
84};
85
86struct cn_device {
87	STAILQ_ENTRY(cn_device) cnd_next;
88	char		cnd_name[16];
89	struct		vnode *cnd_vp;
90	struct		consdev *cnd_cn;
91};
92
93#define CNDEVPATHMAX	32
94#define CNDEVTAB_SIZE	4
95static struct cn_device cn_devtab[CNDEVTAB_SIZE];
96static STAILQ_HEAD(, cn_device) cn_devlist =
97    STAILQ_HEAD_INITIALIZER(cn_devlist);
98
99#define CND_INVALID(cnd, td) 						\
100	(cnd == NULL || cnd->cnd_vp == NULL ||				\
101	    (cnd->cnd_vp->v_type == VBAD && !cn_devopen(cnd, td, 1)))
102
103static udev_t	cn_udev_t;
104SYSCTL_OPAQUE(_machdep, CPU_CONSDEV, consdev, CTLFLAG_RD,
105	&cn_udev_t, sizeof cn_udev_t, "T,dev_t", "");
106
107int	cons_unavail = 0;	/* XXX:
108				 * physical console not available for
109				 * input (i.e., it is in graphics mode)
110				 */
111static int cn_mute;
112static int openflag;			/* how /dev/console was opened */
113static int cn_is_open;
114static dev_t cn_devfsdev;		/* represents the device private info */
115
116void	cndebug(char *);
117
118CONS_DRIVER(cons, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
119SET_DECLARE(cons_set, struct consdev);
120
121void
122cninit(void)
123{
124	struct consdev *best_cn, *cn, **list;
125
126	/*
127	 * Check if we should mute the console (for security reasons perhaps)
128	 * It can be changes dynamically using sysctl kern.consmute
129	 * once we are up and going.
130	 *
131	 */
132        cn_mute = ((boothowto & (RB_MUTE
133			|RB_SINGLE
134			|RB_VERBOSE
135			|RB_ASKNAME
136			|RB_CONFIG)) == RB_MUTE);
137
138	/*
139	 * Find the first console with the highest priority.
140	 */
141	best_cn = NULL;
142	SET_FOREACH(list, cons_set) {
143		cn = *list;
144		if (cn->cn_probe == NULL)
145			continue;
146		cn->cn_probe(cn);
147		if (cn->cn_pri == CN_DEAD)
148			continue;
149		if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri)
150			best_cn = cn;
151		if (boothowto & RB_MULTIPLE) {
152			/*
153			 * Initialize console, and attach to it.
154			 */
155			cnadd(cn);
156			cn->cn_init(cn);
157		}
158	}
159	if (best_cn == NULL)
160		return;
161	if ((boothowto & RB_MULTIPLE) == 0) {
162		cnadd(best_cn);
163		best_cn->cn_init(best_cn);
164	}
165	/*
166	 * Make the best console the preferred console.
167	 */
168	cnselect(best_cn);
169}
170
171/* add a new physical console to back the virtual console */
172int
173cnadd(struct consdev *cn)
174{
175	struct cn_device *cnd;
176	int i;
177
178	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
179		if (cnd->cnd_cn == cn)
180			return (0);
181	for (i = 0; i < CNDEVTAB_SIZE; i++) {
182		cnd = &cn_devtab[i];
183		if (cnd->cnd_cn == NULL)
184			break;
185	}
186	if (cnd->cnd_cn != NULL)
187		return (ENOMEM);
188	cnd->cnd_cn = cn;
189	STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next);
190	return (0);
191}
192
193void
194cnremove(struct consdev *cn)
195{
196	struct cn_device *cnd;
197
198	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
199		if (cnd->cnd_cn != cn)
200			continue;
201		STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
202		if (cnd->cnd_vp != NULL)
203			vn_close(cnd->cnd_vp, openflag, NOCRED, NULL);
204		cnd->cnd_vp = NULL;
205		cnd->cnd_cn = NULL;
206		cnd->cnd_name[0] = '\0';
207#if 0
208		/*
209		 * XXX
210		 * syscons gets really confused if console resources are
211		 * freed after the system has initialized.
212		 */
213		if (cn->cn_term != NULL)
214			cn->cn_term(cn);
215#endif
216		return;
217	}
218}
219
220void
221cnselect(struct consdev *cn)
222{
223	struct cn_device *cnd;
224
225	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
226		if (cnd->cnd_cn != cn)
227			continue;
228		if (cnd == STAILQ_FIRST(&cn_devlist))
229			return;
230		STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
231		STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next);
232		return;
233	}
234}
235
236void
237cndebug(char *str)
238{
239	int i, len;
240
241	len = strlen(str);
242	cnputc('>'); cnputc('>'); cnputc('>'); cnputc(' ');
243	for (i = 0; i < len; i++)
244		cnputc(str[i]);
245	cnputc('\n');
246}
247
248static int
249sysctl_kern_console(SYSCTL_HANDLER_ARGS)
250{
251	struct cn_device *cnd;
252	struct consdev *cp, **list;
253	char *name, *p;
254	int delete, len, error;
255
256	len = 2;
257	SET_FOREACH(list, cons_set) {
258		cp = *list;
259		if (cp->cn_dev != NULL)
260			len += strlen(devtoname(cp->cn_dev)) + 1;
261	}
262	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
263		len += strlen(devtoname(cnd->cnd_cn->cn_dev)) + 1;
264	len = len > CNDEVPATHMAX ? len : CNDEVPATHMAX;
265	MALLOC(name, char *, len, M_TEMP, M_WAITOK | M_ZERO);
266	p = name;
267	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
268		p += sprintf(p, "%s,", devtoname(cnd->cnd_cn->cn_dev));
269	*p++ = '/';
270	SET_FOREACH(list, cons_set) {
271		cp = *list;
272		if (cp->cn_dev != NULL)
273			p += sprintf(p, "%s,", devtoname(cp->cn_dev));
274	}
275	error = sysctl_handle_string(oidp, name, len, req);
276	if (error == 0 && req->newptr != NULL) {
277		p = name;
278		error = ENXIO;
279		delete = 0;
280		if (*p == '-') {
281			delete = 1;
282			p++;
283		}
284		SET_FOREACH(list, cons_set) {
285			cp = *list;
286			if (cp->cn_dev == NULL ||
287			    strcmp(p, devtoname(cp->cn_dev)) != 0)
288				continue;
289			if (delete) {
290				cnremove(cp);
291				error = 0;
292			} else {
293				error = cnadd(cp);
294				if (error == 0)
295					cnselect(cp);
296			}
297			break;
298		}
299	}
300	FREE(name, M_TEMP);
301	return (error);
302}
303
304SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW,
305	0, 0, sysctl_kern_console, "A", "Console device control");
306
307/*
308 * User has changed the state of the console muting.
309 * This may require us to open or close the device in question.
310 */
311static int
312sysctl_kern_consmute(SYSCTL_HANDLER_ARGS)
313{
314	int error;
315	int ocn_mute;
316
317	ocn_mute = cn_mute;
318	error = sysctl_handle_int(oidp, &cn_mute, 0, req);
319	if (error != 0 || req->newptr == NULL)
320		return (error);
321	if (ocn_mute && !cn_mute && cn_is_open)
322		error = cnopen(NODEV, openflag, 0, curthread);
323	else if (!ocn_mute && cn_mute && cn_is_open) {
324		error = cnclose(NODEV, openflag, 0, curthread);
325		cn_is_open = 1;		/* XXX hack */
326	}
327	return (error);
328}
329
330SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW,
331	0, sizeof(cn_mute), sysctl_kern_consmute, "I", "");
332
333static int
334cn_devopen(struct cn_device *cnd, struct thread *td, int forceopen)
335{
336	char path[CNDEVPATHMAX];
337        struct nameidata nd;
338	dev_t dev;
339	int error;
340
341	if (cnd->cnd_vp != NULL) {
342		if (!forceopen) {
343			dev = cnd->cnd_vp->v_rdev;
344			return ((*devsw(dev)->d_open)(dev, openflag, 0, td));
345		}
346		vn_close(cnd->cnd_vp, openflag, td->td_proc->p_ucred, td);
347		cnd->cnd_vp = NULL;
348	}
349	if (cnd->cnd_name[0] == '\0')
350		strncpy(cnd->cnd_name, devtoname(cnd->cnd_cn->cn_dev),
351		    sizeof(cnd->cnd_name));
352	snprintf(path, sizeof(path), "/dev/%s", cnd->cnd_name);
353	NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, path, td);
354	error = vn_open(&nd, &openflag, 0);
355	if (error == 0) {
356		NDFREE(&nd, NDF_ONLY_PNBUF);
357		VOP_UNLOCK(nd.ni_vp, 0, td);
358		if (nd.ni_vp->v_type == VCHR)
359			cnd->cnd_vp = nd.ni_vp;
360		else
361			vn_close(nd.ni_vp, openflag, td->td_proc->p_ucred, td);
362	}
363	return (cnd->cnd_vp != NULL);
364}
365
366static int
367cnopen(dev_t dev, int flag, int mode, struct thread *td)
368{
369	struct cn_device *cnd;
370
371	openflag = flag | FWRITE;	/* XXX */
372	cn_is_open = 1;			/* console is logically open */
373	if (cn_mute)
374		return (0);
375	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
376		cn_devopen(cnd, td, 0);
377	return (0);
378}
379
380static int
381cnclose(dev_t dev, int flag, int mode, struct thread *td)
382{
383	struct cn_device *cnd;
384
385	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
386		if (cnd->cnd_vp == NULL)
387			continue;
388		vn_close(cnd->cnd_vp, openflag, td->td_proc->p_ucred, td);
389		cnd->cnd_vp = NULL;
390	}
391	cn_is_open = 0;
392	return (0);
393}
394
395static int
396cnread(dev_t dev, struct uio *uio, int flag)
397{
398	struct cn_device *cnd;
399
400	cnd = STAILQ_FIRST(&cn_devlist);
401	if (cn_mute || CND_INVALID(cnd, curthread))
402		return (0);
403	dev = cnd->cnd_vp->v_rdev;
404	return ((*devsw(dev)->d_read)(dev, uio, flag));
405}
406
407static int
408cnwrite(dev_t dev, struct uio *uio, int flag)
409{
410	struct cn_device *cnd;
411
412	cnd = STAILQ_FIRST(&cn_devlist);
413	if (cn_mute || CND_INVALID(cnd, curthread))
414		goto done;
415	if (constty)
416		dev = constty->t_dev;
417	else
418		dev = cnd->cnd_vp->v_rdev;
419	if (dev != NULL) {
420		log_console(uio);
421		return ((*devsw(dev)->d_write)(dev, uio, flag));
422	}
423done:
424	uio->uio_resid = 0; /* dump the data */
425	return (0);
426}
427
428static int
429cnioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
430{
431	struct cn_device *cnd;
432	int error;
433
434	cnd = STAILQ_FIRST(&cn_devlist);
435	if (cn_mute || CND_INVALID(cnd, td))
436		return (0);
437	/*
438	 * Superuser can always use this to wrest control of console
439	 * output from the "virtual" console.
440	 */
441	if (cmd == TIOCCONS && constty) {
442		error = suser_td(td);
443		if (error)
444			return (error);
445		constty = NULL;
446		return (0);
447	}
448	dev = cnd->cnd_vp->v_rdev;
449	if (dev != NULL)
450		return ((*devsw(dev)->d_ioctl)(dev, cmd, data, flag, td));
451	return (0);
452}
453
454/*
455 * XXX
456 * poll/kqfilter do not appear to be correct
457 */
458static int
459cnpoll(dev_t dev, int events, struct thread *td)
460{
461	struct cn_device *cnd;
462
463	cnd = STAILQ_FIRST(&cn_devlist);
464	if (cn_mute || CND_INVALID(cnd, td))
465		return (0);
466	dev = cnd->cnd_vp->v_rdev;
467	if (dev != NULL)
468		return ((*devsw(dev)->d_poll)(dev, events, td));
469	return (0);
470}
471
472static int
473cnkqfilter(dev_t dev, struct knote *kn)
474{
475	struct cn_device *cnd;
476
477	cnd = STAILQ_FIRST(&cn_devlist);
478	if (cn_mute || CND_INVALID(cnd, curthread))
479		return (1);
480	dev = cnd->cnd_vp->v_rdev;
481	if (dev != NULL)
482		return ((*devsw(dev)->d_kqfilter)(dev, kn));
483	return (1);
484}
485
486/*
487 * Low level console routines.
488 */
489int
490cngetc(void)
491{
492	int c;
493
494	if (cn_mute)
495		return (-1);
496	while ((c = cncheckc()) == -1)
497		;
498	if (c == '\r')
499		c = '\n';		/* console input is always ICRNL */
500	return (c);
501}
502
503int
504cncheckc(void)
505{
506	struct cn_device *cnd;
507	struct consdev *cn;
508	int c;
509
510	if (cn_mute)
511		return (-1);
512	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
513		cn = cnd->cnd_cn;
514		c = cn->cn_checkc(cn->cn_dev);
515		if (c != -1) {
516			return (c);
517		}
518	}
519	return (-1);
520}
521
522void
523cnputc(int c)
524{
525	struct cn_device *cnd;
526	struct consdev *cn;
527
528	if (cn_mute || c == '\0')
529		return;
530	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
531		cn = cnd->cnd_cn;
532		if (c == '\n')
533			cn->cn_putc(cn->cn_dev, '\r');
534		cn->cn_putc(cn->cn_dev, c);
535	}
536}
537
538void
539cndbctl(int on)
540{
541	struct cn_device *cnd;
542	struct consdev *cn;
543	static int refcount;
544
545	if (!on)
546		refcount--;
547	if (refcount == 0)
548		STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
549			cn = cnd->cnd_cn;
550			if (cn->cn_dbctl != NULL)
551				cn->cn_dbctl(cn->cn_dev, on);
552		}
553	if (on)
554		refcount++;
555}
556
557static void
558cn_drvinit(void *unused)
559{
560
561	cn_devfsdev = make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
562	    "console");
563}
564
565SYSINIT(cndev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,cn_drvinit,NULL)
566