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