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