kern_cons.c revision 85458
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 85458 2001-10-25 04:51:37Z 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	struct vnode *vp;
385
386	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
387		if ((vp = cnd->cnd_vp) == NULL)
388			continue;
389		cnd->cnd_vp = NULL;
390		vn_close(vp, openflag, td->td_proc->p_ucred, td);
391	}
392	cn_is_open = 0;
393	return (0);
394}
395
396static int
397cnread(dev_t dev, struct uio *uio, int flag)
398{
399	struct cn_device *cnd;
400
401	cnd = STAILQ_FIRST(&cn_devlist);
402	if (cn_mute || CND_INVALID(cnd, curthread))
403		return (0);
404	dev = cnd->cnd_vp->v_rdev;
405	return ((*devsw(dev)->d_read)(dev, uio, flag));
406}
407
408static int
409cnwrite(dev_t dev, struct uio *uio, int flag)
410{
411	struct cn_device *cnd;
412
413	cnd = STAILQ_FIRST(&cn_devlist);
414	if (cn_mute || CND_INVALID(cnd, curthread))
415		goto done;
416	if (constty)
417		dev = constty->t_dev;
418	else
419		dev = cnd->cnd_vp->v_rdev;
420	if (dev != NULL) {
421		log_console(uio);
422		return ((*devsw(dev)->d_write)(dev, uio, flag));
423	}
424done:
425	uio->uio_resid = 0; /* dump the data */
426	return (0);
427}
428
429static int
430cnioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct thread *td)
431{
432	struct cn_device *cnd;
433	int error;
434
435	cnd = STAILQ_FIRST(&cn_devlist);
436	if (cn_mute || CND_INVALID(cnd, td))
437		return (0);
438	/*
439	 * Superuser can always use this to wrest control of console
440	 * output from the "virtual" console.
441	 */
442	if (cmd == TIOCCONS && constty) {
443		error = suser_td(td);
444		if (error)
445			return (error);
446		constty = NULL;
447		return (0);
448	}
449	dev = cnd->cnd_vp->v_rdev;
450	if (dev != NULL)
451		return ((*devsw(dev)->d_ioctl)(dev, cmd, data, flag, td));
452	return (0);
453}
454
455/*
456 * XXX
457 * poll/kqfilter do not appear to be correct
458 */
459static int
460cnpoll(dev_t dev, int events, struct thread *td)
461{
462	struct cn_device *cnd;
463
464	cnd = STAILQ_FIRST(&cn_devlist);
465	if (cn_mute || CND_INVALID(cnd, td))
466		return (0);
467	dev = cnd->cnd_vp->v_rdev;
468	if (dev != NULL)
469		return ((*devsw(dev)->d_poll)(dev, events, td));
470	return (0);
471}
472
473static int
474cnkqfilter(dev_t dev, struct knote *kn)
475{
476	struct cn_device *cnd;
477
478	cnd = STAILQ_FIRST(&cn_devlist);
479	if (cn_mute || CND_INVALID(cnd, curthread))
480		return (1);
481	dev = cnd->cnd_vp->v_rdev;
482	if (dev != NULL)
483		return ((*devsw(dev)->d_kqfilter)(dev, kn));
484	return (1);
485}
486
487/*
488 * Low level console routines.
489 */
490int
491cngetc(void)
492{
493	int c;
494
495	if (cn_mute)
496		return (-1);
497	while ((c = cncheckc()) == -1)
498		;
499	if (c == '\r')
500		c = '\n';		/* console input is always ICRNL */
501	return (c);
502}
503
504int
505cncheckc(void)
506{
507	struct cn_device *cnd;
508	struct consdev *cn;
509	int c;
510
511	if (cn_mute)
512		return (-1);
513	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
514		cn = cnd->cnd_cn;
515		c = cn->cn_checkc(cn->cn_dev);
516		if (c != -1) {
517			return (c);
518		}
519	}
520	return (-1);
521}
522
523void
524cnputc(int c)
525{
526	struct cn_device *cnd;
527	struct consdev *cn;
528
529	if (cn_mute || c == '\0')
530		return;
531	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
532		cn = cnd->cnd_cn;
533		if (c == '\n')
534			cn->cn_putc(cn->cn_dev, '\r');
535		cn->cn_putc(cn->cn_dev, c);
536	}
537}
538
539void
540cndbctl(int on)
541{
542	struct cn_device *cnd;
543	struct consdev *cn;
544	static int refcount;
545
546	if (!on)
547		refcount--;
548	if (refcount == 0)
549		STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
550			cn = cnd->cnd_cn;
551			if (cn->cn_dbctl != NULL)
552				cn->cn_dbctl(cn->cn_dev, on);
553		}
554	if (on)
555		refcount++;
556}
557
558static void
559cn_drvinit(void *unused)
560{
561
562	cn_devfsdev = make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600,
563	    "console");
564}
565
566SYSINIT(cndev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,cn_drvinit,NULL)
567