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