]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Add support for EFI_GRAPHICS_OUTPUT_PROTOCOL frame buffer consoles
authorMichael Brown <mcb30@ipxe.org>
Mon, 5 Oct 2015 18:19:26 +0000 (19:19 +0100)
committerMichael Brown <mcb30@ipxe.org>
Fri, 16 Oct 2015 15:38:41 +0000 (16:38 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/config/config_efi.c
src/image/efi_image.c
src/include/ipxe/errfile.h
src/interface/efi/efi_fbcon.c [new file with mode: 0644]
src/interface/efi/efi_snp.c

index ece6eaa3fe1ca21a6d998f896f06806a0f1dda96..1f73dad4db6649b82a49e2ba5b19dc7c68505cfa 100644 (file)
@@ -39,3 +39,9 @@ PROVIDE_REQUIRING_SYMBOL();
 #ifdef CONSOLE_EFI
 REQUIRE_OBJECT ( efi_console );
 #endif
+#ifdef CONSOLE_EFIFB
+REQUIRE_OBJECT ( efi_fbcon );
+#endif
+#ifdef CONSOLE_FRAMEBUFFER
+REQUIRE_OBJECT ( efi_fbcon );
+#endif
index 79dfe0664e84338ae77abd8680e0642eb9220827..47580c0dbb8d1a834bae7507b50629174c1c24be 100644 (file)
@@ -34,6 +34,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/init.h>
 #include <ipxe/features.h>
 #include <ipxe/uri.h>
+#include <ipxe/console.h>
 
 FEATURE ( FEATURE_IMAGE, "EFI", DHCP_EB_FEATURE_EFI, 1 );
 
@@ -236,6 +237,9 @@ static int efi_image_exec ( struct image *image ) {
        /* Wrap calls made by the loaded image (for debugging) */
        efi_wrap ( handle );
 
+       /* Reset console since image will probably use it */
+       console_reset();
+
        /* Start the image */
        if ( ( efirc = bs->StartImage ( handle, NULL, NULL ) ) != 0 ) {
                rc = -EEFI_START ( efirc );
index 6c4dcf503265868e7c165fb2dbdf0b85a886b564..651adbaee442583fec54e38bf10542afdd4ffd52 100644 (file)
@@ -342,6 +342,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_efi_watchdog         ( ERRFILE_OTHER | 0x00490000 )
 #define ERRFILE_efi_pxe                      ( ERRFILE_OTHER | 0x004a0000 )
 #define ERRFILE_efi_usb                      ( ERRFILE_OTHER | 0x004b0000 )
+#define ERRFILE_efi_fbcon            ( ERRFILE_OTHER | 0x004c0000 )
 
 /** @} */
 
diff --git a/src/interface/efi/efi_fbcon.c b/src/interface/efi/efi_fbcon.c
new file mode 100644 (file)
index 0000000..01b691b
--- /dev/null
@@ -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,
+};
index 09a1bf4c7372090eb6ea573f3d7bf8fededc0437..4508967e628940872fa45701fc625b09d2d79f5b 100644 (file)
@@ -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();