grub/grub-core/video/efi_gop.c
Alexander Graf e642c95ab6 efi/gop: Add support for BLT_ONLY adapters
EFI GOP has support for multiple different bitness types of frame buffers
and for a special "BLT only" type which is always defined to be RGBx.

Because grub2 doesn't ever directly access the frame buffer but instead
only renders graphics via the BLT interface anyway, we can easily support
these adapters.

The reason this has come up now is the emerging support for virtio-gpu
in OVMF. That adapter does not have the notion of a memory mapped frame
buffer and thus is BLT only.

Signed-off-by: Alexander Graf <agraf@suse.de>
Signed-off-by: Javier Martinez Canillas <javierm@redhat.com>
Reviewed-by: Daniel Kiper <daniel.kiper@oracle.com>
2020-03-10 21:40:31 +01:00

628 lines
17 KiB
C

/*
* GRUB -- GRand Unified Bootloader
* Copyright (C) 2005,2006,2007,2008,2009 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 <http://www.gnu.org/licenses/>.
*/
#define grub_video_render_target grub_video_fbrender_target
#include <grub/err.h>
#include <grub/types.h>
#include <grub/dl.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/video.h>
#include <grub/video_fb.h>
#include <grub/efi/api.h>
#include <grub/efi/efi.h>
#include <grub/efi/edid.h>
#include <grub/efi/graphics_output.h>
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, void *hook_arg), void *hook_arg);
static struct
{
struct grub_video_mode_info mode_info;
struct grub_video_render_target *render_target;
grub_uint8_t *ptr;
grub_uint8_t *offscreen;
} framebuffer;
static int
check_protocol_hook (const struct grub_video_mode_info *info __attribute__ ((unused)), void *hook_arg)
{
int *have_usable_mode = hook_arg;
*have_usable_mode = 1;
return 1;
}
static int
check_protocol (void)
{
grub_efi_handle_t *handles;
grub_efi_uintn_t num_handles, i;
int have_usable_mode = 0;
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 (check_protocol_hook, &have_usable_mode);
if (have_usable_mode)
{
grub_free (handles);
return 1;
}
}
gop = 0;
gop_handle = 0;
return 0;
}
static grub_err_t
grub_video_gop_init (void)
{
grub_memset (&framebuffer, 0, sizeof(framebuffer));
return grub_video_fb_init ();
}
static grub_err_t
grub_video_gop_fini (void)
{
if (restore_needed)
{
efi_call_2 (gop->set_mode, gop, old_mode);
restore_needed = 0;
}
grub_free (framebuffer.offscreen);
framebuffer.offscreen = 0;
return grub_video_fb_fini ();
}
static int
grub_video_gop_get_bpp (struct grub_efi_gop_mode_info *in)
{
grub_uint32_t total_mask;
int i;
switch (in->pixel_format)
{
case GRUB_EFI_GOT_BGRA8:
case GRUB_EFI_GOT_RGBA8:
case GRUB_EFI_GOT_BLT_ONLY:
return 32;
case GRUB_EFI_GOT_BITMASK:
/* Check overlaps. */
if ((in->pixel_bitmask.r & in->pixel_bitmask.g)
|| (in->pixel_bitmask.r & in->pixel_bitmask.b)
|| (in->pixel_bitmask.g & in->pixel_bitmask.b)
|| (in->pixel_bitmask.r & in->pixel_bitmask.a)
|| (in->pixel_bitmask.g & in->pixel_bitmask.a)
|| (in->pixel_bitmask.b & in->pixel_bitmask.a))
return 0;
total_mask = in->pixel_bitmask.r | in->pixel_bitmask.g
| in->pixel_bitmask.b | in->pixel_bitmask.a;
for (i = 31; i >= 0; i--)
if (total_mask & (1 << i))
return i + 1;
/* Fall through. */
default:
return 0;
}
}
static void
grub_video_gop_get_bitmask (grub_uint32_t mask, unsigned int *mask_size,
unsigned int *field_pos)
{
int i;
int last_p;
for (i = 31; i >= 0; i--)
if (mask & (1 << i))
break;
if (i == -1)
{
*mask_size = *field_pos = 0;
return;
}
last_p = i;
for (; i >= 0; i--)
if (!(mask & (1 << i)))
break;
*field_pos = i + 1;
*mask_size = last_p - *field_pos + 1;
}
static grub_err_t
grub_video_gop_fill_real_mode_info (unsigned mode,
struct grub_efi_gop_mode_info *in,
struct grub_video_mode_info *out)
{
out->mode_number = mode;
out->number_of_colors = 256;
out->width = in->width;
out->height = in->height;
out->mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
out->bpp = grub_video_gop_get_bpp (in);
out->bytes_per_pixel = out->bpp >> 3;
if (!out->bpp)
return grub_error (GRUB_ERR_IO, "unsupported video mode");
out->pitch = in->pixels_per_scanline * out->bytes_per_pixel;
switch (in->pixel_format)
{
case GRUB_EFI_GOT_RGBA8:
case GRUB_EFI_GOT_BLT_ONLY:
out->red_mask_size = 8;
out->red_field_pos = 0;
out->green_mask_size = 8;
out->green_field_pos = 8;
out->blue_mask_size = 8;
out->blue_field_pos = 16;
out->reserved_mask_size = 8;
out->reserved_field_pos = 24;
break;
case GRUB_EFI_GOT_BGRA8:
out->red_mask_size = 8;
out->red_field_pos = 16;
out->green_mask_size = 8;
out->green_field_pos = 8;
out->blue_mask_size = 8;
out->blue_field_pos = 0;
out->reserved_mask_size = 8;
out->reserved_field_pos = 24;
break;
case GRUB_EFI_GOT_BITMASK:
grub_video_gop_get_bitmask (in->pixel_bitmask.r, &out->red_mask_size,
&out->red_field_pos);
grub_video_gop_get_bitmask (in->pixel_bitmask.g, &out->green_mask_size,
&out->green_field_pos);
grub_video_gop_get_bitmask (in->pixel_bitmask.b, &out->blue_mask_size,
&out->blue_field_pos);
grub_video_gop_get_bitmask (in->pixel_bitmask.a, &out->reserved_mask_size,
&out->reserved_field_pos);
break;
default:
return grub_error (GRUB_ERR_IO, "unsupported video mode");
}
out->blit_format = grub_video_get_blit_format (out);
return GRUB_ERR_NONE;
}
static grub_err_t
grub_video_gop_fill_mode_info (unsigned mode,
struct grub_efi_gop_mode_info *in,
struct grub_video_mode_info *out)
{
out->mode_number = mode;
out->number_of_colors = 256;
out->width = in->width;
out->height = in->height;
out->mode_type = GRUB_VIDEO_MODE_TYPE_RGB;
out->bytes_per_pixel = sizeof (struct grub_efi_gop_blt_pixel);
out->bpp = out->bytes_per_pixel << 3;
out->pitch = in->width * out->bytes_per_pixel;
out->red_mask_size = 8;
out->red_field_pos = 16;
out->green_mask_size = 8;
out->green_field_pos = 8;
out->blue_mask_size = 8;
out->blue_field_pos = 0;
out->reserved_mask_size = 8;
out->reserved_field_pos = 24;
out->blit_format = GRUB_VIDEO_BLIT_FORMAT_BGRA_8888;
out->mode_type |= (GRUB_VIDEO_MODE_TYPE_DOUBLE_BUFFERED
| GRUB_VIDEO_MODE_TYPE_UPDATING_SWAP);
return GRUB_ERR_NONE;
}
static int
grub_video_gop_iterate (int (*hook) (const struct grub_video_mode_info *info, void *hook_arg), void *hook_arg)
{
unsigned mode;
for (mode = 0; mode < gop->mode->max_mode; mode++)
{
grub_efi_uintn_t size;
grub_efi_status_t status;
struct grub_efi_gop_mode_info *info = NULL;
grub_err_t err;
struct grub_video_mode_info mode_info;
status = efi_call_4 (gop->query_mode, gop, mode, &size, &info);
if (status)
{
info = 0;
continue;
}
err = grub_video_gop_fill_mode_info (mode, info, &mode_info);
if (err)
{
grub_errno = GRUB_ERR_NONE;
continue;
}
if (hook (&mode_info, hook_arg))
return 1;
}
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,
unsigned int mode_mask __attribute__ ((unused)))
{
unsigned int depth;
struct grub_efi_gop_mode_info *info = NULL;
unsigned best_mode = 0;
grub_err_t err;
unsigned bpp;
int found = 0;
unsigned long long best_volume = 0;
unsigned int preferred_width = 0, preferred_height = 0;
grub_uint8_t *buffer;
depth = (mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK)
>> GRUB_VIDEO_MODE_TYPE_DEPTH_POS;
if (width == 0 && height == 0)
{
err = grub_gop_get_preferred_mode (&preferred_width, &preferred_height);
if (err || preferred_width >= 4096 || preferred_height >= 4096)
{
preferred_width = 800;
preferred_height = 600;
grub_errno = GRUB_ERR_NONE;
}
}
/* Keep current mode if possible. */
if (gop->mode->info)
{
bpp = grub_video_gop_get_bpp (gop->mode->info);
if (bpp && ((width == gop->mode->info->width
&& height == gop->mode->info->height)
|| (width == 0 && height == 0))
&& (depth == bpp || depth == 0))
{
grub_dprintf ("video", "GOP: keeping mode %d\n", gop->mode->mode);
best_mode = gop->mode->mode;
found = 1;
}
}
if (!found)
{
unsigned mode;
grub_dprintf ("video", "GOP: %d modes detected\n", gop->mode->max_mode);
for (mode = 0; mode < gop->mode->max_mode; mode++)
{
grub_efi_uintn_t size;
grub_efi_status_t status;
status = efi_call_4 (gop->query_mode, gop, mode, &size, &info);
if (status)
{
info = 0;
continue;
}
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)
{
grub_dprintf ("video", "GOP: mode %d: incompatible pixel mode\n",
mode);
continue;
}
grub_dprintf ("video", "GOP: mode %d: depth %d\n", mode, bpp);
if (!(((info->width == width && info->height == height)
|| (width == 0 && height == 0))
&& (bpp == depth || depth == 0)))
{
grub_dprintf ("video", "GOP: mode %d: rejected\n", mode);
continue;
}
if (best_volume < ((unsigned long long) info->width)
* ((unsigned long long) info->height)
* ((unsigned long long) bpp))
{
best_volume = ((unsigned long long) info->width)
* ((unsigned long long) info->height)
* ((unsigned long long) bpp);
best_mode = mode;
}
found = 1;
}
}
if (!found)
{
grub_dprintf ("video", "GOP: no mode found\n");
return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "no matching mode found");
}
if (best_mode != gop->mode->mode)
{
if (!restore_needed)
{
old_mode = gop->mode->mode;
restore_needed = 1;
}
efi_call_2 (gop->set_mode, gop, best_mode);
}
info = gop->mode->info;
err = grub_video_gop_fill_mode_info (gop->mode->mode, info,
&framebuffer.mode_info);
if (err)
{
grub_dprintf ("video", "GOP: couldn't fill mode info\n");
return err;
}
framebuffer.ptr = (void *) (grub_addr_t) gop->mode->fb_base;
framebuffer.offscreen
= grub_malloc (framebuffer.mode_info.height
* framebuffer.mode_info.width
* sizeof (struct grub_efi_gop_blt_pixel));
buffer = framebuffer.offscreen;
if (!buffer)
{
grub_dprintf ("video", "GOP: couldn't allocate shadow\n");
grub_errno = 0;
err = grub_video_gop_fill_mode_info (gop->mode->mode, info,
&framebuffer.mode_info);
buffer = framebuffer.ptr;
}
grub_dprintf ("video", "GOP: initialising FB @ %p %dx%dx%d\n",
framebuffer.ptr, framebuffer.mode_info.width,
framebuffer.mode_info.height, framebuffer.mode_info.bpp);
err = grub_video_fb_create_render_target_from_pointer
(&framebuffer.render_target, &framebuffer.mode_info, buffer);
if (err)
{
grub_dprintf ("video", "GOP: Couldn't create FB target\n");
return err;
}
err = grub_video_fb_set_active_render_target (framebuffer.render_target);
if (err)
{
grub_dprintf ("video", "GOP: Couldn't set FB target\n");
return err;
}
err = grub_video_fb_set_palette (0, GRUB_VIDEO_FBSTD_NUMCOLORS,
grub_video_fbstd_colors);
if (err)
grub_dprintf ("video", "GOP: Couldn't set palette\n");
else
grub_dprintf ("video", "GOP: Success\n");
return err;
}
static grub_err_t
grub_video_gop_swap_buffers (void)
{
if (framebuffer.offscreen)
{
efi_call_10 (gop->blt, gop, framebuffer.offscreen,
GRUB_EFI_BLT_BUFFER_TO_VIDEO, 0, 0, 0, 0,
framebuffer.mode_info.width, framebuffer.mode_info.height,
framebuffer.mode_info.width * 4);
}
return GRUB_ERR_NONE;
}
static grub_err_t
grub_video_gop_set_active_render_target (struct grub_video_render_target *target)
{
if (target == GRUB_VIDEO_RENDER_TARGET_DISPLAY)
target = framebuffer.render_target;
return grub_video_fb_set_active_render_target (target);
}
static grub_err_t
grub_video_gop_get_info_and_fini (struct grub_video_mode_info *mode_info,
void **framebuf)
{
grub_err_t err;
err = grub_video_gop_fill_real_mode_info (gop->mode->mode, gop->mode->info,
mode_info);
if (err)
{
grub_dprintf ("video", "GOP: couldn't fill mode info\n");
return err;
}
*framebuf = (char *) framebuffer.ptr;
grub_video_fb_fini ();
grub_free (framebuffer.offscreen);
framebuffer.offscreen = 0;
return GRUB_ERR_NONE;
}
static struct grub_video_adapter grub_video_gop_adapter =
{
.name = "EFI GOP driver",
.id = GRUB_VIDEO_DRIVER_EFI_GOP,
.prio = GRUB_VIDEO_ADAPTER_PRIO_FIRMWARE,
.init = grub_video_gop_init,
.fini = grub_video_gop_fini,
.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,
.get_viewport = grub_video_fb_get_viewport,
.set_region = grub_video_fb_set_region,
.get_region = grub_video_fb_get_region,
.set_area_status = grub_video_fb_set_area_status,
.get_area_status = grub_video_fb_get_area_status,
.map_color = grub_video_fb_map_color,
.map_rgb = grub_video_fb_map_rgb,
.map_rgba = grub_video_fb_map_rgba,
.unmap_color = grub_video_fb_unmap_color,
.fill_rect = grub_video_fb_fill_rect,
.blit_bitmap = grub_video_fb_blit_bitmap,
.blit_render_target = grub_video_fb_blit_render_target,
.scroll = grub_video_fb_scroll,
.swap_buffers = grub_video_gop_swap_buffers,
.create_render_target = grub_video_fb_create_render_target,
.delete_render_target = grub_video_fb_delete_render_target,
.set_active_render_target = grub_video_gop_set_active_render_target,
.get_active_render_target = grub_video_fb_get_active_render_target,
.iterate = grub_video_gop_iterate,
.next = 0
};
GRUB_MOD_INIT(efi_gop)
{
if (check_protocol ())
grub_video_register (&grub_video_gop_adapter);
}
GRUB_MOD_FINI(efi_gop)
{
if (restore_needed)
{
efi_call_2 (gop->set_mode, gop, old_mode);
restore_needed = 0;
}
if (gop)
grub_video_unregister (&grub_video_gop_adapter);
}