xref: /illumos-gate/usr/src/boot/i386/libi386/vbe.c (revision 08f2ce59ccfd4e449c92dd87b23e756e439d4daa)
1 /*
2  * Copyright (c) 2009 Jared D. McNeill <jmcneill@invisible.ca>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
15  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 /*
28  * VESA BIOS Extensions routines
29  */
30 
31 #include <stand.h>
32 #include <stdbool.h>
33 #include <bootstrap.h>
34 #include <machine/bootinfo.h>
35 #include <machine/metadata.h>
36 #include <sys/multiboot2.h>
37 #include <btxv86.h>
38 #include "libi386.h"
39 #include "gfx_fb.h"	/* for EDID */
40 #include "vbe.h"
41 #include <sys/font.h>
42 #include <sys/rgb.h>
43 #include <sys/vgareg.h>
44 #include <sys/vgasubr.h>
45 
46 multiboot_tag_vbe_t vbestate;
47 static struct vbeinfoblock *vbe =
48 	(struct vbeinfoblock *)&vbestate.vbe_control_info;
49 static struct modeinfoblock *vbe_mode =
50 	(struct modeinfoblock *)&vbestate.vbe_mode_info;
51 static uint16_t *vbe_mode_list;
52 static size_t vbe_mode_list_size;
53 struct vesa_edid_info *edid_info = NULL;
54 multiboot_color_t *cmap;
55 /* The default VGA color palette format is 6 bits per primary color. */
56 int palette_format = 6;
57 
58 #define	VESA_MODE_BASE		0x100
59 #define	VESA_END_OF_MODE_LIST	0xffff
60 
61 /* Actually assuming mode 3. */
62 void
63 bios_set_text_mode(int mode)
64 {
65 	int atr;
66 
67 	if (vbe->Capabilities & VBE_CAP_DAC8) {
68 		int m;
69 
70 		/*
71 		 * The mode change should reset the palette format to
72 		 * 6 bits, but apparently some systems do fail with 8-bit
73 		 * palette, so we switch to 6-bit here.
74 		 */
75 		m = 0x0600;
76 		(void) biosvbe_palette_format(&m);
77 		palette_format = m;
78 	}
79 	v86.ctl = V86_FLAGS;
80 	v86.addr = 0x10;
81 	v86.eax = mode;				/* set VGA text mode */
82 	v86int();
83 	atr = vga_get_atr(VGA_REG_ADDR, VGA_ATR_MODE);
84 	atr &= ~VGA_ATR_MODE_BLINK;
85 	atr &= ~VGA_ATR_MODE_9WIDE;
86 	vga_set_atr(VGA_REG_ADDR, VGA_ATR_MODE, atr);
87 
88 	vbestate.vbe_mode = 0;			/* vbe is disabled */
89 	gfx_fb.framebuffer_common.framebuffer_type =
90 	    MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT;
91 	/* 16 bits per character */
92 	gfx_fb.framebuffer_common.framebuffer_bpp = 16;
93 	gfx_fb.framebuffer_common.framebuffer_addr =
94 	    VGA_MEM_ADDR + VGA_COLOR_BASE;
95 	gfx_fb.framebuffer_common.framebuffer_width = TEXT_COLS;
96 	gfx_fb.framebuffer_common.framebuffer_height = TEXT_ROWS;
97 	gfx_fb.framebuffer_common.framebuffer_pitch = TEXT_COLS * 2;
98 }
99 
100 /* Function 00h - Return VBE Controller Information */
101 static int
102 biosvbe_info(struct vbeinfoblock *vbe)
103 {
104 	v86.ctl = V86_FLAGS;
105 	v86.addr = 0x10;
106 	v86.eax = 0x4f00;
107 	v86.es = VTOPSEG(vbe);
108 	v86.edi = VTOPOFF(vbe);
109 	v86int();
110 	return (v86.eax & 0xffff);
111 }
112 
113 /* Function 01h - Return VBE Mode Information */
114 static int
115 biosvbe_get_mode_info(int mode, struct modeinfoblock *mi)
116 {
117 	v86.ctl = V86_FLAGS;
118 	v86.addr = 0x10;
119 	v86.eax = 0x4f01;
120 	v86.ecx = mode;
121 	v86.es = VTOPSEG(mi);
122 	v86.edi = VTOPOFF(mi);
123 	v86int();
124 	return (v86.eax & 0xffff);
125 }
126 
127 /* Function 02h - Set VBE Mode */
128 static int
129 biosvbe_set_mode(int mode, struct crtciinfoblock *ci)
130 {
131 	int rv;
132 
133 	if (vbe->Capabilities & VBE_CAP_DAC8) {
134 		int m;
135 
136 		/*
137 		 * The mode change should reset the palette format to
138 		 * 6 bits, but apparently some systems do fail with 8-bit
139 		 * palette, so we switch to 6-bit here.
140 		 */
141 		m = 0x0600;
142 		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
143 			palette_format = m;
144 	}
145 	v86.ctl = V86_FLAGS;
146 	v86.addr = 0x10;
147 	v86.eax = 0x4f02;
148 	v86.ebx = mode | 0x4000;	/* set linear FB bit */
149 	v86.es = VTOPSEG(ci);
150 	v86.edi = VTOPOFF(ci);
151 	v86int();
152 	rv = v86.eax & 0xffff;
153 	if (vbe->Capabilities & VBE_CAP_DAC8) {
154 		int m;
155 
156 		/* Switch to 8-bits per primary color. */
157 		m = 0x0800;
158 		if (biosvbe_palette_format(&m) == VBE_SUCCESS)
159 			palette_format = m;
160 	}
161 	return (rv);
162 }
163 
164 /* Function 03h - Get VBE Mode */
165 static int
166 biosvbe_get_mode(int *mode)
167 {
168 	v86.ctl = V86_FLAGS;
169 	v86.addr = 0x10;
170 	v86.eax = 0x4f03;
171 	v86int();
172 	*mode = v86.ebx & 0x3fff;	/* Bits 0-13 */
173 	return (v86.eax & 0xffff);
174 }
175 
176 /* Function 08h - Set/Get DAC Palette Format */
177 int
178 biosvbe_palette_format(int *format)
179 {
180 	v86.ctl = V86_FLAGS;
181 	v86.addr = 0x10;
182 	v86.eax = 0x4f08;
183 	v86.ebx = *format;
184 	v86int();
185 	*format = (v86.ebx >> 8) & 0xff;
186 	return (v86.eax & 0xffff);
187 }
188 
189 /* Function 09h - Set/Get Palette Data */
190 static int
191 biosvbe_palette_data(int mode, int reg, struct paletteentry *pe)
192 {
193 	v86.ctl = V86_FLAGS;
194 	v86.addr = 0x10;
195 	v86.eax = 0x4f09;
196 	v86.ebx = mode;
197 	v86.edx = reg;
198 	v86.ecx = 1;
199 	v86.es = VTOPSEG(pe);
200 	v86.edi = VTOPOFF(pe);
201 	v86int();
202 	return (v86.eax & 0xffff);
203 }
204 
205 /*
206  * Function 15h BL=00h - Report VBE/DDC Capabilities
207  *
208  * int biosvbe_ddc_caps(void)
209  * return: VBE/DDC capabilities
210  */
211 static int
212 biosvbe_ddc_caps(void)
213 {
214 	v86.ctl = V86_FLAGS;
215 	v86.addr = 0x10;
216 	v86.eax = 0x4f15;	/* display identification extensions */
217 	v86.ebx = 0;		/* report DDC capabilities */
218 	v86.ecx = 0;		/* controller unit number (00h = primary) */
219 	v86.es = 0;
220 	v86.edi = 0;
221 	v86int();
222 	if (VBE_ERROR(v86.eax & 0xffff))
223 		return (0);
224 	return (v86.ebx & 0xffff);
225 }
226 
227 /* Function 11h BL=01h - Flat Panel status */
228 static int
229 biosvbe_ddc_read_flat_panel_info(void *buf)
230 {
231 	v86.ctl = V86_FLAGS;
232 	v86.addr = 0x10;
233 	v86.eax = 0x4f11;	/* Flat Panel Interface extensions */
234 	v86.ebx = 1;		/* Return Flat Panel Information */
235 	v86.es = VTOPSEG(buf);
236 	v86.edi = VTOPOFF(buf);
237 	v86int();
238 	return (v86.eax & 0xffff);
239 }
240 
241 /* Function 15h BL=01h - Read EDID */
242 static int
243 biosvbe_ddc_read_edid(int blockno, void *buf)
244 {
245 	v86.ctl = V86_FLAGS;
246 	v86.addr = 0x10;
247 	v86.eax = 0x4f15;	/* display identification extensions */
248 	v86.ebx = 1;		/* read EDID */
249 	v86.ecx = 0;		/* controller unit number (00h = primary) */
250 	v86.edx = blockno;
251 	v86.es = VTOPSEG(buf);
252 	v86.edi = VTOPOFF(buf);
253 	v86int();
254 	return (v86.eax & 0xffff);
255 }
256 
257 static int
258 vbe_mode_is_supported(struct modeinfoblock *mi)
259 {
260 	if ((mi->ModeAttributes & 0x01) == 0)
261 		return (0);	/* mode not supported by hardware */
262 	if ((mi->ModeAttributes & 0x08) == 0)
263 		return (0);	/* linear fb not available */
264 	if ((mi->ModeAttributes & 0x10) == 0)
265 		return (0);	/* text mode */
266 	if (mi->NumberOfPlanes != 1)
267 		return (0);	/* planar mode not supported */
268 	if (mi->MemoryModel != 0x04 /* Packed pixel */ &&
269 	    mi->MemoryModel != 0x06 /* Direct Color */)
270 		return (0);	/* unsupported pixel format */
271 	return (1);
272 }
273 
274 static int
275 vbe_check(void)
276 {
277 	if (vbestate.mb_type != MULTIBOOT_TAG_TYPE_VBE) {
278 		printf("VBE not available\n");
279 		return (0);
280 	}
281 	return (1);
282 }
283 
284 /*
285  * Translate selector:offset style address to linear adress.
286  * selector = farptr >> 16;
287  * offset = farptr & 0xffff;
288  * linear = (selector * 4) + offset.
289  * By using mask 0xffff0000, we wil get the optimised line below.
290  * As a final step, translate physical address to loader virtual address.
291  */
292 static void *
293 vbe_farptr(uint32_t farptr)
294 {
295 	return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff))));
296 }
297 
298 void
299 vbe_init(void)
300 {
301 	uint16_t *p, *ml;
302 
303 	/* First set FB for text mode. */
304 	gfx_fb.framebuffer_common.mb_type = MULTIBOOT_TAG_TYPE_FRAMEBUFFER;
305 	gfx_fb.framebuffer_common.framebuffer_type =
306 	    MULTIBOOT_FRAMEBUFFER_TYPE_EGA_TEXT;
307 	/* 16 bits per character */
308 	gfx_fb.framebuffer_common.framebuffer_bpp = 16;
309 	gfx_fb.framebuffer_common.framebuffer_addr =
310 	    VGA_MEM_ADDR + VGA_COLOR_BASE;
311 	gfx_fb.framebuffer_common.framebuffer_width = TEXT_COLS;
312 	gfx_fb.framebuffer_common.framebuffer_height = TEXT_ROWS;
313 	gfx_fb.framebuffer_common.framebuffer_pitch = TEXT_COLS * 2;
314 
315 	/* Now check if we have vesa. */
316 	memset(vbe, 0, sizeof (*vbe));
317 	memcpy(vbe->VbeSignature, "VBE2", 4);
318 	if (biosvbe_info(vbe) != VBE_SUCCESS)
319 		return;
320 	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
321 		return;
322 
323 	/*
324 	 * Copy mode list array. We must do this because some systems do
325 	 * place this array to scratch memory, which will be reused by
326 	 * subsequent VBE calls. (vbox 6.1 is one example).
327 	 */
328 	p = ml = vbe_farptr(vbe->VideoModePtr);
329 	while (*p++ != VESA_END_OF_MODE_LIST)
330 		;
331 
332 	vbe_mode_list_size = (uintptr_t)p - (uintptr_t)ml;
333 
334 	/*
335 	 * Since vbe_init() is used only once at very start of the loader,
336 	 * we assume malloc will not fail there. But in case it does,
337 	 * we point vbe_mode_list to memory pointed by VideoModePtr.
338 	 * If the VideoModePtr memory area is not valid, we will fail to
339 	 * pick usable VBE mode and fall back to use text mode.
340 	 */
341 	vbe_mode_list = malloc(vbe_mode_list_size);
342 	if (vbe_mode_list == NULL)
343 		vbe_mode_list = ml;
344 	else
345 		bcopy(ml, vbe_mode_list, vbe_mode_list_size);
346 
347 	/* reset VideoModePtr, to make sure, we only do use vbe_mode_list. */
348 	vbe->VideoModePtr = 0;
349 
350 	vbestate.mb_type = MULTIBOOT_TAG_TYPE_VBE;
351 	vbestate.mb_size = sizeof (vbestate);
352 	vbestate.vbe_mode = 0;
353 
354 	/* vbe_set_mode() will set up the rest. */
355 }
356 
357 int
358 vbe_available(void)
359 {
360 	return (vbestate.mb_type);
361 }
362 
363 int
364 vbe_set_palette(const struct paletteentry *entry, size_t slot)
365 {
366 	struct paletteentry pe;
367 	int mode, ret;
368 
369 	if (!vbe_check() || (vbe->Capabilities & VBE_CAP_DAC8) == 0)
370 		return (1);
371 
372 	if (gfx_fb.framebuffer_common.framebuffer_type !=
373 	    MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED) {
374 		return (1);
375 	}
376 
377 	if (cmap == NULL)
378 		cmap = calloc(CMAP_SIZE, sizeof (*cmap));
379 
380 	pe.Blue = entry->Blue;
381 	pe.Green = entry->Green;
382 	pe.Red = entry->Red;
383 	pe.Reserved = entry->Reserved;
384 
385 	if (vbe->Capabilities & VBE_CAP_SNOW)
386 		mode = 0x80;
387 	else
388 		mode = 0;
389 
390 	ret = biosvbe_palette_data(mode, slot, &pe);
391 	if (cmap != NULL && slot < CMAP_SIZE) {
392 		cmap[slot].mb_red = entry->Red;
393 		cmap[slot].mb_green = entry->Green;
394 		cmap[slot].mb_blue = entry->Blue;
395 	}
396 
397 	return (ret == VBE_SUCCESS ? 0 : 1);
398 }
399 
400 int
401 vbe_get_mode(void)
402 {
403 	return (vbestate.vbe_mode);
404 }
405 
406 int
407 vbe_set_mode(int modenum)
408 {
409 	extern struct paletteentry *shadow_fb;
410 	struct modeinfoblock mi;
411 	int ret;
412 
413 	if (!vbe_check())
414 		return (1);
415 
416 	ret = biosvbe_get_mode_info(modenum, &mi);
417 	if (VBE_ERROR(ret)) {
418 		printf("mode 0x%x invalid\n", modenum);
419 		return (1);
420 	}
421 
422 	if (!vbe_mode_is_supported(&mi)) {
423 		printf("mode 0x%x not supported\n", modenum);
424 		return (1);
425 	}
426 
427 	/* calculate bytes per pixel */
428 	switch (mi.BitsPerPixel) {
429 	case 32:
430 	case 24:
431 	case 16:
432 	case 15:
433 	case 8:
434 		break;
435 	default:
436 		printf("BitsPerPixel %d is not supported\n", mi.BitsPerPixel);
437 		return (1);
438 	}
439 
440 	ret = biosvbe_set_mode(modenum, NULL);
441 	if (VBE_ERROR(ret)) {
442 		printf("mode 0x%x could not be set\n", modenum);
443 		return (1);
444 	}
445 
446 	/* make sure we have current MI in vbestate */
447 	memcpy(vbe_mode, &mi, sizeof (*vbe_mode));
448 	vbestate.vbe_mode = modenum;
449 
450 	if (shadow_fb != NULL)
451 		free(shadow_fb);
452 	shadow_fb = malloc(mi.XResolution * mi.YResolution *
453 	    sizeof (*shadow_fb));
454 
455 	gfx_fb.framebuffer_common.framebuffer_addr =
456 	    (uint64_t)mi.PhysBasePtr & 0xffffffff;
457 	gfx_fb.framebuffer_common.framebuffer_width = mi.XResolution;
458 	gfx_fb.framebuffer_common.framebuffer_height = mi.YResolution;
459 	gfx_fb.framebuffer_common.framebuffer_bpp = mi.BitsPerPixel;
460 
461 	/* vbe_mode_is_supported() excludes the rest */
462 	switch (mi.MemoryModel) {
463 	case 0x4:
464 		gfx_fb.framebuffer_common.framebuffer_type =
465 		    MULTIBOOT_FRAMEBUFFER_TYPE_INDEXED;
466 		break;
467 	case 0x6:
468 		gfx_fb.framebuffer_common.framebuffer_type =
469 		    MULTIBOOT_FRAMEBUFFER_TYPE_RGB;
470 		break;
471 	}
472 
473 	if (vbe->VbeVersion >= 0x300) {
474 		gfx_fb.framebuffer_common.framebuffer_pitch =
475 		    mi.LinBytesPerScanLine;
476 		gfx_fb.u.fb2.framebuffer_red_field_position =
477 		    mi.LinRedFieldPosition;
478 		gfx_fb.u.fb2.framebuffer_red_mask_size = mi.LinRedMaskSize;
479 		gfx_fb.u.fb2.framebuffer_green_field_position =
480 		    mi.LinGreenFieldPosition;
481 		gfx_fb.u.fb2.framebuffer_green_mask_size = mi.LinGreenMaskSize;
482 		gfx_fb.u.fb2.framebuffer_blue_field_position =
483 		    mi.LinBlueFieldPosition;
484 		gfx_fb.u.fb2.framebuffer_blue_mask_size = mi.LinBlueMaskSize;
485 	} else {
486 		gfx_fb.framebuffer_common.framebuffer_pitch =
487 		    mi.BytesPerScanLine;
488 		gfx_fb.u.fb2.framebuffer_red_field_position =
489 		    mi.RedFieldPosition;
490 		gfx_fb.u.fb2.framebuffer_red_mask_size = mi.RedMaskSize;
491 		gfx_fb.u.fb2.framebuffer_green_field_position =
492 		    mi.GreenFieldPosition;
493 		gfx_fb.u.fb2.framebuffer_green_mask_size = mi.GreenMaskSize;
494 		gfx_fb.u.fb2.framebuffer_blue_field_position =
495 		    mi.BlueFieldPosition;
496 		gfx_fb.u.fb2.framebuffer_blue_mask_size = mi.BlueMaskSize;
497 	}
498 
499 	/*
500 	 * Support for color mapping.
501 	 * For 8, 24 and 32 bit depth, use mask size 8.
502 	 * 15/16 bit depth needs to use mask size from mode, or we will
503 	 * lose color information from 32-bit to 15/16 bit translation.
504 	 */
505 	if (mi.BitsPerPixel == 15 || mi.BitsPerPixel == 16) {
506 		rgb_info.red.size = gfx_fb.u.fb2.framebuffer_red_mask_size;
507 		rgb_info.green.size = gfx_fb.u.fb2.framebuffer_green_mask_size;
508 		rgb_info.blue.size = gfx_fb.u.fb2.framebuffer_blue_mask_size;
509 	} else {
510 		rgb_info.red.size = 8;
511 		rgb_info.green.size = 8;
512 		rgb_info.blue.size = 8;
513 	}
514 	rgb_info.red.pos = 16;
515 	rgb_info.green.pos = 8;
516 	rgb_info.blue.pos = 0;
517 
518 	return (0);
519 }
520 
521 /*
522  * Verify existance of mode number or find mode by
523  * dimensions. If depth is not given, walk values 32, 24, 16, 8.
524  */
525 static int
526 vbe_find_mode_xydm(int x, int y, int depth, int m)
527 {
528 	struct modeinfoblock mi;
529 	uint16_t mode;
530 	size_t idx, nentries;
531 	int i;
532 
533 	memset(vbe, 0, sizeof (vbe));
534 	memcpy(vbe->VbeSignature, "VBE2", 4);
535 	if (biosvbe_info(vbe) != VBE_SUCCESS)
536 		return (0);
537 	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
538 		return (0);
539 
540 	if (m != -1)
541 		i = 8;
542 	else if (depth == -1)
543 		i = 32;
544 	else
545 		i = depth;
546 
547 	nentries = vbe_mode_list_size / sizeof (*vbe_mode_list);
548 	while (i > 0) {
549 		for (idx = 0; idx < nentries; idx++) {
550 			mode = vbe_mode_list[idx];
551 			if (mode == VESA_END_OF_MODE_LIST)
552 				break;
553 
554 			if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS)
555 				continue;
556 
557 			/* we only care about linear modes here */
558 			if (vbe_mode_is_supported(&mi) == 0)
559 				continue;
560 
561 			if (m != -1) {
562 				if (m == mode)
563 					return (mode);
564 				else
565 					continue;
566 			}
567 
568 			if (mi.XResolution == x &&
569 			    mi.YResolution == y &&
570 			    mi.BitsPerPixel == i)
571 				return (mode);
572 		}
573 		if (depth != -1)
574 			break;
575 
576 		i -= 8;
577 	}
578 
579 	return (0);
580 }
581 
582 static int
583 vbe_find_mode(char *str)
584 {
585 	int x, y, depth;
586 
587 	if (!gfx_parse_mode_str(str, &x, &y, &depth))
588 		return (0);
589 
590 	return (vbe_find_mode_xydm(x, y, depth, -1));
591 }
592 
593 static void
594 vbe_dump_mode(int modenum, struct modeinfoblock *mi)
595 {
596 	printf("0x%x=%dx%dx%d", modenum,
597 	    mi->XResolution, mi->YResolution, mi->BitsPerPixel);
598 }
599 
600 static bool
601 vbe_get_edid(edid_res_list_t *res)
602 {
603 	struct vesa_edid_info *edidp;
604 	const uint8_t magic[] = EDID_MAGIC;
605 	int ddc_caps;
606 	bool ret = false;
607 
608 	if (edid_info != NULL)
609 		return (gfx_get_edid_resolution(edid_info, res));
610 
611 	ddc_caps = biosvbe_ddc_caps();
612 	if (ddc_caps == 0) {
613 		return (ret);
614 	}
615 
616 	edidp = bio_alloc(sizeof (*edidp));
617 	if (edidp == NULL)
618 		return (ret);
619 	memset(edidp, 0, sizeof (*edidp));
620 
621 	if (VBE_ERROR(biosvbe_ddc_read_edid(0, edidp)))
622 		goto done;
623 
624 	if (memcmp(edidp, magic, sizeof (magic)) != 0)
625 		goto done;
626 
627 	/* Unknown EDID version. */
628 	if (edidp->header.version != 1)
629 		goto done;
630 
631 	ret = gfx_get_edid_resolution(edidp, res);
632 	edid_info = malloc(sizeof (*edid_info));
633 	if (edid_info != NULL)
634 		memcpy(edid_info, edidp, sizeof (*edid_info));
635 done:
636 	bio_free(edidp, sizeof (*edidp));
637 	return (ret);
638 }
639 
640 static bool
641 vbe_get_flatpanel(uint_t *pwidth, uint_t *pheight)
642 {
643 	struct flatpanelinfo *fp_info;
644 	bool ret = false;
645 
646 	fp_info = bio_alloc(sizeof (*fp_info));
647 	if (fp_info == NULL)
648 		return (ret);
649 	memset(fp_info, 0, sizeof (*fp_info));
650 
651 	if (VBE_ERROR(biosvbe_ddc_read_flat_panel_info(fp_info)))
652 		goto done;
653 
654 	*pwidth = fp_info->HorizontalSize;
655 	*pheight = fp_info->VerticalSize;
656 	ret = true;
657 
658 done:
659 	bio_free(fp_info, sizeof (*fp_info));
660 	return (ret);
661 }
662 
663 static void
664 vbe_print_vbe_info(struct vbeinfoblock *vbep)
665 {
666 	char *oemstring = "";
667 	char *oemvendor = "", *oemproductname = "", *oemproductrev = "";
668 
669 	if (vbep->OemStringPtr != 0)
670 		oemstring = vbe_farptr(vbep->OemStringPtr);
671 
672 	if (vbep->OemVendorNamePtr != 0)
673 		oemvendor = vbe_farptr(vbep->OemVendorNamePtr);
674 
675 	if (vbep->OemProductNamePtr != 0)
676 		oemproductname = vbe_farptr(vbep->OemProductNamePtr);
677 
678 	if (vbep->OemProductRevPtr != 0)
679 		oemproductrev = vbe_farptr(vbep->OemProductRevPtr);
680 
681 	printf("VESA VBE Version %d.%d\n%s\n", vbep->VbeVersion >> 8,
682 	    vbep->VbeVersion & 0xF, oemstring);
683 
684 	if (vbep->OemSoftwareRev != 0) {
685 		printf("OEM Version %d.%d, %s (%s, %s)\n",
686 		    vbep->OemSoftwareRev >> 8, vbep->OemSoftwareRev & 0xF,
687 		    oemvendor, oemproductname, oemproductrev);
688 	}
689 }
690 
691 /* List available modes, filter by depth. If depth is -1, list all. */
692 void
693 vbe_modelist(int depth)
694 {
695 	struct modeinfoblock mi;
696 	uint16_t mode;
697 	int nmodes, idx, nentries;
698 	int ddc_caps;
699 	uint_t width, height;
700 	bool edid = false;
701 	edid_res_list_t res;
702 	struct resolution *rp;
703 
704 	if (!vbe_check())
705 		return;
706 
707 	ddc_caps = biosvbe_ddc_caps();
708 	if (ddc_caps & 3) {
709 		printf("DDC");
710 		if (ddc_caps & 1)
711 			printf(" [DDC1]");
712 		if (ddc_caps & 2)
713 			printf(" [DDC2]");
714 
715 		TAILQ_INIT(&res);
716 		edid = vbe_get_edid(&res);
717 		if (edid) {
718 			printf(": EDID");
719 			while ((rp = TAILQ_FIRST(&res)) != NULL) {
720 				printf(" %dx%d", rp->width, rp->height);
721 				TAILQ_REMOVE(&res, rp, next);
722 				free(rp);
723 			}
724 			printf("\n");
725 		} else {
726 			printf(": no EDID information\n");
727 		}
728 	}
729 	if (!edid)
730 		if (vbe_get_flatpanel(&width, &height))
731 			printf(": Panel %dx%d\n", width, height);
732 
733 	nmodes = 0;
734 	memset(vbe, 0, sizeof (vbe));
735 	memcpy(vbe->VbeSignature, "VBE2", 4);
736 	if (biosvbe_info(vbe) != VBE_SUCCESS)
737 		goto done;
738 	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
739 		goto done;
740 
741 	vbe_print_vbe_info(vbe);
742 	printf("Modes: ");
743 
744 	nentries = vbe_mode_list_size / sizeof (*vbe_mode_list);
745 	for (idx = 0; idx < nentries; idx++) {
746 		mode = vbe_mode_list[idx];
747 		if (mode == VESA_END_OF_MODE_LIST)
748 			break;
749 
750 		if (biosvbe_get_mode_info(mode, &mi) != VBE_SUCCESS)
751 			continue;
752 
753 		/* we only care about linear modes here */
754 		if (vbe_mode_is_supported(&mi) == 0)
755 			continue;
756 
757 		/* apply requested filter */
758 		if (depth != -1 && mi.BitsPerPixel != depth)
759 			continue;
760 
761 		if (nmodes % 4 == 0)
762 			printf("\n");
763 		else
764 			printf("  ");
765 
766 		vbe_dump_mode(mode, &mi);
767 		nmodes++;
768 	}
769 
770 done:
771 	if (nmodes == 0)
772 		printf("none found");
773 	printf("\n");
774 }
775 
776 static void
777 vbe_print_mode(bool verbose)
778 {
779 	int nc, mode, i, rc;
780 
781 	if (verbose)
782 		nc = 256;
783 	else
784 		nc = 16;
785 
786 	memset(vbe, 0, sizeof (vbe));
787 	memcpy(vbe->VbeSignature, "VBE2", 4);
788 	if (biosvbe_info(vbe) != VBE_SUCCESS)
789 		return;
790 
791 	if (memcmp(vbe->VbeSignature, "VESA", 4) != 0)
792 		return;
793 
794 	vbe_print_vbe_info(vbe);
795 
796 	if (biosvbe_get_mode(&mode) != VBE_SUCCESS) {
797 		printf("Error getting current VBE mode\n");
798 		return;
799 	}
800 
801 	if (biosvbe_get_mode_info(mode, vbe_mode) != VBE_SUCCESS ||
802 	    vbe_mode_is_supported(vbe_mode) == 0) {
803 		printf("VBE mode (0x%x) is not framebuffer mode\n", mode);
804 		return;
805 	}
806 
807 	printf("\nCurrent VBE mode: ");
808 	vbe_dump_mode(mode, vbe_mode);
809 	printf("\n");
810 
811 	printf("%ux%ux%u, stride=%u\n",
812 	    gfx_fb.framebuffer_common.framebuffer_width,
813 	    gfx_fb.framebuffer_common.framebuffer_height,
814 	    gfx_fb.framebuffer_common.framebuffer_bpp,
815 	    (gfx_fb.framebuffer_common.framebuffer_pitch << 3) /
816 	    gfx_fb.framebuffer_common.framebuffer_bpp);
817 	printf("    frame buffer: address=%jx, size=%jx\n",
818 	    (uintmax_t)gfx_fb.framebuffer_common.framebuffer_addr,
819 	    (uintmax_t)gfx_fb.framebuffer_common.framebuffer_height *
820 	    gfx_fb.framebuffer_common.framebuffer_pitch);
821 
822 	if (vbe_mode->MemoryModel == 0x6) {
823 		printf("    color mask: R=%08x, G=%08x, B=%08x\n",
824 		    ((1 << gfx_fb.u.fb2.framebuffer_red_mask_size) - 1) <<
825 		    gfx_fb.u.fb2.framebuffer_red_field_position,
826 		    ((1 << gfx_fb.u.fb2.framebuffer_green_mask_size) - 1) <<
827 		    gfx_fb.u.fb2.framebuffer_green_field_position,
828 		    ((1 << gfx_fb.u.fb2.framebuffer_blue_mask_size) - 1) <<
829 		    gfx_fb.u.fb2.framebuffer_blue_field_position);
830 		return;
831 	}
832 
833 	mode = 1;	/* get DAC palette width */
834 	rc = biosvbe_palette_format(&mode);
835 	if (rc != VBE_SUCCESS)
836 		return;
837 
838 	printf("    palette format: %x bits per primary\n", mode);
839 	if (cmap == NULL)
840 		return;
841 
842 	pager_open();
843 	for (i = 0; i < nc; i++) {
844 		printf("%d: R=%02x, G=%02x, B=%02x", i,
845 		    cmap[i].mb_red, cmap[i].mb_green, cmap[i].mb_blue);
846 		if (pager_output("\n") != 0)
847 			break;
848 	}
849 	pager_close();
850 }
851 
852 /*
853  * Try EDID preferred mode, if EDID or the suggested mode is not available,
854  * then try flat panel information.
855  * Fall back to VBE_DEFAULT_MODE.
856  */
857 int
858 vbe_default_mode(void)
859 {
860 	edid_res_list_t res;
861 	struct resolution *rp;
862 	int modenum;
863 	uint_t width, height;
864 
865 	modenum = 0;
866 	TAILQ_INIT(&res);
867 	if (vbe_get_edid(&res)) {
868 		while ((rp = TAILQ_FIRST(&res)) != NULL) {
869 			if (modenum == 0) {
870 				modenum = vbe_find_mode_xydm(
871 				    rp->width, rp->height, -1, -1);
872 			}
873 			TAILQ_REMOVE(&res, rp, next);
874 			free(rp);
875 		}
876 	}
877 
878 	if (modenum == 0 &&
879 	    vbe_get_flatpanel(&width, &height)) {
880 		modenum = vbe_find_mode_xydm(width, height, -1, -1);
881 	}
882 
883 	/* Still no mode? Fall back to default. */
884 	if (modenum == 0)
885 		modenum = vbe_find_mode(VBE_DEFAULT_MODE);
886 	return (modenum);
887 }
888 
889 COMMAND_SET(framebuffer, "framebuffer", "framebuffer mode management",
890     command_vesa);
891 
892 int
893 command_vesa(int argc, char *argv[])
894 {
895 	char *arg, *cp;
896 	int modenum = -1, n;
897 
898 	if (!vbe_check())
899 		return (CMD_OK);
900 
901 	if (argc < 2)
902 		goto usage;
903 
904 	if (strcmp(argv[1], "list") == 0) {
905 		n = -1;
906 		if (argc != 2 && argc != 3)
907 			goto usage;
908 
909 		if (argc == 3) {
910 			arg = argv[2];
911 			errno = 0;
912 			n = strtoul(arg, &cp, 0);
913 			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
914 				snprintf(command_errbuf,
915 				    sizeof (command_errbuf),
916 				    "depth should be an integer");
917 				return (CMD_ERROR);
918 			}
919 		}
920 		vbe_modelist(n);
921 		return (CMD_OK);
922 	}
923 
924 	if (strcmp(argv[1], "get") == 0) {
925 		bool verbose = false;
926 
927 		if (argc > 2) {
928 			if (argc > 3 || strcmp(argv[2], "-v") != 0)
929 				goto usage;
930 			verbose = true;
931 		}
932 
933 		vbe_print_mode(verbose);
934 		return (CMD_OK);
935 	}
936 
937 	if (strcmp(argv[1], "off") == 0) {
938 		if (argc != 2)
939 			goto usage;
940 
941 		if (vbestate.vbe_mode == 0)
942 			return (CMD_OK);
943 
944 		reset_font_flags();
945 		bios_text_font(true);
946 		bios_set_text_mode(VGA_TEXT_MODE);
947 		plat_cons_update_mode(0);
948 		return (CMD_OK);
949 	}
950 
951 	if (strcmp(argv[1], "on") == 0) {
952 		if (argc != 2)
953 			goto usage;
954 
955 		modenum = vbe_default_mode();
956 		if (modenum == 0) {
957 			snprintf(command_errbuf, sizeof (command_errbuf),
958 			    "%s: no suitable VBE mode number found", argv[0]);
959 			return (CMD_ERROR);
960 		}
961 	} else if (strcmp(argv[1], "set") == 0) {
962 		if (argc != 3)
963 			goto usage;
964 
965 		if (strncmp(argv[2], "0x", 2) == 0) {
966 			arg = argv[2];
967 			errno = 0;
968 			n = strtoul(arg, &cp, 0);
969 			if (errno != 0 || *arg == '\0' || cp[0] != '\0') {
970 				snprintf(command_errbuf,
971 				    sizeof (command_errbuf),
972 				    "mode should be an integer");
973 				return (CMD_ERROR);
974 			}
975 			modenum = vbe_find_mode_xydm(0, 0, 0, n);
976 		} else if (strchr(argv[2], 'x') != NULL) {
977 			modenum = vbe_find_mode(argv[2]);
978 		}
979 	} else {
980 		goto usage;
981 	}
982 
983 	if (modenum == 0) {
984 		snprintf(command_errbuf, sizeof (command_errbuf),
985 		    "%s: mode %s not supported by firmware\n",
986 		    argv[0], argv[2]);
987 		return (CMD_ERROR);
988 	}
989 
990 	if (modenum >= VESA_MODE_BASE) {
991 		if (vbestate.vbe_mode != modenum) {
992 			reset_font_flags();
993 			bios_text_font(false);
994 			vbe_set_mode(modenum);
995 			plat_cons_update_mode(1);
996 		}
997 		return (CMD_OK);
998 	} else {
999 		snprintf(command_errbuf, sizeof (command_errbuf),
1000 		    "%s: mode %s is not framebuffer mode\n", argv[0], argv[2]);
1001 		return (CMD_ERROR);
1002 	}
1003 
1004 usage:
1005 	snprintf(command_errbuf, sizeof (command_errbuf),
1006 	    "usage: %s on | off | get [-v] | list [depth] | "
1007 	    "set <display or VBE mode number>", argv[0]);
1008 	return (CMD_ERROR);
1009 }
1010