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