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