File Index
Files and directories
Sign in
FILE

gop.c

File preview with syntax highlighting.

linux-6.19.14/drivers/firmware/efi/libstub/gop.c C
  1// SPDX-License-Identifier: GPL-2.0
  2/* -----------------------------------------------------------------------
  3 *
  4 *   Copyright 2011 Intel Corporation; author Matt Fleming
  5 *
  6 * ----------------------------------------------------------------------- */
  7
  8#include <linux/bitops.h>
  9#include <linux/ctype.h>
 10#include <linux/efi.h>
 11#include <linux/screen_info.h>
 12#include <linux/string.h>
 13#include <asm/efi.h>
 14#include <asm/setup.h>
 15#include <video/edid.h>
 16
 17#include "efistub.h"
 18
 19enum efi_cmdline_option {
 20	EFI_CMDLINE_NONE,
 21	EFI_CMDLINE_MODE_NUM,
 22	EFI_CMDLINE_RES,
 23	EFI_CMDLINE_AUTO,
 24	EFI_CMDLINE_LIST
 25};
 26
 27static struct {
 28	enum efi_cmdline_option option;
 29	union {
 30		u32 mode;
 31		struct {
 32			u32 width, height;
 33			int format;
 34			u8 depth;
 35		} res;
 36	};
 37} cmdline = { .option = EFI_CMDLINE_NONE };
 38
 39static bool parse_modenum(char *option, char **next)
 40{
 41	u32 m;
 42
 43	if (!strstarts(option, "mode="))
 44		return false;
 45	option += strlen("mode=");
 46	m = simple_strtoull(option, &option, 0);
 47	if (*option && *option++ != ',')
 48		return false;
 49	cmdline.option = EFI_CMDLINE_MODE_NUM;
 50	cmdline.mode   = m;
 51
 52	*next = option;
 53	return true;
 54}
 55
 56static bool parse_res(char *option, char **next)
 57{
 58	u32 w, h, d = 0;
 59	int pf = -1;
 60
 61	if (!isdigit(*option))
 62		return false;
 63	w = simple_strtoull(option, &option, 10);
 64	if (*option++ != 'x' || !isdigit(*option))
 65		return false;
 66	h = simple_strtoull(option, &option, 10);
 67	if (*option == '-') {
 68		option++;
 69		if (strstarts(option, "rgb")) {
 70			option += strlen("rgb");
 71			pf = PIXEL_RGB_RESERVED_8BIT_PER_COLOR;
 72		} else if (strstarts(option, "bgr")) {
 73			option += strlen("bgr");
 74			pf = PIXEL_BGR_RESERVED_8BIT_PER_COLOR;
 75		} else if (isdigit(*option))
 76			d = simple_strtoull(option, &option, 10);
 77		else
 78			return false;
 79	}
 80	if (*option && *option++ != ',')
 81		return false;
 82	cmdline.option     = EFI_CMDLINE_RES;
 83	cmdline.res.width  = w;
 84	cmdline.res.height = h;
 85	cmdline.res.format = pf;
 86	cmdline.res.depth  = d;
 87
 88	*next = option;
 89	return true;
 90}
 91
 92static bool parse_auto(char *option, char **next)
 93{
 94	if (!strstarts(option, "auto"))
 95		return false;
 96	option += strlen("auto");
 97	if (*option && *option++ != ',')
 98		return false;
 99	cmdline.option = EFI_CMDLINE_AUTO;
100
101	*next = option;
102	return true;
103}
104
105static bool parse_list(char *option, char **next)
106{
107	if (!strstarts(option, "list"))
108		return false;
109	option += strlen("list");
110	if (*option && *option++ != ',')
111		return false;
112	cmdline.option = EFI_CMDLINE_LIST;
113
114	*next = option;
115	return true;
116}
117
118void efi_parse_option_graphics(char *option)
119{
120	while (*option) {
121		if (parse_modenum(option, &option))
122			continue;
123		if (parse_res(option, &option))
124			continue;
125		if (parse_auto(option, &option))
126			continue;
127		if (parse_list(option, &option))
128			continue;
129
130		while (*option && *option++ != ',')
131			;
132	}
133}
134
135static u32 choose_mode_modenum(efi_graphics_output_protocol_t *gop)
136{
137	efi_graphics_output_mode_info_t *info __free(efi_pool) = NULL;
138	efi_graphics_output_protocol_mode_t *mode;
139	unsigned long info_size;
140	u32 max_mode, cur_mode;
141	efi_status_t status;
142	int pf;
143
144	mode = efi_table_attr(gop, mode);
145
146	cur_mode = efi_table_attr(mode, mode);
147	if (cmdline.mode == cur_mode)
148		return cur_mode;
149
150	max_mode = efi_table_attr(mode, max_mode);
151	if (cmdline.mode >= max_mode) {
152		efi_err("Requested mode is invalid\n");
153		return cur_mode;
154	}
155
156	status = efi_call_proto(gop, query_mode, cmdline.mode, &info_size, &info);
157	if (status != EFI_SUCCESS) {
158		efi_err("Couldn't get mode information\n");
159		return cur_mode;
160	}
161
162	pf = info->pixel_format;
163	if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX) {
164		efi_err("Invalid PixelFormat\n");
165		return cur_mode;
166	}
167
168	return cmdline.mode;
169}
170
171static u32 choose_mode(efi_graphics_output_protocol_t *gop,
172		       bool (*match)(const efi_graphics_output_mode_info_t *, u32, void *),
173		       void *ctx)
174{
175	efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
176	u32 max_mode = efi_table_attr(mode, max_mode);
177
178	for (u32 m = 0; m < max_mode; m++) {
179		efi_graphics_output_mode_info_t *info __free(efi_pool) = NULL;
180		unsigned long info_size;
181		efi_status_t status;
182
183		status = efi_call_proto(gop, query_mode, m, &info_size, &info);
184		if (status != EFI_SUCCESS)
185			continue;
186
187		if (match(info, m, ctx))
188			return m;
189	}
190	return (unsigned long)ctx;
191}
192
193static u8 pixel_bpp(int pixel_format, efi_pixel_bitmask_t pixel_info)
194{
195	if (pixel_format == PIXEL_BIT_MASK) {
196		u32 mask = pixel_info.red_mask | pixel_info.green_mask |
197			   pixel_info.blue_mask | pixel_info.reserved_mask;
198		if (!mask)
199			return 0;
200		return __fls(mask) - __ffs(mask) + 1;
201	} else
202		return 32;
203}
204
205static bool match_res(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
206{
207	efi_pixel_bitmask_t pi = info->pixel_information;
208	int pf = info->pixel_format;
209
210	if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX)
211		return false;
212
213	return cmdline.res.width == info->horizontal_resolution &&
214	       cmdline.res.height == info->vertical_resolution &&
215	       (cmdline.res.format < 0 || cmdline.res.format == pf) &&
216	       (!cmdline.res.depth || cmdline.res.depth == pixel_bpp(pf, pi));
217}
218
219static u32 choose_mode_res(efi_graphics_output_protocol_t *gop)
220{
221	efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
222	unsigned long cur_mode = efi_table_attr(mode, mode);
223
224	if (match_res(efi_table_attr(mode, info), cur_mode, NULL))
225		return cur_mode;
226
227	return choose_mode(gop, match_res, (void *)cur_mode);
228}
229
230struct match {
231	u32	mode;
232	u32	area;
233	u8	depth;
234};
235
236static bool match_auto(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
237{
238	u32 area = info->horizontal_resolution * info->vertical_resolution;
239	efi_pixel_bitmask_t pi = info->pixel_information;
240	int pf = info->pixel_format;
241	u8 depth = pixel_bpp(pf, pi);
242	struct match *m = ctx;
243
244	if (pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX)
245		return false;
246
247	if (area > m->area || (area == m->area && depth > m->depth))
248		*m = (struct match){ mode, area, depth };
249
250	return false;
251}
252
253static u32 choose_mode_auto(efi_graphics_output_protocol_t *gop)
254{
255	struct match match = {};
256
257	choose_mode(gop, match_auto, &match);
258
259	return match.mode;
260}
261
262static bool match_list(const efi_graphics_output_mode_info_t *info, u32 mode, void *ctx)
263{
264	efi_pixel_bitmask_t pi = info->pixel_information;
265	u32 cur_mode = (unsigned long)ctx;
266	int pf = info->pixel_format;
267	const char *dstr;
268	u8 depth = 0;
269	bool valid;
270
271	valid = !(pf == PIXEL_BLT_ONLY || pf >= PIXEL_FORMAT_MAX);
272
273	switch (pf) {
274	case PIXEL_RGB_RESERVED_8BIT_PER_COLOR:
275		dstr = "rgb";
276		break;
277	case PIXEL_BGR_RESERVED_8BIT_PER_COLOR:
278		dstr = "bgr";
279		break;
280	case PIXEL_BIT_MASK:
281		dstr = "";
282		depth = pixel_bpp(pf, pi);
283		break;
284	case PIXEL_BLT_ONLY:
285		dstr = "blt";
286		break;
287	default:
288		dstr = "xxx";
289		break;
290	}
291
292	efi_printk("Mode %3u %c%c: Resolution %ux%u-%s%.0hhu\n",
293		    mode,
294		    (mode == cur_mode) ? '*' : ' ',
295		    !valid ? '-' : ' ',
296		    info->horizontal_resolution,
297		    info->vertical_resolution,
298		    dstr, depth);
299
300	return false;
301}
302
303static u32 choose_mode_list(efi_graphics_output_protocol_t *gop)
304{
305	efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
306	unsigned long cur_mode = efi_table_attr(mode, mode);
307	u32 max_mode = efi_table_attr(mode, max_mode);
308	efi_input_key_t key;
309	efi_status_t status;
310
311	efi_printk("Available graphics modes are 0-%u\n", max_mode-1);
312	efi_puts("  * = current mode\n"
313		 "  - = unusable mode\n");
314
315	choose_mode(gop, match_list, (void *)cur_mode);
316
317	efi_puts("\nPress any key to continue (or wait 10 seconds)\n");
318	status = efi_wait_for_key(10 * EFI_USEC_PER_SEC, &key);
319	if (status != EFI_SUCCESS && status != EFI_TIMEOUT) {
320		efi_err("Unable to read key, continuing in 10 seconds\n");
321		efi_bs_call(stall, 10 * EFI_USEC_PER_SEC);
322	}
323
324	return cur_mode;
325}
326
327static void set_mode(efi_graphics_output_protocol_t *gop)
328{
329	efi_graphics_output_protocol_mode_t *mode;
330	u32 cur_mode, new_mode;
331
332	switch (cmdline.option) {
333	case EFI_CMDLINE_MODE_NUM:
334		new_mode = choose_mode_modenum(gop);
335		break;
336	case EFI_CMDLINE_RES:
337		new_mode = choose_mode_res(gop);
338		break;
339	case EFI_CMDLINE_AUTO:
340		new_mode = choose_mode_auto(gop);
341		break;
342	case EFI_CMDLINE_LIST:
343		new_mode = choose_mode_list(gop);
344		break;
345	default:
346		return;
347	}
348
349	mode = efi_table_attr(gop, mode);
350	cur_mode = efi_table_attr(mode, mode);
351
352	if (new_mode == cur_mode)
353		return;
354
355	if (efi_call_proto(gop, set_mode, new_mode) != EFI_SUCCESS)
356		efi_err("Failed to set requested mode\n");
357}
358
359static void find_bits(u32 mask, u8 *pos, u8 *size)
360{
361	if (!mask) {
362		*pos = *size = 0;
363		return;
364	}
365
366	/* UEFI spec guarantees that the set bits are contiguous */
367	*pos  = __ffs(mask);
368	*size = __fls(mask) - *pos + 1;
369}
370
371static void setup_screen_info(struct screen_info *si, const efi_graphics_output_protocol_t *gop)
372{
373	const efi_graphics_output_protocol_mode_t *mode = efi_table_attr(gop, mode);
374	const efi_graphics_output_mode_info_t *info = efi_table_attr(mode, info);
375
376	si->orig_video_isVGA = VIDEO_TYPE_EFI;
377
378	si->lfb_width  = info->horizontal_resolution;
379	si->lfb_height = info->vertical_resolution;
380
381	efi_set_u64_split(efi_table_attr(mode, frame_buffer_base),
382			  &si->lfb_base, &si->ext_lfb_base);
383	if (si->ext_lfb_base)
384		si->capabilities |= VIDEO_CAPABILITY_64BIT_BASE;
385	si->pages = 1;
386
387	if (info->pixel_format == PIXEL_BIT_MASK) {
388		find_bits(info->pixel_information.red_mask, &si->red_pos, &si->red_size);
389		find_bits(info->pixel_information.green_mask, &si->green_pos, &si->green_size);
390		find_bits(info->pixel_information.blue_mask, &si->blue_pos, &si->blue_size);
391		find_bits(info->pixel_information.reserved_mask, &si->rsvd_pos, &si->rsvd_size);
392		si->lfb_depth = si->red_size + si->green_size + si->blue_size + si->rsvd_size;
393		si->lfb_linelength = (info->pixels_per_scan_line * si->lfb_depth) / 8;
394	} else {
395		if (info->pixel_format == PIXEL_RGB_RESERVED_8BIT_PER_COLOR) {
396			si->red_pos   = 0;
397			si->blue_pos  = 16;
398		} else /* PIXEL_BGR_RESERVED_8BIT_PER_COLOR */ {
399			si->blue_pos  = 0;
400			si->red_pos   = 16;
401		}
402
403		si->green_pos = 8;
404		si->rsvd_pos  = 24;
405		si->red_size = 8;
406		si->green_size = 8;
407		si->blue_size = 8;
408		si->rsvd_size = 8;
409		si->lfb_depth = 32;
410		si->lfb_linelength = info->pixels_per_scan_line * 4;
411	}
412
413	si->lfb_size = si->lfb_linelength * si->lfb_height;
414	si->capabilities |= VIDEO_CAPABILITY_SKIP_QUIRKS;
415}
416
417static void setup_edid_info(struct edid_info *edid, u32 gop_size_of_edid, u8 *gop_edid)
418{
419	if (!gop_edid || gop_size_of_edid < 128)
420		memset(edid->dummy, 0, sizeof(edid->dummy));
421	else
422		memcpy(edid->dummy, gop_edid, min(gop_size_of_edid, sizeof(edid->dummy)));
423}
424
425static efi_handle_t find_handle_with_primary_gop(unsigned long num, const efi_handle_t handles[],
426						 efi_graphics_output_protocol_t **found_gop)
427{
428	efi_graphics_output_protocol_t *first_gop;
429	efi_handle_t h, first_gop_handle;
430
431	first_gop_handle = NULL;
432	first_gop = NULL;
433
434	for_each_efi_handle(h, handles, num) {
435		efi_status_t status;
436
437		efi_graphics_output_protocol_t *gop;
438		efi_graphics_output_protocol_mode_t *mode;
439		efi_graphics_output_mode_info_t *info;
440		void *dummy = NULL;
441
442		status = efi_bs_call(handle_protocol, h,
443				     &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID,
444				     (void **)&gop);
445		if (status != EFI_SUCCESS)
446			continue;
447
448		mode = efi_table_attr(gop, mode);
449		info = efi_table_attr(mode, info);
450		if (info->pixel_format == PIXEL_BLT_ONLY ||
451		    info->pixel_format >= PIXEL_FORMAT_MAX)
452			continue;
453
454		/*
455		 * Systems that use the UEFI Console Splitter may
456		 * provide multiple GOP devices, not all of which are
457		 * backed by real hardware. The workaround is to search
458		 * for a GOP implementing the ConOut protocol, and if
459		 * one isn't found, to just fall back to the first GOP.
460		 *
461		 * Once we've found a GOP supporting ConOut,
462		 * don't bother looking any further.
463		 */
464		status = efi_bs_call(handle_protocol, h,
465				     &EFI_CONSOLE_OUT_DEVICE_GUID, &dummy);
466		if (status == EFI_SUCCESS) {
467			if (found_gop)
468				*found_gop = gop;
469			return h;
470		} else if (!first_gop_handle) {
471			first_gop_handle = h;
472			first_gop = gop;
473		}
474	}
475
476	if (found_gop)
477		*found_gop = first_gop;
478	return first_gop_handle;
479}
480
481efi_status_t efi_setup_graphics(struct screen_info *si, struct edid_info *edid)
482{
483	efi_handle_t *handles __free(efi_pool) = NULL;
484	efi_handle_t handle;
485	efi_graphics_output_protocol_t *gop;
486	efi_status_t status;
487	unsigned long num;
488
489	status = efi_bs_call(locate_handle_buffer, EFI_LOCATE_BY_PROTOCOL,
490			      &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, NULL, &num,
491			      &handles);
492	if (status != EFI_SUCCESS)
493		return status;
494
495	handle = find_handle_with_primary_gop(num, handles, &gop);
496	if (!handle)
497		return EFI_NOT_FOUND;
498
499	/* Change mode if requested */
500	set_mode(gop);
501
502	/* EFI framebuffer */
503	if (si)
504		setup_screen_info(si, gop);
505
506	/* Display EDID for primary GOP */
507	if (edid) {
508		efi_edid_discovered_protocol_t *discovered_edid;
509		efi_edid_active_protocol_t *active_edid;
510		u32 gop_size_of_edid = 0;
511		u8 *gop_edid = NULL;
512
513		status = efi_bs_call(handle_protocol, handle, &EFI_EDID_ACTIVE_PROTOCOL_GUID,
514				     (void **)&active_edid);
515		if (status == EFI_SUCCESS) {
516			gop_size_of_edid = efi_table_attr(active_edid, size_of_edid);
517			gop_edid = efi_table_attr(active_edid, edid);
518		} else {
519			status = efi_bs_call(handle_protocol, handle,
520					     &EFI_EDID_DISCOVERED_PROTOCOL_GUID,
521					     (void **)&discovered_edid);
522			if (status == EFI_SUCCESS) {
523				gop_size_of_edid = efi_table_attr(discovered_edid, size_of_edid);
524				gop_edid = efi_table_attr(discovered_edid, edid);
525			}
526		}
527
528		setup_edid_info(edid, gop_size_of_edid, gop_edid);
529	}
530
531	return EFI_SUCCESS;
532}
~/ Read-only file preview.