From 1708050b6f1305104cf1df8fec38fbb4d1b26f98 Mon Sep 17 00:00:00 2001
From: Vladimir 'phcoder' Serbinenko <>
Date: Sat, 28 Nov 2009 00:15:04 +0100
Subject: [PATCH] GOP support Also-By: Bean Lee <>
---
conf/i386-efi.rmk | 5 +
conf/x86_64-efi.rmk | 5 +
include/grub/efi/graphics_output.h | 96 +++++++
video/efi_gop.c | 398 +++++++++++++++++++++++++++++
4 files changed, 504 insertions(+)
create mode 100644 include/grub/efi/graphics_output.h
create mode 100644 video/efi_gop.c
diff --git a/conf/i386-efi.rmk b/conf/i386-efi.rmk
index 93ea47864..c16ab1f98 100644
--- a/conf/i386-efi.rmk
+++ b/conf/i386-efi.rmk
@@ -148,6 +148,11 @@ efi_uga_mod_SOURCES = video/efi_uga.c
efi_uga_mod_CFLAGS = $(COMMON_CFLAGS)
efi_uga_mod_LDFLAGS = $(COMMON_LDFLAGS)
+pkglib_MODULES += efi_gop.mod
+efi_gop_mod_SOURCES = video/efi_gop.c
+efi_gop_mod_CFLAGS = $(COMMON_CFLAGS)
+efi_gop_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
pkglib_MODULES += xnu.mod
xnu_mod_SOURCES = loader/xnu_resume.c loader/i386/xnu.c loader/i386/efi/xnu.c\
loader/macho.c loader/xnu.c loader/i386/xnu_helper.S
diff --git a/conf/x86_64-efi.rmk b/conf/x86_64-efi.rmk
index 2ae91dbf4..94db5727d 100644
--- a/conf/x86_64-efi.rmk
+++ b/conf/x86_64-efi.rmk
@@ -154,6 +154,11 @@ efi_uga_mod_SOURCES = video/efi_uga.c
efi_uga_mod_CFLAGS = $(COMMON_CFLAGS)
efi_uga_mod_LDFLAGS = $(COMMON_LDFLAGS)
+pkglib_MODULES += efi_gop.mod
+efi_gop_mod_SOURCES = video/efi_gop.c
+efi_gop_mod_CFLAGS = $(COMMON_CFLAGS)
+efi_gop_mod_LDFLAGS = $(COMMON_LDFLAGS)
+
pkglib_MODULES += xnu.mod
xnu_mod_SOURCES = loader/xnu_resume.c loader/i386/xnu.c loader/i386/efi/xnu.c\
loader/macho.c loader/xnu.c loader/i386/xnu_helper.S
diff --git a/include/grub/efi/graphics_output.h b/include/grub/efi/graphics_output.h
new file mode 100644
index 000000000..9127a16d1
--- /dev/null
+++ b/include/grub/efi/graphics_output.h
@@ -0,0 +1,96 @@
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 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 .
+ */
+
+#ifndef GRUB_EFI_GOP_HEADER
+#define GRUB_EFI_GOP_HEADER 1
+
+/* Based on UEFI specification. */
+
+#define GRUB_EFI_GOP_GUID \
+ { 0x9042a9de, 0x23dc, 0x4a38, { 0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a }}
+
+typedef enum
+ {
+ GRUB_EFI_GOT_RGBA8,
+ GRUB_EFI_GOT_BGRA8,
+ GRUB_EFI_GOT_BITMASK
+ }
+ grub_efi_gop_pixel_format_t;
+
+struct grub_efi_gop_pixel_bitmask
+{
+ grub_uint32_t r;
+ grub_uint32_t g;
+ grub_uint32_t b;
+ grub_uint32_t a;
+};
+
+struct grub_efi_gop_mode_info
+{
+ grub_efi_uint32_t version;
+ grub_efi_uint32_t width;
+ grub_efi_uint32_t height;
+ grub_efi_gop_pixel_format_t pixel_format;
+ struct grub_efi_gop_pixel_bitmask pixel_bitmask;
+ grub_efi_uint32_t pixels_per_scanline;
+};
+
+struct grub_efi_gop_mode
+{
+ grub_efi_uint32_t max_mode;
+ grub_efi_uint32_t mode;
+ struct grub_efi_gop_mode_info *info;
+ grub_efi_uintn_t info_size;
+ grub_efi_physical_address_t fb_base;
+ grub_efi_uintn_t fb_size;
+};
+
+/* Forward declaration. */
+struct grub_efi_gop;
+
+typedef grub_efi_status_t
+(*grub_efi_gop_query_mode_t) (struct grub_efi_gop *this,
+ grub_efi_uint32_t mode_number,
+ grub_efi_uintn_t *size_of_info,
+ struct grub_efi_gop_mode_info *info);
+
+typedef grub_efi_status_t
+(*grub_efi_gop_set_mode_t) (struct grub_efi_gop *this,
+ grub_efi_uint32_t mode_number);
+
+typedef grub_efi_status_t
+(*grub_efi_gop_blt_t) (struct grub_efi_gop *this,
+ void *buffer,
+ grub_efi_uintn_t operation,
+ grub_efi_uintn_t sx,
+ grub_efi_uintn_t sy,
+ grub_efi_uintn_t dx,
+ grub_efi_uintn_t dy,
+ grub_efi_uintn_t width,
+ grub_efi_uintn_t height,
+ grub_efi_uintn_t delta);
+
+struct grub_efi_gop
+{
+ grub_efi_gop_query_mode_t query_mode;
+ grub_efi_gop_set_mode_t set_mode;
+ grub_efi_gop_blt_t blt;
+ struct grub_efi_gop_mode *mode;
+};
+
+#endif
diff --git a/video/efi_gop.c b/video/efi_gop.c
new file mode 100644
index 000000000..0123ee274
--- /dev/null
+++ b/video/efi_gop.c
@@ -0,0 +1,398 @@
+/*
+ * 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 .
+ */
+
+#define grub_video_render_target grub_video_fbrender_target
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+static grub_efi_guid_t graphics_output_guid = GRUB_EFI_GOP_GUID;
+static struct grub_efi_gop *gop;
+static unsigned old_mode;
+static int restore_needed;
+
+static struct
+{
+ struct grub_video_mode_info mode_info;
+ struct grub_video_render_target *render_target;
+ grub_uint8_t *ptr;
+} framebuffer;
+
+
+static int
+check_protocol (void)
+{
+ gop = grub_efi_locate_protocol (&graphics_output_guid, 0);
+ if (gop)
+ return 1;
+
+ 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;
+ }
+ 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:
+ 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;
+}
+
+static grub_err_t
+grub_video_gop_fill_mode_info (struct grub_efi_gop_mode_info *in,
+ struct grub_video_mode_info *out)
+{
+ 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:
+ 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_setup (unsigned int width, unsigned int height,
+ unsigned int mode_type)
+{
+ 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;
+
+ depth = (mode_type & GRUB_VIDEO_MODE_TYPE_DEPTH_MASK)
+ >> GRUB_VIDEO_MODE_TYPE_DEPTH_POS;
+
+ /* 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;
+ break;
+ }
+
+ grub_dprintf ("video", "GOP: mode %d: %dx%d\n", mode, info->width,
+ info->height);
+
+ 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 (info, &framebuffer.mode_info);
+ if (err)
+ {
+ grub_dprintf ("video", "GOP: couldn't fill mode info\n");
+ return err;
+ }
+
+ framebuffer.ptr = (void *) gop->mode->fb_base;
+
+ 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, framebuffer.ptr);
+
+ 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)
+{
+ /* TODO: Implement buffer swapping. */
+ 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_memcpy (mode_info, &(framebuffer.mode_info), sizeof (*mode_info));
+ *framebuf = (char *) framebuffer.ptr;
+
+ grub_video_fb_fini ();
+
+ return GRUB_ERR_NONE;
+}
+
+static struct grub_video_adapter grub_video_gop_adapter =
+ {
+ .name = "EFI GOP driver",
+
+ .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,
+ .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,
+ .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,
+
+ .next = 0
+ };
+
+GRUB_MOD_INIT(efi_fb)
+{
+ if (check_protocol ())
+ grub_video_register (&grub_video_gop_adapter);
+}
+
+GRUB_MOD_FINI(efi_fb)
+{
+ if (restore_needed)
+ {
+ efi_call_2 (gop->set_mode, gop, old_mode);
+ restore_needed = 0;
+ }
+ if (gop)
+ grub_video_unregister (&grub_video_gop_adapter);
+}