summaryrefslogtreecommitdiffstats
path: root/src/interface/efi
diff options
context:
space:
mode:
authorMichael Brown2015-10-05 20:19:26 +0200
committerMichael Brown2015-10-16 17:38:41 +0200
commit6847232e70f7fbff2f5637505a85532ec752a11f (patch)
tree40e47f2893a95da42b8ab0eeaf770c5abd399870 /src/interface/efi
parent[build] Generalise CONSOLE_VESAFB to CONSOLE_FRAMEBUFFER (diff)
downloadipxe-6847232e70f7fbff2f5637505a85532ec752a11f.tar.gz
ipxe-6847232e70f7fbff2f5637505a85532ec752a11f.tar.xz
ipxe-6847232e70f7fbff2f5637505a85532ec752a11f.zip
[efi] Add support for EFI_GRAPHICS_OUTPUT_PROTOCOL frame buffer consoles
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/interface/efi')
-rw-r--r--src/interface/efi/efi_fbcon.c551
-rw-r--r--src/interface/efi/efi_snp.c4
2 files changed, 555 insertions, 0 deletions
diff --git a/src/interface/efi/efi_fbcon.c b/src/interface/efi/efi_fbcon.c
new file mode 100644
index 00000000..01b691b6
--- /dev/null
+++ b/src/interface/efi/efi_fbcon.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program 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 2 of the
+ * License, or any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/**
+ * @file
+ *
+ * EFI frame buffer console
+ *
+ */
+
+#include <string.h>
+#include <strings.h>
+#include <ctype.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/Protocol/GraphicsOutput.h>
+#include <ipxe/efi/Protocol/HiiFont.h>
+#include <ipxe/ansicol.h>
+#include <ipxe/fbcon.h>
+#include <ipxe/console.h>
+#include <ipxe/umalloc.h>
+#include <ipxe/rotate.h>
+#include <config/console.h>
+
+/* Avoid dragging in EFI console if not otherwise used */
+extern struct console_driver efi_console;
+struct console_driver efi_console __attribute__ (( weak ));
+
+/* Set default console usage if applicable
+ *
+ * We accept either CONSOLE_FRAMEBUFFER or CONSOLE_EFIFB.
+ */
+#if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_EFIFB ) )
+#define CONSOLE_EFIFB CONSOLE_FRAMEBUFFER
+#endif
+#if ! ( defined ( CONSOLE_EFIFB ) && CONSOLE_EXPLICIT ( CONSOLE_EFIFB ) )
+#undef CONSOLE_EFIFB
+#define CONSOLE_EFIFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
+#endif
+
+/* Forward declaration */
+struct console_driver efifb_console __console_driver;
+
+/** An EFI frame buffer */
+struct efifb {
+ /** EFI graphics output protocol */
+ EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
+ /** EFI HII font protocol */
+ EFI_HII_FONT_PROTOCOL *hiifont;
+ /** Saved mode */
+ UINT32 saved_mode;
+
+ /** Frame buffer console */
+ struct fbcon fbcon;
+ /** Physical start address */
+ physaddr_t start;
+ /** Pixel geometry */
+ struct fbcon_geometry pixel;
+ /** Colour mapping */
+ struct fbcon_colour_map map;
+ /** Font definition */
+ struct fbcon_font font;
+ /** Character glyphs */
+ userptr_t glyphs;
+};
+
+/** The EFI frame buffer */
+static struct efifb efifb;
+
+/**
+ * Get character glyph
+ *
+ * @v character Character
+ * @v glyph Character glyph to fill in
+ */
+static void efifb_glyph ( unsigned int character, uint8_t *glyph ) {
+ size_t offset = ( character * efifb.font.height );
+
+ copy_from_user ( glyph, efifb.glyphs, offset, efifb.font.height );
+}
+
+/**
+ * Get character glyphs
+ *
+ * @ret rc Return status code
+ */
+static int efifb_glyphs ( void ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_FONT_DISPLAY_INFO *info;
+ EFI_IMAGE_OUTPUT *blt;
+ EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel;
+ size_t offset;
+ size_t len;
+ uint8_t bitmask;
+ unsigned int character;
+ unsigned int x;
+ unsigned int y;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Get font height */
+ if ( ( efirc = efifb.hiifont->GetFontInfo ( efifb.hiifont, NULL, NULL,
+ &info, NULL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not get font information: %s\n",
+ strerror ( rc ) );
+ goto err_info;
+ }
+ assert ( info != NULL );
+ efifb.font.height = info->FontInfo.FontSize;
+
+ /* Allocate glyph data */
+ len = ( 256 * efifb.font.height * sizeof ( bitmask ) );
+ efifb.glyphs = umalloc ( len );
+ if ( ! efifb.glyphs ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ memset_user ( efifb.glyphs, 0, 0, len );
+
+ /* Get font data */
+ for ( character = 0 ; character < 256 ; character++ ) {
+
+ /* Skip non-printable characters */
+ if ( ! isprint ( character ) )
+ continue;
+
+ /* Get glyph */
+ blt = NULL;
+ if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont,
+ character, info, &blt,
+ NULL ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n",
+ character, strerror ( rc ) );
+ continue;
+ }
+ assert ( blt != NULL );
+
+ /* Sanity check */
+ if ( blt->Width > 8 ) {
+ DBGC ( &efifb, "EFIFB glyph %d invalid width %d\n",
+ character, blt->Width );
+ continue;
+ }
+ if ( blt->Height > efifb.font.height ) {
+ DBGC ( &efifb, "EFIFB glyph %d invalid height %d\n",
+ character, blt->Height );
+ continue;
+ }
+
+ /* Convert glyph to bitmap */
+ pixel = blt->Image.Bitmap;
+ offset = ( character * efifb.font.height );
+ for ( y = 0 ; y < blt->Height ; y++ ) {
+ bitmask = 0;
+ for ( x = 0 ; x < blt->Width ; x++ ) {
+ bitmask = rol8 ( bitmask, 1 );
+ if ( pixel->Blue || pixel->Green || pixel->Red )
+ bitmask |= 0x01;
+ pixel++;
+ }
+ copy_to_user ( efifb.glyphs, offset++, &bitmask,
+ sizeof ( bitmask ) );
+ }
+ bs->FreePool ( blt );
+ }
+
+ /* Free font information */
+ bs->FreePool ( info );
+
+ efifb.font.glyph = efifb_glyph;
+ return 0;
+
+ ufree ( efifb.glyphs );
+ err_alloc:
+ bs->FreePool ( info );
+ err_info:
+ return rc;
+}
+
+/**
+ * Generate colour mapping for a single colour component
+ *
+ * @v mask Mask value
+ * @v scale Scale value to fill in
+ * @v lsb LSB value to fill in
+ * @ret rc Return status code
+ */
+static int efifb_colour_map_mask ( uint32_t mask, uint8_t *scale,
+ uint8_t *lsb ) {
+ uint32_t check;
+
+ /* Fill in LSB and scale */
+ *lsb = ( mask ? ( ffs ( mask ) - 1 ) : 0 );
+ *scale = ( mask ? ( 8 - ( fls ( mask ) - *lsb ) ) : 8 );
+
+ /* Check that original mask was contiguous */
+ check = ( ( 0xff >> *scale ) << *lsb );
+ if ( check != mask )
+ return -ENOTSUP;
+
+ return 0;
+}
+
+/**
+ * Generate colour mapping
+ *
+ * @v info EFI mode information
+ * @v map Colour mapping to fill in
+ * @ret bpp Number of bits per pixel, or negative error
+ */
+static int efifb_colour_map ( EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info,
+ struct fbcon_colour_map *map ) {
+ static EFI_PIXEL_BITMASK rgb_mask = {
+ 0x000000ffUL, 0x0000ff00UL, 0x00ff0000UL, 0xff000000UL
+ };
+ static EFI_PIXEL_BITMASK bgr_mask = {
+ 0x00ff0000UL, 0x0000ff00UL, 0x000000ffUL, 0xff000000UL
+ };
+ EFI_PIXEL_BITMASK *mask;
+ uint8_t reserved_scale;
+ uint8_t reserved_lsb;
+ int rc;
+
+ /* Determine applicable mask */
+ switch ( info->PixelFormat ) {
+ case PixelRedGreenBlueReserved8BitPerColor:
+ mask = &rgb_mask;
+ break;
+ case PixelBlueGreenRedReserved8BitPerColor:
+ mask = &bgr_mask;
+ break;
+ case PixelBitMask:
+ mask = &info->PixelInformation;
+ break;
+ default:
+ DBGC ( &efifb, "EFIFB unrecognised pixel format %d\n",
+ info->PixelFormat );
+ return -ENOTSUP;
+ }
+
+ /* Map each colour component */
+ if ( ( rc = efifb_colour_map_mask ( mask->RedMask, &map->red_scale,
+ &map->red_lsb ) ) != 0 )
+ return rc;
+ if ( ( rc = efifb_colour_map_mask ( mask->GreenMask, &map->green_scale,
+ &map->green_lsb ) ) != 0 )
+ return rc;
+ if ( ( rc = efifb_colour_map_mask ( mask->BlueMask, &map->blue_scale,
+ &map->blue_lsb ) ) != 0 )
+ return rc;
+ if ( ( rc = efifb_colour_map_mask ( mask->ReservedMask, &reserved_scale,
+ &reserved_lsb ) ) != 0 )
+ return rc;
+
+ /* Calculate total number of bits per pixel */
+ return ( 32 - ( reserved_scale + map->red_scale + map->green_scale +
+ map->blue_scale ) );
+}
+
+/**
+ * Select video mode
+ *
+ * @v min_width Minimum required width (in pixels)
+ * @v min_height Minimum required height (in pixels)
+ * @v min_bpp Minimum required colour depth (in bits per pixel)
+ * @ret mode_number Mode number, or negative error
+ */
+static int efifb_select_mode ( unsigned int min_width, unsigned int min_height,
+ unsigned int min_bpp ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct fbcon_colour_map map;
+ EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
+ int best_mode_number = -ENOENT;
+ unsigned int best_score = INT_MAX;
+ unsigned int score;
+ unsigned int mode;
+ int bpp;
+ UINTN size;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Find the best mode */
+ for ( mode = 0 ; mode < efifb.gop->Mode->MaxMode ; mode++ ) {
+
+ /* Get mode information */
+ if ( ( efirc = efifb.gop->QueryMode ( efifb.gop, mode, &size,
+ &info ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not get mode %d "
+ "information: %s\n", mode, strerror ( rc ) );
+ goto err_query;
+ }
+
+ /* Skip unusable modes */
+ bpp = efifb_colour_map ( info, &map );
+ if ( bpp < 0 ) {
+ rc = bpp;
+ DBGC ( &efifb, "EFIFB could not build colour map for "
+ "mode %d: %s\n", mode, strerror ( rc ) );
+ goto err_map;
+ }
+
+ /* Skip modes not meeting the requirements */
+ if ( ( info->HorizontalResolution < min_width ) ||
+ ( info->VerticalResolution < min_height ) ||
+ ( ( ( unsigned int ) bpp ) < min_bpp ) ) {
+ goto err_requirements;
+ }
+
+ /* Select this mode if it has the best (i.e. lowest)
+ * score. We choose the scoring system to favour
+ * modes close to the specified width and height;
+ * within modes of the same width and height we prefer
+ * a higher colour depth.
+ */
+ score = ( ( info->HorizontalResolution *
+ info->VerticalResolution ) - bpp );
+ if ( score < best_score ) {
+ best_mode_number = mode;
+ best_score = score;
+ }
+
+ err_requirements:
+ err_map:
+ bs->FreePool ( info );
+ err_query:
+ continue;
+ }
+
+ if ( best_mode_number < 0 )
+ DBGC ( &efifb, "EFIFB found no suitable mode\n" );
+ return best_mode_number;
+}
+
+/**
+ * Restore video mode
+ *
+ * @v rc Return status code
+ */
+static int efifb_restore ( void ) {
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Restore original mode */
+ if ( ( efirc = efifb.gop->SetMode ( efifb.gop,
+ efifb.saved_mode ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not restore mode %d: %s\n",
+ efifb.saved_mode, strerror ( rc ) );
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Initialise EFI frame buffer
+ *
+ * @v config Console configuration, or NULL to reset
+ * @ret rc Return status code
+ */
+static int efifb_init ( struct console_configuration *config ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
+ void *interface;
+ int mode;
+ int bpp;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Locate graphics output protocol */
+ if ( ( efirc = bs->LocateProtocol ( &efi_graphics_output_protocol_guid,
+ NULL, &interface ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not locate graphics output "
+ "protocol: %s\n", strerror ( rc ) );
+ goto err_locate_gop;
+ }
+ efifb.gop = interface;
+
+ /* Locate HII font protocol */
+ if ( ( efirc = bs->LocateProtocol ( &efi_hii_font_protocol_guid,
+ NULL, &interface ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not locate HII font protocol: %s\n",
+ strerror ( rc ) );
+ goto err_locate_hiifont;
+ }
+ efifb.hiifont = interface;
+
+ /* Locate glyphs */
+ if ( ( rc = efifb_glyphs() ) != 0 )
+ goto err_glyphs;
+
+ /* Save original mode */
+ efifb.saved_mode = efifb.gop->Mode->Mode;
+
+ /* Select mode */
+ if ( ( mode = efifb_select_mode ( config->width, config->height,
+ config->depth ) ) < 0 ) {
+ rc = mode;
+ goto err_select_mode;
+ }
+
+ /* Set mode */
+ if ( ( efirc = efifb.gop->SetMode ( efifb.gop, mode ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( &efifb, "EFIFB could not set mode %d: %s\n",
+ mode, strerror ( rc ) );
+ goto err_set_mode;
+ }
+ info = efifb.gop->Mode->Info;
+
+ /* Populate colour map */
+ bpp = efifb_colour_map ( info, &efifb.map );
+ if ( bpp < 0 ) {
+ rc = bpp;
+ DBGC ( &efifb, "EFIFB could not build colour map for "
+ "mode %d: %s\n", mode, strerror ( rc ) );
+ goto err_map;
+ }
+
+ /* Populate pixel geometry */
+ efifb.pixel.width = info->HorizontalResolution;
+ efifb.pixel.height = info->VerticalResolution;
+ efifb.pixel.len = ( ( bpp + 7 ) / 8 );
+ efifb.pixel.stride = ( efifb.pixel.len * info->PixelsPerScanLine );
+
+ /* Populate frame buffer address */
+ efifb.start = efifb.gop->Mode->FrameBufferBase;
+ DBGC ( &efifb, "EFIFB using mode %d (%dx%d %dbpp at %#08lx)\n",
+ mode, efifb.pixel.width, efifb.pixel.height, bpp, efifb.start );
+
+ /* Initialise frame buffer console */
+ if ( ( rc = fbcon_init ( &efifb.fbcon, phys_to_user ( efifb.start ),
+ &efifb.pixel, &efifb.map, &efifb.font,
+ config ) ) != 0 )
+ goto err_fbcon_init;
+
+ return 0;
+
+ fbcon_fini ( &efifb.fbcon );
+ err_fbcon_init:
+ err_map:
+ efifb_restore();
+ err_set_mode:
+ err_select_mode:
+ ufree ( efifb.glyphs );
+ err_glyphs:
+ err_locate_hiifont:
+ err_locate_gop:
+ return rc;
+}
+
+/**
+ * Finalise EFI frame buffer
+ *
+ */
+static void efifb_fini ( void ) {
+
+ /* Finalise frame buffer console */
+ fbcon_fini ( &efifb.fbcon );
+
+ /* Restore saved mode */
+ efifb_restore();
+
+ /* Free glyphs */
+ ufree ( efifb.glyphs );
+}
+
+/**
+ * Print a character to current cursor position
+ *
+ * @v character Character
+ */
+static void efifb_putchar ( int character ) {
+
+ fbcon_putchar ( &efifb.fbcon, character );
+}
+
+/**
+ * Configure console
+ *
+ * @v config Console configuration, or NULL to reset
+ * @ret rc Return status code
+ */
+static int efifb_configure ( struct console_configuration *config ) {
+ int rc;
+
+ /* Reset console, if applicable */
+ if ( ! efifb_console.disabled ) {
+ efifb_fini();
+ efi_console.disabled &= ~CONSOLE_DISABLED_OUTPUT;
+ ansicol_reset_magic();
+ }
+ efifb_console.disabled = CONSOLE_DISABLED;
+
+ /* Do nothing more unless we have a usable configuration */
+ if ( ( config == NULL ) ||
+ ( config->width == 0 ) || ( config->height == 0 ) ) {
+ return 0;
+ }
+
+ /* Initialise EFI frame buffer */
+ if ( ( rc = efifb_init ( config ) ) != 0 )
+ return rc;
+
+ /* Mark console as enabled */
+ efifb_console.disabled = 0;
+ efi_console.disabled |= CONSOLE_DISABLED_OUTPUT;
+
+ /* Set magic colour to transparent if we have a background picture */
+ if ( config->pixbuf )
+ ansicol_set_magic_transparent();
+
+ return 0;
+}
+
+/** EFI graphics output protocol console driver */
+struct console_driver efifb_console __console_driver = {
+ .usage = CONSOLE_EFIFB,
+ .putchar = efifb_putchar,
+ .configure = efifb_configure,
+ .disabled = CONSOLE_DISABLED,
+};
diff --git a/src/interface/efi/efi_snp.c b/src/interface/efi/efi_snp.c
index 09a1bf4c..4508967e 100644
--- a/src/interface/efi/efi_snp.c
+++ b/src/interface/efi/efi_snp.c
@@ -28,6 +28,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
#include <ipxe/iobuf.h>
#include <ipxe/in.h>
#include <ipxe/version.h>
+#include <ipxe/console.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_strings.h>
@@ -1495,6 +1496,9 @@ efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file,
if ( ( rc = ipxe ( netdev ) ) != 0 )
goto err_ipxe;
+ /* Reset console */
+ console_reset();
+
err_ipxe:
efi_watchdog_stop();
efi_snp_release();