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