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