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); +}