From 3935dde2f2b6c08746e8688a84c67871b225125a Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Sun, 4 Mar 2012 00:48:21 +0100 Subject: [PATCH] Use EDID on EFI. * grub-core/kern/efi/efi.c (grub_efi_get_variable): New argument datasize_out. * grub-core/video/efi_gop.c (check_protocol): Check that GOP has usable modes. Set gop_handle. (grub_video_gop_get_edid): New function. (grub_gop_get_preferred_mode): Likewise. (grub_video_gop_setup): Use grub_gop_get_preferred_mode. (grub_video_efi_gop_adapter): Set .get_edid. * include/grub/efi/edid.h: New file. * include/grub/efi/efi.h (grub_efi_get_variable): Update proto. Also-By: Vladimir Serbinenko --- ChangeLog | 16 +++++ grub-core/kern/efi/efi.c | 13 ++++- grub-core/video/efi_gop.c | 119 +++++++++++++++++++++++++++++++++++++- include/grub/efi/edid.h | 54 +++++++++++++++++ include/grub/efi/efi.h | 3 +- 5 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 include/grub/efi/edid.h diff --git a/ChangeLog b/ChangeLog index 36fc62209..d4a34fc1d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2012-03-03 Matthew Garrett +2012-03-03 Vladimir Serbinenko + + Use EDID on EFI. + + * grub-core/kern/efi/efi.c (grub_efi_get_variable): New argument + datasize_out. + * grub-core/video/efi_gop.c (check_protocol): Check that GOP has usable + modes. Set gop_handle. + (grub_video_gop_get_edid): New function. + (grub_gop_get_preferred_mode): Likewise. + (grub_video_gop_setup): Use grub_gop_get_preferred_mode. + (grub_video_efi_gop_adapter): Set .get_edid. + * include/grub/efi/edid.h: New file. + * include/grub/efi/efi.h (grub_efi_get_variable): Update proto. + 2012-03-03 Vladimir Serbinenko * util/grub-install.in: Load efivars unconditionally. diff --git a/grub-core/kern/efi/efi.c b/grub-core/kern/efi/efi.c index 2b0a8b489..6f12c7618 100644 --- a/grub-core/kern/efi/efi.c +++ b/grub-core/kern/efi/efi.c @@ -183,7 +183,8 @@ grub_efi_set_virtual_address_map (grub_efi_uintn_t memory_map_size, } void * -grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid) +grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid, + grub_size_t *datasize_out) { grub_efi_status_t status; grub_efi_uintn_t datasize = 0; @@ -192,6 +193,8 @@ grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid) void *data; grub_size_t len, len16; + *datasize_out = 0; + len = grub_strlen (var); len16 = len * GRUB_MAX_UTF16_PER_UTF8; var16 = grub_malloc ((len16 + 1) * sizeof (var16[0])); @@ -204,6 +207,9 @@ grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid) status = efi_call_5 (r->get_variable, var16, guid, NULL, &datasize, NULL); + if (!datasize) + return NULL; + data = grub_malloc (datasize); if (!data) { @@ -215,7 +221,10 @@ grub_efi_get_variable (const char *var, const grub_efi_guid_t *guid) grub_free (var16); if (status == GRUB_EFI_SUCCESS) - return data; + { + *datasize_out = datasize; + return data; + } grub_free (data); return NULL; diff --git a/grub-core/video/efi_gop.c b/grub-core/video/efi_gop.c index d14ae98d2..73c5d08e5 100644 --- a/grub-core/video/efi_gop.c +++ b/grub-core/video/efi_gop.c @@ -27,14 +27,22 @@ #include #include #include +#include #include GRUB_MOD_LICENSE ("GPLv3+"); static grub_efi_guid_t graphics_output_guid = GRUB_EFI_GOP_GUID; +static grub_efi_guid_t active_edid_guid = GRUB_EFI_EDID_ACTIVE_GUID; +static grub_efi_guid_t discovered_edid_guid = GRUB_EFI_EDID_DISCOVERED_GUID; +static grub_efi_guid_t efi_var_guid = GRUB_EFI_GLOBAL_VARIABLE_GUID; static struct grub_efi_gop *gop; static unsigned old_mode; static int restore_needed; +static grub_efi_handle_t gop_handle; + +static int +grub_video_gop_iterate (int (*hook) (const struct grub_video_mode_info *info)); static struct { @@ -47,9 +55,37 @@ static struct static int check_protocol (void) { - gop = grub_efi_locate_protocol (&graphics_output_guid, 0); - if (gop) + grub_efi_handle_t *handles; + grub_efi_uintn_t num_handles, i; + int have_usable_mode = 0; + + auto int hook (const struct grub_video_mode_info *info); + int hook (const struct grub_video_mode_info *info __attribute__ ((unused))) + { + have_usable_mode = 1; return 1; + } + + handles = grub_efi_locate_handle (GRUB_EFI_BY_PROTOCOL, + &graphics_output_guid, NULL, &num_handles); + if (!handles || num_handles == 0) + return 0; + + for (i = 0; i < num_handles; i++) + { + gop_handle = handles[i]; + gop = grub_efi_open_protocol (gop_handle, &graphics_output_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + grub_video_gop_iterate (hook); + if (have_usable_mode) + { + grub_free (handles); + return 1; + } + } + + gop = 0; + gop_handle = 0; return 0; } @@ -220,6 +256,64 @@ grub_video_gop_iterate (int (*hook) (const struct grub_video_mode_info *info)) return 0; } +static grub_err_t +grub_video_gop_get_edid (struct grub_video_edid_info *edid_info) +{ + struct grub_efi_active_edid *edid; + grub_size_t copy_size; + + grub_memset (edid_info, 0, sizeof (*edid_info)); + + edid = grub_efi_open_protocol (gop_handle, &active_edid_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + if (!edid || edid->size_of_edid == 0) + edid = grub_efi_open_protocol (gop_handle, &discovered_edid_guid, + GRUB_EFI_OPEN_PROTOCOL_GET_PROTOCOL); + + if (!edid || edid->size_of_edid == 0) + { + char edidname[] = "agp-internal-edid"; + grub_size_t datasize; + grub_uint8_t *data; + data = grub_efi_get_variable (edidname, &efi_var_guid, &datasize); + if (data && datasize > 16) + { + copy_size = datasize - 16; + if (copy_size > sizeof (*edid_info)) + copy_size = sizeof (*edid_info); + grub_memcpy (edid_info, data + 16, copy_size); + grub_free (data); + return GRUB_ERR_NONE; + } + return grub_error (GRUB_ERR_BAD_DEVICE, "EDID information not available"); + } + + copy_size = edid->size_of_edid; + if (copy_size > sizeof (*edid_info)) + copy_size = sizeof (*edid_info); + grub_memcpy (edid_info, edid->edid, copy_size); + + return GRUB_ERR_NONE; +} + +static grub_err_t +grub_gop_get_preferred_mode (unsigned int *width, unsigned int *height) +{ + struct grub_video_edid_info edid_info; + grub_err_t err; + + err = grub_video_gop_get_edid (&edid_info); + if (err) + return err; + err = grub_video_edid_checksum (&edid_info); + if (err) + return err; + err = grub_video_edid_preferred_mode (&edid_info, width, height); + if (err) + return err; + return GRUB_ERR_NONE; +} + static grub_err_t grub_video_gop_setup (unsigned int width, unsigned int height, unsigned int mode_type, @@ -232,10 +326,23 @@ grub_video_gop_setup (unsigned int width, unsigned int height, unsigned bpp; int found = 0; unsigned long long best_volume = 0; + unsigned int preferred_width = 0, preferred_height = 0; depth = (mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK) >> GRUB_VIDEO_MODE_TYPE_DEPTH_POS; + if (width == 0 && height == 0) + { + err = 1; + grub_gop_get_preferred_mode (&preferred_width, &preferred_height); + if (err) + { + preferred_width = 800; + preferred_height = 600; + grub_errno = GRUB_ERR_NONE; + } + } + /* Keep current mode if possible. */ if (gop->mode->info) { @@ -270,6 +377,13 @@ grub_video_gop_setup (unsigned int width, unsigned int height, grub_dprintf ("video", "GOP: mode %d: %dx%d\n", mode, info->width, info->height); + if (preferred_width && (info->width > preferred_width + || info->height > preferred_height)) + { + grub_dprintf ("video", "GOP: mode %d: too large\n", mode); + continue; + } + bpp = grub_video_gop_get_bpp (info); if (!bpp) { @@ -401,6 +515,7 @@ static struct grub_video_adapter grub_video_gop_adapter = .setup = grub_video_gop_setup, .get_info = grub_video_fb_get_info, .get_info_and_fini = grub_video_gop_get_info_and_fini, + .get_edid = grub_video_gop_get_edid, .set_palette = grub_video_fb_set_palette, .get_palette = grub_video_fb_get_palette, .set_viewport = grub_video_fb_set_viewport, diff --git a/include/grub/efi/edid.h b/include/grub/efi/edid.h new file mode 100644 index 000000000..a0140b81c --- /dev/null +++ b/include/grub/efi/edid.h @@ -0,0 +1,54 @@ +/* + * GRUB -- GRand Unified Bootloader + * Copyright (C) 2012 Free Software Foundation, Inc. + * + * GRUB is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * GRUB is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with GRUB. If not, see . + */ + +#ifndef GRUB_EFI_EDID_HEADER +#define GRUB_EFI_EDID_HEADER 1 + +/* Based on UEFI specification. */ + +#define GRUB_EFI_EDID_ACTIVE_GUID \ + { 0xbd8c1056, 0x9f36, 0x44ec, { 0x92, 0xa8, 0xa6, 0x33, 0x7f, 0x81, 0x79, 0x86 }} + +#define GRUB_EFI_EDID_DISCOVERED_GUID \ + {0x1c0c34f6,0xd380,0x41fa, {0xa0,0x49,0x8a,0xd0,0x6c,0x1a,0x66,0xaa}} + +#define GRUB_EFI_EDID_OVERRIDE_GUID \ + {0x48ecb431,0xfb72,0x45c0, {0xa9,0x22,0xf4,0x58,0xfe,0x4,0xb,0xd5}} + +struct grub_efi_edid_override; + +typedef grub_efi_status_t +(*grub_efi_edid_override_get_edid) (struct grub_efi_edid_override *this, + grub_efi_handle_t *childhandle, + grub_efi_uint32_t *attributes, + grub_efi_uintn_t *edidsize, + grub_efi_uint8_t *edid); +struct grub_efi_edid_override { + grub_efi_edid_override_get_edid get_edid; +}; + +typedef struct grub_efi_edid_override grub_efi_edid_override_t; + + +struct grub_efi_active_edid +{ + grub_uint32_t size_of_edid; + grub_uint8_t *edid; +}; + +#endif diff --git a/include/grub/efi/efi.h b/include/grub/efi/efi.h index 067e3bdac..a1a28cd4e 100644 --- a/include/grub/efi/efi.h +++ b/include/grub/efi/efi.h @@ -62,7 +62,8 @@ grub_err_t EXPORT_FUNC (grub_efi_set_virtual_address_map) (grub_efi_uintn_t memo grub_efi_uint32_t descriptor_version, grub_efi_memory_descriptor_t *virtual_map); void *EXPORT_FUNC (grub_efi_get_variable) (const char *variable, - const grub_efi_guid_t *guid); + const grub_efi_guid_t *guid, + grub_size_t *datasize_out); int EXPORT_FUNC (grub_efi_compare_device_paths) (const grub_efi_device_path_t *dp1, const grub_efi_device_path_t *dp2);