subr_terminal.c revision 243802
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: user/ed/newcons/sys/kern/subr_terminal.c 243802 2012-12-02 22:21:40Z nwhitehorn $"); 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 46static 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 84static tsw_open_t termtty_open; 85static tsw_close_t termtty_close; 86static tsw_outwakeup_t termtty_outwakeup; 87static tsw_ioctl_t termtty_ioctl; 88 89static 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}; 95 96/* 97 * Terminal emulator routines. 98 */ 99 100static tf_bell_t termteken_bell; 101static tf_cursor_t termteken_cursor; 102static tf_putchar_t termteken_putchar; 103static tf_fill_t termteken_fill; 104static tf_copy_t termteken_copy; 105static tf_param_t termteken_param; 106static tf_respond_t termteken_respond; 107 108static teken_funcs_t terminal_drawmethods = { 109 .tf_bell = termteken_bell, 110 .tf_cursor = termteken_cursor, 111 .tf_putchar = termteken_putchar, 112 .tf_fill = termteken_fill, 113 .tf_copy = termteken_copy, 114 .tf_param = termteken_param, 115 .tf_respond = termteken_respond, 116}; 117 118/* Kernel message formatting. */ 119static const teken_attr_t kernel_message = { 120 .ta_fgcolor = TC_WHITE, 121 .ta_bgcolor = TC_BLACK, 122 .ta_format = TF_BOLD, 123}; 124 125static const teken_attr_t default_message = { 126 .ta_fgcolor = TC_WHITE, 127 .ta_bgcolor = TC_BLACK, 128}; 129 130#define TCHAR_CREATE(c, a) ((c) | \ 131 (a)->ta_format << 22 | \ 132 teken_256to8((a)->ta_fgcolor) << 26 | \ 133 teken_256to8((a)->ta_bgcolor) << 29) 134 135static void 136terminal_init(struct terminal *tm) 137{ 138 139 if (tm->tm_flags & TF_CONS) 140 mtx_init(&tm->tm_mtx, "trmlck", NULL, MTX_SPIN); 141 teken_init(&tm->tm_emulator, &terminal_drawmethods, tm); 142 teken_set_defattr(&tm->tm_emulator, &default_message); 143 144} 145 146struct terminal * 147terminal_alloc(const struct terminal_class *tc, void *softc) 148{ 149 struct terminal *tm; 150 151 tm = malloc(sizeof(struct terminal), M_TERMINAL, M_WAITOK|M_ZERO); 152 terminal_init(tm); 153 154 tm->tm_class = tc; 155 tm->tm_softc = softc; 156 157 return (tm); 158} 159 160static void 161terminal_sync_ttysize(struct terminal *tm) 162{ 163 struct tty *tp; 164 165 tp = tm->tm_tty; 166 if (tp == NULL) 167 return; 168 169 tty_lock(tp); 170 tty_set_winsize(tp, &tm->tm_winsize); 171 tty_unlock(tp); 172} 173 174void 175terminal_maketty(struct terminal *tm, const char *fmt, ...) 176{ 177 struct tty *tp; 178 char name[8]; 179 va_list ap; 180 181 va_start(ap, fmt); 182 vsnrprintf(name, sizeof name, 32, fmt, ap); 183 va_end(ap); 184 185 tp = tty_alloc(&terminal_tty_class, tm); 186 tty_makedev(tp, NULL, "%s", name); 187 tm->tm_tty = tp; 188 terminal_sync_ttysize(tm); 189} 190 191void 192terminal_set_winsize(struct terminal *tm, const struct winsize *size) 193{ 194 term_rect_t r; 195 196 tm->tm_winsize = *size; 197 198 r.tr_begin.tp_row = r.tr_begin.tp_col = 0; 199 r.tr_end.tp_row = size->ws_row; 200 r.tr_end.tp_col = size->ws_col; 201 202 TERMINAL_LOCK(tm); 203 teken_set_winsize(&tm->tm_emulator, &r.tr_end); 204 TERMINAL_UNLOCK(tm); 205 206 /* Blank screen. */ 207 tm->tm_class->tc_fill(tm, &r, 208 TCHAR_CREATE((teken_char_t)' ', &default_message)); 209 210 terminal_sync_ttysize(tm); 211} 212 213/* 214 * XXX: This function is a kludge. Drivers like vt(4) need to 215 * temporarily stop input when resizing, etc. This should ideally be 216 * handled within the driver. 217 */ 218 219void 220terminal_mute(struct terminal *tm, int yes) 221{ 222 223 TERMINAL_LOCK(tm); 224 if (yes) 225 tm->tm_flags |= TF_MUTE; 226 else 227 tm->tm_flags &= ~TF_MUTE; 228 TERMINAL_UNLOCK(tm); 229} 230 231void 232terminal_input_char(struct terminal *tm, term_char_t c) 233{ 234 struct tty *tp; 235 236 tp = tm->tm_tty; 237 if (tp == NULL) 238 return; 239 240 /* Strip off any attributes. */ 241 c = TCHAR_CHARACTER(c); 242 243 tty_lock(tp); 244 /* 245 * Conversion to UTF-8. 246 */ 247 if (c < 0x80) { 248 ttydisc_rint(tp, c, 0); 249 } else if (c < 0x800) { 250 char str[2] = { 251 0xc0 | (c >> 6), 252 0x80 | (c & 0x3f) 253 }; 254 255 ttydisc_rint_simple(tp, str, sizeof str); 256 } else if (c < 0x10000) { 257 char str[3] = { 258 0xe0 | (c >> 12), 259 0x80 | ((c >> 6) & 0x3f), 260 0x80 | (c & 0x3f) 261 }; 262 263 ttydisc_rint_simple(tp, str, sizeof str); 264 } else { 265 char str[4] = { 266 0xf0 | (c >> 18), 267 0x80 | ((c >> 12) & 0x3f), 268 0x80 | ((c >> 6) & 0x3f), 269 0x80 | (c & 0x3f) 270 }; 271 272 ttydisc_rint_simple(tp, str, sizeof str); 273 } 274 ttydisc_rint_done(tp); 275 tty_unlock(tp); 276} 277 278void 279terminal_input_raw(struct terminal *tm, char c) 280{ 281 struct tty *tp; 282 283 tp = tm->tm_tty; 284 if (tp == NULL) 285 return; 286 287 tty_lock(tp); 288 ttydisc_rint(tp, c, 0); 289 ttydisc_rint_done(tp); 290 tty_unlock(tp); 291} 292 293void 294terminal_input_special(struct terminal *tm, unsigned int k) 295{ 296 struct tty *tp; 297 const char *str; 298 299 tp = tm->tm_tty; 300 if (tp == NULL) 301 return; 302 303 str = teken_get_sequence(&tm->tm_emulator, k); 304 if (str == NULL) 305 return; 306 307 tty_lock(tp); 308 ttydisc_rint_simple(tp, str, strlen(str)); 309 ttydisc_rint_done(tp); 310 tty_unlock(tp); 311} 312 313/* 314 * Binding with the TTY layer. 315 */ 316 317static int 318termtty_open(struct tty *tp) 319{ 320 struct terminal *tm = tty_softc(tp); 321 322 tm->tm_class->tc_opened(tm, 1); 323 return (0); 324} 325 326static void 327termtty_close(struct tty *tp) 328{ 329 struct terminal *tm = tty_softc(tp); 330 331 tm->tm_class->tc_opened(tm, 0); 332} 333 334static void 335termtty_outwakeup(struct tty *tp) 336{ 337 struct terminal *tm = tty_softc(tp); 338 char obuf[128]; 339 size_t olen; 340 unsigned int flags = 0; 341 342 while ((olen = ttydisc_getc(tp, obuf, sizeof obuf)) > 0) { 343 TERMINAL_LOCK_TTY(tm); 344 if (!(tm->tm_flags & TF_MUTE)) { 345 tm->tm_flags &= ~TF_BELL; 346 teken_input(&tm->tm_emulator, obuf, olen); 347 flags |= tm->tm_flags; 348 } 349 TERMINAL_UNLOCK_TTY(tm); 350 } 351 352 tm->tm_class->tc_done(tm); 353 if (flags & TF_BELL) 354 tm->tm_class->tc_bell(tm); 355} 356 357static int 358termtty_ioctl(struct tty *tp, u_long cmd, caddr_t data, struct thread *td) 359{ 360 struct terminal *tm = tty_softc(tp); 361 int error; 362 363 switch (cmd) { 364 case CONS_GETINFO: { 365 vid_info_t *vi = (vid_info_t *)data; 366 const teken_pos_t *p; 367 int fg, bg; 368 369 if (vi->size != sizeof(vid_info_t)) 370 return (EINVAL); 371 372 /* Already help the console driver by filling in some data. */ 373 p = teken_get_cursor(&tm->tm_emulator); 374 vi->mv_row = p->tp_row; 375 vi->mv_col = p->tp_col; 376 377 p = teken_get_winsize(&tm->tm_emulator); 378 vi->mv_rsz = p->tp_row; 379 vi->mv_csz = p->tp_col; 380 381 teken_get_defattr_cons25(&tm->tm_emulator, &fg, &bg); 382 vi->mv_norm.fore = fg; 383 vi->mv_norm.back = bg; 384 /* XXX: keep vidcontrol happy; bold backgrounds. */ 385 vi->mv_rev.fore = bg; 386 vi->mv_rev.back = fg & 0x7; 387 break; 388 } 389 } 390 391 /* 392 * Unlike various other drivers, this driver will never 393 * deallocate TTYs. This means it's safe to temporarily unlock 394 * the TTY when handling ioctls. 395 */ 396 tty_unlock(tp); 397 error = tm->tm_class->tc_ioctl(tm, cmd, data, td); 398 tty_lock(tp); 399 return (error); 400} 401 402/* 403 * Binding with the kernel and debug console. 404 */ 405 406static cn_probe_t termcn_probe; 407static cn_init_t termcn_init; 408static cn_term_t termcn_term; 409static cn_getc_t termcn_getc; 410static cn_putc_t termcn_putc; 411 412const struct consdev_ops termcn_ops = { 413 .cn_probe = termcn_probe, 414 .cn_init = termcn_init, 415 .cn_term = termcn_term, 416 .cn_getc = termcn_getc, 417 .cn_putc = termcn_putc, 418}; 419 420static void 421termcn_probe(struct consdev *cp) 422{ 423 struct terminal *tm = cp->cn_arg; 424 425 terminal_init(tm); 426 427 tm->tm_class->tc_cnprobe(tm, cp); 428} 429 430static void 431termcn_init(struct consdev *cp) 432{ 433} 434 435static void 436termcn_term(struct consdev *cp) 437{ 438} 439 440static int 441termcn_getc(struct consdev *cp) 442{ 443 struct terminal *tm = cp->cn_arg; 444 445 return (tm->tm_class->tc_cngetc(tm)); 446} 447 448static void 449termcn_putc(struct consdev *cp, int c) 450{ 451 struct terminal *tm = cp->cn_arg; 452 teken_attr_t backup; 453 char cv = c; 454 455 TERMINAL_LOCK_CONS(tm); 456 if (!(tm->tm_flags & TF_MUTE)) { 457 backup = *teken_get_curattr(&tm->tm_emulator); 458 teken_set_curattr(&tm->tm_emulator, &kernel_message); 459 teken_input(&tm->tm_emulator, &cv, 1); 460 teken_set_curattr(&tm->tm_emulator, &backup); 461 } 462 TERMINAL_UNLOCK_CONS(tm); 463 464 tm->tm_class->tc_done(tm); 465} 466 467/* 468 * Binding with the terminal emulator. 469 */ 470 471static void 472termteken_bell(void *softc) 473{ 474 struct terminal *tm = softc; 475 476 tm->tm_flags |= TF_BELL; 477} 478 479static void 480termteken_cursor(void *softc, const teken_pos_t *p) 481{ 482 struct terminal *tm = softc; 483 484 tm->tm_class->tc_cursor(tm, p); 485} 486 487static void 488termteken_putchar(void *softc, const teken_pos_t *p, teken_char_t c, 489 const teken_attr_t *a) 490{ 491 struct terminal *tm = softc; 492 493 tm->tm_class->tc_putchar(tm, p, TCHAR_CREATE(c, a)); 494} 495 496static void 497termteken_fill(void *softc, const teken_rect_t *r, teken_char_t c, 498 const teken_attr_t *a) 499{ 500 struct terminal *tm = softc; 501 502 tm->tm_class->tc_fill(tm, r, TCHAR_CREATE(c, a)); 503} 504 505static void 506termteken_copy(void *softc, const teken_rect_t *r, const teken_pos_t *p) 507{ 508 struct terminal *tm = softc; 509 510 tm->tm_class->tc_copy(tm, r, p); 511} 512 513static void 514termteken_param(void *softc, int cmd, unsigned int arg) 515{ 516 struct terminal *tm = softc; 517 518 tm->tm_class->tc_param(tm, cmd, arg); 519} 520 521static void 522termteken_respond(void *softc, const void *buf, size_t len) 523{ 524#if 0 525 struct terminal *tm = softc; 526 struct tty *tp; 527 528 /* 529 * Only inject a response into the TTY if the data actually 530 * originated from the TTY. 531 * 532 * XXX: This cannot be done right now. The TTY could pick up 533 * other locks. It could also in theory cause loops, when the 534 * TTY performs echoing of a command that generates even more 535 * input. 536 */ 537 tp = tm->tm_tty; 538 if (tp == NULL) 539 return; 540 541 ttydisc_rint_simple(tp, buf, len); 542 ttydisc_rint_done(tp); 543#endif 544} 545