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