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