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