xref: /freebsd/stand/i386/libi386/vbe.c (revision 58661b3b)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
5  * All rights reserved.
6  * Copyright 2020 Toomas Soome
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  * $FreeBSD$
30  */
31 
32 #include <sys/cdefs.h>
33 #include <stand.h>
34 #include <sys/param.h>
35 #include <machine/psl.h>
36 #include <machine/cpufunc.h>
37 #include <stdbool.h>
38 #include <bootstrap.h>
39 #include <btxv86.h>
40 #include <gfx_fb.h>
41 #include <dev/vt/hw/vga/vt_vga_reg.h>
42 #include "libi386.h"
43 #include "vbe.h"
44 
45 /*
46  * VESA BIOS Extensions routines
47  */
48 
49 static struct vbeinfoblock *vbe;
50 static struct modeinfoblock *vbe_mode;
51 /* The default VGA color palette format is 6 bits per primary color. */
52 int palette_format = 6;
53 
54 #define	VESA_MODE_BASE	0x100
55 #define	VESA_MODE_MAX	0x1ff
56 #define	VESA_MODE_COUNT	(VESA_MODE_MAX - VESA_MODE_BASE + 1)
57 
58 /*
59  * palette array for 8-bit indexed colors. In this case, cmap does store
60  * index and pe8 does store actual RGB. This is needed because we may
61  * not be able to read palette data from hardware.
62  */
63 struct paletteentry *pe8 = NULL;
64 
65 static struct named_resolution {
66 	const char *name;
67 	const char *alias;
68 	unsigned int width;
69 	unsigned int height;
70 } resolutions[] = {
71 	{
72 		.name = "480p",
73 		.width = 640,
74 		.height = 480,
75 	},
76 	{
77 		.name = "720p",
78 		.width = 1280,
79 		.height = 720,
80 	},
81 	{
82 		.name = "1080p",
83 		.width = 1920,
84 		.height = 1080,
85 	},
86 	{
87 		.name = "2160p",
88 		.alias = "4k",
89 		.width = 3840,
90 		.height = 2160,
91 	},
92 	{
93 		.name = "5k",
94 		.width = 5120,
95 		.height = 2880,
96 	}
97 };
98 
99 static bool
100 vbe_resolution_compare(struct named_resolution *res, const char *cmp)
101 {
102 
103 	if (strcasecmp(res->name, cmp) == 0)
104 		return (true);
105 	if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0)
106 		return (true);
107 	return (false);
108 }
109 
110 static void
111 vbe_get_max_resolution(int *width, int *height)
112 {
113 	struct named_resolution *res;
114 	char *maxres;
115 	char *height_start, *width_start;
116 	int idx;
117 
118 	*width = *height = 0;
119 	maxres = getenv("vbe_max_resolution");
120 	/* No max_resolution set? Bail out; choose highest resolution */
121 	if (maxres == NULL)
122 		return;
123 	/* See if it matches one of our known resolutions */
124 	for (idx = 0; idx < nitems(resolutions); ++idx) {
125 		res = &resolutions[idx];
126 		if (vbe_resolution_compare(res, maxres)) {
127 			*width = res->width;
128 			*height = res->height;
129 			return;
130 		}
131 	}
132 	/* Not a known resolution, try to parse it; make a copy we can modify */
133 	maxres = strdup(maxres);
134 	if (maxres == NULL)
135 		return;
136 	height_start = strchr(maxres, 'x');
137 	if (height_start == NULL) {
138 		free(maxres);
139 		return;
140 	}
141 	width_start = maxres;
142 	*height_start++ = 0;
143 	/* Errors from this will effectively mean "no max" */
144 	*width = (int)strtol(width_start, NULL, 0);
145 	*height = (int)strtol(height_start, NULL, 0);
146 	free(maxres);
147 }
148 
149 int
150 vga_get_reg(int reg, int index)
151 {
152 	return (inb(reg + index));
153 }
154 
155 int
156 vga_get_atr(int reg, int i)
157 {
158 	int ret;
159 
160 	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
161 	outb(reg + VGA_AC_WRITE, i);
162 	ret = inb(reg + VGA_AC_READ);
163 
164 	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
165 
166 	return (ret);
167 }
168 
169 void
170 vga_set_atr(int reg, int i, int v)
171 {
172 	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
173 	outb(reg + VGA_AC_WRITE, i);
174 	outb(reg + VGA_AC_WRITE, v);
175 
176 	(void) inb(reg + VGA_GEN_INPUT_STAT_1);
177 }
178 
179 void
180 vga_set_indexed(int reg, int indexreg, int datareg, uint8_t index, uint8_t val)
181 {
182 	outb(reg + indexreg, index);
183 	outb(reg + datareg, val);
184 }
185 
186 int
187 vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index)
188 {
189 	outb(reg + indexreg, index);
190 	return (inb(reg + datareg));
191 }
192 
193 int
194 vga_get_crtc(int reg, int i)
195 {
196 	return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i));
197 }
198 
199 void
200 vga_set_crtc(int reg, int i, int v)
201 {
202 	vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v);
203 }
204 
205 int
206 vga_get_seq(int reg, int i)
207 {
208 	return (vga_get_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i));
209 }
210 
211 void
212 vga_set_seq(int reg, int i, int v)
213 {
214 	vga_set_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i, v);
215 }
216 
217 int
218 vga_get_grc(int reg, int i)
219 {
220 	return (vga_get_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i));
221 }
222 
223 void
224 vga_set_grc(int reg, int i, int v)
225 {
226 	vga_set_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i, v);
227 }
228 
229 /*
230  * Return true when this controller is VGA compatible.
231  */
232 bool
233 vbe_is_vga(void)
234 {
235 	if (vbe == NULL)
236 		return (false);
237 
238 	return ((vbe->Capabilities & VBE_CAP_NONVGA) == 0);
239 }
240 
241 /* Actually assuming mode 3. */
242 void
243 bios_set_text_mode(int mode)
244 {
245 	int atr;
246 
247 	if (vbe->Capabilities & VBE_CAP_DAC8) {
248 		int m;
249 
250 		/*
251 		 * The mode change should reset the palette format to
252 		 * 6 bits, but apparently some systems do fail with 8-bit
253 		 * palette, so we switch to 6-bit here.
254 		 */
255 		m = 0x0600;
256 		(void) biosvbe_palette_format(&m);
257 		palette_format = m;
258 	}
259 	v86.ctl = V86_FLAGS;
260 	v86.addr = 0x10;
261 	v86.eax = mode;				/* set VGA text mode */
262 	v86int();
263 	atr = vga_get_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL);
264 	atr &= ~VGA_AC_MC_BI;
265 	atr &= ~VGA_AC_MC_ELG;
266 	vga_set_atr(VGA_REG_BASE, VGA_AC_MODE_CONTROL, atr);
267 
268 	gfx_state.tg_mode = mode;
269 	gfx_state.tg_fb_type = FB_TEXT;
270 	gfx_state.tg_fb.fb_height = TEXT_ROWS;
271 	gfx_state.tg_fb.fb_width = TEXT_COLS;
272 
273 	gfx_state.tg_fb.fb_mask_red = (1 << palette_format) - 1 << 16;
274 	gfx_state.tg_fb.fb_mask_green = (1 << palette_format) - 1 << 8;
275 	gfx_state.tg_fb.fb_mask_blue = (1 << palette_format) - 1 << 0;
276 	gfx_state.tg_ctype = CT_INDEXED;
277 	env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK, "1", NULL, NULL);
278 }
279 
280 /* Function 00h - Return VBE Controller Information */
281 static int
282 biosvbe_info(struct vbeinfoblock *vbep)
283 {
284 	struct vbeinfoblock *rvbe;
285 	int ret;
286 
287 	if (vbep == NULL)
288 		return (VBE_FAILED);
289 
290 	rvbe = bio_alloc(sizeof(*rvbe));
291 	if (rvbe == NULL)
292 		return (VBE_FAILED);
293 
294 	/* Now check if we have vesa. */
295 	memset(rvbe, 0, sizeof (*vbe));
296 	memcpy(rvbe->VbeSignature, "VBE2", 4);
297 
298 	v86.ctl = V86_FLAGS;
299 	v86.addr = 0x10;
300 	v86.eax = 0x4f00;
301 	v86.es = VTOPSEG(rvbe);
302 	v86.edi = VTOPOFF(rvbe);
303 	v86int();
304 	ret = v86.eax & 0xffff;
305 
306 	if (ret != VBE_SUCCESS)
307 		goto done;
308 
309 	if (memcmp(rvbe->VbeSignature, "VESA", 4) != 0) {
310 		ret = VBE_NOTSUP;
311 		goto done;
312 	}
313 	bcopy(rvbe, vbep, sizeof(*vbep));
314 done:
315 	bio_free(rvbe, sizeof(*rvbe));
316 	return (ret);
317 }
318 
319 /* Function 01h - Return VBE Mode Information */
320 static int
321 biosvbe_get_mode_info(int mode, struct modeinfoblock *mi)
322 {
323 	struct modeinfoblock *rmi;
324 	int ret;
325 
326 	rmi = bio_alloc(sizeof(*rmi));
327 	if (rmi == NULL)
328 		return (VBE_FAILED);
329 
330 	v86.ctl = V86_FLAGS;
331 	v86.addr = 0x10;
332 	v86.eax = 0x4f01;
333 	v86.ecx = mode;
334 	v86.es = VTOPSEG(rmi);
335 	v86.edi = VTOPOFF(rmi);
336 	v86int();
337 
338 	ret = v86.eax & 0xffff;
339 	if (ret != VBE_SUCCESS)
340 		goto done;
341 	bcopy(rmi, mi, sizeof(*rmi));
342 done:
343 	bio_free(rmi, sizeof(*rmi));
344 	return (ret);
345 }
346 
347 /* Function 02h - Set VBE Mode */
348 static int
349 biosvbe_set_mode(int mode, struct crtciinfoblock *ci)
350 {
351 	int rv;
352 
353 	if (vbe->Capabilities & VBE_CAP_DAC8) {
354 		int m;
355 
356 		/*
357 		 * The mode change should reset the palette format to
358 		 * 6 bits, but apparently some systems do fail with 8-bit
359 		 * palette, so we switch to 6-bit here.
360 		 */
361 		m = 0x0600;
362 		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
363 			palette_format = m;
364 	}
365 	v86.ctl = V86_FLAGS;
366 	v86.addr = 0x10;
367 	v86.eax = 0x4f02;
368 	v86.ebx = mode | 0x4000;	/* set linear FB bit */
369 	v86.es = VTOPSEG(ci);
370 	v86.edi = VTOPOFF(ci);
371 	v86int();
372 	rv = v86.eax & 0xffff;
373 	if (vbe->Capabilities & VBE_CAP_DAC8) {
374 		int m;
375 
376 		/* Switch to 8-bits per primary color. */
377 		m = 0x0800;
378 		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
379 			palette_format = m;
380 	}
381 	env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK, "0", NULL, NULL);
382 	return (rv);
383 }
384 
385 /* Function 03h - Get VBE Mode */
386 static int
387 biosvbe_get_mode(int *mode)
388 {
389 	v86.ctl = V86_FLAGS;
390 	v86.addr = 0x10;
391 	v86.eax = 0x4f03;
392 	v86int();
393 	*mode = v86.ebx & 0x3fff;	/* Bits 0-13 */
394 	return (v86.eax & 0xffff);
395 }
396 
397 /* Function 08h - Set/Get DAC Palette Format */
398 int
399 biosvbe_palette_format(int *format)
400 {
401 	v86.ctl = V86_FLAGS;
402 	v86.addr = 0x10;
403 	v86.eax = 0x4f08;
404 	v86.ebx = *format;
405 	v86int();
406 	*format = (v86.ebx >> 8) & 0xff;
407 	return (v86.eax & 0xffff);
408 }
409 
410 /* Function 09h - Set/Get Palette Data */
411 static int
412 biosvbe_palette_data(int mode, int reg, struct paletteentry *pe)
413 {
414 	v86.ctl = V86_FLAGS;
415 	v86.addr = 0x10;
416 	v86.eax = 0x4f09;
417 	v86.ebx = mode;
418 	v86.edx = reg;
419 	v86.ecx = 1;
420 	v86.es = VTOPSEG(pe);
421 	v86.edi = VTOPOFF(pe);
422 	v86int();
423 	return (v86.eax & 0xffff);
424 }
425 
426 /*
427  * Function 15h BL=00h - Report VBE/DDC Capabilities
428  *
429  * int biosvbe_ddc_caps(void)
430  * return: VBE/DDC capabilities
431  */
432 static int
433 biosvbe_ddc_caps(void)
434 {
435 	v86.ctl = V86_FLAGS;
436 	v86.addr = 0x10;
437 	v86.eax = 0x4f15;	/* display identification extensions */
438 	v86.ebx = 0;		/* report DDC capabilities */
439 	v86.ecx = 0;		/* controller unit number (00h = primary) */
440 	v86.es = 0;
441 	v86.edi = 0;
442 	v86int();
443 	if (VBE_ERROR(v86.eax & 0xffff))
444 		return (0);
445 	return (v86.ebx & 0xffff);
446 }
447 
448 /* Function 11h BL=01h - Flat Panel status */
449 static int
450 biosvbe_ddc_read_flat_panel_info(void *buf)
451 {
452 	v86.ctl = V86_FLAGS;
453 	v86.addr = 0x10;
454 	v86.eax = 0x4f11;	/* Flat Panel Interface extensions */
455 	v86.ebx = 1;		/* Return Flat Panel Information */
456 	v86.es = VTOPSEG(buf);
457 	v86.edi = VTOPOFF(buf);
458 	v86int();
459 	return (v86.eax & 0xffff);
460 }
461 
462 /* Function 15h BL=01h - Read EDID */
463 static int
464 biosvbe_ddc_read_edid(int blockno, void *buf)
465 {
466 	v86.ctl = V86_FLAGS;
467 	v86.addr = 0x10;
468 	v86.eax = 0x4f15;	/* display identification extensions */
469 	v86.ebx = 1;		/* read EDID */
470 	v86.ecx = 0;		/* controller unit number (00h = primary) */
471 	v86.edx = blockno;
472 	v86.es = VTOPSEG(buf);
473 	v86.edi = VTOPOFF(buf);
474 	v86int();
475 	return (v86.eax & 0xffff);
476 }
477 
478 static int
479 vbe_mode_is_supported(struct modeinfoblock *mi)
480 {
481 	if ((mi->ModeAttributes & 0x01) == 0)
482 		return (0);	/* mode not supported by hardware */
483 	if ((mi->ModeAttributes & 0x08) == 0)
484 		return (0);	/* linear fb not available */
485 	if ((mi->ModeAttributes & 0x10) == 0)
486 		return (0);	/* text mode */
487 	if (mi->NumberOfPlanes != 1)
488 		return (0);	/* planar mode not supported */
489 	if (mi->MemoryModel != 0x04 /* Packed pixel */ &&
490 	    mi->MemoryModel != 0x06 /* Direct Color */)
491 		return (0);	/* unsupported pixel format */
492 	return (1);
493 }
494 
495 static bool
496 vbe_check(void)
497 {
498 
499 	if (vbe == NULL) {
500 		printf("VBE not available\n");
501 		return (false);
502 	}
503 	return (true);
504 }
505 
506 static int
507 mode_set(struct env_var *ev, int flags __unused, const void *value)
508 {
509 	int mode;
510 
511 	if (strcmp(ev->ev_name, "hw.vga.textmode") == 0) {
512 		unsigned long v;
513 		char *end;
514 
515 		if (value == NULL)
516 			return (0);
517 		/* VT(4) describes hw.vga.textmode values 0 or 1. */
518 		errno = 0;
519 		v = strtoul(value, &end, 0);
520 		if (errno != 0 || *(char *)value == '\0' || *end != '\0' ||
521 		    (v != 0 && v != 1))
522 			return (EINVAL);
523 		env_setenv("hw.vga.textmode", EV_VOLATILE | EV_NOHOOK,
524 		    value, NULL, NULL);
525 		if (v == 1) {
526 			reset_font_flags();
527 			bios_text_font(true);
528 			bios_set_text_mode(VGA_TEXT_MODE);
529 			(void) cons_update_mode(false);
530 			return (0);
531 		}
532 	} else if (strcmp(ev->ev_name, "vbe_max_resolution") == 0) {
533 		env_setenv("vbe_max_resolution", EV_VOLATILE | EV_NOHOOK,
534 		    value, NULL, NULL);
535 	} else {
536 		return (EINVAL);
537 	}
538 
539 	mode = vbe_default_mode();
540 	if (gfx_state.tg_mode != mode) {
541 		reset_font_flags();
542 		bios_text_font(false);
543 		vbe_set_mode(mode);
544 		cons_update_mode(true);
545 	}
546 	return (0);
547 }
548 
549 void
550 vbe_init(void)
551 {
552 	/* First set FB for text mode. */
553 	gfx_state.tg_fb_type = FB_TEXT;
554 	gfx_state.tg_fb.fb_height = TEXT_ROWS;
555 	gfx_state.tg_fb.fb_width = TEXT_COLS;
556 	gfx_state.tg_ctype = CT_INDEXED;
557 	gfx_state.tg_mode = 3;
558 
559 	if (vbe == NULL)
560 		vbe = malloc(sizeof(*vbe));
561 
562 	if (vbe_mode == NULL) {
563 		vbe_mode = malloc(sizeof(*vbe_mode));
564 		if (vbe_mode == NULL) {
565 			free(vbe);
566 			vbe = NULL;
567 		}
568 	}
569 
570 	if (biosvbe_info(vbe) != VBE_SUCCESS) {
571 		free(vbe);
572 		vbe = NULL;
573 		free(vbe_mode);
574 		vbe_mode = NULL;
575 	}
576 
577 	env_setenv("hw.vga.textmode", EV_VOLATILE, "1", mode_set,
578 	    env_nounset);
579 	env_setenv("vbe_max_resolution", EV_VOLATILE, NULL, mode_set,
580 	    env_nounset);
581 	/* vbe_set_mode() will set up the rest. */
582 }
583 
584 bool
585 vbe_available(void)
586 {
587 	return (gfx_state.tg_fb_type == FB_VBE);
588 }
589 
590 int
591 vbe_set_palette(const struct paletteentry *entry, size_t slot)
592 {
593 	struct paletteentry pe;
594 	int mode, ret;
595 
596 	if (!vbe_check() || (vbe->Capabilities & VBE_CAP_DAC8) == 0)
597 		return (1);
598 
599 	if (gfx_state.tg_ctype != CT_INDEXED) {
600 		return (1);
601 	}
602 
603 	pe.Blue = entry->Blue;
604 	pe.Green = entry->Green;
605 	pe.Red = entry->Red;
606 	pe.Reserved = entry->Reserved;
607 
608 	if (vbe->Capabilities & VBE_CAP_SNOW)
609 		mode = 0x80;
610 	else
611 		mode = 0;
612 
613 	ret = biosvbe_palette_data(mode, slot, &pe);
614 
615 	return (ret == VBE_SUCCESS ? 0 : 1);
616 }
617 
618 int
619 vbe_get_mode(void)
620 {
621 	return (gfx_state.tg_mode);
622 }
623 
624 int
625 vbe_set_mode(int modenum)
626 {
627 	struct modeinfoblock mi;
628 	int bpp, ret;
629 
630 	if (!vbe_check())
631 		return (1);
632 
633 	ret = biosvbe_get_mode_info(modenum, &mi);
634 	if (VBE_ERROR(ret)) {
635 		printf("mode 0x%x invalid\n", modenum);
636 		return (1);
637 	}
638 
639 	if (!vbe_mode_is_supported(&mi)) {
640 		printf("mode 0x%x not supported\n", modenum);
641 		return (1);
642 	}
643 
644 	/* calculate bytes per pixel */
645 	switch (mi.BitsPerPixel) {
646 	case 32:
647 	case 24:
648 	case 16:
649 	case 15:
650 	case 8:
651 		break;
652 	default:
653 		printf("BitsPerPixel %d is not supported\n", mi.BitsPerPixel);
654 		return (1);
655 	}
656 
657 	ret = biosvbe_set_mode(modenum, NULL);
658 	if (VBE_ERROR(ret)) {
659 		printf("mode 0x%x could not be set\n", modenum);
660 		return (1);
661 	}
662 
663 	gfx_state.tg_mode = modenum;
664 	gfx_state.tg_fb_type = FB_VBE;
665 	/* make sure we have current MI in vbestate */
666 	memcpy(vbe_mode, &mi, sizeof (*vbe_mode));
667 
668 	gfx_state.tg_fb.fb_addr = (uint64_t)mi.PhysBasePtr & 0xffffffff;
669 	gfx_state.tg_fb.fb_height = mi.YResolution;
670 	gfx_state.tg_fb.fb_width = mi.XResolution;
671 	gfx_state.tg_fb.fb_bpp = mi.BitsPerPixel;
672 
673 	/* Bytes per pixel */
674 	bpp = roundup2(mi.BitsPerPixel, NBBY) / NBBY;
675 
676 	/* vbe_mode_is_supported() excludes the rest */
677 	switch (mi.MemoryModel) {
678 	case 0x4:
679 		gfx_state.tg_ctype = CT_INDEXED;
680 		break;
681 	case 0x6:
682 		gfx_state.tg_ctype = CT_RGB;
683 		break;
684 	}
685 
686 #define	COLOR_MASK(size, pos) (((1 << size) - 1) << pos)
687 	if (gfx_state.tg_ctype == CT_INDEXED) {
688 		gfx_state.tg_fb.fb_mask_red = COLOR_MASK(palette_format, 16);
689 		gfx_state.tg_fb.fb_mask_green = COLOR_MASK(palette_format, 8);
690 		gfx_state.tg_fb.fb_mask_blue = COLOR_MASK(palette_format, 0);
691 	} else if (vbe->VbeVersion >= 0x300) {
692 		gfx_state.tg_fb.fb_mask_red =
693 		    COLOR_MASK(mi.LinRedMaskSize, mi.LinRedFieldPosition);
694 		gfx_state.tg_fb.fb_mask_green =
695 		    COLOR_MASK(mi.LinGreenMaskSize, mi.LinGreenFieldPosition);
696 		gfx_state.tg_fb.fb_mask_blue =
697 		    COLOR_MASK(mi.LinBlueMaskSize, mi.LinBlueFieldPosition);
698 	} else {
699 		gfx_state.tg_fb.fb_mask_red =
700 		    COLOR_MASK(mi.RedMaskSize, mi.RedFieldPosition);
701 		gfx_state.tg_fb.fb_mask_green =
702 		    COLOR_MASK(mi.GreenMaskSize, mi.GreenFieldPosition);
703 		gfx_state.tg_fb.fb_mask_blue =
704 		    COLOR_MASK(mi.BlueMaskSize, mi.BlueFieldPosition);
705 	}
706 	gfx_state.tg_fb.fb_mask_reserved = ~(gfx_state.tg_fb.fb_mask_red |
707 	    gfx_state.tg_fb.fb_mask_green |
708 	    gfx_state.tg_fb.fb_mask_blue);
709 
710 	if (vbe->VbeVersion >= 0x300)
711 		gfx_state.tg_fb.fb_stride = mi.LinBytesPerScanLine / bpp;
712 	else
713 		gfx_state.tg_fb.fb_stride = mi.BytesPerScanLine / bpp;
714 
715 	gfx_state.tg_fb.fb_size = mi.YResolution * gfx_state.tg_fb.fb_stride *
716 	    bpp;
717 
718 	return (0);
719 }
720 
721 static void *
722 vbe_farptr(uint32_t farptr)
723 {
724 	return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff))));
725 }
726 
727 /*
728  * Verify existance of mode number or find mode by
729  * dimensions. If depth is not given, walk values 32, 24, 16, 8.
730  */
731 static int
732 vbe_find_mode_xydm(int x, int y, int depth, int m)
733 {
734 	struct modeinfoblock mi;
735 	uint32_t farptr;
736 	uint16_t mode;
737 	int safety, i;
738 
739 	memset(vbe, 0, sizeof (*vbe));
740 	if (biosvbe_info(vbe) != VBE_SUCCESS)
741 		return (0);
742 	if (vbe->VideoModePtr == 0)
743 		return (0);
744 
745 	if (m != -1)
746 		i = 8;
747 	else if (depth == -1)
748 		i = 32;
749 	else
750 		i = depth;
751 
752 	while (i > 0) {
753 		farptr = vbe->VideoModePtr;
754 		safety = 0;
755 		while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
756 			safety++;
757 			farptr += 2;
758 			if (safety == VESA_MODE_COUNT)
759 				break;
760 			if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS) {
761 				continue;
762 			}
763 			/* we only care about linear modes here */
764 			if (vbe_mode_is_supported(&mi) == 0)
765 				continue;
766 
767 			if (m != -1) {
768 				if (m == mode)
769 					return (mode);
770 				else
771 					continue;
772 			}
773 
774 			if (mi.XResolution == x &&
775 			    mi.YResolution == y &&
776 			    mi.BitsPerPixel == i)
777 				return (mode);
778 		}
779 		if (depth != -1)
780 			break;
781 
782 		i -= 8;
783 	}
784 
785 	return (0);
786 }
787 
788 static int
789 vbe_find_mode(char *str)
790 {
791 	int x, y, depth;
792 
793 	if (!gfx_parse_mode_str(str, &x, &y, &depth))
794 		return (0);
795 
796 	return (vbe_find_mode_xydm(x, y, depth, -1));
797 }
798 
799 static void
800 vbe_dump_mode(int modenum, struct modeinfoblock *mi)
801 {
802 	printf("0x%x=%dx%dx%d", modenum,
803 	    mi->XResolution, mi->YResolution, mi->BitsPerPixel);
804 }
805 
806 static bool
807 vbe_get_edid(edid_res_list_t *res)
808 {
809 	struct vesa_edid_info *edid_info;
810 	const uint8_t magic[] = EDID_MAGIC;
811 	int ddc_caps;
812 	bool ret = false;
813 
814 	ddc_caps = biosvbe_ddc_caps();
815 	if (ddc_caps == 0) {
816 		return (ret);
817 	}
818 
819 	edid_info = bio_alloc(sizeof (*edid_info));
820 	if (edid_info == NULL)
821 		return (ret);
822 	memset(edid_info, 0, sizeof (*edid_info));
823 
824 	if (VBE_ERROR(biosvbe_ddc_read_edid(0, edid_info)))
825 		goto done;
826 
827 	if (memcmp(edid_info, magic, sizeof (magic)) != 0)
828 		goto done;
829 
830 	/* Unknown EDID version. */
831 	if (edid_info->header.version != 1)
832 		goto done;
833 
834 	ret = gfx_get_edid_resolution(edid_info, res);
835 done:
836 	bio_free(edid_info, sizeof (*edid_info));
837 	return (ret);
838 }
839 
840 static bool
841 vbe_get_flatpanel(uint32_t *pwidth, uint32_t *pheight)
842 {
843 	struct vesa_flat_panel_info *fp_info;
844 	bool ret = false;
845 
846 	fp_info = bio_alloc(sizeof (*fp_info));
847 	if (fp_info == NULL)
848 		return (ret);
849 	memset(fp_info, 0, sizeof (*fp_info));
850 
851 	if (VBE_ERROR(biosvbe_ddc_read_flat_panel_info(fp_info)))
852 		goto done;
853 
854 	*pwidth = fp_info->HSize;
855 	*pheight = fp_info->VSize;
856 	ret = true;
857 
858 done:
859 	bio_free(fp_info, sizeof (*fp_info));
860 	return (ret);
861 }
862 
863 static void
864 vbe_print_memory(unsigned vmem)
865 {
866 	char unit = 'K';
867 
868 	vmem /= 1024;
869 	if (vmem >= 10240000) {
870 		vmem /= 1048576;
871 		unit = 'G';
872 	} else if (vmem >= 10000) {
873 		vmem /= 1024;
874 		unit = 'M';
875 	}
876 	printf("Total memory: %u%cB\n", vmem, unit);
877 }
878 
879 static void
880 vbe_print_vbe_info(struct vbeinfoblock *vbep)
881 {
882 	char *oemstring = "";
883 	char *oemvendor = "", *oemproductname = "", *oemproductrev = "";
884 
885 	if (vbep->OemStringPtr != 0)
886 		oemstring = vbe_farptr(vbep->OemStringPtr);
887 
888 	if (vbep->OemVendorNamePtr != 0)
889 		oemvendor = vbe_farptr(vbep->OemVendorNamePtr);
890 
891 	if (vbep->OemProductNamePtr != 0)
892 		oemproductname = vbe_farptr(vbep->OemProductNamePtr);
893 
894 	if (vbep->OemProductRevPtr != 0)
895 		oemproductrev = vbe_farptr(vbep->OemProductRevPtr);
896 
897 	printf("VESA VBE Version %d.%d\n%s\n", vbep->VbeVersion >> 8,
898 	    vbep->VbeVersion & 0xF, oemstring);
899 
900 	if (vbep->OemSoftwareRev != 0) {
901 		printf("OEM Version %d.%d, %s (%s, %s)\n",
902 		    vbep->OemSoftwareRev >> 8, vbep->OemSoftwareRev & 0xF,
903 		    oemvendor, oemproductname, oemproductrev);
904 	}
905 	vbe_print_memory(vbep->TotalMemory << 16);
906 	printf("Number of Image Pages: %d\n", vbe_mode->LinNumberOfImagePages);
907 }
908 
909 /* List available modes, filter by depth. If depth is -1, list all. */
910 void
911 vbe_modelist(int depth)
912 {
913 	struct modeinfoblock mi;
914 	uint32_t farptr;
915 	uint16_t mode;
916 	int nmodes = 0, safety = 0;
917 	int ddc_caps;
918 	uint32_t width, height;
919 	bool edid = false;
920 	edid_res_list_t res;
921 	struct resolution *rp;
922 
923 	if (!vbe_check())
924 		return;
925 
926 	ddc_caps = biosvbe_ddc_caps();
927 	if (ddc_caps & 3) {
928 		printf("DDC");
929 		if (ddc_caps & 1)
930 			printf(" [DDC1]");
931 		if (ddc_caps & 2)
932 			printf(" [DDC2]");
933 
934 		TAILQ_INIT(&res);
935 		edid = vbe_get_edid(&res);
936 		if (edid) {
937 			printf(": EDID");
938 			while ((rp = TAILQ_FIRST(&res)) != NULL) {
939 				printf(" %dx%d", rp->width, rp->height);
940 				TAILQ_REMOVE(&res, rp, next);
941 				free(rp);
942 			}
943 			printf("\n");
944 		} else {
945 			printf(": no EDID information\n");
946 		}
947 	}
948 	if (!edid)
949 		if (vbe_get_flatpanel(&width, &height))
950 			printf(": Panel %dx%d\n", width, height);
951 
952 	memset(vbe, 0, sizeof (*vbe));
953 	memcpy(vbe->VbeSignature, "VBE2", 4);
954 	if (biosvbe_info(vbe) != VBE_SUCCESS)
955 		goto done;
956 	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
957 		goto done;
958 
959 	vbe_print_vbe_info(vbe);
960 	printf("Modes: ");
961 
962 	farptr = vbe->VideoModePtr;
963 	if (farptr == 0)
964 		goto done;
965 
966 	while ((mode = *(uint16_t *)vbe_farptr(farptr)) != 0xffff) {
967 		safety++;
968 		farptr += 2;
969 		if (safety == VESA_MODE_COUNT) {
970 			printf("[?] ");
971 			break;
972 		}
973 		if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS)
974 			continue;
975 		/* we only care about linear modes here */
976 		if (vbe_mode_is_supported(&mi) == 0)
977 			continue;
978 
979 		/* we found some mode so reset safety counter */
980 		safety = 0;
981 
982 		/* apply requested filter */
983 		if (depth != -1 && mi.BitsPerPixel != depth)
984 			continue;
985 
986 		if (nmodes % 4 == 0)
987 			printf("\n");
988 		else
989 			printf("  ");
990 
991 		vbe_dump_mode(mode, &mi);
992 		nmodes++;
993 	}
994 
995 done:
996 	if (nmodes == 0)
997 		printf("none found");
998 	printf("\n");
999 }
1000 
1001 static void
1002 vbe_print_mode(bool verbose __unused)
1003 {
1004 	int nc, mode, i, rc;
1005 
1006 	nc = NCOLORS;
1007 
1008 	memset(vbe, 0, sizeof (*vbe));
1009 	if (biosvbe_info(vbe) != VBE_SUCCESS)
1010 		return;
1011 
1012 	vbe_print_vbe_info(vbe);
1013 
1014 	if (biosvbe_get_mode(&mode) != VBE_SUCCESS) {
1015 		printf("Error getting current VBE mode\n");
1016 		return;
1017 	}
1018 
1019 	if (biosvbe_get_mode_info(mode, vbe_mode) != VBE_SUCCESS ||
1020 	    vbe_mode_is_supported(vbe_mode) == 0) {
1021 		printf("VBE mode (0x%x) is not framebuffer mode\n", mode);
1022 		return;
1023 	}
1024 
1025 	printf("\nCurrent VBE mode: ");
1026 	vbe_dump_mode(mode, vbe_mode);
1027 	printf("\n");
1028 
1029 	printf("%ux%ux%u, stride=%u\n",
1030 	    gfx_state.tg_fb.fb_width,
1031 	    gfx_state.tg_fb.fb_height,
1032 	    gfx_state.tg_fb.fb_bpp,
1033 	    gfx_state.tg_fb.fb_stride *
1034 	    (roundup2(gfx_state.tg_fb.fb_bpp, NBBY) / NBBY));
1035 	printf("    frame buffer: address=%jx, size=%jx\n",
1036 	    (uintmax_t)gfx_state.tg_fb.fb_addr,
1037 	    (uintmax_t)gfx_state.tg_fb.fb_size);
1038 
1039 	if (vbe_mode->MemoryModel == 0x6) {
1040 		printf("    color mask: R=%08x, G=%08x, B=%08x\n",
1041 		    gfx_state.tg_fb.fb_mask_red,
1042 		    gfx_state.tg_fb.fb_mask_green,
1043 		    gfx_state.tg_fb.fb_mask_blue);
1044 		pager_open();
1045 		for (i = 0; i < nc; i++) {
1046 			printf("%d: R=%02x, G=%02x, B=%02x %08x", i,
1047 			    (cmap[i] & gfx_state.tg_fb.fb_mask_red) >>
1048 			    ffs(gfx_state.tg_fb.fb_mask_red) - 1,
1049 			    (cmap[i] & gfx_state.tg_fb.fb_mask_green) >>
1050 			    ffs(gfx_state.tg_fb.fb_mask_green) - 1,
1051 			    (cmap[i] & gfx_state.tg_fb.fb_mask_blue) >>
1052 			    ffs(gfx_state.tg_fb.fb_mask_blue) - 1, cmap[i]);
1053 			if (pager_output("\n") != 0)
1054 				break;
1055 		}
1056 		pager_close();
1057 		return;
1058 	}
1059 
1060 	mode = 1;	/* get DAC palette width */
1061 	rc = biosvbe_palette_format(&mode);
1062 	if (rc != VBE_SUCCESS)
1063 		return;
1064 
1065 	printf("    palette format: %x bits per primary\n", mode);
1066 	if (pe8 == NULL)
1067 		return;
1068 
1069 	pager_open();
1070 	for (i = 0; i < nc; i++) {
1071 		printf("%d: R=%02x, G=%02x, B=%02x", i,
1072 		    pe8[i].Red, pe8[i].Green, pe8[i].Blue);
1073 		if (pager_output("\n") != 0)
1074 			break;
1075 	}
1076 	pager_close();
1077 }
1078 
1079 /*
1080  * Try EDID preferred mode, if EDID or the suggested mode is not available,
1081  * then try flat panel information.
1082  * Fall back to VBE_DEFAULT_MODE.
1083  */
1084 int
1085 vbe_default_mode(void)
1086 {
1087 	edid_res_list_t res;
1088 	struct resolution *rp;
1089 	int modenum;
1090 	uint32_t width, height;
1091 
1092 	modenum = 0;
1093 	vbe_get_max_resolution(&width, &height);
1094 	if (width != 0 && height != 0)
1095 		modenum = vbe_find_mode_xydm(width, height, -1, -1);
1096 
1097 	TAILQ_INIT(&res);
1098 	if (vbe_get_edid(&res)) {
1099 		while ((rp = TAILQ_FIRST(&res)) != NULL) {
1100 			if (modenum == 0) {
1101 				modenum = vbe_find_mode_xydm(
1102 				    rp->width, rp->height, -1, -1);
1103 			}
1104 			TAILQ_REMOVE(&res, rp, next);
1105 			free(rp);
1106 		}
1107 	}
1108 
1109 	if (modenum == 0 &&
1110 	    vbe_get_flatpanel(&width, &height)) {
1111 		modenum = vbe_find_mode_xydm(width, height, -1, -1);
1112 	}
1113 
1114 	/* Still no mode? Fall back to default. */
1115 	if (modenum == 0)
1116 		modenum = vbe_find_mode(VBE_DEFAULT_MODE);
1117 	return (modenum);
1118 }
1119 
1120 COMMAND_SET(vbe, "vbe", "vesa framebuffer mode management", command_vesa);
1121 
1122 int
1123 command_vesa(int argc, char *argv[])
1124 {
1125 	char *arg, *cp;
1126 	int modenum = -1, n;
1127 
1128 	if (!vbe_check())
1129 		return (CMD_OK);
1130 
1131 	if (argc < 2)
1132 		goto usage;
1133 
1134 	if (strcmp(argv[1], "list") == 0) {
1135 		n = -1;
1136 		if (argc != 2 && argc != 3)
1137 			goto usage;
1138 
1139 		if (argc == 3) {
1140 			arg = argv[2];
1141 			errno = 0;
1142 			n = strtoul(arg, &cp, 0);
1143 			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
1144 				snprintf(command_errbuf,
1145 				    sizeof (command_errbuf),
1146 				    "depth should be an integer");
1147 				return (CMD_ERROR);
1148 			}
1149 		}
1150 		vbe_modelist(n);
1151 		return (CMD_OK);
1152 	}
1153 
1154 	if (strcmp(argv[1], "get") == 0) {
1155 		bool verbose = false;
1156 
1157 		if (argc != 2) {
1158 			if (argc > 3 || strcmp(argv[2], "-v") != 0)
1159 				goto usage;
1160 			verbose = true;
1161 		}
1162 		vbe_print_mode(verbose);
1163 		return (CMD_OK);
1164 	}
1165 
1166 	if (strcmp(argv[1], "off") == 0) {
1167 		if (argc != 2)
1168 			goto usage;
1169 
1170 		if (gfx_state.tg_mode == VGA_TEXT_MODE)
1171 			return (CMD_OK);
1172 
1173 		reset_font_flags();
1174 		bios_text_font(true);
1175 		bios_set_text_mode(VGA_TEXT_MODE);
1176 		cons_update_mode(false);
1177 		return (CMD_OK);
1178 	}
1179 
1180 	if (strcmp(argv[1], "on") == 0) {
1181 		if (argc != 2)
1182 			goto usage;
1183 
1184 		modenum = vbe_default_mode();
1185 		if (modenum == 0) {
1186 			snprintf(command_errbuf, sizeof (command_errbuf),
1187 			    "%s: no suitable VBE mode number found", argv[0]);
1188 			return (CMD_ERROR);
1189 		}
1190 	} else if (strcmp(argv[1], "set") == 0) {
1191 		if (argc != 3)
1192 			goto usage;
1193 
1194 		if (strncmp(argv[2], "0x", 2) == 0) {
1195 			arg = argv[2];
1196 			errno = 0;
1197 			n = strtoul(arg, &cp, 0);
1198 			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
1199 				snprintf(command_errbuf,
1200 				    sizeof (command_errbuf),
1201 				    "mode should be an integer");
1202 				return (CMD_ERROR);
1203 			}
1204 			modenum = vbe_find_mode_xydm(0, 0, 0, n);
1205 		} else if (strchr(argv[2], 'x') != NULL) {
1206 			modenum = vbe_find_mode(argv[2]);
1207 		}
1208 	} else {
1209 		goto usage;
1210 	}
1211 
1212 	if (modenum == 0) {
1213 		snprintf(command_errbuf, sizeof (command_errbuf),
1214 		    "%s: mode %s not supported by firmware\n",
1215 		    argv[0], argv[2]);
1216 		return (CMD_ERROR);
1217 	}
1218 
1219 	if (modenum >= VESA_MODE_BASE) {
1220 		if (gfx_state.tg_mode != modenum) {
1221 			reset_font_flags();
1222 			bios_text_font(false);
1223 			vbe_set_mode(modenum);
1224 			cons_update_mode(true);
1225 		}
1226 		return (CMD_OK);
1227 	} else {
1228 		snprintf(command_errbuf, sizeof (command_errbuf),
1229 		    "%s: mode %s is not framebuffer mode\n", argv[0], argv[2]);
1230 		return (CMD_ERROR);
1231 	}
1232 
1233 usage:
1234 	snprintf(command_errbuf, sizeof (command_errbuf),
1235 	    "usage: %s on | off | get | list [depth] | "
1236 	    "set <display or VBE mode number>", argv[0]);
1237 	return (CMD_ERROR);
1238 }
1239