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