| /* |
| * 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 |
| |
| /** Number of ASCII glyphs in cache */ |
| #define EFIFB_ASCII 128 |
| |
| /** Number of dynamic non-ASCII glyphs in cache */ |
| #define EFIFB_DYNAMIC 32 |
| |
| /* 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 glyph cache */ |
| userptr_t glyphs; |
| /** Dynamic characters in cache */ |
| unsigned int dynamic[EFIFB_DYNAMIC]; |
| /** Next dynamic character cache entry to evict */ |
| unsigned int next; |
| }; |
| |
| /** The EFI frame buffer */ |
| static struct efifb efifb; |
| |
| /** |
| * Draw character glyph |
| * |
| * @v character Character |
| * @v index Index within glyph cache |
| * @v toggle Bits to toggle in each bitmask |
| * @ret height Character height, or negative error |
| */ |
| static int efifb_draw ( unsigned int character, unsigned int index, |
| unsigned int toggle ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_IMAGE_OUTPUT *blt; |
| EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel; |
| unsigned int height; |
| unsigned int x; |
| unsigned int y; |
| uint8_t bitmask; |
| size_t offset; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Clear existing glyph */ |
| offset = ( index * efifb.font.height ); |
| memset_user ( efifb.glyphs, offset, 0, efifb.font.height ); |
| |
| /* Get glyph */ |
| blt = NULL; |
| if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont, character, |
| NULL, &blt, NULL ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( &efifb, "EFIFB could not get glyph %#02x: %s\n", |
| character, strerror ( rc ) ); |
| goto err_get; |
| } |
| assert ( blt != NULL ); |
| |
| /* Sanity check */ |
| if ( blt->Width > 8 ) { |
| DBGC ( &efifb, "EFIFB glyph %#02x invalid width %d\n", |
| character, blt->Width ); |
| rc = -EINVAL; |
| goto err_width; |
| } |
| |
| /* Convert glyph to bitmap */ |
| pixel = blt->Image.Bitmap; |
| height = blt->Height; |
| for ( y = 0 ; ( ( y < height ) && ( y < efifb.font.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++; |
| } |
| bitmask ^= toggle; |
| copy_to_user ( efifb.glyphs, offset++, &bitmask, |
| sizeof ( bitmask ) ); |
| } |
| |
| /* Free glyph */ |
| bs->FreePool ( blt ); |
| |
| return height; |
| |
| err_width: |
| bs->FreePool ( blt ); |
| err_get: |
| return rc; |
| } |
| |
| /** |
| * Draw "unknown character" glyph |
| * |
| * @v index Index within glyph cache |
| * @ret rc Return status code |
| */ |
| static int efifb_draw_unknown ( unsigned int index ) { |
| |
| /* Draw an inverted '?' glyph */ |
| return efifb_draw ( '?', index, -1U ); |
| } |
| |
| /** |
| * Get dynamic glyph index |
| * |
| * @v character Unicode character |
| * @ret index Glyph cache index |
| */ |
| static unsigned int efifb_dynamic ( unsigned int character ) { |
| unsigned int dynamic; |
| unsigned int index; |
| unsigned int i; |
| int height; |
| |
| /* Search existing cached entries */ |
| for ( i = 0 ; i < EFIFB_DYNAMIC ; i++ ) { |
| if ( character == efifb.dynamic[i] ) |
| return ( EFIFB_ASCII + i ); |
| } |
| |
| /* Overwrite the oldest cache entry */ |
| dynamic = ( efifb.next++ % EFIFB_DYNAMIC ); |
| index = ( EFIFB_ASCII + dynamic ); |
| DBGC2 ( &efifb, "EFIFB dynamic %#02x is glyph %#02x\n", |
| dynamic, character ); |
| |
| /* Draw glyph */ |
| height = efifb_draw ( character, index, 0 ); |
| if ( height < 0 ) |
| efifb_draw_unknown ( index ); |
| |
| /* Record cached character */ |
| efifb.dynamic[dynamic] = character; |
| |
| return index; |
| } |
| |
| /** |
| * Get character glyph |
| * |
| * @v character Unicode character |
| * @v glyph Character glyph to fill in |
| */ |
| static void efifb_glyph ( unsigned int character, uint8_t *glyph ) { |
| unsigned int index; |
| size_t offset; |
| |
| /* Identify glyph */ |
| if ( character < EFIFB_ASCII ) { |
| |
| /* ASCII character: use fixed cache entry */ |
| index = character; |
| |
| } else { |
| |
| /* Non-ASCII character: use dynamic glyph cache */ |
| index = efifb_dynamic ( character ); |
| } |
| |
| /* Copy cached glyph */ |
| offset = ( index * 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 ) { |
| unsigned int character; |
| int height; |
| int max; |
| size_t len; |
| int rc; |
| |
| /* Get font height. The GetFontInfo() call nominally returns |
| * this information in an EFI_FONT_DISPLAY_INFO structure, but |
| * is known to fail on many UEFI implementations. Instead, we |
| * iterate over all printable characters to find the maximum |
| * height. |
| */ |
| efifb.font.height = 0; |
| max = 0; |
| for ( character = 0 ; character < EFIFB_ASCII ; character++ ) { |
| |
| /* Skip non-printable characters */ |
| if ( ! isprint ( character ) ) |
| continue; |
| |
| /* Get glyph */ |
| height = efifb_draw ( character, 0, 0 ); |
| if ( height < 0 ) { |
| rc = height; |
| goto err_height; |
| } |
| |
| /* Calculate maximum height */ |
| if ( max < height ) |
| max = height; |
| } |
| if ( ! max ) { |
| DBGC ( &efifb, "EFIFB could not get font height\n" ); |
| return -ENOENT; |
| } |
| efifb.font.height = max; |
| |
| /* Allocate glyph data */ |
| len = ( ( EFIFB_ASCII + EFIFB_DYNAMIC ) * efifb.font.height ); |
| 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 < EFIFB_ASCII ; character++ ) { |
| |
| /* Skip non-printable characters */ |
| if ( ! isprint ( character ) ) { |
| efifb_draw_unknown ( character ); |
| continue; |
| } |
| |
| /* Get glyph */ |
| height = efifb_draw ( character, character, 0 ); |
| if ( height < 0 ) { |
| rc = height; |
| goto err_draw; |
| } |
| } |
| |
| /* Clear dynamic glyph character cache */ |
| memset ( efifb.dynamic, 0, sizeof ( efifb.dynamic ) ); |
| |
| efifb.font.glyph = efifb_glyph; |
| return 0; |
| |
| err_draw: |
| ufree ( efifb.glyphs ); |
| err_alloc: |
| err_height: |
| 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, |
| }; |