vt_core.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/dev/vt/vt_core.c 243802 2012-12-02 22:21:40Z nwhitehorn $");
32
33#include <sys/param.h>
34#include <sys/consio.h>
35#include <sys/eventhandler.h>
36#include <sys/fbio.h>
37#include <sys/kbio.h>
38#include <sys/kdb.h>
39#include <sys/kernel.h>
40#include <sys/lock.h>
41#include <sys/malloc.h>
42#include <sys/mutex.h>
43#include <sys/reboot.h>
44#include <sys/systm.h>
45#include <sys/terminal.h>
46
47#include <dev/kbd/kbdreg.h>
48#include <dev/vt/vt.h>
49
50static tc_bell_t	vtterm_bell;
51static tc_cursor_t	vtterm_cursor;
52static tc_putchar_t	vtterm_putchar;
53static tc_fill_t	vtterm_fill;
54static tc_copy_t	vtterm_copy;
55static tc_param_t	vtterm_param;
56static tc_done_t	vtterm_done;
57
58static tc_cnprobe_t	vtterm_cnprobe;
59static tc_cngetc_t	vtterm_cngetc;
60
61static tc_opened_t	vtterm_opened;
62static tc_ioctl_t	vtterm_ioctl;
63
64const struct terminal_class vt_termclass = {
65	.tc_bell	= vtterm_bell,
66	.tc_cursor	= vtterm_cursor,
67	.tc_putchar	= vtterm_putchar,
68	.tc_fill	= vtterm_fill,
69	.tc_copy	= vtterm_copy,
70	.tc_param	= vtterm_param,
71	.tc_done	= vtterm_done,
72
73	.tc_cnprobe	= vtterm_cnprobe,
74	.tc_cngetc	= vtterm_cngetc,
75
76	.tc_opened	= vtterm_opened,
77	.tc_ioctl	= vtterm_ioctl,
78};
79
80/*
81 * Use a constant timer of 25 Hz to redraw the screen.
82 *
83 * XXX: In theory we should only fire up the timer when there is really
84 * activity. Unfortunately we cannot always start timers. We really
85 * don't want to process kernel messages synchronously, because it
86 * really slows down the system.
87 */
88#define	VT_TIMERFREQ	25
89
90/* Bell pitch/duration. */
91#define VT_BELLDURATION	((5 * hz + 99) / 100)
92#define VT_BELLPITCH	800
93
94#define	VT_LOCK(vd)	mtx_lock(&(vd)->vd_lock)
95#define	VT_UNLOCK(vd)	mtx_unlock(&(vd)->vd_lock)
96
97#define	VT_UNIT(vw)	((vw)->vw_device->vd_unit * VT_MAXWINDOWS + \
98			(vw)->vw_number)
99
100static unsigned int vt_unit = 0;
101static MALLOC_DEFINE(M_VT, "vt", "vt device");
102
103/* Boot logo. */
104extern unsigned int vt_logo_width;
105extern unsigned int vt_logo_height;
106extern unsigned int vt_logo_depth;
107extern unsigned char vt_logo_image[];
108
109/* Font. */
110extern struct vt_font vt_font_default;
111
112static void
113vt_window_switch(struct vt_window *vw)
114{
115	struct vt_device *vd = vw->vw_device;
116	keyboard_t *kbd;
117
118	VT_LOCK(vd);
119	if (vd->vd_curwindow == vw ||
120	    !(vw->vw_flags & (VWF_OPENED|VWF_CONSOLE))) {
121		VT_UNLOCK(vd);
122		return;
123	}
124	vd->vd_curwindow = vw;
125	vd->vd_flags |= VDF_INVALID;
126	cv_broadcast(&vd->vd_winswitch);
127	VT_UNLOCK(vd);
128
129	/* Restore per-window keyboard mode. */
130	mtx_lock(&Giant);
131	kbd = kbd_get_keyboard(vd->vd_keyboard);
132	if (kbd != NULL)
133		kbdd_ioctl(kbd, KDSKBMODE, (void *)&vw->vw_kbdmode);
134	mtx_unlock(&Giant);
135}
136
137static inline void
138vt_termsize(struct vt_device *vd, struct vt_font *vf, term_pos_t *size)
139{
140
141	size->tp_row = vd->vd_height;
142	size->tp_col = vd->vd_width;
143	if (vf != NULL) {
144		size->tp_row /= vf->vf_height;
145		size->tp_col /= vf->vf_width;
146	}
147}
148
149static inline void
150vt_winsize(struct vt_device *vd, struct vt_font *vf, struct winsize *size)
151{
152
153	size->ws_row = size->ws_ypixel = vd->vd_height;
154	size->ws_col = size->ws_xpixel = vd->vd_width;
155	if (vf != NULL) {
156		size->ws_row /= vf->vf_height;
157		size->ws_col /= vf->vf_width;
158	}
159}
160
161static int
162vt_kbdevent(keyboard_t *kbd, int event, void *arg)
163{
164	struct vt_device *vd = arg;
165	struct vt_window *vw = vd->vd_curwindow;
166	u_int c;
167
168	switch (event) {
169	case KBDIO_KEYINPUT:
170		break;
171	case KBDIO_UNLOADING:
172		mtx_lock(&Giant);
173		vd->vd_keyboard = -1;
174		kbd_release(kbd, (void *)&vd->vd_keyboard);
175		mtx_unlock(&Giant);
176		return (0);
177	default:
178		return (EINVAL);
179	}
180
181	c = kbdd_read_char(kbd, 0);
182	if (c & RELKEY)
183		return (0);
184
185	if (c & SPCLKEY) {
186		c &= ~SPCLKEY;
187
188		if (c >= F_SCR && c <= MIN(L_SCR, F_SCR + VT_MAXWINDOWS - 1)) {
189			vw = vd->vd_windows[c - F_SCR];
190			if (vw != NULL)
191				vt_window_switch(vw);
192			return (0);
193		}
194
195		switch (c) {
196		case DBG:
197			kdb_enter(KDB_WHY_BREAK, "manual escape to debugger");
198			break;
199		case RBT:
200			/* XXX: Make this configurable! */
201			shutdown_nice(0);
202			break;
203		case HALT:
204			shutdown_nice(RB_HALT);
205			break;
206		case PDWN:
207			shutdown_nice(RB_HALT|RB_POWEROFF);
208			break;
209		case SLK: {
210			int state = 0;
211
212			kbdd_ioctl(kbd, KDGKBSTATE, (caddr_t)&state);
213			VT_LOCK(vd);
214			if (state & SLKED) {
215				/* Turn scrolling on. */
216				vw->vw_flags |= VWF_SCROLL;
217			} else {
218				/* Turn scrolling off. */
219				vw->vw_flags &= ~VWF_SCROLL;
220				vthistory_seek(&vw->vw_history, 0, VHS_SET);
221			}
222			VT_UNLOCK(vd);
223			break;
224		}
225		case FKEY | F(1):  case FKEY | F(2):  case FKEY | F(3):
226		case FKEY | F(4):  case FKEY | F(5):  case FKEY | F(6):
227		case FKEY | F(7):  case FKEY | F(8):  case FKEY | F(9):
228		case FKEY | F(10): case FKEY | F(11): case FKEY | F(12):
229			/* F1 through F12 keys. */
230			terminal_input_special(vw->vw_terminal,
231			    TKEY_F1 + c - (FKEY | F(1)));
232			break;
233		case FKEY | F(49): /* Home key. */
234			VT_LOCK(vd);
235			if (vw->vw_flags & VWF_SCROLL) {
236				vthistory_seek(&vw->vw_history, 0, VHS_END);
237				VT_UNLOCK(vd);
238				break;
239			}
240			VT_UNLOCK(vd);
241			terminal_input_special(vw->vw_terminal, TKEY_HOME);
242			break;
243		case FKEY | F(50): /* Arrow up. */
244			VT_LOCK(vd);
245			if (vw->vw_flags & VWF_SCROLL) {
246				vthistory_seek(&vw->vw_history, 1, VHS_CUR);
247				VT_UNLOCK(vd);
248				break;
249			}
250			VT_UNLOCK(vd);
251			terminal_input_special(vw->vw_terminal, TKEY_UP);
252			break;
253		case FKEY | F(51): /* Page up. */
254			VT_LOCK(vd);
255			if (vw->vw_flags & VWF_SCROLL) {
256				term_pos_t size;
257
258				vt_termsize(vd, vw->vw_font, &size);
259				vthistory_seek(&vw->vw_history, size.tp_row,
260				    VHS_CUR);
261				VT_UNLOCK(vd);
262				break;
263			}
264			VT_UNLOCK(vd);
265			terminal_input_special(vw->vw_terminal, TKEY_PAGE_UP);
266			break;
267		case FKEY | F(53): /* Arrow left. */
268			terminal_input_special(vw->vw_terminal, TKEY_LEFT);
269			break;
270		case FKEY | F(55): /* Arrow right. */
271			terminal_input_special(vw->vw_terminal, TKEY_RIGHT);
272			break;
273		case FKEY | F(57): /* End key. */
274			VT_LOCK(vd);
275			if (vw->vw_flags & VWF_SCROLL) {
276				vthistory_seek(&vw->vw_history, 0, VHS_SET);
277				VT_UNLOCK(vd);
278				break;
279			}
280			VT_UNLOCK(vd);
281			terminal_input_special(vw->vw_terminal, TKEY_END);
282			break;
283		case FKEY | F(58): /* Arrow down. */
284			VT_LOCK(vd);
285			if (vw->vw_flags & VWF_SCROLL) {
286				vthistory_seek(&vw->vw_history, -1, VHS_CUR);
287				VT_UNLOCK(vd);
288				break;
289			}
290			VT_UNLOCK(vd);
291			terminal_input_special(vw->vw_terminal, TKEY_DOWN);
292			break;
293		case FKEY | F(59): /* Page down. */
294			VT_LOCK(vd);
295			if (vw->vw_flags & VWF_SCROLL) {
296				term_pos_t size;
297
298				vt_termsize(vd, vw->vw_font, &size);
299				vthistory_seek(&vw->vw_history, -size.tp_row,
300				    VHS_CUR);
301				VT_UNLOCK(vd);
302				break;
303			}
304			VT_UNLOCK(vd);
305			terminal_input_special(vw->vw_terminal, TKEY_PAGE_DOWN);
306			break;
307		case FKEY | F(60): /* Insert key. */
308			terminal_input_special(vw->vw_terminal, TKEY_INSERT);
309			break;
310		case FKEY | F(61): /* Delete key. */
311			terminal_input_special(vw->vw_terminal, TKEY_DELETE);
312			break;
313		}
314	} else if (KEYFLAGS(c) == 0) {
315		c = KEYCHAR(c);
316
317		/* Don't do UTF-8 conversion when doing raw mode. */
318		if (vw->vw_kbdmode == K_XLATE)
319			terminal_input_char(vw->vw_terminal, c);
320		else
321			terminal_input_raw(vw->vw_terminal, c);
322	}
323
324	return (0);
325}
326
327static int
328vt_allocate_keyboard(struct vt_device *vd)
329{
330	int		 idx0, idx;
331	keyboard_t	*k0, *k;
332	keyboard_info_t	 ki;
333
334	idx0 = kbd_allocate("kbdmux", -1, (void *)&vd->vd_keyboard,
335	    vt_kbdevent, vd);
336	if (idx0 != -1) {
337		k0 = kbd_get_keyboard(idx0);
338
339		for (idx = kbd_find_keyboard2("*", -1, 0);
340		     idx != -1;
341		     idx = kbd_find_keyboard2("*", -1, idx + 1)) {
342			k = kbd_get_keyboard(idx);
343
344			if (idx == idx0 || KBD_IS_BUSY(k))
345				continue;
346
347			bzero(&ki, sizeof(ki));
348			strcpy(ki.kb_name, k->kb_name);
349			ki.kb_unit = k->kb_unit;
350
351			kbdd_ioctl(k0, KBADDKBD, (caddr_t) &ki);
352		}
353	} else
354		idx0 = kbd_allocate("*", -1, (void *)&vd->vd_keyboard,
355		    vt_kbdevent, vd);
356
357	return (idx0);
358}
359
360static void
361vtterm_bell(struct terminal *tm)
362{
363
364	sysbeep(1193182 / VT_BELLPITCH, VT_BELLDURATION);
365}
366
367static void
368vtterm_cursor(struct terminal *tm, const term_pos_t *p)
369{
370	struct vt_window *vw = tm->tm_softc;
371
372	vtbuf_cursor_position(&vw->vw_buf, p);
373}
374
375static void
376vtterm_putchar(struct terminal *tm, const term_pos_t *p, term_char_t c)
377{
378	struct vt_window *vw = tm->tm_softc;
379
380	vtbuf_putchar(&vw->vw_buf, p, c);
381}
382
383static void
384vtterm_fill(struct terminal *tm, const term_rect_t *r, term_char_t c)
385{
386	struct vt_window *vw = tm->tm_softc;
387
388	vtbuf_fill(&vw->vw_buf, r, c);
389}
390
391static void
392vtterm_copy(struct terminal *tm, const term_rect_t *r,
393    const term_pos_t *p)
394{
395	struct vt_window *vw = tm->tm_softc;
396	struct vt_device *vd = vw->vw_device;
397	term_pos_t size;
398
399	/*
400	 * We copy lines into the history buffer when we have to do a
401	 * copy of the entire width of the screen to a region above it.
402	 */
403	vt_termsize(vd, vw->vw_font, &size);
404	if (r->tr_begin.tp_row > p->tp_row &&
405	    r->tr_begin.tp_col == 0 && r->tr_end.tp_col == size.tp_col) {
406		term_rect_t area;
407
408		area.tr_begin.tp_row = p->tp_row;
409		area.tr_begin.tp_col = 0;
410		area.tr_end.tp_row = r->tr_begin.tp_row;
411		area.tr_end.tp_col = size.tp_col;
412		vthistory_add(&vw->vw_history, &vw->vw_buf, &area);
413	}
414
415	vtbuf_copy(&vw->vw_buf, r, p);
416}
417
418static void
419vtterm_param(struct terminal *tm, int cmd, unsigned int arg)
420{
421	struct vt_window *vw = tm->tm_softc;
422
423	switch (cmd) {
424	case TP_SHOWCURSOR:
425		vtbuf_cursor_visibility(&vw->vw_buf, arg);
426		break;
427	}
428}
429
430static inline void
431vt_determine_colors(term_char_t c, int cursor,
432    term_color_t *fg, term_color_t *bg)
433{
434
435	*fg = TCHAR_FGCOLOR(c);
436	if (TCHAR_FORMAT(c) & TF_BOLD)
437		*fg = TCOLOR_LIGHT(*fg);
438	*bg = TCHAR_BGCOLOR(c);
439
440	if (TCHAR_FORMAT(c) & TF_REVERSE) {
441		term_color_t tmp;
442
443		tmp = *fg;
444		*fg = *bg;
445		*bg = tmp;
446	}
447
448	if (cursor) {
449		*fg = *bg;
450		*bg = TC_WHITE;
451	}
452}
453
454static void
455vt_bitblt_char(struct vt_device *vd, struct vt_font *vf, term_char_t c,
456    int iscursor, unsigned int row, unsigned int col)
457{
458	term_color_t fg, bg;
459
460	vt_determine_colors(c, iscursor, &fg, &bg);
461
462	if (vf != NULL) {
463		const uint8_t *src;
464		vt_axis_t top, left;
465
466		src = vtfont_lookup(vf, c);
467
468		/*
469		 * Align the terminal to the centre of the screen.
470		 * Fonts may not always be able to fill the entire
471		 * screen.
472		 */
473		top = row * vf->vf_height +
474		    (vd->vd_height % vf->vf_height) / 2;
475		left = col * vf->vf_width +
476		    (vd->vd_width % vf->vf_width) / 2;
477
478		vd->vd_driver->vd_bitblt(vd, src, top, left,
479		    vf->vf_width, vf->vf_height, fg, bg);
480	} else {
481		vd->vd_driver->vd_putchar(vd, TCHAR_CHARACTER(c),
482		    row, col, fg, bg);
483	}
484}
485
486static void
487vt_flush(struct vt_device *vd)
488{
489	struct vt_window *vw = vd->vd_curwindow;
490	struct vt_font *vf = vw->vw_font;
491	term_pos_t size;
492	term_rect_t tarea;
493	struct vt_bufmask tmask;
494	unsigned int row, col, scrollpos;
495	term_char_t c;
496
497	if (vd->vd_flags & VDF_SPLASH || vw->vw_flags & VWF_BUSY)
498		return;
499
500	vtbuf_undirty(&vw->vw_buf, &tarea, &tmask);
501	vthistory_getpos(&vw->vw_history, &scrollpos);
502	vt_termsize(vd, vf, &size);
503
504	/* Force a full redraw when the screen contents are invalid. */
505	if (vd->vd_scrollpos != scrollpos || vd->vd_flags & VDF_INVALID) {
506		tarea.tr_begin.tp_row = tarea.tr_begin.tp_col = 0;
507		tarea.tr_end = size;
508		tmask.vbm_row = tmask.vbm_col = VBM_DIRTY;
509
510		/*
511		 * Blank to prevent borders with artifacts.  This is
512		 * only required when the font doesn't exactly fill the
513		 * screen.
514		 */
515		if (vd->vd_flags & VDF_INVALID && vf != NULL &&
516		    (vd->vd_width % vf->vf_width != 0 ||
517		    vd->vd_height % vf->vf_height != 0))
518			vd->vd_driver->vd_blank(vd, TC_BLACK);
519
520		/* Draw the scrollback history. */
521		for (row = 0; row < scrollpos; row++) {
522			for (col = 0; col < size.tp_col; col++) {
523				c = VTHISTORY_FIELD(&vw->vw_history, row, col);
524				vt_bitblt_char(vd, vf, c, 0, row, col);
525			}
526		}
527
528		vd->vd_flags &= ~VDF_INVALID;
529		vd->vd_scrollpos = scrollpos;
530	}
531
532	/*
533	 * Clamp the terminal rendering size if it exceeds the window
534	 * size, because of scrollback.
535	 */
536	if (tarea.tr_end.tp_row + scrollpos > size.tp_row) {
537		if (size.tp_row <= scrollpos)
538			/* Terminal completely invisible. */
539			tarea.tr_end.tp_row = 0;
540		else
541			/* Terminal partially visible. */
542			tarea.tr_end.tp_row = size.tp_row - scrollpos;
543	}
544
545	for (row = tarea.tr_begin.tp_row; row < tarea.tr_end.tp_row; row++) {
546		if (!VTBUF_DIRTYROW(&tmask, row))
547			continue;
548		for (col = tarea.tr_begin.tp_col;
549		    col < tarea.tr_end.tp_col; col++) {
550			if (!VTBUF_DIRTYCOL(&tmask, col))
551				continue;
552
553			c = VTBUF_FIELD(&vw->vw_buf, row, col);
554			vt_bitblt_char(vd, vf, c,
555			    VTBUF_ISCURSOR(&vw->vw_buf, row, col),
556			    row + scrollpos, col);
557		}
558	}
559}
560
561static void
562vt_timer(void *arg)
563{
564	struct vt_device *vd = arg;
565
566	vt_flush(vd);
567	callout_schedule(&vd->vd_timer, hz / VT_TIMERFREQ);
568}
569
570static void
571vtterm_done(struct terminal *tm)
572{
573	struct vt_window *vw = tm->tm_softc;
574	struct vt_device *vd = vw->vw_device;
575
576	if (kdb_active || panicstr != NULL) {
577		/* Switch to the debugger. */
578		if (vd->vd_curwindow != vw) {
579			vd->vd_curwindow = vw;
580			vd->vd_flags |= VDF_INVALID;
581		}
582		vd->vd_flags &= ~VDF_SPLASH;
583		vt_flush(vd);
584	} else if (!(vd->vd_flags & VDF_ASYNC)) {
585		vt_flush(vd);
586	}
587}
588
589static void
590vtterm_cnprobe(struct terminal *tm, struct consdev *cp)
591{
592	struct vt_window *vw = tm->tm_softc;
593	struct vt_device *vd = vw->vw_device;
594	struct winsize wsz;
595
596	cp->cn_pri = vd->vd_driver->vd_init(vd);
597	if (cp->cn_pri == CN_DEAD) {
598		vd->vd_flags |= VDF_DEAD;
599		return;
600	}
601
602	/* Initialize any early-boot keyboard drivers */
603	kbd_configure(KB_CONF_PROBE_ONLY);
604
605	vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1);
606	sprintf(cp->cn_name, "ttyv%r", VT_UNIT(vw));
607
608	if (!(vd->vd_flags & VDF_TEXTMODE))
609		vw->vw_font = vtfont_ref(&vt_font_default);
610
611	vtbuf_init_early(&vw->vw_buf);
612	vt_winsize(vd, vw->vw_font, &wsz);
613	terminal_set_winsize(tm, &wsz);
614
615	/* Display a nice boot splash. */
616	if (!(vd->vd_flags & VDF_TEXTMODE)) {
617		vt_axis_t top, left;
618
619		top = (vd->vd_height - vt_logo_height) / 2;
620		left = (vd->vd_width - vt_logo_width) / 2;
621		switch (vt_logo_depth) {
622		case 1:
623			/* XXX: Unhardcode colors! */
624			vd->vd_driver->vd_bitblt(vd, vt_logo_image, top, left,
625			    vt_logo_width, vt_logo_height, 0xf, 0x0);
626		}
627		vd->vd_flags |= VDF_SPLASH;
628	}
629}
630
631static int
632vtterm_cngetc(struct terminal *tm)
633{
634	struct vt_window *vw = tm->tm_softc;
635	struct vt_device *vd = vw->vw_device;
636	keyboard_t *kbd;
637	u_int c;
638
639	/* Make sure the splash screen is not there. */
640	if (vd->vd_flags & VDF_SPLASH) {
641		vd->vd_flags &= ~VDF_SPLASH;
642		vt_flush(vd);
643	}
644
645	/* Stripped down keyboard handler. */
646	kbd = kbd_get_keyboard(vd->vd_keyboard);
647	if (kbd == NULL)
648		return (-1);
649
650	/* Switch the keyboard to polling to make it work here. */
651	kbdd_poll(kbd, TRUE);
652	c = kbdd_read_char(kbd, 0);
653	kbdd_poll(kbd, FALSE);
654	if (c & RELKEY)
655		return (-1);
656
657	/* Stripped down handling of vt_kbdevent(), without locking, etc. */
658	if (c & SPCLKEY) {
659		c &= ~SPCLKEY;
660
661		switch (c) {
662		case SLK: {
663			int state = 0;
664
665			kbdd_ioctl(kbd, KDGKBSTATE, (caddr_t)&state);
666			if (state & SLKED) {
667				/* Turn scrolling on. */
668				vw->vw_flags |= VWF_SCROLL;
669			} else {
670				/* Turn scrolling off. */
671				vw->vw_flags &= ~VWF_SCROLL;
672				vthistory_seek(&vw->vw_history, 0, VHS_SET);
673			}
674			break;
675		}
676		case FKEY | F(49): /* Home key. */
677			if (vw->vw_flags & VWF_SCROLL)
678				vthistory_seek(&vw->vw_history, 0, VHS_END);
679			break;
680		case FKEY | F(50): /* Arrow up. */
681			if (vw->vw_flags & VWF_SCROLL)
682				vthistory_seek(&vw->vw_history, 1, VHS_CUR);
683			break;
684		case FKEY | F(51): /* Page up. */
685			if (vw->vw_flags & VWF_SCROLL) {
686				term_pos_t size;
687
688				vt_termsize(vd, vw->vw_font, &size);
689				vthistory_seek(&vw->vw_history, size.tp_row,
690				    VHS_CUR);
691			}
692			break;
693		case FKEY | F(57): /* End key. */
694			if (vw->vw_flags & VWF_SCROLL)
695				vthistory_seek(&vw->vw_history, 0, VHS_SET);
696			break;
697		case FKEY | F(58): /* Arrow down. */
698			if (vw->vw_flags & VWF_SCROLL)
699				vthistory_seek(&vw->vw_history, -1, VHS_CUR);
700			break;
701		case FKEY | F(59): /* Page down. */
702			if (vw->vw_flags & VWF_SCROLL) {
703				term_pos_t size;
704
705				vt_termsize(vd, vw->vw_font, &size);
706				vthistory_seek(&vw->vw_history, -size.tp_row,
707				    VHS_CUR);
708			}
709			break;
710		}
711
712		/* Force refresh to make scrollback work. */
713		vt_flush(vd);
714	} else if (KEYFLAGS(c) == 0) {
715		return KEYCHAR(c);
716	}
717
718	return (-1);
719}
720
721static void
722vtterm_opened(struct terminal *tm, int opened)
723{
724	struct vt_window *vw = tm->tm_softc;
725	struct vt_device *vd = vw->vw_device;
726
727	VT_LOCK(vd);
728	vd->vd_flags &= ~VDF_SPLASH;
729	if (opened)
730		vw->vw_flags |= VWF_OPENED;
731	else
732		vw->vw_flags &= ~VWF_OPENED;
733	VT_UNLOCK(vd);
734}
735
736static int
737vt_change_font(struct vt_window *vw, struct vt_font *vf)
738{
739	struct vt_device *vd = vw->vw_device;
740	struct terminal *tm = vw->vw_terminal;
741	term_pos_t size;
742	struct winsize wsz;
743
744	/*
745	 * Changing fonts.
746	 *
747	 * Changing fonts is a little tricky.  We must prevent
748	 * simultaneous access to the device, so we must stop
749	 * the display timer and the terminal from accessing.
750	 * We need to switch fonts and grow our screen buffer.
751	 *
752	 * XXX: Right now the code uses terminal_mute() to
753	 * prevent data from reaching the console driver while
754	 * resizing the screen buffer.  This isn't elegant...
755	 */
756
757	VT_LOCK(vd);
758	if (vw->vw_flags & VWF_BUSY) {
759		/* Another process is changing the font. */
760		VT_UNLOCK(vd);
761		return (EBUSY);
762	}
763	if (vw->vw_font == NULL) {
764		/* Our device doesn't need fonts. */
765		VT_UNLOCK(vd);
766		return (ENOTTY);
767	}
768	vw->vw_flags |= VWF_BUSY;
769	VT_UNLOCK(vd);
770
771	vt_termsize(vd, vf, &size);
772	vt_winsize(vd, vf, &wsz);
773
774	/* Grow the screen buffer and terminal. */
775	terminal_mute(tm, 1);
776	vtbuf_grow(&vw->vw_buf, &size);
777	terminal_set_winsize(tm, &wsz);
778	terminal_mute(tm, 0);
779
780	/* Actually apply the font to the current window. */
781	VT_LOCK(vd);
782	vtfont_unref(vw->vw_font);
783	vw->vw_font = vtfont_ref(vf);
784
785	/* Force a full redraw the next timer tick. */
786	if (vd->vd_curwindow == vw)
787		vd->vd_flags |= VDF_INVALID;
788	vw->vw_flags &= ~VWF_BUSY;
789	VT_UNLOCK(vd);
790	return (0);
791}
792
793static int
794vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
795    struct thread *td)
796{
797	struct vt_window *vw = tm->tm_softc;
798	struct vt_device *vd = vw->vw_device;
799
800	switch (cmd) {
801	case GIO_KEYMAP:
802	case PIO_KEYMAP:
803	case GIO_DEADKEYMAP:
804	case PIO_DEADKEYMAP:
805	case GETFKEY:
806	case SETFKEY:
807	case KDGKBINFO: {
808		keyboard_t *kbd;
809		int error = 0;
810
811		mtx_lock(&Giant);
812		kbd = kbd_get_keyboard(vd->vd_keyboard);
813		if (kbd != NULL)
814			error = kbdd_ioctl(kbd, cmd, data);
815		mtx_unlock(&Giant);
816		if (error == ENOIOCTL)
817			return (ENODEV);
818		return (error);
819	}
820	case KDSKBMODE: {
821		int mode;
822
823		mode = *(int *)data;
824		switch (mode) {
825		case K_XLATE:
826		case K_RAW:
827		case K_CODE:
828			vw->vw_kbdmode = mode;
829			if (vw == vd->vd_curwindow) {
830				keyboard_t *kbd;
831
832				mtx_lock(&Giant);
833				kbd = kbd_get_keyboard(vd->vd_keyboard);
834				if (kbd != NULL)
835					kbdd_ioctl(kbd, KDSKBMODE,
836					    (void *)&mode);
837				mtx_unlock(&Giant);
838			}
839			return (0);
840		default:
841			return (EINVAL);
842		}
843	}
844	case CONS_BLANKTIME:
845		/* XXX */
846		return (0);
847	case CONS_GET:
848		/* XXX */
849		*(int *)data = M_CG640x480;
850		return (0);
851	case CONS_GETINFO: {
852		vid_info_t *vi = (vid_info_t *)data;
853
854		vi->m_num = vd->vd_curwindow->vw_number + 1;
855		/* XXX: other fields! */
856		return (0);
857	}
858	case CONS_GETVERS:
859		*(int *)data = 0x200;
860		return 0;
861	case CONS_MODEINFO:
862		/* XXX */
863		return (0);
864	case CONS_MOUSECTL: {
865		mouse_info_t *mouse = (mouse_info_t*)data;
866
867		/*
868		 * This has no effect on vt(4).  We don't draw any mouse
869		 * cursor.  Just ignore MOUSE_HIDE and MOUSE_SHOW to
870		 * prevent excessive errors.  All the other commands
871		 * should not be applied to individual TTYs, but only to
872		 * consolectl.
873		 */
874		switch (mouse->operation) {
875		case MOUSE_HIDE:
876		case MOUSE_SHOW:
877			return (0);
878		default:
879			return (EINVAL);
880		}
881	}
882	case PIO_VFONT: {
883		struct vt_font *vf;
884		int error;
885
886		error = vtfont_load((void *)data, &vf);
887		if (error != 0)
888			return (error);
889
890		error = vt_change_font(vw, vf);
891		vtfont_unref(vf);
892		return (error);
893	}
894	case GIO_SCRNMAP: {
895		scrmap_t *sm = (scrmap_t *)data;
896		int i;
897
898		/* We don't have screen maps, so return a handcrafted one. */
899		for (i = 0; i < 256; i++)
900			sm->scrmap[i] = i;
901		return (0);
902	}
903	case KDGETLED:
904		/* XXX */
905		return (0);
906	case KDSETLED:
907		/* XXX */
908		return (0);
909	case KDSETMODE:
910		/* XXX */
911		return (0);
912	case KDSETRAD:
913		/* XXX */
914		return (0);
915	case VT_ACTIVATE:
916		vt_window_switch(vw);
917		return (0);
918	case VT_GETACTIVE:
919		*(int *)data = vd->vd_curwindow->vw_number + 1;
920		return (0);
921	case VT_GETINDEX:
922		*(int *)data = vw->vw_number + 1;
923		return (0);
924	case VT_OPENQRY: {
925		unsigned int i;
926
927		VT_LOCK(vd);
928		for (i = 0; i < VT_MAXWINDOWS; i++) {
929			vw = vd->vd_windows[i];
930			if (vw == NULL)
931				continue;
932			if (!(vw->vw_flags & VWF_OPENED)) {
933				*(int *)data = vw->vw_number + 1;
934				VT_UNLOCK(vd);
935				return (0);
936			}
937		}
938		VT_UNLOCK(vd);
939		return (EINVAL);
940	}
941	case VT_WAITACTIVE: {
942		unsigned int i;
943		int error = 0;
944
945		i = *(unsigned int *)data;
946		if (i > VT_MAXWINDOWS)
947			return (EINVAL);
948		if (i != 0)
949			vw = vd->vd_windows[i - 1];
950
951		VT_LOCK(vd);
952		while (vd->vd_curwindow != vw && error == 0)
953			error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock);
954		VT_UNLOCK(vd);
955		return (error);
956	}
957	case VT_GETMODE:
958		/* XXX */
959		return (0);
960	case VT_SETMODE:
961		/* XXX */
962		return (0);
963	}
964
965	return (ENOIOCTL);
966}
967
968static struct vt_window *
969vt_allocate_window(struct vt_device *vd, unsigned int window)
970{
971	struct vt_window *vw;
972	struct terminal *tm;
973	term_pos_t size;
974	struct winsize wsz;
975
976	vw = malloc(sizeof *vw, M_VT, M_WAITOK|M_ZERO);
977	vw->vw_device = vd;
978	vw->vw_number = window;
979	vw->vw_kbdmode = K_XLATE;
980
981	if (!(vd->vd_flags & VDF_TEXTMODE))
982		vw->vw_font = vtfont_ref(&vt_font_default);
983
984	vt_termsize(vd, vw->vw_font, &size);
985	vt_winsize(vd, vw->vw_font, &wsz);
986	vtbuf_init(&vw->vw_buf, &size);
987
988	tm = vw->vw_terminal = terminal_alloc(&vt_termclass, vw);
989	terminal_set_winsize(tm, &wsz);
990	vd->vd_windows[window] = vw;
991
992	return (vw);
993}
994
995void
996vt_upgrade(struct vt_device *vd)
997{
998	struct vt_window *vw;
999	unsigned int i;
1000
1001	/* Device didn't pass vd_init(). */
1002	if (vd->vd_flags & VDF_DEAD)
1003		return;
1004
1005	mtx_init(&vd->vd_lock, "vtdev", NULL, MTX_DEF);
1006	cv_init(&vd->vd_winswitch, "vtwswt");
1007
1008	/* Start 25 Hz timer. */
1009	callout_init_mtx(&vd->vd_timer, &vd->vd_lock, 0);
1010	callout_reset(&vd->vd_timer, hz / VT_TIMERFREQ, vt_timer, vd);
1011	vd->vd_flags |= VDF_ASYNC;
1012
1013	for (i = 0; i < VT_MAXWINDOWS; i++) {
1014		vw = vd->vd_windows[i];
1015		if (vw == NULL) {
1016			/* New window. */
1017			vw = vt_allocate_window(vd, i);
1018		} else {
1019			/* Console window. */
1020			EVENTHANDLER_REGISTER(shutdown_pre_sync,
1021			    vt_window_switch, vw, SHUTDOWN_PRI_DEFAULT);
1022		}
1023		terminal_maketty(vw->vw_terminal, "v%r", VT_UNIT(vw));
1024	}
1025
1026	/* Attach keyboard. */
1027	vt_allocate_keyboard(vd);
1028}
1029
1030void
1031vt_allocate(struct vt_driver *drv, void *softc)
1032{
1033	struct vt_device *vd;
1034
1035	vd = malloc(sizeof *vd, M_VT, M_WAITOK|M_ZERO);
1036	vd->vd_driver = drv;
1037	vd->vd_softc = softc;
1038	vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1);
1039	vd->vd_flags = VDF_INVALID;
1040	vd->vd_driver->vd_init(vd);
1041
1042	vt_upgrade(vd);
1043	vd->vd_curwindow = vd->vd_windows[0];
1044}
1045