iPXE
efi_fbcon.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 /**
00027  * @file
00028  *
00029  * EFI frame buffer console
00030  *
00031  */
00032 
00033 #include <string.h>
00034 #include <strings.h>
00035 #include <ctype.h>
00036 #include <errno.h>
00037 #include <assert.h>
00038 #include <limits.h>
00039 #include <ipxe/efi/efi.h>
00040 #include <ipxe/efi/Protocol/GraphicsOutput.h>
00041 #include <ipxe/efi/Protocol/HiiFont.h>
00042 #include <ipxe/ansicol.h>
00043 #include <ipxe/fbcon.h>
00044 #include <ipxe/console.h>
00045 #include <ipxe/umalloc.h>
00046 #include <ipxe/rotate.h>
00047 #include <config/console.h>
00048 
00049 /* Avoid dragging in EFI console if not otherwise used */
00050 extern struct console_driver efi_console;
00051 struct console_driver efi_console __attribute__ (( weak ));
00052 
00053 /* Set default console usage if applicable
00054  *
00055  * We accept either CONSOLE_FRAMEBUFFER or CONSOLE_EFIFB.
00056  */
00057 #if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_EFIFB ) )
00058 #define CONSOLE_EFIFB CONSOLE_FRAMEBUFFER
00059 #endif
00060 #if ! ( defined ( CONSOLE_EFIFB ) && CONSOLE_EXPLICIT ( CONSOLE_EFIFB ) )
00061 #undef CONSOLE_EFIFB
00062 #define CONSOLE_EFIFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
00063 #endif
00064 
00065 /* Forward declaration */
00066 struct console_driver efifb_console __console_driver;
00067 
00068 /** An EFI frame buffer */
00069 struct efifb {
00070         /** EFI graphics output protocol */
00071         EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
00072         /** EFI HII font protocol */
00073         EFI_HII_FONT_PROTOCOL *hiifont;
00074         /** Saved mode */
00075         UINT32 saved_mode;
00076 
00077         /** Frame buffer console */
00078         struct fbcon fbcon;
00079         /** Physical start address */
00080         physaddr_t start;
00081         /** Pixel geometry */
00082         struct fbcon_geometry pixel;
00083         /** Colour mapping */
00084         struct fbcon_colour_map map;
00085         /** Font definition */
00086         struct fbcon_font font;
00087         /** Character glyphs */
00088         userptr_t glyphs;
00089 };
00090 
00091 /** The EFI frame buffer */
00092 static struct efifb efifb;
00093 
00094 /**
00095  * Get character glyph
00096  *
00097  * @v character         Character
00098  * @v glyph             Character glyph to fill in
00099  */
00100 static void efifb_glyph ( unsigned int character, uint8_t *glyph ) {
00101         size_t offset = ( character * efifb.font.height );
00102 
00103         copy_from_user ( glyph, efifb.glyphs, offset, efifb.font.height );
00104 }
00105 
00106 /**
00107  * Get character glyphs
00108  *
00109  * @ret rc              Return status code
00110  */
00111 static int efifb_glyphs ( void ) {
00112         EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
00113         EFI_IMAGE_OUTPUT *blt;
00114         EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel;
00115         size_t offset;
00116         size_t len;
00117         uint8_t bitmask;
00118         unsigned int character;
00119         unsigned int x;
00120         unsigned int y;
00121         EFI_STATUS efirc;
00122         int rc;
00123 
00124         /* Get font height.  The GetFontInfo() call nominally returns
00125          * this information in an EFI_FONT_DISPLAY_INFO structure, but
00126          * is known to fail on many UEFI implementations.  Instead, we
00127          * iterate over all printable characters to find the maximum
00128          * height.
00129          */
00130         efifb.font.height = 0;
00131         for ( character = 0 ; character < 256 ; character++ ) {
00132 
00133                 /* Skip non-printable characters */
00134                 if ( ! isprint ( character ) )
00135                         continue;
00136 
00137                 /* Get glyph */
00138                 blt = NULL;
00139                 if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont,
00140                                                          character, NULL, &blt,
00141                                                          NULL ) ) != 0 ) {
00142                         rc = -EEFI ( efirc );
00143                         DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n",
00144                                character, strerror ( rc ) );
00145                         continue;
00146                 }
00147                 assert ( blt != NULL );
00148 
00149                 /* Calculate maximum height */
00150                 if ( efifb.font.height < blt->Height )
00151                         efifb.font.height = blt->Height;
00152 
00153                 /* Free glyph */
00154                 bs->FreePool ( blt );
00155         }
00156         if ( ! efifb.font.height ) {
00157                 DBGC ( &efifb, "EFIFB could not get font height\n" );
00158                 return -ENOENT;
00159         }
00160 
00161         /* Allocate glyph data */
00162         len = ( 256 * efifb.font.height * sizeof ( bitmask ) );
00163         efifb.glyphs = umalloc ( len );
00164         if ( ! efifb.glyphs ) {
00165                 rc = -ENOMEM;
00166                 goto err_alloc;
00167         }
00168         memset_user ( efifb.glyphs, 0, 0, len );
00169 
00170         /* Get font data */
00171         for ( character = 0 ; character < 256 ; character++ ) {
00172 
00173                 /* Skip non-printable characters */
00174                 if ( ! isprint ( character ) )
00175                         continue;
00176 
00177                 /* Get glyph */
00178                 blt = NULL;
00179                 if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont,
00180                                                          character, NULL, &blt,
00181                                                          NULL ) ) != 0 ) {
00182                         rc = -EEFI ( efirc );
00183                         DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n",
00184                                character, strerror ( rc ) );
00185                         continue;
00186                 }
00187                 assert ( blt != NULL );
00188 
00189                 /* Sanity check */
00190                 if ( blt->Width > 8 ) {
00191                         DBGC ( &efifb, "EFIFB glyph %d invalid width %d\n",
00192                                character, blt->Width );
00193                         continue;
00194                 }
00195                 if ( blt->Height > efifb.font.height ) {
00196                         DBGC ( &efifb, "EFIFB glyph %d invalid height %d\n",
00197                                character, blt->Height );
00198                         continue;
00199                 }
00200 
00201                 /* Convert glyph to bitmap */
00202                 pixel = blt->Image.Bitmap;
00203                 offset = ( character * efifb.font.height );
00204                 for ( y = 0 ; y < blt->Height ; y++ ) {
00205                         bitmask = 0;
00206                         for ( x = 0 ; x < blt->Width ; x++ ) {
00207                                 bitmask = rol8 ( bitmask, 1 );
00208                                 if ( pixel->Blue || pixel->Green || pixel->Red )
00209                                         bitmask |= 0x01;
00210                                 pixel++;
00211                         }
00212                         copy_to_user ( efifb.glyphs, offset++, &bitmask,
00213                                        sizeof ( bitmask ) );
00214                 }
00215 
00216                 /* Free glyph */
00217                 bs->FreePool ( blt );
00218         }
00219 
00220         efifb.font.glyph = efifb_glyph;
00221         return 0;
00222 
00223         ufree ( efifb.glyphs );
00224  err_alloc:
00225         return rc;
00226 }
00227 
00228 /**
00229  * Generate colour mapping for a single colour component
00230  *
00231  * @v mask              Mask value
00232  * @v scale             Scale value to fill in
00233  * @v lsb               LSB value to fill in
00234  * @ret rc              Return status code
00235  */
00236 static int efifb_colour_map_mask ( uint32_t mask, uint8_t *scale,
00237                                    uint8_t *lsb ) {
00238         uint32_t check;
00239 
00240         /* Fill in LSB and scale */
00241         *lsb = ( mask ? ( ffs ( mask ) - 1 ) : 0 );
00242         *scale = ( mask ? ( 8 - ( fls ( mask ) - *lsb ) ) : 8 );
00243 
00244         /* Check that original mask was contiguous */
00245         check = ( ( 0xff >> *scale ) << *lsb );
00246         if ( check != mask )
00247                 return -ENOTSUP;
00248 
00249         return 0;
00250 }
00251 
00252 /**
00253  * Generate colour mapping
00254  *
00255  * @v info              EFI mode information
00256  * @v map               Colour mapping to fill in
00257  * @ret bpp             Number of bits per pixel, or negative error
00258  */
00259 static int efifb_colour_map ( EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info,
00260                               struct fbcon_colour_map *map ) {
00261         static EFI_PIXEL_BITMASK rgb_mask = {
00262                 0x000000ffUL, 0x0000ff00UL, 0x00ff0000UL, 0xff000000UL
00263         };
00264         static EFI_PIXEL_BITMASK bgr_mask = {
00265                 0x00ff0000UL, 0x0000ff00UL, 0x000000ffUL, 0xff000000UL
00266         };
00267         EFI_PIXEL_BITMASK *mask;
00268         uint8_t reserved_scale;
00269         uint8_t reserved_lsb;
00270         int rc;
00271 
00272         /* Determine applicable mask */
00273         switch ( info->PixelFormat ) {
00274         case PixelRedGreenBlueReserved8BitPerColor:
00275                 mask = &rgb_mask;
00276                 break;
00277         case PixelBlueGreenRedReserved8BitPerColor:
00278                 mask = &bgr_mask;
00279                 break;
00280         case PixelBitMask:
00281                 mask = &info->PixelInformation;
00282                 break;
00283         default:
00284                 DBGC ( &efifb, "EFIFB unrecognised pixel format %d\n",
00285                        info->PixelFormat );
00286                 return -ENOTSUP;
00287         }
00288 
00289         /* Map each colour component */
00290         if ( ( rc = efifb_colour_map_mask ( mask->RedMask, &map->red_scale,
00291                                             &map->red_lsb ) ) != 0 )
00292                 return rc;
00293         if ( ( rc = efifb_colour_map_mask ( mask->GreenMask, &map->green_scale,
00294                                             &map->green_lsb ) ) != 0 )
00295                 return rc;
00296         if ( ( rc = efifb_colour_map_mask ( mask->BlueMask, &map->blue_scale,
00297                                             &map->blue_lsb ) ) != 0 )
00298                 return rc;
00299         if ( ( rc = efifb_colour_map_mask ( mask->ReservedMask, &reserved_scale,
00300                                             &reserved_lsb ) ) != 0 )
00301                 return rc;
00302 
00303         /* Calculate total number of bits per pixel */
00304         return ( 32 - ( reserved_scale + map->red_scale + map->green_scale +
00305                         map->blue_scale ) );
00306 }
00307 
00308 /**
00309  * Select video mode
00310  *
00311  * @v min_width         Minimum required width (in pixels)
00312  * @v min_height        Minimum required height (in pixels)
00313  * @v min_bpp           Minimum required colour depth (in bits per pixel)
00314  * @ret mode_number     Mode number, or negative error
00315  */
00316 static int efifb_select_mode ( unsigned int min_width, unsigned int min_height,
00317                                unsigned int min_bpp ) {
00318         EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
00319         struct fbcon_colour_map map;
00320         EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
00321         int best_mode_number = -ENOENT;
00322         unsigned int best_score = INT_MAX;
00323         unsigned int score;
00324         unsigned int mode;
00325         int bpp;
00326         UINTN size;
00327         EFI_STATUS efirc;
00328         int rc;
00329 
00330         /* Find the best mode */
00331         for ( mode = 0 ; mode < efifb.gop->Mode->MaxMode ; mode++ ) {
00332 
00333                 /* Get mode information */
00334                 if ( ( efirc = efifb.gop->QueryMode ( efifb.gop, mode, &size,
00335                                                       &info ) ) != 0 ) {
00336                         rc = -EEFI ( efirc );
00337                         DBGC ( &efifb, "EFIFB could not get mode %d "
00338                                "information: %s\n", mode, strerror ( rc ) );
00339                         goto err_query;
00340                 }
00341 
00342                 /* Skip unusable modes */
00343                 bpp = efifb_colour_map ( info, &map );
00344                 if ( bpp < 0 ) {
00345                         rc = bpp;
00346                         DBGC ( &efifb, "EFIFB could not build colour map for "
00347                                "mode %d: %s\n", mode, strerror ( rc ) );
00348                         goto err_map;
00349                 }
00350 
00351                 /* Skip modes not meeting the requirements */
00352                 if ( ( info->HorizontalResolution < min_width ) ||
00353                      ( info->VerticalResolution < min_height ) ||
00354                      ( ( ( unsigned int ) bpp ) < min_bpp ) ) {
00355                         goto err_requirements;
00356                 }
00357 
00358                 /* Select this mode if it has the best (i.e. lowest)
00359                  * score.  We choose the scoring system to favour
00360                  * modes close to the specified width and height;
00361                  * within modes of the same width and height we prefer
00362                  * a higher colour depth.
00363                  */
00364                 score = ( ( info->HorizontalResolution *
00365                             info->VerticalResolution ) - bpp );
00366                 if ( score < best_score ) {
00367                         best_mode_number = mode;
00368                         best_score = score;
00369                 }
00370 
00371         err_requirements:
00372         err_map:
00373                 bs->FreePool ( info );
00374         err_query:
00375                 continue;
00376         }
00377 
00378         if ( best_mode_number < 0 )
00379                 DBGC ( &efifb, "EFIFB found no suitable mode\n" );
00380         return best_mode_number;
00381 }
00382 
00383 /**
00384  * Restore video mode
00385  *
00386  * @v rc                Return status code
00387  */
00388 static int efifb_restore ( void ) {
00389         EFI_STATUS efirc;
00390         int rc;
00391 
00392         /* Restore original mode */
00393         if ( ( efirc = efifb.gop->SetMode ( efifb.gop,
00394                                             efifb.saved_mode ) ) != 0 ) {
00395                 rc = -EEFI ( efirc );
00396                 DBGC ( &efifb, "EFIFB could not restore mode %d: %s\n",
00397                        efifb.saved_mode, strerror ( rc ) );
00398                 return rc;
00399         }
00400 
00401         return 0;
00402 }
00403 
00404 /**
00405  * Initialise EFI frame buffer
00406  *
00407  * @v config            Console configuration, or NULL to reset
00408  * @ret rc              Return status code
00409  */
00410 static int efifb_init ( struct console_configuration *config ) {
00411         EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
00412         EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
00413         void *interface;
00414         int mode;
00415         int bpp;
00416         EFI_STATUS efirc;
00417         int rc;
00418 
00419         /* Locate graphics output protocol */
00420         if ( ( efirc = bs->LocateProtocol ( &efi_graphics_output_protocol_guid,
00421                                             NULL, &interface ) ) != 0 ) {
00422                 rc = -EEFI ( efirc );
00423                 DBGC ( &efifb, "EFIFB could not locate graphics output "
00424                        "protocol: %s\n", strerror ( rc ) );
00425                 goto err_locate_gop;
00426         }
00427         efifb.gop = interface;
00428 
00429         /* Locate HII font protocol */
00430         if ( ( efirc = bs->LocateProtocol ( &efi_hii_font_protocol_guid,
00431                                             NULL, &interface ) ) != 0 ) {
00432                 rc = -EEFI ( efirc );
00433                 DBGC ( &efifb, "EFIFB could not locate HII font protocol: %s\n",
00434                        strerror ( rc ) );
00435                 goto err_locate_hiifont;
00436         }
00437         efifb.hiifont = interface;
00438 
00439         /* Locate glyphs */
00440         if ( ( rc = efifb_glyphs() ) != 0 )
00441                 goto err_glyphs;
00442 
00443         /* Save original mode */
00444         efifb.saved_mode = efifb.gop->Mode->Mode;
00445 
00446         /* Select mode */
00447         if ( ( mode = efifb_select_mode ( config->width, config->height,
00448                                           config->depth ) ) < 0 ) {
00449                 rc = mode;
00450                 goto err_select_mode;
00451         }
00452 
00453         /* Set mode */
00454         if ( ( efirc = efifb.gop->SetMode ( efifb.gop, mode ) ) != 0 ) {
00455                 rc = -EEFI ( efirc );
00456                 DBGC ( &efifb, "EFIFB could not set mode %d: %s\n",
00457                        mode, strerror ( rc ) );
00458                 goto err_set_mode;
00459         }
00460         info = efifb.gop->Mode->Info;
00461 
00462         /* Populate colour map */
00463         bpp = efifb_colour_map ( info, &efifb.map );
00464         if ( bpp < 0 ) {
00465                 rc = bpp;
00466                 DBGC ( &efifb, "EFIFB could not build colour map for "
00467                        "mode %d: %s\n", mode, strerror ( rc ) );
00468                 goto err_map;
00469         }
00470 
00471         /* Populate pixel geometry */
00472         efifb.pixel.width = info->HorizontalResolution;
00473         efifb.pixel.height = info->VerticalResolution;
00474         efifb.pixel.len = ( ( bpp + 7 ) / 8 );
00475         efifb.pixel.stride = ( efifb.pixel.len * info->PixelsPerScanLine );
00476 
00477         /* Populate frame buffer address */
00478         efifb.start = efifb.gop->Mode->FrameBufferBase;
00479         DBGC ( &efifb, "EFIFB using mode %d (%dx%d %dbpp at %#08lx)\n",
00480                mode, efifb.pixel.width, efifb.pixel.height, bpp, efifb.start );
00481 
00482         /* Initialise frame buffer console */
00483         if ( ( rc = fbcon_init ( &efifb.fbcon, phys_to_user ( efifb.start ),
00484                                  &efifb.pixel, &efifb.map, &efifb.font,
00485                                  config ) ) != 0 )
00486                 goto err_fbcon_init;
00487 
00488         return 0;
00489 
00490         fbcon_fini ( &efifb.fbcon );
00491  err_fbcon_init:
00492  err_map:
00493         efifb_restore();
00494  err_set_mode:
00495  err_select_mode:
00496         ufree ( efifb.glyphs );
00497  err_glyphs:
00498  err_locate_hiifont:
00499  err_locate_gop:
00500         return rc;
00501 }
00502 
00503 /**
00504  * Finalise EFI frame buffer
00505  *
00506  */
00507 static void efifb_fini ( void ) {
00508 
00509         /* Finalise frame buffer console */
00510         fbcon_fini ( &efifb.fbcon );
00511 
00512         /* Restore saved mode */
00513         efifb_restore();
00514 
00515         /* Free glyphs */
00516         ufree ( efifb.glyphs );
00517 }
00518 
00519 /**
00520  * Print a character to current cursor position
00521  *
00522  * @v character         Character
00523  */
00524 static void efifb_putchar ( int character ) {
00525 
00526         fbcon_putchar ( &efifb.fbcon, character );
00527 }
00528 
00529 /**
00530  * Configure console
00531  *
00532  * @v config            Console configuration, or NULL to reset
00533  * @ret rc              Return status code
00534  */
00535 static int efifb_configure ( struct console_configuration *config ) {
00536         int rc;
00537 
00538         /* Reset console, if applicable */
00539         if ( ! efifb_console.disabled ) {
00540                 efifb_fini();
00541                 efi_console.disabled &= ~CONSOLE_DISABLED_OUTPUT;
00542                 ansicol_reset_magic();
00543         }
00544         efifb_console.disabled = CONSOLE_DISABLED;
00545 
00546         /* Do nothing more unless we have a usable configuration */
00547         if ( ( config == NULL ) ||
00548              ( config->width == 0 ) || ( config->height == 0 ) ) {
00549                 return 0;
00550         }
00551 
00552         /* Initialise EFI frame buffer */
00553         if ( ( rc = efifb_init ( config ) ) != 0 )
00554                 return rc;
00555 
00556         /* Mark console as enabled */
00557         efifb_console.disabled = 0;
00558         efi_console.disabled |= CONSOLE_DISABLED_OUTPUT;
00559 
00560         /* Set magic colour to transparent if we have a background picture */
00561         if ( config->pixbuf )
00562                 ansicol_set_magic_transparent();
00563 
00564         return 0;
00565 }
00566 
00567 /** EFI graphics output protocol console driver */
00568 struct console_driver efifb_console __console_driver = {
00569         .usage = CONSOLE_EFIFB,
00570         .putchar = efifb_putchar,
00571         .configure = efifb_configure,
00572         .disabled = CONSOLE_DISABLED,
00573 };