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