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