]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - stand/efi/loader/framebuffer.c
loader: use display pixel density for font autoselection
[FreeBSD/FreeBSD.git] / stand / efi / loader / framebuffer.c
1 /*-
2  * Copyright (c) 2013 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Benno Rice under sponsorship from
6  * the FreeBSD Foundation.
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31
32 #include <bootstrap.h>
33 #include <sys/endian.h>
34 #include <sys/param.h>
35 #include <stand.h>
36
37 #include <efi.h>
38 #include <efilib.h>
39 #include <efiuga.h>
40 #include <efipciio.h>
41 #include <Protocol/EdidActive.h>
42 #include <Protocol/EdidDiscovered.h>
43 #include <machine/metadata.h>
44
45 #include "bootstrap.h"
46 #include "framebuffer.h"
47
48 static EFI_GUID conout_guid = EFI_CONSOLE_OUT_DEVICE_GUID;
49 EFI_GUID gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID;
50 static EFI_GUID pciio_guid = EFI_PCI_IO_PROTOCOL_GUID;
51 static EFI_GUID uga_guid = EFI_UGA_DRAW_PROTOCOL_GUID;
52 static EFI_GUID active_edid_guid = EFI_EDID_ACTIVE_PROTOCOL_GUID;
53 static EFI_GUID discovered_edid_guid = EFI_EDID_DISCOVERED_PROTOCOL_GUID;
54 static EFI_HANDLE gop_handle;
55
56 /* Cached EDID. */
57 struct vesa_edid_info *edid_info = NULL;
58
59 static EFI_GRAPHICS_OUTPUT *gop;
60 static EFI_UGA_DRAW_PROTOCOL *uga;
61
62 static struct named_resolution {
63         const char *name;
64         const char *alias;
65         unsigned int width;
66         unsigned int height;
67 } resolutions[] = {
68         {
69                 .name = "480p",
70                 .width = 640,
71                 .height = 480,
72         },
73         {
74                 .name = "720p",
75                 .width = 1280,
76                 .height = 720,
77         },
78         {
79                 .name = "1080p",
80                 .width = 1920,
81                 .height = 1080,
82         },
83         {
84                 .name = "2160p",
85                 .alias = "4k",
86                 .width = 3840,
87                 .height = 2160,
88         },
89         {
90                 .name = "5k",
91                 .width = 5120,
92                 .height = 2880,
93         }
94 };
95
96 static u_int
97 efifb_color_depth(struct efi_fb *efifb)
98 {
99         uint32_t mask;
100         u_int depth;
101
102         mask = efifb->fb_mask_red | efifb->fb_mask_green |
103             efifb->fb_mask_blue | efifb->fb_mask_reserved;
104         if (mask == 0)
105                 return (0);
106         for (depth = 1; mask != 1; depth++)
107                 mask >>= 1;
108         return (depth);
109 }
110
111 static int
112 efifb_mask_from_pixfmt(struct efi_fb *efifb, EFI_GRAPHICS_PIXEL_FORMAT pixfmt,
113     EFI_PIXEL_BITMASK *pixinfo)
114 {
115         int result;
116
117         result = 0;
118         switch (pixfmt) {
119         case PixelRedGreenBlueReserved8BitPerColor:
120         case PixelBltOnly:
121                 efifb->fb_mask_red = 0x000000ff;
122                 efifb->fb_mask_green = 0x0000ff00;
123                 efifb->fb_mask_blue = 0x00ff0000;
124                 efifb->fb_mask_reserved = 0xff000000;
125                 break;
126         case PixelBlueGreenRedReserved8BitPerColor:
127                 efifb->fb_mask_red = 0x00ff0000;
128                 efifb->fb_mask_green = 0x0000ff00;
129                 efifb->fb_mask_blue = 0x000000ff;
130                 efifb->fb_mask_reserved = 0xff000000;
131                 break;
132         case PixelBitMask:
133                 efifb->fb_mask_red = pixinfo->RedMask;
134                 efifb->fb_mask_green = pixinfo->GreenMask;
135                 efifb->fb_mask_blue = pixinfo->BlueMask;
136                 efifb->fb_mask_reserved = pixinfo->ReservedMask;
137                 break;
138         default:
139                 result = 1;
140                 break;
141         }
142         return (result);
143 }
144
145 static int
146 efifb_from_gop(struct efi_fb *efifb, EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *mode,
147     EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info)
148 {
149         int result;
150
151         efifb->fb_addr = mode->FrameBufferBase;
152         efifb->fb_size = mode->FrameBufferSize;
153         efifb->fb_height = info->VerticalResolution;
154         efifb->fb_width = info->HorizontalResolution;
155         efifb->fb_stride = info->PixelsPerScanLine;
156         result = efifb_mask_from_pixfmt(efifb, info->PixelFormat,
157             &info->PixelInformation);
158         return (result);
159 }
160
161 static ssize_t
162 efifb_uga_find_pixel(EFI_UGA_DRAW_PROTOCOL *uga, u_int line,
163     EFI_PCI_IO_PROTOCOL *pciio, uint64_t addr, uint64_t size)
164 {
165         EFI_UGA_PIXEL pix0, pix1;
166         uint8_t *data1, *data2;
167         size_t count, maxcount = 1024;
168         ssize_t ofs;
169         EFI_STATUS status;
170         u_int idx;
171
172         status = uga->Blt(uga, &pix0, EfiUgaVideoToBltBuffer,
173             0, line, 0, 0, 1, 1, 0);
174         if (EFI_ERROR(status)) {
175                 printf("UGA BLT operation failed (video->buffer)");
176                 return (-1);
177         }
178         pix1.Red = ~pix0.Red;
179         pix1.Green = ~pix0.Green;
180         pix1.Blue = ~pix0.Blue;
181         pix1.Reserved = 0;
182
183         data1 = calloc(maxcount, 2);
184         if (data1 == NULL) {
185                 printf("Unable to allocate memory");
186                 return (-1);
187         }
188         data2 = data1 + maxcount;
189
190         ofs = 0;
191         while (size > 0) {
192                 count = min(size, maxcount);
193
194                 status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
195                     EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
196                     data1);
197                 if (EFI_ERROR(status)) {
198                         printf("Error reading frame buffer (before)");
199                         goto fail;
200                 }
201                 status = uga->Blt(uga, &pix1, EfiUgaBltBufferToVideo,
202                     0, 0, 0, line, 1, 1, 0);
203                 if (EFI_ERROR(status)) {
204                         printf("UGA BLT operation failed (modify)");
205                         goto fail;
206                 }
207                 status = pciio->Mem.Read(pciio, EfiPciIoWidthUint32,
208                     EFI_PCI_IO_PASS_THROUGH_BAR, addr + ofs, count >> 2,
209                     data2);
210                 if (EFI_ERROR(status)) {
211                         printf("Error reading frame buffer (after)");
212                         goto fail;
213                 }
214                 status = uga->Blt(uga, &pix0, EfiUgaBltBufferToVideo,
215                     0, 0, 0, line, 1, 1, 0);
216                 if (EFI_ERROR(status)) {
217                         printf("UGA BLT operation failed (restore)");
218                         goto fail;
219                 }
220                 for (idx = 0; idx < count; idx++) {
221                         if (data1[idx] != data2[idx]) {
222                                 free(data1);
223                                 return (ofs + (idx & ~3));
224                         }
225                 }
226                 ofs += count;
227                 size -= count;
228         }
229         printf("No change detected in frame buffer");
230
231  fail:
232         printf(" -- error %lu\n", EFI_ERROR_CODE(status));
233         free(data1);
234         return (-1);
235 }
236
237 static EFI_PCI_IO_PROTOCOL *
238 efifb_uga_get_pciio(void)
239 {
240         EFI_PCI_IO_PROTOCOL *pciio;
241         EFI_HANDLE *buf, *hp;
242         EFI_STATUS status;
243         UINTN bufsz;
244
245         /* Get all handles that support the UGA protocol. */
246         bufsz = 0;
247         status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, NULL);
248         if (status != EFI_BUFFER_TOO_SMALL)
249                 return (NULL);
250         buf = malloc(bufsz);
251         status = BS->LocateHandle(ByProtocol, &uga_guid, NULL, &bufsz, buf);
252         if (status != EFI_SUCCESS) {
253                 free(buf);
254                 return (NULL);
255         }
256         bufsz /= sizeof(EFI_HANDLE);
257
258         /* Get the PCI I/O interface of the first handle that supports it. */
259         pciio = NULL;
260         for (hp = buf; hp < buf + bufsz; hp++) {
261                 status = OpenProtocolByHandle(*hp, &pciio_guid,
262                     (void **)&pciio);
263                 if (status == EFI_SUCCESS) {
264                         free(buf);
265                         return (pciio);
266                 }
267         }
268         free(buf);
269         return (NULL);
270 }
271
272 static EFI_STATUS
273 efifb_uga_locate_framebuffer(EFI_PCI_IO_PROTOCOL *pciio, uint64_t *addrp,
274     uint64_t *sizep)
275 {
276         uint8_t *resattr;
277         uint64_t addr, size;
278         EFI_STATUS status;
279         u_int bar;
280
281         if (pciio == NULL)
282                 return (EFI_DEVICE_ERROR);
283
284         /* Attempt to get the frame buffer address (imprecise). */
285         *addrp = 0;
286         *sizep = 0;
287         for (bar = 0; bar < 6; bar++) {
288                 status = pciio->GetBarAttributes(pciio, bar, NULL,
289                     (void **)&resattr);
290                 if (status != EFI_SUCCESS)
291                         continue;
292                 /* XXX magic offsets and constants. */
293                 if (resattr[0] == 0x87 && resattr[3] == 0) {
294                         /* 32-bit address space descriptor (MEMIO) */
295                         addr = le32dec(resattr + 10);
296                         size = le32dec(resattr + 22);
297                 } else if (resattr[0] == 0x8a && resattr[3] == 0) {
298                         /* 64-bit address space descriptor (MEMIO) */
299                         addr = le64dec(resattr + 14);
300                         size = le64dec(resattr + 38);
301                 } else {
302                         addr = 0;
303                         size = 0;
304                 }
305                 BS->FreePool(resattr);
306                 if (addr == 0 || size == 0)
307                         continue;
308
309                 /* We assume the largest BAR is the frame buffer. */
310                 if (size > *sizep) {
311                         *addrp = addr;
312                         *sizep = size;
313                 }
314         }
315         return ((*addrp == 0 || *sizep == 0) ? EFI_DEVICE_ERROR : 0);
316 }
317
318 static int
319 efifb_from_uga(struct efi_fb *efifb)
320 {
321         EFI_PCI_IO_PROTOCOL *pciio;
322         char *ev, *p;
323         EFI_STATUS status;
324         ssize_t offset;
325         uint64_t fbaddr;
326         uint32_t horiz, vert, stride;
327         uint32_t np, depth, refresh;
328
329         status = uga->GetMode(uga, &horiz, &vert, &depth, &refresh);
330         if (EFI_ERROR(status))
331                 return (1);
332         efifb->fb_height = vert;
333         efifb->fb_width = horiz;
334         /* Paranoia... */
335         if (efifb->fb_height == 0 || efifb->fb_width == 0)
336                 return (1);
337
338         /* The color masks are fixed AFAICT. */
339         efifb_mask_from_pixfmt(efifb, PixelBlueGreenRedReserved8BitPerColor,
340             NULL);
341
342         /* pciio can be NULL on return! */
343         pciio = efifb_uga_get_pciio();
344
345         /* Try to find the frame buffer. */
346         status = efifb_uga_locate_framebuffer(pciio, &efifb->fb_addr,
347             &efifb->fb_size);
348         if (EFI_ERROR(status)) {
349                 efifb->fb_addr = 0;
350                 efifb->fb_size = 0;
351         }
352
353         /*
354          * There's no reliable way to detect the frame buffer or the
355          * offset within the frame buffer of the visible region, nor
356          * the stride. Our only option is to look at the system and
357          * fill in the blanks based on that. Luckily, UGA was mostly
358          * only used on Apple hardware.
359          */
360         offset = -1;
361         ev = getenv("smbios.system.maker");
362         if (ev != NULL && !strcmp(ev, "Apple Inc.")) {
363                 ev = getenv("smbios.system.product");
364                 if (ev != NULL && !strcmp(ev, "iMac7,1")) {
365                         /* These are the expected values we should have. */
366                         horiz = 1680;
367                         vert = 1050;
368                         fbaddr = 0xc0000000;
369                         /* These are the missing bits. */
370                         offset = 0x10000;
371                         stride = 1728;
372                 } else if (ev != NULL && !strcmp(ev, "MacBook3,1")) {
373                         /* These are the expected values we should have. */
374                         horiz = 1280;
375                         vert = 800;
376                         fbaddr = 0xc0000000;
377                         /* These are the missing bits. */
378                         offset = 0x0;
379                         stride = 2048;
380                 }
381         }
382
383         /*
384          * If this is hardware we know, make sure that it looks familiar
385          * before we accept our hardcoded values.
386          */
387         if (offset >= 0 && efifb->fb_width == horiz &&
388             efifb->fb_height == vert && efifb->fb_addr == fbaddr) {
389                 efifb->fb_addr += offset;
390                 efifb->fb_size -= offset;
391                 efifb->fb_stride = stride;
392                 return (0);
393         } else if (offset >= 0) {
394                 printf("Hardware make/model known, but graphics not "
395                     "as expected.\n");
396                 printf("Console may not work!\n");
397         }
398
399         /*
400          * The stride is equal or larger to the width. Often it's the
401          * next larger power of two. We'll start with that...
402          */
403         efifb->fb_stride = efifb->fb_width;
404         do {
405                 np = efifb->fb_stride & (efifb->fb_stride - 1);
406                 if (np) {
407                         efifb->fb_stride |= (np - 1);
408                         efifb->fb_stride++;
409                 }
410         } while (np);
411
412         ev = getenv("hw.efifb.address");
413         if (ev == NULL) {
414                 if (efifb->fb_addr == 0) {
415                         printf("Please set hw.efifb.address and "
416                             "hw.efifb.stride.\n");
417                         return (1);
418                 }
419
420                 /*
421                  * The visible part of the frame buffer may not start at
422                  * offset 0, so try to detect it. Note that we may not
423                  * always be able to read from the frame buffer, which
424                  * means that we may not be able to detect anything. In
425                  * that case, we would take a long time scanning for a
426                  * pixel change in the frame buffer, which would have it
427                  * appear that we're hanging, so we limit the scan to
428                  * 1/256th of the frame buffer. This number is mostly
429                  * based on PR 202730 and the fact that on a MacBoook,
430                  * where we can't read from the frame buffer the offset
431                  * of the visible region is 0. In short: we want to scan
432                  * enough to handle all adapters that have an offset
433                  * larger than 0 and we want to scan as little as we can
434                  * to not appear to hang when we can't read from the
435                  * frame buffer.
436                  */
437                 offset = efifb_uga_find_pixel(uga, 0, pciio, efifb->fb_addr,
438                     efifb->fb_size >> 8);
439                 if (offset == -1) {
440                         printf("Unable to reliably detect frame buffer.\n");
441                 } else if (offset > 0) {
442                         efifb->fb_addr += offset;
443                         efifb->fb_size -= offset;
444                 }
445         } else {
446                 offset = 0;
447                 efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
448                 efifb->fb_addr = strtoul(ev, &p, 0);
449                 if (*p != '\0')
450                         return (1);
451         }
452
453         ev = getenv("hw.efifb.stride");
454         if (ev == NULL) {
455                 if (pciio != NULL && offset != -1) {
456                         /* Determine the stride. */
457                         offset = efifb_uga_find_pixel(uga, 1, pciio,
458                             efifb->fb_addr, horiz * 8);
459                         if (offset != -1)
460                                 efifb->fb_stride = offset >> 2;
461                 } else {
462                         printf("Unable to reliably detect the stride.\n");
463                 }
464         } else {
465                 efifb->fb_stride = strtoul(ev, &p, 0);
466                 if (*p != '\0')
467                         return (1);
468         }
469
470         /*
471          * We finalized on the stride, so recalculate the size of the
472          * frame buffer.
473          */
474         efifb->fb_size = efifb->fb_height * efifb->fb_stride * 4;
475         return (0);
476 }
477
478 /*
479  * Fetch EDID info. Caller must free the buffer.
480  */
481 static struct vesa_edid_info *
482 efifb_gop_get_edid(EFI_HANDLE h)
483 {
484         const uint8_t magic[] = EDID_MAGIC;
485         EFI_EDID_ACTIVE_PROTOCOL *edid;
486         struct vesa_edid_info *edid_infop;
487         EFI_GUID *guid;
488         EFI_STATUS status;
489         size_t size;
490
491         guid = &active_edid_guid;
492         status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL,
493             EFI_OPEN_PROTOCOL_GET_PROTOCOL);
494         if (status != EFI_SUCCESS ||
495             edid->SizeOfEdid == 0) {
496                 guid = &discovered_edid_guid;
497                 status = BS->OpenProtocol(h, guid, (void **)&edid, IH, NULL,
498                     EFI_OPEN_PROTOCOL_GET_PROTOCOL);
499                 if (status != EFI_SUCCESS ||
500                     edid->SizeOfEdid == 0)
501                         return (NULL);
502         }
503
504         size = MAX(sizeof(*edid_infop), edid->SizeOfEdid);
505
506         edid_infop = calloc(1, size);
507         if (edid_infop == NULL)
508                 return (NULL);
509
510         memcpy(edid_infop, edid->Edid, edid->SizeOfEdid);
511
512         /* Validate EDID */
513         if (memcmp(edid_infop, magic, sizeof (magic)) != 0)
514                 goto error;
515
516         if (edid_infop->header.version != 1)
517                 goto error;
518
519         return (edid_infop);
520 error:
521         free(edid_infop);
522         return (NULL);
523 }
524
525 static bool
526 efifb_get_edid(edid_res_list_t *res)
527 {
528         bool rv = false;
529
530         if (edid_info == NULL)
531                 edid_info = efifb_gop_get_edid(gop_handle);
532
533         if (edid_info != NULL)
534                 rv = gfx_get_edid_resolution(edid_info, res);
535
536         return (rv);
537 }
538
539 int
540 efi_find_framebuffer(teken_gfx_t *gfx_state)
541 {
542         EFI_HANDLE *hlist;
543         UINTN nhandles, i, hsize;
544         struct efi_fb efifb;
545         EFI_STATUS status;
546         int rv;
547
548         gfx_state->tg_fb_type = FB_TEXT;
549
550         hsize = 0;
551         hlist = NULL;
552         status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize, hlist);
553         if (status == EFI_BUFFER_TOO_SMALL) {
554                 hlist = malloc(hsize);
555                 if (hlist == NULL)
556                         return (ENOMEM);
557                 status = BS->LocateHandle(ByProtocol, &gop_guid, NULL, &hsize,
558                     hlist);
559                 if (EFI_ERROR(status))
560                         free(hlist);
561         }
562         if (EFI_ERROR(status))
563                 return (efi_status_to_errno(status));
564
565         nhandles = hsize / sizeof(*hlist);
566
567         /*
568          * Search for ConOut protocol, if not found, use first handle.
569          */
570         gop_handle = *hlist;
571         for (i = 0; i < nhandles; i++) {
572                 void *dummy = NULL;
573
574                 status = OpenProtocolByHandle(hlist[i], &conout_guid, &dummy);
575                 if (status == EFI_SUCCESS) {
576                         gop_handle = hlist[i];
577                         break;
578                 }
579         }
580
581         status = OpenProtocolByHandle(gop_handle, &gop_guid, (void **)&gop);
582         free(hlist);
583
584         if (status == EFI_SUCCESS) {
585                 gfx_state->tg_fb_type = FB_GOP;
586                 gfx_state->tg_private = gop;
587                 if (edid_info == NULL)
588                         edid_info = efifb_gop_get_edid(gop_handle);
589         } else {
590                 status = BS->LocateProtocol(&uga_guid, NULL, (VOID **)&uga);
591                 if (status == EFI_SUCCESS) {
592                         gfx_state->tg_fb_type = FB_UGA;
593                         gfx_state->tg_private = uga;
594                 } else {
595                         return (1);
596                 }
597         }
598
599         switch (gfx_state->tg_fb_type) {
600         case FB_GOP:
601                 rv = efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info);
602                 break;
603
604         case FB_UGA:
605                 rv = efifb_from_uga(&efifb);
606                 break;
607
608         default:
609                 return (1);
610         }
611
612         gfx_state->tg_fb.fb_addr = efifb.fb_addr;
613         gfx_state->tg_fb.fb_size = efifb.fb_size;
614         gfx_state->tg_fb.fb_height = efifb.fb_height;
615         gfx_state->tg_fb.fb_width = efifb.fb_width;
616         gfx_state->tg_fb.fb_stride = efifb.fb_stride;
617         gfx_state->tg_fb.fb_mask_red = efifb.fb_mask_red;
618         gfx_state->tg_fb.fb_mask_green = efifb.fb_mask_green;
619         gfx_state->tg_fb.fb_mask_blue = efifb.fb_mask_blue;
620         gfx_state->tg_fb.fb_mask_reserved = efifb.fb_mask_reserved;
621
622         gfx_state->tg_fb.fb_bpp = fls(efifb.fb_mask_red | efifb.fb_mask_green |
623             efifb.fb_mask_blue | efifb.fb_mask_reserved);
624
625         return (0);
626 }
627
628 static void
629 print_efifb(int mode, struct efi_fb *efifb, int verbose)
630 {
631         u_int depth;
632
633         if (mode >= 0)
634                 printf("mode %d: ", mode);
635         depth = efifb_color_depth(efifb);
636         printf("%ux%ux%u, stride=%u", efifb->fb_width, efifb->fb_height,
637             depth, efifb->fb_stride);
638         if (verbose) {
639                 printf("\n    frame buffer: address=%jx, size=%jx",
640                     (uintmax_t)efifb->fb_addr, (uintmax_t)efifb->fb_size);
641                 printf("\n    color mask: R=%08x, G=%08x, B=%08x\n",
642                     efifb->fb_mask_red, efifb->fb_mask_green,
643                     efifb->fb_mask_blue);
644         }
645 }
646
647 static bool
648 efi_resolution_compare(struct named_resolution *res, const char *cmp)
649 {
650
651         if (strcasecmp(res->name, cmp) == 0)
652                 return (true);
653         if (res->alias != NULL && strcasecmp(res->alias, cmp) == 0)
654                 return (true);
655         return (false);
656 }
657
658
659 static void
660 efi_get_max_resolution(int *width, int *height)
661 {
662         struct named_resolution *res;
663         char *maxres;
664         char *height_start, *width_start;
665         int idx;
666
667         *width = *height = 0;
668         maxres = getenv("efi_max_resolution");
669         /* No max_resolution set? Bail out; choose highest resolution */
670         if (maxres == NULL)
671                 return;
672         /* See if it matches one of our known resolutions */
673         for (idx = 0; idx < nitems(resolutions); ++idx) {
674                 res = &resolutions[idx];
675                 if (efi_resolution_compare(res, maxres)) {
676                         *width = res->width;
677                         *height = res->height;
678                         return;
679                 }
680         }
681         /* Not a known resolution, try to parse it; make a copy we can modify */
682         maxres = strdup(maxres);
683         if (maxres == NULL)
684                 return;
685         height_start = strchr(maxres, 'x');
686         if (height_start == NULL) {
687                 free(maxres);
688                 return;
689         }
690         width_start = maxres;
691         *height_start++ = 0;
692         /* Errors from this will effectively mean "no max" */
693         *width = (int)strtol(width_start, NULL, 0);
694         *height = (int)strtol(height_start, NULL, 0);
695         free(maxres);
696 }
697
698 static int
699 gop_autoresize(void)
700 {
701         struct efi_fb efifb;
702         EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
703         EFI_STATUS status;
704         UINTN infosz;
705         UINT32 best_mode, currdim, maxdim, mode;
706         int height, max_height, max_width, width;
707
708         best_mode = maxdim = 0;
709         efi_get_max_resolution(&max_width, &max_height);
710         for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
711                 status = gop->QueryMode(gop, mode, &infosz, &info);
712                 if (EFI_ERROR(status))
713                         continue;
714                 efifb_from_gop(&efifb, gop->Mode, info);
715                 width = info->HorizontalResolution;
716                 height = info->VerticalResolution;
717                 currdim = width * height;
718                 if (currdim > maxdim) {
719                         if ((max_width != 0 && width > max_width) ||
720                             (max_height != 0 && height > max_height))
721                                 continue;
722                         maxdim = currdim;
723                         best_mode = mode;
724                 }
725         }
726
727         if (maxdim != 0) {
728                 status = gop->SetMode(gop, best_mode);
729                 if (EFI_ERROR(status)) {
730                         snprintf(command_errbuf, sizeof(command_errbuf),
731                             "gop_autoresize: Unable to set mode to %u (error=%lu)",
732                             mode, EFI_ERROR_CODE(status));
733                         return (CMD_ERROR);
734                 }
735                 (void) cons_update_mode(true);
736         }
737         return (CMD_OK);
738 }
739
740 static int
741 text_autoresize()
742 {
743         SIMPLE_TEXT_OUTPUT_INTERFACE *conout;
744         EFI_STATUS status;
745         UINTN i, max_dim, best_mode, cols, rows;
746
747         conout = ST->ConOut;
748         max_dim = best_mode = 0;
749         for (i = 0; i < conout->Mode->MaxMode; i++) {
750                 status = conout->QueryMode(conout, i, &cols, &rows);
751                 if (EFI_ERROR(status))
752                         continue;
753                 if (cols * rows > max_dim) {
754                         max_dim = cols * rows;
755                         best_mode = i;
756                 }
757         }
758         if (max_dim > 0)
759                 conout->SetMode(conout, best_mode);
760         (void) cons_update_mode(true);
761         return (CMD_OK);
762 }
763
764 static int
765 uga_autoresize(void)
766 {
767
768         return (text_autoresize());
769 }
770
771 COMMAND_SET(efi_autoresize, "efi-autoresizecons", "EFI Auto-resize Console", command_autoresize);
772
773 static int
774 command_autoresize(int argc, char *argv[])
775 {
776         char *textmode;
777
778         textmode = getenv("hw.vga.textmode");
779         /* If it's set and non-zero, we'll select a console mode instead */
780         if (textmode != NULL && strcmp(textmode, "0") != 0)
781                 return (text_autoresize());
782
783         if (gop != NULL)
784                 return (gop_autoresize());
785
786         if (uga != NULL)
787                 return (uga_autoresize());
788
789         snprintf(command_errbuf, sizeof(command_errbuf),
790             "%s: Neither Graphics Output Protocol nor Universal Graphics Adapter present",
791             argv[0]);
792
793         /*
794          * Default to text_autoresize if we have neither GOP or UGA.  This won't
795          * give us the most ideal resolution, but it will at least leave us
796          * functional rather than failing the boot for an objectively bad
797          * reason.
798          */
799         return (text_autoresize());
800 }
801
802 COMMAND_SET(gop, "gop", "graphics output protocol", command_gop);
803
804 static int
805 command_gop(int argc, char *argv[])
806 {
807         struct efi_fb efifb;
808         EFI_STATUS status;
809         u_int mode;
810
811         if (gop == NULL) {
812                 snprintf(command_errbuf, sizeof(command_errbuf),
813                     "%s: Graphics Output Protocol not present", argv[0]);
814                 return (CMD_ERROR);
815         }
816
817         if (argc < 2)
818                 goto usage;
819
820         if (!strcmp(argv[1], "set")) {
821                 char *cp;
822
823                 if (argc != 3)
824                         goto usage;
825                 mode = strtol(argv[2], &cp, 0);
826                 if (cp[0] != '\0') {
827                         sprintf(command_errbuf, "mode is an integer");
828                         return (CMD_ERROR);
829                 }
830                 status = gop->SetMode(gop, mode);
831                 if (EFI_ERROR(status)) {
832                         snprintf(command_errbuf, sizeof(command_errbuf),
833                             "%s: Unable to set mode to %u (error=%lu)",
834                             argv[0], mode, EFI_ERROR_CODE(status));
835                         return (CMD_ERROR);
836                 }
837                 (void) cons_update_mode(true);
838         } else if (strcmp(argv[1], "off") == 0) {
839                 (void) cons_update_mode(false);
840         } else if (strcmp(argv[1], "get") == 0) {
841                 edid_res_list_t res;
842
843                 if (argc != 2)
844                         goto usage;
845                 TAILQ_INIT(&res);
846                 efifb_from_gop(&efifb, gop->Mode, gop->Mode->Info);
847                 if (efifb_get_edid(&res)) {
848                         struct resolution *rp;
849
850                         printf("EDID");
851                         while ((rp = TAILQ_FIRST(&res)) != NULL) {
852                                 printf(" %dx%d", rp->width, rp->height);
853                                 TAILQ_REMOVE(&res, rp, next);
854                                 free(rp);
855                         }
856                         printf("\n");
857                 } else {
858                         printf("no EDID information\n");
859                 }
860                 print_efifb(gop->Mode->Mode, &efifb, 1);
861                 printf("\n");
862         } else if (!strcmp(argv[1], "list")) {
863                 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
864                 UINTN infosz;
865
866                 if (argc != 2)
867                         goto usage;
868
869                 pager_open();
870                 for (mode = 0; mode < gop->Mode->MaxMode; mode++) {
871                         status = gop->QueryMode(gop, mode, &infosz, &info);
872                         if (EFI_ERROR(status))
873                                 continue;
874                         efifb_from_gop(&efifb, gop->Mode, info);
875                         print_efifb(mode, &efifb, 0);
876                         if (pager_output("\n"))
877                                 break;
878                 }
879                 pager_close();
880         }
881         return (CMD_OK);
882
883  usage:
884         snprintf(command_errbuf, sizeof(command_errbuf),
885             "usage: %s [list | get | set <mode> | off]", argv[0]);
886         return (CMD_ERROR);
887 }
888
889 COMMAND_SET(uga, "uga", "universal graphics adapter", command_uga);
890
891 static int
892 command_uga(int argc, char *argv[])
893 {
894         struct efi_fb efifb;
895
896         if (uga == NULL) {
897                 snprintf(command_errbuf, sizeof(command_errbuf),
898                     "%s: UGA Protocol not present", argv[0]);
899                 return (CMD_ERROR);
900         }
901
902         if (argc != 1)
903                 goto usage;
904
905         if (efifb_from_uga(&efifb) != CMD_OK) {
906                 snprintf(command_errbuf, sizeof(command_errbuf),
907                     "%s: Unable to get UGA information", argv[0]);
908                 return (CMD_ERROR);
909         }
910
911         print_efifb(-1, &efifb, 1);
912         printf("\n");
913         return (CMD_OK);
914
915  usage:
916         snprintf(command_errbuf, sizeof(command_errbuf), "usage: %s", argv[0]);
917         return (CMD_ERROR);
918 }