xref: /openbmc/linux/drivers/firmware/efi/libstub/gop.c (revision fffb6804)
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>
9fc372064SArd Biesheuvel #include <linux/efi.h>
10fc372064SArd Biesheuvel #include <linux/screen_info.h>
11fffb6804SArvind Sankar #include <linux/string.h>
12fc372064SArd Biesheuvel #include <asm/efi.h>
13fc372064SArd Biesheuvel #include <asm/setup.h>
14fc372064SArd Biesheuvel 
152fcdad2aSArd Biesheuvel #include "efistub.h"
162fcdad2aSArd Biesheuvel 
17fffb6804SArvind Sankar enum efi_cmdline_option {
18fffb6804SArvind Sankar 	EFI_CMDLINE_NONE,
19fffb6804SArvind Sankar 	EFI_CMDLINE_MODE_NUM,
20fffb6804SArvind Sankar };
21fffb6804SArvind Sankar 
22fffb6804SArvind Sankar static struct {
23fffb6804SArvind Sankar 	enum efi_cmdline_option option;
24fffb6804SArvind Sankar 	u32 mode;
25fffb6804SArvind Sankar } cmdline __efistub_global = { .option = EFI_CMDLINE_NONE };
26fffb6804SArvind Sankar 
27fffb6804SArvind Sankar static bool parse_modenum(char *option, char **next)
28fffb6804SArvind Sankar {
29fffb6804SArvind Sankar 	u32 m;
30fffb6804SArvind Sankar 
31fffb6804SArvind Sankar 	if (!strstarts(option, "mode="))
32fffb6804SArvind Sankar 		return false;
33fffb6804SArvind Sankar 	option += strlen("mode=");
34fffb6804SArvind Sankar 	m = simple_strtoull(option, &option, 0);
35fffb6804SArvind Sankar 	if (*option && *option++ != ',')
36fffb6804SArvind Sankar 		return false;
37fffb6804SArvind Sankar 	cmdline.option = EFI_CMDLINE_MODE_NUM;
38fffb6804SArvind Sankar 	cmdline.mode   = m;
39fffb6804SArvind Sankar 
40fffb6804SArvind Sankar 	*next = option;
41fffb6804SArvind Sankar 	return true;
42fffb6804SArvind Sankar }
43fffb6804SArvind Sankar 
44fffb6804SArvind Sankar void efi_parse_option_graphics(char *option)
45fffb6804SArvind Sankar {
46fffb6804SArvind Sankar 	while (*option) {
47fffb6804SArvind Sankar 		if (parse_modenum(option, &option))
48fffb6804SArvind Sankar 			continue;
49fffb6804SArvind Sankar 
50fffb6804SArvind Sankar 		while (*option && *option++ != ',')
51fffb6804SArvind Sankar 			;
52fffb6804SArvind Sankar 	}
53fffb6804SArvind Sankar }
54fffb6804SArvind Sankar 
55fffb6804SArvind Sankar static u32 choose_mode_modenum(efi_graphics_output_protocol_t *gop)
56fffb6804SArvind Sankar {
57fffb6804SArvind Sankar 	efi_status_t status;
58fffb6804SArvind Sankar 
59fffb6804SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
60fffb6804SArvind Sankar 	efi_graphics_output_mode_info_t *info;
61fffb6804SArvind Sankar 	unsigned long info_size;
62fffb6804SArvind Sankar 
63fffb6804SArvind Sankar 	u32 max_mode, cur_mode;
64fffb6804SArvind Sankar 	int pf;
65fffb6804SArvind Sankar 
66fffb6804SArvind Sankar 	mode = efi_table_attr(gop, mode);
67fffb6804SArvind Sankar 
68fffb6804SArvind Sankar 	cur_mode = efi_table_attr(mode, mode);
69fffb6804SArvind Sankar 	if (cmdline.mode == cur_mode)
70fffb6804SArvind Sankar 		return cur_mode;
71fffb6804SArvind Sankar 
72fffb6804SArvind Sankar 	max_mode = efi_table_attr(mode, max_mode);
73fffb6804SArvind Sankar 	if (cmdline.mode >= max_mode) {
74fffb6804SArvind Sankar 		efi_printk("Requested mode is invalid\n");
75fffb6804SArvind Sankar 		return cur_mode;
76fffb6804SArvind Sankar 	}
77fffb6804SArvind Sankar 
78fffb6804SArvind Sankar 	status = efi_call_proto(gop, query_mode, cmdline.mode,
79fffb6804SArvind Sankar 				&info_size, &info);
80fffb6804SArvind Sankar 	if (status != EFI_SUCCESS) {
81fffb6804SArvind Sankar 		efi_printk("Couldn't get mode information\n");
82fffb6804SArvind Sankar 		return cur_mode;
83fffb6804SArvind Sankar 	}
84fffb6804SArvind Sankar 
85fffb6804SArvind Sankar 	pf = info->pixel_format;
86fffb6804SArvind Sankar 
87fffb6804SArvind Sankar 	efi_bs_call(free_pool, info);
88fffb6804SArvind Sankar 
89fffb6804SArvind Sankar 	if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX) {
90fffb6804SArvind Sankar 		efi_printk("Invalid PixelFormat\n");
91fffb6804SArvind Sankar 		return cur_mode;
92fffb6804SArvind Sankar 	}
93fffb6804SArvind Sankar 
94fffb6804SArvind Sankar 	return cmdline.mode;
95fffb6804SArvind Sankar }
96fffb6804SArvind Sankar 
97fffb6804SArvind Sankar static void set_mode(efi_graphics_output_protocol_t *gop)
98fffb6804SArvind Sankar {
99fffb6804SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
100fffb6804SArvind Sankar 	u32 cur_mode, new_mode;
101fffb6804SArvind Sankar 
102fffb6804SArvind Sankar 	switch (cmdline.option) {
103fffb6804SArvind Sankar 	case EFI_CMDLINE_MODE_NUM:
104fffb6804SArvind Sankar 		new_mode = choose_mode_modenum(gop);
105fffb6804SArvind Sankar 		break;
106fffb6804SArvind Sankar 	default:
107fffb6804SArvind Sankar 		return;
108fffb6804SArvind Sankar 	}
109fffb6804SArvind Sankar 
110fffb6804SArvind Sankar 	mode = efi_table_attr(gop, mode);
111fffb6804SArvind Sankar 	cur_mode = efi_table_attr(mode, mode);
112fffb6804SArvind Sankar 
113fffb6804SArvind Sankar 	if (new_mode == cur_mode)
114fffb6804SArvind Sankar 		return;
115fffb6804SArvind Sankar 
116fffb6804SArvind Sankar 	if (efi_call_proto(gop, set_mode, new_mode) != EFI_SUCCESS)
117fffb6804SArvind Sankar 		efi_printk("Failed to set requested mode\n");
118fffb6804SArvind Sankar }
119fffb6804SArvind Sankar 
1209867fc9dSArvind Sankar static void find_bits(u32 mask, u8 *pos, u8 *size)
121fc372064SArd Biesheuvel {
1229867fc9dSArvind Sankar 	if (!mask) {
1239867fc9dSArvind Sankar 		*pos = *size = 0;
1249867fc9dSArvind Sankar 		return;
125fc372064SArd Biesheuvel 	}
126fc372064SArd Biesheuvel 
1279867fc9dSArvind Sankar 	/* UEFI spec guarantees that the set bits are contiguous */
1289867fc9dSArvind Sankar 	*pos  = __ffs(mask);
1299867fc9dSArvind Sankar 	*size = __fls(mask) - *pos + 1;
130fc372064SArd Biesheuvel }
131fc372064SArd Biesheuvel 
132fc372064SArd Biesheuvel static void
133fc372064SArd Biesheuvel setup_pixel_info(struct screen_info *si, u32 pixels_per_scan_line,
13444c84b4aSArvind Sankar 		 efi_pixel_bitmask_t pixel_info, int pixel_format)
135fc372064SArd Biesheuvel {
136d49fd4bbSArvind Sankar 	if (pixel_format == PIXEL_BIT_MASK) {
137d49fd4bbSArvind Sankar 		find_bits(pixel_info.red_mask,
138d49fd4bbSArvind Sankar 			  &si->red_pos, &si->red_size);
139d49fd4bbSArvind Sankar 		find_bits(pixel_info.green_mask,
140d49fd4bbSArvind Sankar 			  &si->green_pos, &si->green_size);
141d49fd4bbSArvind Sankar 		find_bits(pixel_info.blue_mask,
142d49fd4bbSArvind Sankar 			  &si->blue_pos, &si->blue_size);
143d49fd4bbSArvind Sankar 		find_bits(pixel_info.reserved_mask,
144d49fd4bbSArvind Sankar 			  &si->rsvd_pos, &si->rsvd_size);
145fc372064SArd Biesheuvel 		si->lfb_depth = si->red_size + si->green_size +
146fc372064SArd Biesheuvel 			si->blue_size + si->rsvd_size;
147fc372064SArd Biesheuvel 		si->lfb_linelength = (pixels_per_scan_line * si->lfb_depth) / 8;
148fc372064SArd Biesheuvel 	} else {
149d49fd4bbSArvind Sankar 		if (pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) {
150fc372064SArd Biesheuvel 			si->red_pos   = 0;
151d49fd4bbSArvind Sankar 			si->blue_pos  = 16;
152d49fd4bbSArvind Sankar 		} else /* PIXEL_BGR_RESERVED_8BIT_PER_COLOR */ {
153fc372064SArd Biesheuvel 			si->blue_pos  = 0;
154d49fd4bbSArvind Sankar 			si->red_pos   = 16;
155d49fd4bbSArvind Sankar 		}
156d49fd4bbSArvind Sankar 
157d49fd4bbSArvind Sankar 		si->green_pos = 8;
158d49fd4bbSArvind Sankar 		si->rsvd_pos  = 24;
159d49fd4bbSArvind Sankar 		si->red_size = si->green_size =
160d49fd4bbSArvind Sankar 			si->blue_size = si->rsvd_size = 8;
161d49fd4bbSArvind Sankar 
162d49fd4bbSArvind Sankar 		si->lfb_depth = 32;
163d49fd4bbSArvind Sankar 		si->lfb_linelength = pixels_per_scan_line * 4;
164fc372064SArd Biesheuvel 	}
165fc372064SArd Biesheuvel }
166fc372064SArd Biesheuvel 
167ecf53091SArvind Sankar static efi_graphics_output_protocol_t *
168ecf53091SArvind Sankar find_gop(efi_guid_t *proto, unsigned long size, void **handles)
169fc372064SArd Biesheuvel {
170e484c594SArvind Sankar 	efi_graphics_output_protocol_t *first_gop;
1712732ea0dSArd Biesheuvel 	efi_handle_t h;
172fc372064SArd Biesheuvel 	int i;
173fc372064SArd Biesheuvel 
174fc372064SArd Biesheuvel 	first_gop = NULL;
175fc372064SArd Biesheuvel 
1762732ea0dSArd Biesheuvel 	for_each_efi_handle(h, handles, size, i) {
177e484c594SArvind Sankar 		efi_status_t status;
178e484c594SArvind Sankar 
179e484c594SArvind Sankar 		efi_graphics_output_protocol_t *gop;
180e484c594SArvind Sankar 		efi_graphics_output_protocol_mode_t *mode;
181e484c594SArvind Sankar 		efi_graphics_output_mode_info_t *info;
182e484c594SArvind Sankar 
183fc372064SArd Biesheuvel 		efi_guid_t conout_proto = EFI_CONSOLE_OUT_DEVICE_GUID;
184fc372064SArd Biesheuvel 		void *dummy = NULL;
185fc372064SArd Biesheuvel 
186966291f6SArd Biesheuvel 		status = efi_bs_call(handle_protocol, h, proto, (void **)&gop);
187fc372064SArd Biesheuvel 		if (status != EFI_SUCCESS)
188fc372064SArd Biesheuvel 			continue;
189fc372064SArd Biesheuvel 
1908cd20797SArvind Sankar 		mode = efi_table_attr(gop, mode);
1918cd20797SArvind Sankar 		info = efi_table_attr(mode, info);
192d49fd4bbSArvind Sankar 		if (info->pixel_format == PIXEL_BLT_ONLY ||
193d49fd4bbSArvind Sankar 		    info->pixel_format >= PIXEL_FORMAT_MAX)
1948cd20797SArvind Sankar 			continue;
1958cd20797SArvind Sankar 
196fc372064SArd Biesheuvel 		/*
197fc372064SArd Biesheuvel 		 * Systems that use the UEFI Console Splitter may
198fc372064SArd Biesheuvel 		 * provide multiple GOP devices, not all of which are
199fc372064SArd Biesheuvel 		 * backed by real hardware. The workaround is to search
200fc372064SArd Biesheuvel 		 * for a GOP implementing the ConOut protocol, and if
201fc372064SArd Biesheuvel 		 * one isn't found, to just fall back to the first GOP.
2026327e6d0SArvind Sankar 		 *
203fc372064SArd Biesheuvel 		 * Once we've found a GOP supporting ConOut,
204fc372064SArd Biesheuvel 		 * don't bother looking any further.
205fc372064SArd Biesheuvel 		 */
2068e0a22e2SArvind Sankar 		status = efi_bs_call(handle_protocol, h, &conout_proto, &dummy);
2078e0a22e2SArvind Sankar 		if (status == EFI_SUCCESS)
2088e0a22e2SArvind Sankar 			return gop;
2098e0a22e2SArvind Sankar 
2108e0a22e2SArvind Sankar 		if (!first_gop)
2118de8788dSArvind Sankar 			first_gop = gop;
212fc372064SArd Biesheuvel 	}
213fc372064SArd Biesheuvel 
214ecf53091SArvind Sankar 	return first_gop;
215ecf53091SArvind Sankar }
216ecf53091SArvind Sankar 
217ecf53091SArvind Sankar static efi_status_t setup_gop(struct screen_info *si, efi_guid_t *proto,
218ecf53091SArvind Sankar 			      unsigned long size, void **handles)
219ecf53091SArvind Sankar {
220ecf53091SArvind Sankar 	efi_graphics_output_protocol_t *gop;
221ecf53091SArvind Sankar 	efi_graphics_output_protocol_mode_t *mode;
222e484c594SArvind Sankar 	efi_graphics_output_mode_info_t *info;
223ecf53091SArvind Sankar 	efi_physical_addr_t fb_base;
224ecf53091SArvind Sankar 
225ecf53091SArvind Sankar 	gop = find_gop(proto, size, handles);
226ecf53091SArvind Sankar 
227fc372064SArd Biesheuvel 	/* Did we find any GOPs? */
228ecf53091SArvind Sankar 	if (!gop)
2296fc3cec3SArvind Sankar 		return EFI_NOT_FOUND;
230fc372064SArd Biesheuvel 
231fffb6804SArvind Sankar 	/* Change mode if requested */
232fffb6804SArvind Sankar 	set_mode(gop);
233fffb6804SArvind Sankar 
234fc372064SArd Biesheuvel 	/* EFI framebuffer */
235ecf53091SArvind Sankar 	mode = efi_table_attr(gop, mode);
2366327e6d0SArvind Sankar 	info = efi_table_attr(mode, info);
2376327e6d0SArvind Sankar 
238fc372064SArd Biesheuvel 	si->orig_video_isVGA = VIDEO_TYPE_EFI;
239fc372064SArd Biesheuvel 
2406327e6d0SArvind Sankar 	si->lfb_width  = info->horizontal_resolution;
2416327e6d0SArvind Sankar 	si->lfb_height = info->vertical_resolution;
242fc372064SArd Biesheuvel 
2436327e6d0SArvind Sankar 	fb_base		 = efi_table_attr(mode, frame_buffer_base);
244f1d1853bSArvind Sankar 	si->lfb_base	 = lower_32_bits(fb_base);
245f1d1853bSArvind Sankar 	si->ext_lfb_base = upper_32_bits(fb_base);
2466327e6d0SArvind Sankar 	if (si->ext_lfb_base)
247fc372064SArd Biesheuvel 		si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE;
248fc372064SArd Biesheuvel 
249fc372064SArd Biesheuvel 	si->pages = 1;
250fc372064SArd Biesheuvel 
2516327e6d0SArvind Sankar 	setup_pixel_info(si, info->pixels_per_scan_line,
2526327e6d0SArvind Sankar 			     info->pixel_information, info->pixel_format);
253fc372064SArd Biesheuvel 
254fc372064SArd Biesheuvel 	si->lfb_size = si->lfb_linelength * si->lfb_height;
255fc372064SArd Biesheuvel 
256fc372064SArd Biesheuvel 	si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS;
2576fc3cec3SArvind Sankar 
258dbd89c30SArvind Sankar 	return EFI_SUCCESS;
259fc372064SArd Biesheuvel }
260fc372064SArd Biesheuvel 
261fc372064SArd Biesheuvel /*
262fc372064SArd Biesheuvel  * See if we have Graphics Output Protocol
263fc372064SArd Biesheuvel  */
264cd33a5c1SArd Biesheuvel efi_status_t efi_setup_gop(struct screen_info *si, efi_guid_t *proto,
265fc372064SArd Biesheuvel 			   unsigned long size)
266fc372064SArd Biesheuvel {
267fc372064SArd Biesheuvel 	efi_status_t status;
268fc372064SArd Biesheuvel 	void **gop_handle = NULL;
269fc372064SArd Biesheuvel 
270966291f6SArd Biesheuvel 	status = efi_bs_call(allocate_pool, EFI_LOADER_DATA, size,
271966291f6SArd Biesheuvel 			     (void **)&gop_handle);
272fc372064SArd Biesheuvel 	if (status != EFI_SUCCESS)
273fc372064SArd Biesheuvel 		return status;
274fc372064SArd Biesheuvel 
275966291f6SArd Biesheuvel 	status = efi_bs_call(locate_handle, EFI_LOCATE_BY_PROTOCOL, proto, NULL,
276966291f6SArd Biesheuvel 			     &size, gop_handle);
277fc372064SArd Biesheuvel 	if (status != EFI_SUCCESS)
278fc372064SArd Biesheuvel 		goto free_handle;
279fc372064SArd Biesheuvel 
280cd33a5c1SArd Biesheuvel 	status = setup_gop(si, proto, size, gop_handle);
281fc372064SArd Biesheuvel 
282fc372064SArd Biesheuvel free_handle:
283966291f6SArd Biesheuvel 	efi_bs_call(free_pool, gop_handle);
284fc372064SArd Biesheuvel 	return status;
285fc372064SArd Biesheuvel }
286