xref: /openbmc/linux/drivers/firmware/efi/libstub/gop.c (revision d9ff0323)
14febfb8dSArd Biesheuvel // SPDX-License-Identifier: GPL-2.0
2fc372064SArd Biesheuvel /* -----------------------------------------------------------------------
3fc372064SArd Biesheuvel  *
4fc372064SArd Biesheuvel  *   Copyright 2011 Intel Corporation; author Matt Fleming
5fc372064SArd Biesheuvel  *
6fc372064SArd Biesheuvel  * ----------------------------------------------------------------------- */
7fc372064SArd Biesheuvel 
89867fc9dSArvind Sankar #include <linux/bitops.h>
9d9ff0323SArvind Sankar #include <linux/ctype.h>
10fc372064SArd Biesheuvel #include <linux/efi.h>
11fc372064SArd Biesheuvel #include <linux/screen_info.h>
12fffb6804SArvind Sankar #include <linux/string.h>
13fc372064SArd Biesheuvel #include <asm/efi.h>
14fc372064SArd Biesheuvel #include <asm/setup.h>
15fc372064SArd Biesheuvel 
162fcdad2aSArd Biesheuvel #include "efistub.h"
172fcdad2aSArd Biesheuvel 
18fffb6804SArvind Sankar enum efi_cmdline_option {
19fffb6804SArvind Sankar 	EFI_CMDLINE_NONE,
20fffb6804SArvind Sankar 	EFI_CMDLINE_MODE_NUM,
21d9ff0323SArvind Sankar 	EFI_CMDLINE_RES
22fffb6804SArvind Sankar };
23fffb6804SArvind Sankar 
24fffb6804SArvind Sankar static struct {
25fffb6804SArvind Sankar 	enum efi_cmdline_option option;
26d9ff0323SArvind Sankar 	union {
27fffb6804SArvind Sankar 		u32 mode;
28d9ff0323SArvind Sankar 		struct {
29d9ff0323SArvind Sankar 			u32 width, height;
30d9ff0323SArvind Sankar 		} res;
31d9ff0323SArvind Sankar 	};
32fffb6804SArvind Sankar } cmdline __efistub_global = { .option = EFI_CMDLINE_NONE };
33fffb6804SArvind Sankar 
34fffb6804SArvind Sankar static bool parse_modenum(char *option, char **next)
35fffb6804SArvind Sankar {
36fffb6804SArvind Sankar 	u32 m;
37fffb6804SArvind Sankar 
38fffb6804SArvind Sankar 	if (!strstarts(option, "mode="))
39fffb6804SArvind Sankar 		return false;
40fffb6804SArvind Sankar 	option += strlen("mode=");
41fffb6804SArvind Sankar 	m = simple_strtoull(option, &option, 0);
42fffb6804SArvind Sankar 	if (*option && *option++ != ',')
43fffb6804SArvind Sankar 		return false;
44fffb6804SArvind Sankar 	cmdline.option = EFI_CMDLINE_MODE_NUM;
45fffb6804SArvind Sankar 	cmdline.mode   = m;
46fffb6804SArvind Sankar 
47fffb6804SArvind Sankar 	*next = option;
48fffb6804SArvind Sankar 	return true;
49fffb6804SArvind Sankar }
50fffb6804SArvind Sankar 
51d9ff0323SArvind Sankar static bool parse_res(char *option, char **next)
52d9ff0323SArvind Sankar {
53d9ff0323SArvind Sankar 	u32 w, h;
54d9ff0323SArvind Sankar 
55d9ff0323SArvind Sankar 	if (!isdigit(*option))
56d9ff0323SArvind Sankar 		return false;
57d9ff0323SArvind Sankar 	w = simple_strtoull(option, &option, 10);
58d9ff0323SArvind Sankar 	if (*option++ != 'x' || !isdigit(*option))
59d9ff0323SArvind Sankar 		return false;
60d9ff0323SArvind Sankar 	h = simple_strtoull(option, &option, 10);
61d9ff0323SArvind Sankar 	if (*option && *option++ != ',')
62d9ff0323SArvind Sankar 		return false;
63d9ff0323SArvind Sankar 	cmdline.option     = EFI_CMDLINE_RES;
64d9ff0323SArvind Sankar 	cmdline.res.width  = w;
65d9ff0323SArvind Sankar 	cmdline.res.height = h;
66d9ff0323SArvind Sankar 
67d9ff0323SArvind Sankar 	*next = option;
68d9ff0323SArvind Sankar 	return true;
69d9ff0323SArvind Sankar }
70d9ff0323SArvind Sankar 
71fffb6804SArvind Sankar void efi_parse_option_graphics(char *option)
72fffb6804SArvind Sankar {
73fffb6804SArvind Sankar 	while (*option) {
74fffb6804SArvind Sankar 		if (parse_modenum(option, &option))
75fffb6804SArvind Sankar 			continue;
76d9ff0323SArvind Sankar 		if (parse_res(option, &option))
77d9ff0323SArvind Sankar 			continue;
78fffb6804SArvind Sankar 
79fffb6804SArvind Sankar 		while (*option && *option++ != ',')
80fffb6804SArvind Sankar 			;
81fffb6804SArvind Sankar 	}
82fffb6804SArvind Sankar }
83fffb6804SArvind Sankar 
84fffb6804SArvind Sankar static u32 choose_mode_modenum(efi_graphics_output_protocol_t *gop)
85fffb6804SArvind Sankar {
86fffb6804SArvind Sankar 	efi_status_t status;
87fffb6804SArvind Sankar 
88fffb6804SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
89fffb6804SArvind Sankar 	efi_graphics_output_mode_info_t *info;
90fffb6804SArvind Sankar 	unsigned long info_size;
91fffb6804SArvind Sankar 
92fffb6804SArvind Sankar 	u32 max_mode, cur_mode;
93fffb6804SArvind Sankar 	int pf;
94fffb6804SArvind Sankar 
95fffb6804SArvind Sankar 	mode = efi_table_attr(gop, mode);
96fffb6804SArvind Sankar 
97fffb6804SArvind Sankar 	cur_mode = efi_table_attr(mode, mode);
98fffb6804SArvind Sankar 	if (cmdline.mode == cur_mode)
99fffb6804SArvind Sankar 		return cur_mode;
100fffb6804SArvind Sankar 
101fffb6804SArvind Sankar 	max_mode = efi_table_attr(mode, max_mode);
102fffb6804SArvind Sankar 	if (cmdline.mode >= max_mode) {
103fffb6804SArvind Sankar 		efi_printk("Requested mode is invalid\n");
104fffb6804SArvind Sankar 		return cur_mode;
105fffb6804SArvind Sankar 	}
106fffb6804SArvind Sankar 
107fffb6804SArvind Sankar 	status = efi_call_proto(gop, query_mode, cmdline.mode,
108fffb6804SArvind Sankar 				&info_size, &info);
109fffb6804SArvind Sankar 	if (status != EFI_SUCCESS) {
110fffb6804SArvind Sankar 		efi_printk("Couldn't get mode information\n");
111fffb6804SArvind Sankar 		return cur_mode;
112fffb6804SArvind Sankar 	}
113fffb6804SArvind Sankar 
114fffb6804SArvind Sankar 	pf = info->pixel_format;
115fffb6804SArvind Sankar 
116fffb6804SArvind Sankar 	efi_bs_call(free_pool, info);
117fffb6804SArvind Sankar 
118fffb6804SArvind Sankar 	if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX) {
119fffb6804SArvind Sankar 		efi_printk("Invalid PixelFormat\n");
120fffb6804SArvind Sankar 		return cur_mode;
121fffb6804SArvind Sankar 	}
122fffb6804SArvind Sankar 
123fffb6804SArvind Sankar 	return cmdline.mode;
124fffb6804SArvind Sankar }
125fffb6804SArvind Sankar 
126d9ff0323SArvind Sankar static u32 choose_mode_res(efi_graphics_output_protocol_t *gop)
127d9ff0323SArvind Sankar {
128d9ff0323SArvind Sankar 	efi_status_t status;
129d9ff0323SArvind Sankar 
130d9ff0323SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
131d9ff0323SArvind Sankar 	efi_graphics_output_mode_info_t *info;
132d9ff0323SArvind Sankar 	unsigned long info_size;
133d9ff0323SArvind Sankar 
134d9ff0323SArvind Sankar 	u32 max_mode, cur_mode;
135d9ff0323SArvind Sankar 	int pf;
136d9ff0323SArvind Sankar 	u32 m, w, h;
137d9ff0323SArvind Sankar 
138d9ff0323SArvind Sankar 	mode = efi_table_attr(gop, mode);
139d9ff0323SArvind Sankar 
140d9ff0323SArvind Sankar 	cur_mode = efi_table_attr(mode, mode);
141d9ff0323SArvind Sankar 	info = efi_table_attr(mode, info);
142d9ff0323SArvind Sankar 	w = info->horizontal_resolution;
143d9ff0323SArvind Sankar 	h = info->vertical_resolution;
144d9ff0323SArvind Sankar 
145d9ff0323SArvind Sankar 	if (w == cmdline.res.width && h == cmdline.res.height)
146d9ff0323SArvind Sankar 		return cur_mode;
147d9ff0323SArvind Sankar 
148d9ff0323SArvind Sankar 	max_mode = efi_table_attr(mode, max_mode);
149d9ff0323SArvind Sankar 
150d9ff0323SArvind Sankar 	for (m = 0; m < max_mode; m++) {
151d9ff0323SArvind Sankar 		if (m == cur_mode)
152d9ff0323SArvind Sankar 			continue;
153d9ff0323SArvind Sankar 
154d9ff0323SArvind Sankar 		status = efi_call_proto(gop, query_mode, m,
155d9ff0323SArvind Sankar 					&info_size, &info);
156d9ff0323SArvind Sankar 		if (status != EFI_SUCCESS)
157d9ff0323SArvind Sankar 			continue;
158d9ff0323SArvind Sankar 
159d9ff0323SArvind Sankar 		pf = info->pixel_format;
160d9ff0323SArvind Sankar 		w  = info->horizontal_resolution;
161d9ff0323SArvind Sankar 		h  = info->vertical_resolution;
162d9ff0323SArvind Sankar 
163d9ff0323SArvind Sankar 		efi_bs_call(free_pool, info);
164d9ff0323SArvind Sankar 
165d9ff0323SArvind Sankar 		if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX)
166d9ff0323SArvind Sankar 			continue;
167d9ff0323SArvind Sankar 		if (w == cmdline.res.width && h == cmdline.res.height)
168d9ff0323SArvind Sankar 			return m;
169d9ff0323SArvind Sankar 	}
170d9ff0323SArvind Sankar 
171d9ff0323SArvind Sankar 	efi_printk("Couldn't find requested mode\n");
172d9ff0323SArvind Sankar 
173d9ff0323SArvind Sankar 	return cur_mode;
174d9ff0323SArvind Sankar }
175d9ff0323SArvind Sankar 
176fffb6804SArvind Sankar static void set_mode(efi_graphics_output_protocol_t *gop)
177fffb6804SArvind Sankar {
178fffb6804SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
179fffb6804SArvind Sankar 	u32 cur_mode, new_mode;
180fffb6804SArvind Sankar 
181fffb6804SArvind Sankar 	switch (cmdline.option) {
182fffb6804SArvind Sankar 	case EFI_CMDLINE_MODE_NUM:
183fffb6804SArvind Sankar 		new_mode = choose_mode_modenum(gop);
184fffb6804SArvind Sankar 		break;
185d9ff0323SArvind Sankar 	case EFI_CMDLINE_RES:
186d9ff0323SArvind Sankar 		new_mode = choose_mode_res(gop);
187d9ff0323SArvind Sankar 		break;
188fffb6804SArvind Sankar 	default:
189fffb6804SArvind Sankar 		return;
190fffb6804SArvind Sankar 	}
191fffb6804SArvind Sankar 
192fffb6804SArvind Sankar 	mode = efi_table_attr(gop, mode);
193fffb6804SArvind Sankar 	cur_mode = efi_table_attr(mode, mode);
194fffb6804SArvind Sankar 
195fffb6804SArvind Sankar 	if (new_mode == cur_mode)
196fffb6804SArvind Sankar 		return;
197fffb6804SArvind Sankar 
198fffb6804SArvind Sankar 	if (efi_call_proto(gop, set_mode, new_mode) != EFI_SUCCESS)
199fffb6804SArvind Sankar 		efi_printk("Failed to set requested mode\n");
200fffb6804SArvind Sankar }
201fffb6804SArvind Sankar 
2029867fc9dSArvind Sankar static void find_bits(u32 mask, u8 *pos, u8 *size)
203fc372064SArd Biesheuvel {
2049867fc9dSArvind Sankar 	if (!mask) {
2059867fc9dSArvind Sankar 		*pos = *size = 0;
2069867fc9dSArvind Sankar 		return;
207fc372064SArd Biesheuvel 	}
208fc372064SArd Biesheuvel 
2099867fc9dSArvind Sankar 	/* UEFI spec guarantees that the set bits are contiguous */
2109867fc9dSArvind Sankar 	*pos  = __ffs(mask);
2119867fc9dSArvind Sankar 	*size = __fls(mask) - *pos + 1;
212fc372064SArd Biesheuvel }
213fc372064SArd Biesheuvel 
214fc372064SArd Biesheuvel static void
215fc372064SArd Biesheuvel setup_pixel_info(struct screen_info *si, u32 pixels_per_scan_line,
21644c84b4aSArvind Sankar 		 efi_pixel_bitmask_t pixel_info, int pixel_format)
217fc372064SArd Biesheuvel {
218d49fd4bbSArvind Sankar 	if (pixel_format == PIXEL_BIT_MASK) {
219d49fd4bbSArvind Sankar 		find_bits(pixel_info.red_mask,
220d49fd4bbSArvind Sankar 			  &si->red_pos, &si->red_size);
221d49fd4bbSArvind Sankar 		find_bits(pixel_info.green_mask,
222d49fd4bbSArvind Sankar 			  &si->green_pos, &si->green_size);
223d49fd4bbSArvind Sankar 		find_bits(pixel_info.blue_mask,
224d49fd4bbSArvind Sankar 			  &si->blue_pos, &si->blue_size);
225d49fd4bbSArvind Sankar 		find_bits(pixel_info.reserved_mask,
226d49fd4bbSArvind Sankar 			  &si->rsvd_pos, &si->rsvd_size);
227fc372064SArd Biesheuvel 		si->lfb_depth = si->red_size + si->green_size +
228fc372064SArd Biesheuvel 			si->blue_size + si->rsvd_size;
229fc372064SArd Biesheuvel 		si->lfb_linelength = (pixels_per_scan_line * si->lfb_depth) / 8;
230fc372064SArd Biesheuvel 	} else {
231d49fd4bbSArvind Sankar 		if (pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) {
232fc372064SArd Biesheuvel 			si->red_pos   = 0;
233d49fd4bbSArvind Sankar 			si->blue_pos  = 16;
234d49fd4bbSArvind Sankar 		} else /* PIXEL_BGR_RESERVED_8BIT_PER_COLOR */ {
235fc372064SArd Biesheuvel 			si->blue_pos  = 0;
236d49fd4bbSArvind Sankar 			si->red_pos   = 16;
237d49fd4bbSArvind Sankar 		}
238d49fd4bbSArvind Sankar 
239d49fd4bbSArvind Sankar 		si->green_pos = 8;
240d49fd4bbSArvind Sankar 		si->rsvd_pos  = 24;
241d49fd4bbSArvind Sankar 		si->red_size = si->green_size =
242d49fd4bbSArvind Sankar 			si->blue_size = si->rsvd_size = 8;
243d49fd4bbSArvind Sankar 
244d49fd4bbSArvind Sankar 		si->lfb_depth = 32;
245d49fd4bbSArvind Sankar 		si->lfb_linelength = pixels_per_scan_line * 4;
246fc372064SArd Biesheuvel 	}
247fc372064SArd Biesheuvel }
248fc372064SArd Biesheuvel 
249ecf53091SArvind Sankar static efi_graphics_output_protocol_t *
250ecf53091SArvind Sankar find_gop(efi_guid_t *proto, unsigned long size, void **handles)
251fc372064SArd Biesheuvel {
252e484c594SArvind Sankar 	efi_graphics_output_protocol_t *first_gop;
2532732ea0dSArd Biesheuvel 	efi_handle_t h;
254fc372064SArd Biesheuvel 	int i;
255fc372064SArd Biesheuvel 
256fc372064SArd Biesheuvel 	first_gop = NULL;
257fc372064SArd Biesheuvel 
2582732ea0dSArd Biesheuvel 	for_each_efi_handle(h, handles, size, i) {
259e484c594SArvind Sankar 		efi_status_t status;
260e484c594SArvind Sankar 
261e484c594SArvind Sankar 		efi_graphics_output_protocol_t *gop;
262e484c594SArvind Sankar 		efi_graphics_output_protocol_mode_t *mode;
263e484c594SArvind Sankar 		efi_graphics_output_mode_info_t *info;
264e484c594SArvind Sankar 
265fc372064SArd Biesheuvel 		efi_guid_t conout_proto = EFI_CONSOLE_OUT_DEVICE_GUID;
266fc372064SArd Biesheuvel 		void *dummy = NULL;
267fc372064SArd Biesheuvel 
268966291f6SArd Biesheuvel 		status = efi_bs_call(handle_protocol, h, proto, (void **)&gop);
269fc372064SArd Biesheuvel 		if (status != EFI_SUCCESS)
270fc372064SArd Biesheuvel 			continue;
271fc372064SArd Biesheuvel 
2728cd20797SArvind Sankar 		mode = efi_table_attr(gop, mode);
2738cd20797SArvind Sankar 		info = efi_table_attr(mode, info);
274d49fd4bbSArvind Sankar 		if (info->pixel_format == PIXEL_BLT_ONLY ||
275d49fd4bbSArvind Sankar 		    info->pixel_format >= PIXEL_FORMAT_MAX)
2768cd20797SArvind Sankar 			continue;
2778cd20797SArvind Sankar 
278fc372064SArd Biesheuvel 		/*
279fc372064SArd Biesheuvel 		 * Systems that use the UEFI Console Splitter may
280fc372064SArd Biesheuvel 		 * provide multiple GOP devices, not all of which are
281fc372064SArd Biesheuvel 		 * backed by real hardware. The workaround is to search
282fc372064SArd Biesheuvel 		 * for a GOP implementing the ConOut protocol, and if
283fc372064SArd Biesheuvel 		 * one isn't found, to just fall back to the first GOP.
2846327e6d0SArvind Sankar 		 *
285fc372064SArd Biesheuvel 		 * Once we've found a GOP supporting ConOut,
286fc372064SArd Biesheuvel 		 * don't bother looking any further.
287fc372064SArd Biesheuvel 		 */
2888e0a22e2SArvind Sankar 		status = efi_bs_call(handle_protocol, h, &conout_proto, &dummy);
2898e0a22e2SArvind Sankar 		if (status == EFI_SUCCESS)
2908e0a22e2SArvind Sankar 			return gop;
2918e0a22e2SArvind Sankar 
2928e0a22e2SArvind Sankar 		if (!first_gop)
2938de8788dSArvind Sankar 			first_gop = gop;
294fc372064SArd Biesheuvel 	}
295fc372064SArd Biesheuvel 
296ecf53091SArvind Sankar 	return first_gop;
297ecf53091SArvind Sankar }
298ecf53091SArvind Sankar 
299ecf53091SArvind Sankar static efi_status_t setup_gop(struct screen_info *si, efi_guid_t *proto,
300ecf53091SArvind Sankar 			      unsigned long size, void **handles)
301ecf53091SArvind Sankar {
302ecf53091SArvind Sankar 	efi_graphics_output_protocol_t *gop;
303ecf53091SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
304e484c594SArvind Sankar 	efi_graphics_output_mode_info_t *info;
305ecf53091SArvind Sankar 	efi_physical_addr_t fb_base;
306ecf53091SArvind Sankar 
307ecf53091SArvind Sankar 	gop = find_gop(proto, size, handles);
308ecf53091SArvind Sankar 
309fc372064SArd Biesheuvel 	/* Did we find any GOPs? */
310ecf53091SArvind Sankar 	if (!gop)
3116fc3cec3SArvind Sankar 		return EFI_NOT_FOUND;
312fc372064SArd Biesheuvel 
313fffb6804SArvind Sankar 	/* Change mode if requested */
314fffb6804SArvind Sankar 	set_mode(gop);
315fffb6804SArvind Sankar 
316fc372064SArd Biesheuvel 	/* EFI framebuffer */
317ecf53091SArvind Sankar 	mode = efi_table_attr(gop, mode);
3186327e6d0SArvind Sankar 	info = efi_table_attr(mode, info);
3196327e6d0SArvind Sankar 
320fc372064SArd Biesheuvel 	si->orig_video_isVGA = VIDEO_TYPE_EFI;
321fc372064SArd Biesheuvel 
3226327e6d0SArvind Sankar 	si->lfb_width  = info->horizontal_resolution;
3236327e6d0SArvind Sankar 	si->lfb_height = info->vertical_resolution;
324fc372064SArd Biesheuvel 
3256327e6d0SArvind Sankar 	fb_base		 = efi_table_attr(mode, frame_buffer_base);
326f1d1853bSArvind Sankar 	si->lfb_base	 = lower_32_bits(fb_base);
327f1d1853bSArvind Sankar 	si->ext_lfb_base = upper_32_bits(fb_base);
3286327e6d0SArvind Sankar 	if (si->ext_lfb_base)
329fc372064SArd Biesheuvel 		si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE;
330fc372064SArd Biesheuvel 
331fc372064SArd Biesheuvel 	si->pages = 1;
332fc372064SArd Biesheuvel 
3336327e6d0SArvind Sankar 	setup_pixel_info(si, info->pixels_per_scan_line,
3346327e6d0SArvind Sankar 			     info->pixel_information, info->pixel_format);
335fc372064SArd Biesheuvel 
336fc372064SArd Biesheuvel 	si->lfb_size = si->lfb_linelength * si->lfb_height;
337fc372064SArd Biesheuvel 
338fc372064SArd Biesheuvel 	si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS;
3396fc3cec3SArvind Sankar 
340dbd89c30SArvind Sankar 	return EFI_SUCCESS;
341fc372064SArd Biesheuvel }
342fc372064SArd Biesheuvel 
343fc372064SArd Biesheuvel /*
344fc372064SArd Biesheuvel  * See if we have Graphics Output Protocol
345fc372064SArd Biesheuvel  */
346cd33a5c1SArd Biesheuvel efi_status_t efi_setup_gop(struct screen_info *si, efi_guid_t *proto,
347fc372064SArd Biesheuvel 			   unsigned long size)
348fc372064SArd Biesheuvel {
349fc372064SArd Biesheuvel 	efi_status_t status;
350fc372064SArd Biesheuvel 	void **gop_handle = NULL;
351fc372064SArd Biesheuvel 
352966291f6SArd Biesheuvel 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, size,
353966291f6SArd Biesheuvel 			     (void **)&gop_handle);
354fc372064SArd Biesheuvel 	if (status != EFI_SUCCESS)
355fc372064SArd Biesheuvel 		return status;
356fc372064SArd Biesheuvel 
357966291f6SArd Biesheuvel 	status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, proto, NULL,
358966291f6SArd Biesheuvel 			     &size, gop_handle);
359fc372064SArd Biesheuvel 	if (status != EFI_SUCCESS)
360fc372064SArd Biesheuvel 		goto free_handle;
361fc372064SArd Biesheuvel 
362cd33a5c1SArd Biesheuvel 	status = setup_gop(si, proto, size, gop_handle);
363fc372064SArd Biesheuvel 
364fc372064SArd Biesheuvel free_handle:
365966291f6SArd Biesheuvel 	efi_bs_call(free_pool, gop_handle);
366fc372064SArd Biesheuvel 	return status;
367fc372064SArd Biesheuvel }
368