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