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