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
46static struct vbeinfoblock *vbe;
47static struct modeinfoblock *vbe_mode;
48
49static uint16_t *vbe_mode_list;
50static size_t vbe_mode_list_size;
51struct vesa_edid_info *edid_info = NULL;
52
53/* The default VGA color palette format is 6 bits per primary color. */
54int 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 */
63struct paletteentry *pe8 = NULL;
64
65static 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
104static bool
105vbe_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
115static void
116vbe_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
154int
155vga_get_reg(int reg, int index)
156{
157	return (inb(reg + index));
158}
159
160int
161vga_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
174void
175vga_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
184void
185vga_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
191int
192vga_get_indexed(int reg, int indexreg, int datareg, uint8_t index)
193{
194	outb(reg + indexreg, index);
195	return (inb(reg + datareg));
196}
197
198int
199vga_get_crtc(int reg, int i)
200{
201	return (vga_get_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i));
202}
203
204void
205vga_set_crtc(int reg, int i, int v)
206{
207	vga_set_indexed(reg, VGA_CRTC_ADDRESS, VGA_CRTC_DATA, i, v);
208}
209
210int
211vga_get_seq(int reg, int i)
212{
213	return (vga_get_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i));
214}
215
216void
217vga_set_seq(int reg, int i, int v)
218{
219	vga_set_indexed(reg, VGA_SEQ_ADDRESS, VGA_SEQ_DATA, i, v);
220}
221
222int
223vga_get_grc(int reg, int i)
224{
225	return (vga_get_indexed(reg, VGA_GC_ADDRESS, VGA_GC_DATA, i));
226}
227
228void
229vga_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 */
237bool
238vbe_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. */
247void
248bios_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 */
286static int
287biosvbe_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));
319done:
320	bio_free(rvbe, sizeof(*rvbe));
321	return (ret);
322}
323
324/* Function 01h - Return VBE Mode Information */
325static int
326biosvbe_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));
347done:
348	bio_free(rmi, sizeof(*rmi));
349	return (ret);
350}
351
352/* Function 02h - Set VBE Mode */
353static int
354biosvbe_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 */
391static int
392biosvbe_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 */
403int
404biosvbe_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 */
416static int
417biosvbe_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 */
437static int
438biosvbe_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 */
454static int
455biosvbe_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 */
468static int
469biosvbe_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
483static int
484vbe_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
500static bool
501vbe_check(void)
502{
503
504	if (vbe == NULL) {
505		printf("VBE not available\n");
506		return (false);
507	}
508	return (true);
509}
510
511static int
512mode_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
553static void *
554vbe_farptr(uint32_t farptr)
555{
556	return (PTOV((((farptr & 0xffff0000) >> 12) + (farptr & 0xffff))));
557}
558
559void
560vbe_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
625bool
626vbe_available(void)
627{
628	return (gfx_state.tg_fb_type == FB_VBE);
629}
630
631int
632vbe_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
659int
660vbe_get_mode(void)
661{
662	return (gfx_state.tg_mode);
663}
664
665int
666vbe_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 */
770static int
771vbe_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
825static int
826vbe_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
836static void
837vbe_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
843static bool
844vbe_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));
878done:
879	bio_free(edidp, sizeof(*edidp));
880	return (ret);
881}
882
883static bool
884vbe_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
901done:
902	bio_free(fp_info, sizeof (*fp_info));
903	return (ret);
904}
905
906static void
907vbe_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
922static void
923vbe_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. */
953void
954vbe_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
1031done:
1032	if (nmodes == 0)
1033		printf("none found");
1034	printf("\n");
1035}
1036
1037static void
1038vbe_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 */
1120int
1121vbe_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
1156COMMAND_SET(vbe, "vbe", "vesa framebuffer mode management", command_vesa);
1157
1158int
1159command_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
1269usage:
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