subr_terminal.c revision 267978
126497Sache/*- 226497Sache * Copyright (c) 2009 The FreeBSD Foundation 326497Sache * All rights reserved. 426497Sache * 558310Sache * This software was developed by Ed Schouten under sponsorship from the 626497Sache * FreeBSD Foundation. 726497Sache * 8119610Sache * Redistribution and use in source and binary forms, with or without 9119610Sache * modification, are permitted provided that the following conditions 10119610Sache * are met: 11119610Sache * 1. Redistributions of source code must retain the above copyright 12119610Sache * notice, this list of conditions and the following disclaimer. 13119610Sache * 2. Redistributions in binary form must reproduce the above copyright 14119610Sache * notice, this list of conditions and the following disclaimer in the 15119610Sache * documentation and/or other materials provided with the distribution. 16119610Sache * 17119610Sache * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18119610Sache * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19119610Sache * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20119610Sache * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21119610Sache * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22119610Sache * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23119610Sache * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24119610Sache * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25119610Sache * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26119610Sache * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27119610Sache * SUCH DAMAGE. 2826497Sache */ 2935486Sache 3026497Sache#include <sys/cdefs.h> 3126497Sache__FBSDID("$FreeBSD: head/sys/kern/subr_terminal.c 267978 2014-06-27 19:57:57Z marius $"); 3226497Sache 3326497Sache#include <sys/param.h> 3426497Sache#include <sys/cons.h> 35157184Sache#include <sys/consio.h> 36157184Sache#include <sys/kernel.h> 37157184Sache#include <sys/lock.h> 38157184Sache#include <sys/malloc.h> 39157184Sache#include <sys/mutex.h> 40157184Sache#include <sys/systm.h> 4158310Sache#include <sys/terminal.h> 42157184Sache#include <sys/tty.h> 4358310Sache 4458310Sache#include <machine/stdarg.h> 4558310Sache 46157184Sachestatic MALLOC_DEFINE(M_TERMINAL, "terminal", "terminal device"); 4758310Sache 4858310Sache/* 4958310Sache * Locking. 5058310Sache * 5126497Sache * Normally we don't need to lock down the terminal emulator, because 5226497Sache * the TTY lock is already held when calling teken_input(). 5326497Sache * Unfortunately this is not the case when the terminal acts as a 5435486Sache * console device, because cnputc() can be called at the same time. 5526497Sache * This means terminals may need to be locked down using a spin lock. 5635486Sache */ 5726497Sache#define TERMINAL_LOCK(tm) do { \ 5826497Sache if ((tm)->tm_flags & TF_CONS) \ 5926497Sache mtx_lock_spin(&(tm)->tm_mtx); \ 6026497Sache else if ((tm)->tm_tty != NULL) \ 6126497Sache tty_lock((tm)->tm_tty); \ 6226497Sache} while (0) 6326497Sache#define TERMINAL_UNLOCK(tm) do { \ 6426497Sache if ((tm)->tm_flags & TF_CONS) \ 6526497Sache mtx_unlock_spin(&(tm)->tm_mtx); \ 6626497Sache else if ((tm)->tm_tty != NULL) \ 6726497Sache tty_unlock((tm)->tm_tty); \ 6875406Sache} while (0) 6926497Sache#define TERMINAL_LOCK_TTY(tm) do { \ 7058310Sache if ((tm)->tm_flags & TF_CONS) \ 7126497Sache mtx_lock_spin(&(tm)->tm_mtx); \ 7226497Sache} while (0) 7347558Sache#define TERMINAL_UNLOCK_TTY(tm) do { \ 7426497Sache if ((tm)->tm_flags & TF_CONS) \ 7526497Sache mtx_unlock_spin(&(tm)->tm_mtx); \ 7658310Sache} while (0) 7726497Sache#define TERMINAL_LOCK_CONS(tm) mtx_lock_spin(&(tm)->tm_mtx) 7826497Sache#define TERMINAL_UNLOCK_CONS(tm) mtx_unlock_spin(&(tm)->tm_mtx) 7926497Sache 8058310Sache/* 8126497Sache * TTY routines. 8226497Sache */ 8326497Sache 8426497Sachestatic tsw_open_t termtty_open; 8526497Sachestatic tsw_close_t termtty_close; 8626497Sachestatic tsw_outwakeup_t termtty_outwakeup; 8758310Sachestatic tsw_ioctl_t termtty_ioctl; 8826497Sachestatic tsw_mmap_t termtty_mmap; 8926497Sache 9026497Sachestatic struct ttydevsw terminal_tty_class = { 9126497Sache .tsw_open = termtty_open, 9226497Sache .tsw_close = termtty_close, 9326497Sache .tsw_outwakeup = termtty_outwakeup, 9426497Sache .tsw_ioctl = termtty_ioctl, 9526497Sache .tsw_mmap = termtty_mmap, 9626497Sache}; 9726497Sache 9858310Sache/* 9926497Sache * Terminal emulator routines. 10026497Sache */ 10158310Sache 10226497Sachestatic tf_bell_t termteken_bell; 10326497Sachestatic tf_cursor_t termteken_cursor; 10426497Sachestatic tf_putchar_t termteken_putchar; 10526497Sachestatic tf_fill_t termteken_fill; 10626497Sachestatic tf_copy_t termteken_copy; 10726497Sachestatic tf_param_t termteken_param; 10826497Sachestatic tf_respond_t termteken_respond; 10926497Sache 11026497Sachestatic teken_funcs_t terminal_drawmethods = { 11126497Sache .tf_bell = termteken_bell, 11226497Sache .tf_cursor = termteken_cursor, 11326497Sache .tf_putchar = termteken_putchar, 11426497Sache .tf_fill = termteken_fill, 11526497Sache .tf_copy = termteken_copy, 11626497Sache .tf_param = termteken_param, 11726497Sache .tf_respond = termteken_respond, 11826497Sache}; 11958310Sache 12058310Sache/* Kernel message formatting. */ 12158310Sachestatic const teken_attr_t kernel_message = { 12258310Sache .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_KERN_ATTR), 12358310Sache .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_KERN_ATTR), 12458310Sache .ta_format = TCHAR_FORMAT(TERMINAL_KERN_ATTR) 12558310Sache}; 12658310Sache 12726497Sachestatic const teken_attr_t default_message = { 12826497Sache .ta_fgcolor = TCHAR_FGCOLOR(TERMINAL_NORM_ATTR), 12926497Sache .ta_bgcolor = TCHAR_BGCOLOR(TERMINAL_NORM_ATTR), 13026497Sache .ta_format = TCHAR_FORMAT(TERMINAL_NORM_ATTR) 13126497Sache}; 13226497Sache 13326497Sache#define TCHAR_CREATE(c, a) ((c) | TFORMAT((a)->ta_format) | \ 13426497Sache TCOLOR_FG(teken_256to8((a)->ta_fgcolor)) | \ 13526497Sache TCOLOR_BG(teken_256to8((a)->ta_bgcolor))) 13626497Sache 13726497Sachestatic void 13826497Sacheterminal_init(struct terminal *tm) 13926497Sache{ 14026497Sache 14126497Sache if (tm->tm_flags & TF_CONS) 14226497Sache mtx_init(&tm->tm_mtx, "trmlck", NULL, MTX_SPIN); 14326497Sache teken_init(&tm->tm_emulator, &terminal_drawmethods, tm); 14426497Sache teken_set_defattr(&tm->tm_emulator, &default_message); 14526497Sache} 14626497Sache 14758310Sachestruct terminal * 14858310Sacheterminal_alloc(const struct terminal_class *tc, void *softc) 14958310Sache{ 15026497Sache struct terminal *tm; 15126497Sache 15226497Sache tm = malloc(sizeof(struct terminal), M_TERMINAL, M_WAITOK|M_ZERO); 15326497Sache terminal_init(tm); 15426497Sache 15526497Sache tm->tm_class = tc; 15675406Sache tm->tm_softc = softc; 15726497Sache 15826497Sache return (tm); 159} 160 161static void 162terminal_sync_ttysize(struct terminal *tm) 163{ 164 struct tty *tp; 165 166 tp = tm->tm_tty; 167 if (tp == NULL) 168 return; 169 170 tty_lock(tp); 171 tty_set_winsize(tp, &tm->tm_winsize); 172 tty_unlock(tp); 173} 174 175void 176terminal_maketty(struct terminal *tm, const char *fmt, ...) 177{ 178 struct tty *tp; 179 char name[8]; 180 va_list ap; 181 182 va_start(ap, fmt); 183 vsnrprintf(name, sizeof name, 32, fmt, ap); 184 va_end(ap); 185 186 tp = tty_alloc(&terminal_tty_class, tm); 187 tty_makedev(tp, NULL, "%s", name); 188 tm->tm_tty = tp; 189 terminal_sync_ttysize(tm); 190} 191 192void 193terminal_set_winsize_blank(struct terminal *tm, const struct winsize *size, 194 int blank, const term_attr_t *attr) 195{ 196 term_rect_t r; 197 198 tm->tm_winsize = *size; 199 200 r.tr_begin.tp_row = r.tr_begin.tp_col = 0; 201 r.tr_end.tp_row = size->ws_row; 202 r.tr_end.tp_col = size->ws_col; 203 204 TERMINAL_LOCK(tm); 205 if (blank == 0) 206 teken_set_winsize_noreset(&tm->tm_emulator, &r.tr_end); 207 else 208 teken_set_winsize(&tm->tm_emulator, &r.tr_end); 209 TERMINAL_UNLOCK(tm); 210 211 if ((blank != 0) && !(tm->tm_flags & TF_MUTE)) 212 tm->tm_class->tc_fill(tm, &r, 213 TCHAR_CREATE((teken_char_t)' ', attr)); 214 215 terminal_sync_ttysize(tm); 216} 217 218void 219terminal_set_winsize(struct terminal *tm, const struct winsize *size) 220{ 221 222 terminal_set_winsize_blank(tm, size, 1, 223 (const term_attr_t *)&default_message); 224} 225 226/* 227 * XXX: This function is a kludge. Drivers like vt(4) need to 228 * temporarily stop input when resizing, etc. This should ideally be 229 * handled within the driver. 230 */ 231 232void 233terminal_mute(struct terminal *tm, int yes) 234{ 235 236 TERMINAL_LOCK(tm); 237 if (yes) 238 tm->tm_flags |= TF_MUTE; 239 else 240 tm->tm_flags &= ~TF_MUTE; 241 TERMINAL_UNLOCK(tm); 242} 243 244void 245terminal_input_char(struct terminal *tm, term_char_t c) 246{ 247 struct tty *tp; 248 249 tp = tm->tm_tty; 250 if (tp == NULL) 251 return; 252 253 /* 254 * Strip off any attributes. Also ignore input of second part of 255 * CJK fullwidth characters, as we don't want to return these 256 * characters twice. 257 */ 258 if (TCHAR_FORMAT(c) & TF_CJK_RIGHT) 259 return; 260 c = TCHAR_CHARACTER(c); 261 262 tty_lock(tp); 263 /* 264 * Conversion to UTF-8. 265 */ 266 if (c < 0x80) { 267 ttydisc_rint(tp, c, 0); 268 } else if (c < 0x800) { 269 char str[2] = { 270 0xc0 | (c >> 6), 271 0x80 | (c & 0x3f) 272 }; 273 274 ttydisc_rint_simple(tp, str, sizeof str); 275 } else if (c < 0x10000) { 276 char str[3] = { 277 0xe0 | (c >> 12), 278 0x80 | ((c >> 6) & 0x3f), 279 0x80 | (c & 0x3f) 280 }; 281 282 ttydisc_rint_simple(tp, str, sizeof str); 283 } else { 284 char str[4] = { 285 0xf0 | (c >> 18), 286 0x80 | ((c >> 12) & 0x3f), 287 0x80 | ((c >> 6) & 0x3f), 288 0x80 | (c & 0x3f) 289 }; 290 291 ttydisc_rint_simple(tp, str, sizeof str); 292 } 293 ttydisc_rint_done(tp); 294 tty_unlock(tp); 295} 296 297void 298terminal_input_raw(struct terminal *tm, char c) 299{ 300 struct tty *tp; 301 302 tp = tm->tm_tty; 303 if (tp == NULL) 304 return; 305 306 tty_lock(tp); 307 ttydisc_rint(tp, c, 0); 308 ttydisc_rint_done(tp); 309 tty_unlock(tp); 310} 311 312void 313terminal_input_special(struct terminal *tm, unsigned int k) 314{ 315 struct tty *tp; 316 const char *str; 317 318 tp = tm->tm_tty; 319 if (tp == NULL) 320 return; 321 322 str = teken_get_sequence(&tm->tm_emulator, k); 323 if (str == NULL) 324 return; 325 326 tty_lock(tp); 327 ttydisc_rint_simple(tp, str, strlen(str)); 328 ttydisc_rint_done(tp); 329 tty_unlock(tp); 330} 331 332/* 333 * Binding with the TTY layer. 334 */ 335 336static int 337termtty_open(struct tty *tp) 338{ 339 struct terminal *tm = tty_softc(tp); 340 341 tm->tm_class->tc_opened(tm, 1); 342 return (0); 343} 344 345static void 346termtty_close(struct tty *tp) 347{ 348 struct terminal *tm = tty_softc(tp); 349 350 tm->tm_class->tc_opened(tm, 0); 351} 352 353static void 354termtty_outwakeup(struct tty *tp) 355{ 356 struct terminal *tm = tty_softc(tp); 357 char obuf[128]; 358 size_t olen; 359 unsigned int flags = 0; 360 361 while ((olen = ttydisc_getc(tp, obuf, sizeof obuf)) > 0) { 362 TERMINAL_LOCK_TTY(tm); 363 if (!(tm->tm_flags & TF_MUTE)) { 364 tm->tm_flags &= ~TF_BELL; 365 teken_input(&tm->tm_emulator, obuf, olen); 366 flags |= tm->tm_flags; 367 } 368 TERMINAL_UNLOCK_TTY(tm); 369 } 370 371 tm->tm_class->tc_done(tm); 372 if (flags & TF_BELL) 373 tm->tm_class->tc_bell(tm); 374} 375 376static int 377termtty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) 378{ 379 struct terminal *tm = tty_softc(tp); 380 int error; 381 382 switch (cmd) { 383 case CONS_GETINFO: { 384 vid_info_t *vi = (vid_info_t *)data; 385 const teken_pos_t *p; 386 int fg, bg; 387 388 if (vi->size != sizeof(vid_info_t)) 389 return (EINVAL); 390 391 /* Already help the console driver by filling in some data. */ 392 p = teken_get_cursor(&tm->tm_emulator); 393 vi->mv_row = p->tp_row; 394 vi->mv_col = p->tp_col; 395 396 p = teken_get_winsize(&tm->tm_emulator); 397 vi->mv_rsz = p->tp_row; 398 vi->mv_csz = p->tp_col; 399 400 teken_get_defattr_cons25(&tm->tm_emulator, &fg, &bg); 401 vi->mv_norm.fore = fg; 402 vi->mv_norm.back = bg; 403 /* XXX: keep vidcontrol happy; bold backgrounds. */ 404 vi->mv_rev.fore = bg; 405 vi->mv_rev.back = fg & 0x7; 406 break; 407 } 408 } 409 410 /* 411 * Unlike various other drivers, this driver will never 412 * deallocate TTYs. This means it's safe to temporarily unlock 413 * the TTY when handling ioctls. 414 */ 415 tty_unlock(tp); 416 error = tm->tm_class->tc_ioctl(tm, cmd, data, td); 417 tty_lock(tp); 418 return (error); 419} 420 421static int 422termtty_mmap(struct tty *tp, vm_ooffset_t offset, vm_paddr_t * paddr, 423 int nprot, vm_memattr_t *memattr) 424{ 425 struct terminal *tm = tty_softc(tp); 426 427 return (tm->tm_class->tc_mmap(tm, offset, paddr, nprot, memattr)); 428} 429 430/* 431 * Binding with the kernel and debug console. 432 */ 433 434static cn_probe_t termcn_cnprobe; 435static cn_init_t termcn_cninit; 436static cn_term_t termcn_cnterm; 437static cn_getc_t termcn_cngetc; 438static cn_putc_t termcn_cnputc; 439static cn_grab_t termcn_cngrab; 440static cn_ungrab_t termcn_cnungrab; 441 442const struct consdev_ops termcn_cnops = { 443 .cn_probe = termcn_cnprobe, 444 .cn_init = termcn_cninit, 445 .cn_term = termcn_cnterm, 446 .cn_getc = termcn_cngetc, 447 .cn_putc = termcn_cnputc, 448 .cn_grab = termcn_cngrab, 449 .cn_ungrab = termcn_cnungrab, 450}; 451 452void 453termcn_cnregister(struct terminal *tm) 454{ 455 struct consdev *cp; 456 457 cp = tm->consdev; 458 if (cp == NULL) { 459 cp = malloc(sizeof(struct consdev), M_TERMINAL, 460 M_WAITOK|M_ZERO); 461 cp->cn_ops = &termcn_cnops; 462 cp->cn_arg = tm; 463 cp->cn_pri = CN_INTERNAL; 464 sprintf(cp->cn_name, "ttyv0"); 465 466 tm->tm_flags = TF_CONS; 467 tm->consdev = cp; 468 469 terminal_init(tm); 470 } 471 472 /* Attach terminal as console. */ 473 cnadd(cp); 474} 475 476static void 477termcn_cngrab(struct consdev *cp) 478{ 479 480} 481 482static void 483termcn_cnungrab(struct consdev *cp) 484{ 485 486} 487 488static void 489termcn_cnprobe(struct consdev *cp) 490{ 491 struct terminal *tm = cp->cn_arg; 492 493 if (tm == NULL) { 494 cp->cn_pri = CN_DEAD; 495 return; 496 } 497 498 tm->consdev = cp; 499 terminal_init(tm); 500 501 tm->tm_class->tc_cnprobe(tm, cp); 502} 503 504static void 505termcn_cninit(struct consdev *cp) 506{ 507 508} 509 510static void 511termcn_cnterm(struct consdev *cp) 512{ 513 514} 515 516static int 517termcn_cngetc(struct consdev *cp) 518{ 519 struct terminal *tm = cp->cn_arg; 520 521 return (tm->tm_class->tc_cngetc(tm)); 522} 523 524static void 525termcn_cnputc(struct consdev *cp, int c) 526{ 527 struct terminal *tm = cp->cn_arg; 528 teken_attr_t backup; 529 char cv = c; 530 531 TERMINAL_LOCK_CONS(tm); 532 if (!(tm->tm_flags & TF_MUTE)) { 533 backup = *teken_get_curattr(&tm->tm_emulator); 534 teken_set_curattr(&tm->tm_emulator, &kernel_message); 535 teken_input(&tm->tm_emulator, &cv, 1); 536 teken_set_curattr(&tm->tm_emulator, &backup); 537 } 538 TERMINAL_UNLOCK_CONS(tm); 539 540 tm->tm_class->tc_done(tm); 541} 542 543/* 544 * Binding with the terminal emulator. 545 */ 546 547static void 548termteken_bell(void *softc) 549{ 550 struct terminal *tm = softc; 551 552 tm->tm_flags |= TF_BELL; 553} 554 555static void 556termteken_cursor(void *softc, const teken_pos_t *p) 557{ 558 struct terminal *tm = softc; 559 560 tm->tm_class->tc_cursor(tm, p); 561} 562 563static void 564termteken_putchar(void *softc, const teken_pos_t *p, teken_char_t c, 565 const teken_attr_t *a) 566{ 567 struct terminal *tm = softc; 568 569 tm->tm_class->tc_putchar(tm, p, TCHAR_CREATE(c, a)); 570} 571 572static void 573termteken_fill(void *softc, const teken_rect_t *r, teken_char_t c, 574 const teken_attr_t *a) 575{ 576 struct terminal *tm = softc; 577 578 tm->tm_class->tc_fill(tm, r, TCHAR_CREATE(c, a)); 579} 580 581static void 582termteken_copy(void *softc, const teken_rect_t *r, const teken_pos_t *p) 583{ 584 struct terminal *tm = softc; 585 586 tm->tm_class->tc_copy(tm, r, p); 587} 588 589static void 590termteken_param(void *softc, int cmd, unsigned int arg) 591{ 592 struct terminal *tm = softc; 593 594 tm->tm_class->tc_param(tm, cmd, arg); 595} 596 597static void 598termteken_respond(void *softc, const void *buf, size_t len) 599{ 600#if 0 601 struct terminal *tm = softc; 602 struct tty *tp; 603 604 /* 605 * Only inject a response into the TTY if the data actually 606 * originated from the TTY. 607 * 608 * XXX: This cannot be done right now. The TTY could pick up 609 * other locks. It could also in theory cause loops, when the 610 * TTY performs echoing of a command that generates even more 611 * input. 612 */ 613 tp = tm->tm_tty; 614 if (tp == NULL) 615 return; 616 617 ttydisc_rint_simple(tp, buf, len); 618 ttydisc_rint_done(tp); 619#endif 620} 621