1 /*- 2 * Copyright (c) 2009 The FreeBSD Foundation 3 * All rights reserved. 4 * 5 * This software was developed by Ed Schouten under sponsorship from the 6 * FreeBSD Foundation. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 __FBSDID("$FreeBSD$"); 32 33 #include <sys/param.h> 34 #include <sys/cons.h> 35 #include <sys/consio.h> 36 #include <sys/kernel.h> 37 #include <sys/lock.h> 38 #include <sys/malloc.h> 39 #include <sys/mutex.h> 40 #include <sys/systm.h> 41 #include <sys/terminal.h> 42 #include <sys/tty.h> 43 44 #include <machine/stdarg.h> 45 46 static MALLOC_DEFINE(M_TERMINAL, "terminal", "terminal device"); 47 48 /* 49 * Locking. 50 * 51 * Normally we don't need to lock down the terminal emulator, because 52 * the TTY lock is already held when calling teken_input(). 53 * Unfortunately this is not the case when the terminal acts as a 54 * console device, because cnputc() can be called at the same time. 55 * This means terminals may need to be locked down using a spin lock. 56 */ 57 #define TERMINAL_LOCK(tm) do { \ 58 if ((tm)->tm_flags & TF_CONS) \ 59 mtx_lock_spin(&(tm)->tm_mtx); \ 60 else if ((tm)->tm_tty != NULL) \ 61 tty_lock((tm)->tm_tty); \ 62 } while (0) 63 #define TERMINAL_UNLOCK(tm) do { \ 64 if ((tm)->tm_flags & TF_CONS) \ 65 mtx_unlock_spin(&(tm)->tm_mtx); \ 66 else if ((tm)->tm_tty != NULL) \ 67 tty_unlock((tm)->tm_tty); \ 68 } while (0) 69 #define TERMINAL_LOCK_TTY(tm) do { \ 70 if ((tm)->tm_flags & TF_CONS) \ 71 mtx_lock_spin(&(tm)->tm_mtx); \ 72 } while (0) 73 #define TERMINAL_UNLOCK_TTY(tm) do { \ 74 if ((tm)->tm_flags & TF_CONS) \ 75 mtx_unlock_spin(&(tm)->tm_mtx); \ 76 } while (0) 77 #define TERMINAL_LOCK_CONS(tm) mtx_lock_spin(&(tm)->tm_mtx) 78 #define TERMINAL_UNLOCK_CONS(tm) mtx_unlock_spin(&(tm)->tm_mtx) 79 80 /* 81 * TTY routines. 82 */ 83 84 static tsw_open_t termtty_open; 85 static tsw_close_t termtty_close; 86 static tsw_outwakeup_t termtty_outwakeup; 87 static tsw_ioctl_t termtty_ioctl; 88 static tsw_mmap_t termtty_mmap; 89 90 static struct ttydevsw terminal_tty_class = { 91 .tsw_open = termtty_open, 92 .tsw_close = termtty_close, 93 .tsw_outwakeup = termtty_outwakeup, 94 .tsw_ioctl = termtty_ioctl, 95 .tsw_mmap = termtty_mmap, 96 }; 97 98 /* 99 * Terminal emulator routines. 100 */ 101 102 static tf_bell_t termteken_bell; 103 static tf_cursor_t termteken_cursor; 104 static tf_putchar_t termteken_putchar; 105 static tf_fill_t termteken_fill; 106 static tf_copy_t termteken_copy; 107 static tf_param_t termteken_param; 108 static tf_respond_t termteken_respond; 109 110 static teken_funcs_t terminal_drawmethods = { 111 .tf_bell = termteken_bell, 112 .tf_cursor = termteken_cursor, 113 .tf_putchar = termteken_putchar, 114 .tf_fill = termteken_fill, 115 .tf_copy = termteken_copy, 116 .tf_param = termteken_param, 117 .tf_respond = termteken_respond, 118 }; 119 120 /* Kernel message formatting. */ 121 static const teken_attr_t kernel_message = { 122 .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_KERN_ATTR), 123 .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_KERN_ATTR), 124 .ta_format = TCHAR_FORMAT(TERMINAL_KERN_ATTR) 125 }; 126 127 static const teken_attr_t default_message = { 128 .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_NORM_ATTR), 129 .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_NORM_ATTR), 130 .ta_format = TCHAR_FORMAT(TERMINAL_NORM_ATTR) 131 }; 132 133 /* Fudge fg brightness as TF_BOLD (shifted). */ 134 #define TCOLOR_FG_FUDGED(color) __extension__ ({ \ 135 teken_color_t _c; \ 136 \ 137 _c = (color); \ 138 TCOLOR_FG(_c & 7) | ((_c & 8) << 18); \ 139 }) 140 141 /* Fudge bg brightness as TF_BLINK (shifted). */ 142 #define TCOLOR_BG_FUDGED(color) __extension__ ({ \ 143 teken_color_t _c; \ 144 \ 145 _c = (color); \ 146 TCOLOR_BG(_c & 7) | ((_c & 8) << 20); \ 147 }) 148 149 #define TCOLOR_256TO16(color) __extension__ ({ \ 150 teken_color_t _c; \ 151 \ 152 _c = (color); \ 153 if (_c >= 16) \ 154 _c = teken_256to16(_c); \ 155 _c; \ 156 }) 157 158 #define TCHAR_CREATE(c, a) ((c) | TFORMAT((a)->ta_format) | \ 159 TCOLOR_FG_FUDGED(TCOLOR_256TO16((a)->ta_fgcolor)) | \ 160 TCOLOR_BG_FUDGED(TCOLOR_256TO16((a)->ta_bgcolor))) 161 162 static void 163 terminal_init(struct terminal *tm) 164 { 165 166 if (tm->tm_flags & TF_CONS) 167 mtx_init(&tm->tm_mtx, "trmlck", NULL, MTX_SPIN); 168 teken_init(&tm->tm_emulator, &terminal_drawmethods, tm); 169 teken_set_defattr(&tm->tm_emulator, &default_message); 170 } 171 172 struct terminal * 173 terminal_alloc(const struct terminal_class *tc, void *softc) 174 { 175 struct terminal *tm; 176 177 tm = malloc(sizeof(struct terminal), M_TERMINAL, M_WAITOK|M_ZERO); 178 terminal_init(tm); 179 180 tm->tm_class = tc; 181 tm->tm_softc = softc; 182 183 return (tm); 184 } 185 186 static void 187 terminal_sync_ttysize(struct terminal *tm) 188 { 189 struct tty *tp; 190 191 tp = tm->tm_tty; 192 if (tp == NULL) 193 return; 194 195 tty_lock(tp); 196 tty_set_winsize(tp, &tm->tm_winsize); 197 tty_unlock(tp); 198 } 199 200 void 201 terminal_maketty(struct terminal *tm, const char *fmt, ...) 202 { 203 struct tty *tp; 204 char name[8]; 205 va_list ap; 206 207 va_start(ap, fmt); 208 vsnrprintf(name, sizeof name, 32, fmt, ap); 209 va_end(ap); 210 211 tp = tty_alloc(&terminal_tty_class, tm); 212 tty_makedev(tp, NULL, "%s", name); 213 tm->tm_tty = tp; 214 terminal_sync_ttysize(tm); 215 } 216 217 void 218 terminal_set_cursor(struct terminal *tm, const term_pos_t *pos) 219 { 220 221 teken_set_cursor(&tm->tm_emulator, pos); 222 } 223 224 void 225 terminal_set_winsize_blank(struct terminal *tm, const struct winsize *size, 226 int blank, const term_attr_t *attr) 227 { 228 term_rect_t r; 229 230 tm->tm_winsize = *size; 231 232 r.tr_begin.tp_row = r.tr_begin.tp_col = 0; 233 r.tr_end.tp_row = size->ws_row; 234 r.tr_end.tp_col = size->ws_col; 235 236 TERMINAL_LOCK(tm); 237 if (blank == 0) 238 teken_set_winsize_noreset(&tm->tm_emulator, &r.tr_end); 239 else 240 teken_set_winsize(&tm->tm_emulator, &r.tr_end); 241 TERMINAL_UNLOCK(tm); 242 243 if ((blank != 0) && !(tm->tm_flags & TF_MUTE)) 244 tm->tm_class->tc_fill(tm, &r, 245 TCHAR_CREATE((teken_char_t)' ', attr)); 246 247 terminal_sync_ttysize(tm); 248 } 249 250 void 251 terminal_set_winsize(struct terminal *tm, const struct winsize *size) 252 { 253 254 terminal_set_winsize_blank(tm, size, 1, 255 (const term_attr_t *)&default_message); 256 } 257 258 /* 259 * XXX: This function is a kludge. Drivers like vt(4) need to 260 * temporarily stop input when resizing, etc. This should ideally be 261 * handled within the driver. 262 */ 263 264 void 265 terminal_mute(struct terminal *tm, int yes) 266 { 267 268 TERMINAL_LOCK(tm); 269 if (yes) 270 tm->tm_flags |= TF_MUTE; 271 else 272 tm->tm_flags &= ~TF_MUTE; 273 TERMINAL_UNLOCK(tm); 274 } 275 276 void 277 terminal_input_char(struct terminal *tm, term_char_t c) 278 { 279 struct tty *tp; 280 281 tp = tm->tm_tty; 282 if (tp == NULL) 283 return; 284 285 /* 286 * Strip off any attributes. Also ignore input of second part of 287 * CJK fullwidth characters, as we don't want to return these 288 * characters twice. 289 */ 290 if (TCHAR_FORMAT(c) & TF_CJK_RIGHT) 291 return; 292 c = TCHAR_CHARACTER(c); 293 294 tty_lock(tp); 295 /* 296 * Conversion to UTF-8. 297 */ 298 if (c < 0x80) { 299 ttydisc_rint(tp, c, 0); 300 } else if (c < 0x800) { 301 char str[2] = { 302 0xc0 | (c >> 6), 303 0x80 | (c & 0x3f) 304 }; 305 306 ttydisc_rint_simple(tp, str, sizeof str); 307 } else if (c < 0x10000) { 308 char str[3] = { 309 0xe0 | (c >> 12), 310 0x80 | ((c >> 6) & 0x3f), 311 0x80 | (c & 0x3f) 312 }; 313 314 ttydisc_rint_simple(tp, str, sizeof str); 315 } else { 316 char str[4] = { 317 0xf0 | (c >> 18), 318 0x80 | ((c >> 12) & 0x3f), 319 0x80 | ((c >> 6) & 0x3f), 320 0x80 | (c & 0x3f) 321 }; 322 323 ttydisc_rint_simple(tp, str, sizeof str); 324 } 325 ttydisc_rint_done(tp); 326 tty_unlock(tp); 327 } 328 329 void 330 terminal_input_raw(struct terminal *tm, char c) 331 { 332 struct tty *tp; 333 334 tp = tm->tm_tty; 335 if (tp == NULL) 336 return; 337 338 tty_lock(tp); 339 ttydisc_rint(tp, c, 0); 340 ttydisc_rint_done(tp); 341 tty_unlock(tp); 342 } 343 344 void 345 terminal_input_special(struct terminal *tm, unsigned int k) 346 { 347 struct tty *tp; 348 const char *str; 349 350 tp = tm->tm_tty; 351 if (tp == NULL) 352 return; 353 354 str = teken_get_sequence(&tm->tm_emulator, k); 355 if (str == NULL) 356 return; 357 358 tty_lock(tp); 359 ttydisc_rint_simple(tp, str, strlen(str)); 360 ttydisc_rint_done(tp); 361 tty_unlock(tp); 362 } 363 364 /* 365 * Binding with the TTY layer. 366 */ 367 368 static int 369 termtty_open(struct tty *tp) 370 { 371 struct terminal *tm = tty_softc(tp); 372 373 tm->tm_class->tc_opened(tm, 1); 374 return (0); 375 } 376 377 static void 378 termtty_close(struct tty *tp) 379 { 380 struct terminal *tm = tty_softc(tp); 381 382 tm->tm_class->tc_opened(tm, 0); 383 } 384 385 static void 386 termtty_outwakeup(struct tty *tp) 387 { 388 struct terminal *tm = tty_softc(tp); 389 char obuf[128]; 390 size_t olen; 391 unsigned int flags = 0; 392 393 while ((olen = ttydisc_getc(tp, obuf, sizeof obuf)) > 0) { 394 TERMINAL_LOCK_TTY(tm); 395 if (!(tm->tm_flags & TF_MUTE)) { 396 tm->tm_flags &= ~TF_BELL; 397 teken_input(&tm->tm_emulator, obuf, olen); 398 flags |= tm->tm_flags; 399 } 400 TERMINAL_UNLOCK_TTY(tm); 401 } 402 403 TERMINAL_LOCK_TTY(tm); 404 if (!(tm->tm_flags & TF_MUTE)) 405 tm->tm_class->tc_done(tm); 406 TERMINAL_UNLOCK_TTY(tm); 407 if (flags & TF_BELL) 408 tm->tm_class->tc_bell(tm); 409 } 410 411 static int 412 termtty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) 413 { 414 struct terminal *tm = tty_softc(tp); 415 int error; 416 417 switch (cmd) { 418 case CONS_GETINFO: { 419 vid_info_t *vi = (vid_info_t *)data; 420 const teken_pos_t *p; 421 int fg, bg; 422 423 if (vi->size != sizeof(vid_info_t)) 424 return (EINVAL); 425 426 /* Already help the console driver by filling in some data. */ 427 p = teken_get_cursor(&tm->tm_emulator); 428 vi->mv_row = p->tp_row; 429 vi->mv_col = p->tp_col; 430 431 p = teken_get_winsize(&tm->tm_emulator); 432 vi->mv_rsz = p->tp_row; 433 vi->mv_csz = p->tp_col; 434 435 teken_get_defattr_cons25(&tm->tm_emulator, &fg, &bg); 436 vi->mv_norm.fore = fg; 437 vi->mv_norm.back = bg; 438 /* XXX: keep vidcontrol happy; bold backgrounds. */ 439 vi->mv_rev.fore = bg; 440 vi->mv_rev.back = fg & 0x7; 441 break; 442 } 443 } 444 445 /* 446 * Unlike various other drivers, this driver will never 447 * deallocate TTYs. This means it's safe to temporarily unlock 448 * the TTY when handling ioctls. 449 */ 450 tty_unlock(tp); 451 error = tm->tm_class->tc_ioctl(tm, cmd, data, td); 452 tty_lock(tp); 453 return (error); 454 } 455 456 static int 457 termtty_mmap(struct tty *tp, vm_ooffset_t offset, vm_paddr_t * paddr, 458 int nprot, vm_memattr_t *memattr) 459 { 460 struct terminal *tm = tty_softc(tp); 461 462 return (tm->tm_class->tc_mmap(tm, offset, paddr, nprot, memattr)); 463 } 464 465 /* 466 * Binding with the kernel and debug console. 467 */ 468 469 static cn_probe_t termcn_cnprobe; 470 static cn_init_t termcn_cninit; 471 static cn_term_t termcn_cnterm; 472 static cn_getc_t termcn_cngetc; 473 static cn_putc_t termcn_cnputc; 474 static cn_grab_t termcn_cngrab; 475 static cn_ungrab_t termcn_cnungrab; 476 477 const struct consdev_ops termcn_cnops = { 478 .cn_probe = termcn_cnprobe, 479 .cn_init = termcn_cninit, 480 .cn_term = termcn_cnterm, 481 .cn_getc = termcn_cngetc, 482 .cn_putc = termcn_cnputc, 483 .cn_grab = termcn_cngrab, 484 .cn_ungrab = termcn_cnungrab, 485 }; 486 487 void 488 termcn_cnregister(struct terminal *tm) 489 { 490 struct consdev *cp; 491 492 cp = tm->consdev; 493 if (cp == NULL) { 494 cp = malloc(sizeof(struct consdev), M_TERMINAL, 495 M_WAITOK|M_ZERO); 496 cp->cn_ops = &termcn_cnops; 497 cp->cn_arg = tm; 498 cp->cn_pri = CN_INTERNAL; 499 sprintf(cp->cn_name, "ttyv0"); 500 501 tm->tm_flags = TF_CONS; 502 tm->consdev = cp; 503 504 terminal_init(tm); 505 } 506 507 /* Attach terminal as console. */ 508 cnadd(cp); 509 } 510 511 static void 512 termcn_cngrab(struct consdev *cp) 513 { 514 struct terminal *tm = cp->cn_arg; 515 516 tm->tm_class->tc_cngrab(tm); 517 } 518 519 static void 520 termcn_cnungrab(struct consdev *cp) 521 { 522 struct terminal *tm = cp->cn_arg; 523 524 tm->tm_class->tc_cnungrab(tm); 525 } 526 527 static void 528 termcn_cnprobe(struct consdev *cp) 529 { 530 struct terminal *tm = cp->cn_arg; 531 532 if (tm == NULL) { 533 cp->cn_pri = CN_DEAD; 534 return; 535 } 536 537 tm->consdev = cp; 538 terminal_init(tm); 539 540 tm->tm_class->tc_cnprobe(tm, cp); 541 } 542 543 static void 544 termcn_cninit(struct consdev *cp) 545 { 546 547 } 548 549 static void 550 termcn_cnterm(struct consdev *cp) 551 { 552 553 } 554 555 static int 556 termcn_cngetc(struct consdev *cp) 557 { 558 struct terminal *tm = cp->cn_arg; 559 560 return (tm->tm_class->tc_cngetc(tm)); 561 } 562 563 static void 564 termcn_cnputc(struct consdev *cp, int c) 565 { 566 struct terminal *tm = cp->cn_arg; 567 teken_attr_t backup; 568 char cv = c; 569 570 TERMINAL_LOCK_CONS(tm); 571 if (!(tm->tm_flags & TF_MUTE)) { 572 backup = *teken_get_curattr(&tm->tm_emulator); 573 teken_set_curattr(&tm->tm_emulator, &kernel_message); 574 teken_input(&tm->tm_emulator, &cv, 1); 575 teken_set_curattr(&tm->tm_emulator, &backup); 576 tm->tm_class->tc_done(tm); 577 } 578 TERMINAL_UNLOCK_CONS(tm); 579 } 580 581 /* 582 * Binding with the terminal emulator. 583 */ 584 585 static void 586 termteken_bell(void *softc) 587 { 588 struct terminal *tm = softc; 589 590 tm->tm_flags |= TF_BELL; 591 } 592 593 static void 594 termteken_cursor(void *softc, const teken_pos_t *p) 595 { 596 struct terminal *tm = softc; 597 598 tm->tm_class->tc_cursor(tm, p); 599 } 600 601 static void 602 termteken_putchar(void *softc, const teken_pos_t *p, teken_char_t c, 603 const teken_attr_t *a) 604 { 605 struct terminal *tm = softc; 606 607 tm->tm_class->tc_putchar(tm, p, TCHAR_CREATE(c, a)); 608 } 609 610 static void 611 termteken_fill(void *softc, const teken_rect_t *r, teken_char_t c, 612 const teken_attr_t *a) 613 { 614 struct terminal *tm = softc; 615 616 tm->tm_class->tc_fill(tm, r, TCHAR_CREATE(c, a)); 617 } 618 619 static void 620 termteken_copy(void *softc, const teken_rect_t *r, const teken_pos_t *p) 621 { 622 struct terminal *tm = softc; 623 624 tm->tm_class->tc_copy(tm, r, p); 625 } 626 627 static void 628 termteken_param(void *softc, int cmd, unsigned int arg) 629 { 630 struct terminal *tm = softc; 631 632 tm->tm_class->tc_param(tm, cmd, arg); 633 } 634 635 static void 636 termteken_respond(void *softc, const void *buf, size_t len) 637 { 638 #if 0 639 struct terminal *tm = softc; 640 struct tty *tp; 641 642 /* 643 * Only inject a response into the TTY if the data actually 644 * originated from the TTY. 645 * 646 * XXX: This cannot be done right now. The TTY could pick up 647 * other locks. It could also in theory cause loops, when the 648 * TTY performs echoing of a command that generates even more 649 * input. 650 */ 651 tp = tm->tm_tty; 652 if (tp == NULL) 653 return; 654 655 ttydisc_rint_simple(tp, buf, len); 656 ttydisc_rint_done(tp); 657 #endif 658 } 659