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