vt_core.c revision 219888
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 219888 2011-03-22 21:31:31Z ed $");
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;
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	vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1);
603	sprintf(cp->cn_name, "ttyv%r", VT_UNIT(vw));
604
605	if (!(vd->vd_flags & VDF_TEXTMODE))
606		vw->vw_font = vtfont_ref(&vt_font_default);
607
608	vtbuf_init_early(&vw->vw_buf);
609	vt_winsize(vd, vw->vw_font, &wsz);
610	terminal_set_winsize(tm, &wsz);
611
612	/* Display a nice boot splash. */
613	if (!(vd->vd_flags & VDF_TEXTMODE)) {
614		vt_axis_t top, left;
615
616		top = (vd->vd_height - vt_logo_height) / 2;
617		left = (vd->vd_width - vt_logo_width) / 2;
618		switch (vt_logo_depth) {
619		case 1:
620			/* XXX: Unhardcode colors! */
621			vd->vd_driver->vd_bitblt(vd, vt_logo_image, top, left,
622			    vt_logo_width, vt_logo_height, 0xf, 0x0);
623		}
624		vd->vd_flags |= VDF_SPLASH;
625	}
626}
627
628static int
629vtterm_cngetc(struct terminal *tm)
630{
631	struct vt_window *vw = tm->tm_softc;
632	struct vt_device *vd = vw->vw_device;
633	keyboard_t *kbd;
634	u_int c;
635
636	/* Make sure the splash screen is not there. */
637	if (vd->vd_flags & VDF_SPLASH) {
638		vd->vd_flags &= ~VDF_SPLASH;
639		vt_flush(vd);
640	}
641
642	/* Stripped down keyboard handler. */
643	kbd = kbd_get_keyboard(vd->vd_keyboard);
644	if (kbd == NULL)
645		return (-1);
646
647	/* Switch the keyboard to polling to make it work here. */
648	kbdd_poll(kbd, TRUE);
649	c = kbdd_read_char(kbd, 0);
650	kbdd_poll(kbd, FALSE);
651	if (c & RELKEY)
652		return (-1);
653
654	/* Stripped down handling of vt_kbdevent(), without locking, etc. */
655	if (c & SPCLKEY) {
656		c &= ~SPCLKEY;
657
658		switch (c) {
659		case SLK: {
660			int state;
661
662			kbdd_ioctl(kbd, KDGKBSTATE, (caddr_t)&state);
663			if (state & SLKED) {
664				/* Turn scrolling on. */
665				vw->vw_flags |= VWF_SCROLL;
666			} else {
667				/* Turn scrolling off. */
668				vw->vw_flags &= ~VWF_SCROLL;
669				vthistory_seek(&vw->vw_history, 0, VHS_SET);
670			}
671			break;
672		}
673		case FKEY | F(49): /* Home key. */
674			if (vw->vw_flags & VWF_SCROLL)
675				vthistory_seek(&vw->vw_history, 0, VHS_END);
676			break;
677		case FKEY | F(50): /* Arrow up. */
678			if (vw->vw_flags & VWF_SCROLL)
679				vthistory_seek(&vw->vw_history, 1, VHS_CUR);
680			break;
681		case FKEY | F(51): /* Page up. */
682			if (vw->vw_flags & VWF_SCROLL) {
683				term_pos_t size;
684
685				vt_termsize(vd, vw->vw_font, &size);
686				vthistory_seek(&vw->vw_history, size.tp_row,
687				    VHS_CUR);
688			}
689			break;
690		case FKEY | F(57): /* End key. */
691			if (vw->vw_flags & VWF_SCROLL)
692				vthistory_seek(&vw->vw_history, 0, VHS_SET);
693			break;
694		case FKEY | F(58): /* Arrow down. */
695			if (vw->vw_flags & VWF_SCROLL)
696				vthistory_seek(&vw->vw_history, -1, VHS_CUR);
697			break;
698		case FKEY | F(59): /* Page down. */
699			if (vw->vw_flags & VWF_SCROLL) {
700				term_pos_t size;
701
702				vt_termsize(vd, vw->vw_font, &size);
703				vthistory_seek(&vw->vw_history, -size.tp_row,
704				    VHS_CUR);
705			}
706			break;
707		}
708
709		/* Force refresh to make scrollback work. */
710		vt_flush(vd);
711	} else if (KEYFLAGS(c) == 0) {
712		return KEYCHAR(c);
713	}
714
715	return (-1);
716}
717
718static void
719vtterm_opened(struct terminal *tm, int opened)
720{
721	struct vt_window *vw = tm->tm_softc;
722	struct vt_device *vd = vw->vw_device;
723
724	VT_LOCK(vd);
725	vd->vd_flags &= ~VDF_SPLASH;
726	if (opened)
727		vw->vw_flags |= VWF_OPENED;
728	else
729		vw->vw_flags &= ~VWF_OPENED;
730	VT_UNLOCK(vd);
731}
732
733static int
734vt_change_font(struct vt_window *vw, struct vt_font *vf)
735{
736	struct vt_device *vd = vw->vw_device;
737	struct terminal *tm = vw->vw_terminal;
738	term_pos_t size;
739	struct winsize wsz;
740
741	/*
742	 * Changing fonts.
743	 *
744	 * Changing fonts is a little tricky.  We must prevent
745	 * simultaneous access to the device, so we must stop
746	 * the display timer and the terminal from accessing.
747	 * We need to switch fonts and grow our screen buffer.
748	 *
749	 * XXX: Right now the code uses terminal_mute() to
750	 * prevent data from reaching the console driver while
751	 * resizing the screen buffer.  This isn't elegant...
752	 */
753
754	VT_LOCK(vd);
755	if (vw->vw_flags & VWF_BUSY) {
756		/* Another process is changing the font. */
757		VT_UNLOCK(vd);
758		return (EBUSY);
759	}
760	if (vw->vw_font == NULL) {
761		/* Our device doesn't need fonts. */
762		VT_UNLOCK(vd);
763		return (ENOTTY);
764	}
765	vw->vw_flags |= VWF_BUSY;
766	VT_UNLOCK(vd);
767
768	vt_termsize(vd, vf, &size);
769	vt_winsize(vd, vf, &wsz);
770
771	/* Grow the screen buffer and terminal. */
772	terminal_mute(tm, 1);
773	vtbuf_grow(&vw->vw_buf, &size);
774	terminal_set_winsize(tm, &wsz);
775	terminal_mute(tm, 0);
776
777	/* Actually apply the font to the current window. */
778	VT_LOCK(vd);
779	vtfont_unref(vw->vw_font);
780	vw->vw_font = vtfont_ref(vf);
781
782	/* Force a full redraw the next timer tick. */
783	if (vd->vd_curwindow == vw)
784		vd->vd_flags |= VDF_INVALID;
785	vw->vw_flags &= ~VWF_BUSY;
786	VT_UNLOCK(vd);
787	return (0);
788}
789
790static int
791vtterm_ioctl(struct terminal *tm, u_long cmd, caddr_t data,
792    struct thread *td)
793{
794	struct vt_window *vw = tm->tm_softc;
795	struct vt_device *vd = vw->vw_device;
796
797	switch (cmd) {
798	case GIO_KEYMAP:
799	case PIO_KEYMAP:
800	case GIO_DEADKEYMAP:
801	case PIO_DEADKEYMAP:
802	case GETFKEY:
803	case SETFKEY:
804	case KDGKBINFO: {
805		keyboard_t *kbd;
806		int error = 0;
807
808		mtx_lock(&Giant);
809		kbd = kbd_get_keyboard(vd->vd_keyboard);
810		if (kbd != NULL)
811			error = kbdd_ioctl(kbd, cmd, data);
812		mtx_unlock(&Giant);
813		if (error == ENOIOCTL)
814			return (ENODEV);
815		return (error);
816	}
817	case KDSKBMODE: {
818		int mode;
819
820		mode = *(int *)data;
821		switch (mode) {
822		case K_XLATE:
823		case K_RAW:
824		case K_CODE:
825			vw->vw_kbdmode = mode;
826			if (vw == vd->vd_curwindow) {
827				keyboard_t *kbd;
828
829				mtx_lock(&Giant);
830				kbd = kbd_get_keyboard(vd->vd_keyboard);
831				if (kbd != NULL)
832					kbdd_ioctl(kbd, KDSKBMODE,
833					    (void *)&mode);
834				mtx_unlock(&Giant);
835			}
836			return (0);
837		default:
838			return (EINVAL);
839		}
840	}
841	case CONS_BLANKTIME:
842		/* XXX */
843		return (0);
844	case CONS_GET:
845		/* XXX */
846		*(int *)data = M_CG640x480;
847		return (0);
848	case CONS_GETINFO: {
849		vid_info_t *vi = (vid_info_t *)data;
850
851		vi->m_num = vd->vd_curwindow->vw_number + 1;
852		/* XXX: other fields! */
853		return (0);
854	}
855	case CONS_GETVERS:
856		*(int *)data = 0x200;
857		return 0;
858	case CONS_MODEINFO:
859		/* XXX */
860		return (0);
861	case CONS_MOUSECTL: {
862		mouse_info_t *mouse = (mouse_info_t*)data;
863
864		/*
865		 * This has no effect on vt(4).  We don't draw any mouse
866		 * cursor.  Just ignore MOUSE_HIDE and MOUSE_SHOW to
867		 * prevent excessive errors.  All the other commands
868		 * should not be applied to individual TTYs, but only to
869		 * consolectl.
870		 */
871		switch (mouse->operation) {
872		case MOUSE_HIDE:
873		case MOUSE_SHOW:
874			return (0);
875		default:
876			return (EINVAL);
877		}
878	}
879	case PIO_VFONT: {
880		struct vt_font *vf;
881		int error;
882
883		error = vtfont_load((void *)data, &vf);
884		if (error != 0)
885			return (error);
886
887		error = vt_change_font(vw, vf);
888		vtfont_unref(vf);
889		return (error);
890	}
891	case GIO_SCRNMAP: {
892		scrmap_t *sm = (scrmap_t *)data;
893		int i;
894
895		/* We don't have screen maps, so return a handcrafted one. */
896		for (i = 0; i < 256; i++)
897			sm->scrmap[i] = i;
898		return (0);
899	}
900	case KDGETLED:
901		/* XXX */
902		return (0);
903	case KDSETLED:
904		/* XXX */
905		return (0);
906	case KDSETMODE:
907		/* XXX */
908		return (0);
909	case KDSETRAD:
910		/* XXX */
911		return (0);
912	case VT_ACTIVATE:
913		vt_window_switch(vw);
914		return (0);
915	case VT_GETACTIVE:
916		*(int *)data = vd->vd_curwindow->vw_number + 1;
917		return (0);
918	case VT_GETINDEX:
919		*(int *)data = vw->vw_number + 1;
920		return (0);
921	case VT_OPENQRY: {
922		unsigned int i;
923
924		VT_LOCK(vd);
925		for (i = 0; i < VT_MAXWINDOWS; i++) {
926			vw = vd->vd_windows[i];
927			if (vw == NULL)
928				continue;
929			if (!(vw->vw_flags & VWF_OPENED)) {
930				*(int *)data = vw->vw_number + 1;
931				VT_UNLOCK(vd);
932				return (0);
933			}
934		}
935		VT_UNLOCK(vd);
936		return (EINVAL);
937	}
938	case VT_WAITACTIVE: {
939		unsigned int i;
940		int error = 0;
941
942		i = *(unsigned int *)data;
943		if (i > VT_MAXWINDOWS)
944			return (EINVAL);
945		if (i != 0)
946			vw = vd->vd_windows[i - 1];
947
948		VT_LOCK(vd);
949		while (vd->vd_curwindow != vw && error == 0)
950			error = cv_wait_sig(&vd->vd_winswitch, &vd->vd_lock);
951		VT_UNLOCK(vd);
952		return (error);
953	}
954	case VT_GETMODE:
955		/* XXX */
956		return (0);
957	case VT_SETMODE:
958		/* XXX */
959		return (0);
960	}
961
962	return (ENOIOCTL);
963}
964
965static struct vt_window *
966vt_allocate_window(struct vt_device *vd, unsigned int window)
967{
968	struct vt_window *vw;
969	struct terminal *tm;
970	term_pos_t size;
971	struct winsize wsz;
972
973	vw = malloc(sizeof *vw, M_VT, M_WAITOK|M_ZERO);
974	vw->vw_device = vd;
975	vw->vw_number = window;
976	vw->vw_kbdmode = K_XLATE;
977
978	if (!(vd->vd_flags & VDF_TEXTMODE))
979		vw->vw_font = vtfont_ref(&vt_font_default);
980
981	vt_termsize(vd, vw->vw_font, &size);
982	vt_winsize(vd, vw->vw_font, &wsz);
983	vtbuf_init(&vw->vw_buf, &size);
984
985	tm = vw->vw_terminal = terminal_alloc(&vt_termclass, vw);
986	terminal_set_winsize(tm, &wsz);
987	vd->vd_windows[window] = vw;
988
989	return (vw);
990}
991
992void
993vt_upgrade(struct vt_device *vd)
994{
995	struct vt_window *vw;
996	unsigned int i;
997
998	/* Device didn't pass vd_init(). */
999	if (vd->vd_flags & VDF_DEAD)
1000		return;
1001
1002	mtx_init(&vd->vd_lock, "vtdev", NULL, MTX_DEF);
1003	cv_init(&vd->vd_winswitch, "vtwswt");
1004
1005	/* Start 25 Hz timer. */
1006	callout_init_mtx(&vd->vd_timer, &vd->vd_lock, 0);
1007	callout_reset(&vd->vd_timer, hz / VT_TIMERFREQ, vt_timer, vd);
1008	vd->vd_flags |= VDF_ASYNC;
1009
1010	for (i = 0; i < VT_MAXWINDOWS; i++) {
1011		vw = vd->vd_windows[i];
1012		if (vw == NULL) {
1013			/* New window. */
1014			vw = vt_allocate_window(vd, i);
1015		} else {
1016			/* Console window. */
1017			EVENTHANDLER_REGISTER(shutdown_pre_sync,
1018			    vt_window_switch, vw, SHUTDOWN_PRI_DEFAULT);
1019		}
1020		terminal_maketty(vw->vw_terminal, "v%r", VT_UNIT(vw));
1021	}
1022
1023	/* Attach keyboard. */
1024	vt_allocate_keyboard(vd);
1025}
1026
1027void
1028vt_allocate(struct vt_driver *drv, void *softc)
1029{
1030	struct vt_device *vd;
1031
1032	vd = malloc(sizeof *vd, M_VT, M_WAITOK|M_ZERO);
1033	vd->vd_driver = drv;
1034	vd->vd_softc = softc;
1035	vd->vd_unit = atomic_fetchadd_int(&vt_unit, 1);
1036	vd->vd_flags = VDF_INVALID;
1037	vd->vd_driver->vd_init(vd);
1038
1039	vt_upgrade(vd);
1040	vd->vd_curwindow = vd->vd_windows[0];
1041}
1042