iPXE
vesafb.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2013 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 /** @file
00027  *
00028  * VESA frame buffer console
00029  *
00030  */
00031 
00032 #include <stdlib.h>
00033 #include <errno.h>
00034 #include <limits.h>
00035 #include <realmode.h>
00036 #include <ipxe/console.h>
00037 #include <ipxe/io.h>
00038 #include <ipxe/ansicol.h>
00039 #include <ipxe/fbcon.h>
00040 #include <ipxe/vesafb.h>
00041 #include <config/console.h>
00042 
00043 /* Avoid dragging in BIOS console if not otherwise used */
00044 extern struct console_driver bios_console;
00045 struct console_driver bios_console __attribute__ (( weak ));
00046 
00047 /* Disambiguate the various error causes */
00048 #define EIO_FAILED __einfo_error ( EINFO_EIO_FAILED )
00049 #define EINFO_EIO_FAILED                                                \
00050         __einfo_uniqify ( EINFO_EIO, 0x01,                              \
00051                           "Function call failed" )
00052 #define EIO_HARDWARE __einfo_error ( EINFO_EIO_HARDWARE )
00053 #define EINFO_EIO_HARDWARE                                              \
00054         __einfo_uniqify ( EINFO_EIO, 0x02,                              \
00055                           "Not supported in current configuration" )
00056 #define EIO_MODE __einfo_error ( EINFO_EIO_MODE )
00057 #define EINFO_EIO_MODE                                                  \
00058         __einfo_uniqify ( EINFO_EIO, 0x03,                              \
00059                           "Invalid in current video mode" )
00060 #define EIO_VBE( code )                                                 \
00061         EUNIQ ( EINFO_EIO, (code), EIO_FAILED, EIO_HARDWARE, EIO_MODE )
00062 
00063 /* Set default console usage if applicable
00064  *
00065  * We accept either CONSOLE_FRAMEBUFFER or CONSOLE_VESAFB.
00066  */
00067 #if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_VESAFB ) )
00068 #define CONSOLE_VESAFB CONSOLE_FRAMEBUFFER
00069 #endif
00070 #if ! ( defined ( CONSOLE_VESAFB ) && CONSOLE_EXPLICIT ( CONSOLE_VESAFB ) )
00071 #undef CONSOLE_VESAFB
00072 #define CONSOLE_VESAFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
00073 #endif
00074 
00075 /** Character height */
00076 #define VESAFB_CHAR_HEIGHT 16
00077 
00078 /** Font corresponding to selected character width and height */
00079 #define VESAFB_FONT VBE_FONT_8x16
00080 
00081 /* Forward declaration */
00082 struct console_driver vesafb_console __console_driver;
00083 
00084 /** A VESA frame buffer */
00085 struct vesafb {
00086         /** Frame buffer console */
00087         struct fbcon fbcon;
00088         /** Physical start address */
00089         physaddr_t start;
00090         /** Pixel geometry */
00091         struct fbcon_geometry pixel;
00092         /** Colour mapping */
00093         struct fbcon_colour_map map;
00094         /** Font definition */
00095         struct fbcon_font font;
00096         /** Character glyphs */
00097         struct segoff glyphs;
00098         /** Saved VGA mode */
00099         uint8_t saved_mode;
00100 };
00101 
00102 /** The VESA frame buffer */
00103 static struct vesafb vesafb;
00104 
00105 /** Base memory buffer used for VBE calls */
00106 union vbe_buffer {
00107         /** VBE controller information block */
00108         struct vbe_controller_info controller;
00109         /** VBE mode information block */
00110         struct vbe_mode_info mode;
00111 };
00112 static union vbe_buffer __bss16 ( vbe_buf );
00113 #define vbe_buf __use_data16 ( vbe_buf )
00114 
00115 /**
00116  * Convert VBE status code to iPXE status code
00117  *
00118  * @v status            VBE status code
00119  * @ret rc              Return status code
00120  */
00121 static int vesafb_rc ( unsigned int status ) {
00122         unsigned int code;
00123 
00124         if ( ( status & 0xff ) != 0x4f )
00125                 return -ENOTSUP;
00126         code = ( ( status >> 8 ) & 0xff );
00127         return ( code ? -EIO_VBE ( code ) : 0 );
00128 }
00129 
00130 /**
00131  * Get character glyph
00132  *
00133  * @v character         Character
00134  * @v glyph             Character glyph to fill in
00135  */
00136 static void vesafb_glyph ( unsigned int character, uint8_t *glyph ) {
00137         size_t offset = ( character * VESAFB_CHAR_HEIGHT );
00138 
00139         copy_from_real ( glyph, vesafb.glyphs.segment,
00140                          ( vesafb.glyphs.offset + offset ), VESAFB_CHAR_HEIGHT);
00141 }
00142 
00143 /**
00144  * Get font definition
00145  *
00146  */
00147 static void vesafb_font ( void ) {
00148 
00149         /* Get font information
00150          *
00151          * Working around gcc bugs is icky here.  The value we want is
00152          * returned in %ebp, but there's no way to specify %ebp in an
00153          * output constraint.  We can't put %ebp in the clobber list,
00154          * because this tends to cause random build failures on some
00155          * gcc versions.  We can't manually push/pop %ebp and return
00156          * the value via a generic register output constraint, because
00157          * gcc might choose to use %ebp to satisfy that constraint
00158          * (and we have no way to prevent it from so doing).
00159          *
00160          * Work around this hideous mess by using %ecx and %edx as the
00161          * output registers, since they get clobbered anyway.
00162          */
00163         __asm__ __volatile__ ( REAL_CODE ( "pushw %%bp\n\t" /* gcc bug */
00164                                            "int $0x10\n\t"
00165                                            "movw %%es, %%cx\n\t"
00166                                            "movw %%bp, %%dx\n\t"
00167                                            "popw %%bp\n\t" /* gcc bug */ )
00168                                : "=c" ( vesafb.glyphs.segment ),
00169                                  "=d" ( vesafb.glyphs.offset )
00170                                : "a" ( VBE_GET_FONT ),
00171                                  "b" ( VESAFB_FONT ) );
00172         DBGC ( &vbe_buf, "VESAFB has font %04x at %04x:%04x\n",
00173                VESAFB_FONT, vesafb.glyphs.segment, vesafb.glyphs.offset );
00174         vesafb.font.height = VESAFB_CHAR_HEIGHT;
00175         vesafb.font.glyph = vesafb_glyph;
00176 }
00177 
00178 /**
00179  * Get VBE mode list
00180  *
00181  * @ret mode_numbers    Mode number list (terminated with VBE_MODE_END)
00182  * @ret rc              Return status code
00183  *
00184  * The caller is responsible for eventually freeing the mode list.
00185  */
00186 static int vesafb_mode_list ( uint16_t **mode_numbers ) {
00187         struct vbe_controller_info *controller = &vbe_buf.controller;
00188         userptr_t video_mode_ptr;
00189         uint16_t mode_number;
00190         uint16_t status;
00191         size_t len;
00192         int rc;
00193 
00194         /* Avoid returning uninitialised data on error */
00195         *mode_numbers = NULL;
00196 
00197         /* Get controller information block */
00198         controller->vbe_signature = 0;
00199         __asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
00200                                : "=a" ( status )
00201                                : "a" ( VBE_CONTROLLER_INFO ),
00202                                  "D" ( __from_data16 ( controller ) )
00203                                : "memory", "ebx", "edx" );
00204         if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
00205                 DBGC ( &vbe_buf, "VESAFB could not get controller information: "
00206                        "[%04x] %s\n", status, strerror ( rc ) );
00207                 return rc;
00208         }
00209         if ( controller->vbe_signature != VBE_CONTROLLER_SIGNATURE ) {
00210                 DBGC ( &vbe_buf, "VESAFB invalid controller signature "
00211                        "\"%c%c%c%c\"\n", ( controller->vbe_signature >> 0 ),
00212                        ( controller->vbe_signature >> 8 ),
00213                        ( controller->vbe_signature >> 16 ),
00214                        ( controller->vbe_signature >> 24 ) );
00215                 DBGC_HDA ( &vbe_buf, 0, controller, sizeof ( *controller ) );
00216                 return -EINVAL;
00217         }
00218         DBGC ( &vbe_buf, "VESAFB found VBE version %d.%d with mode list at "
00219                "%04x:%04x\n", controller->vbe_major_version,
00220                controller->vbe_minor_version,
00221                controller->video_mode_ptr.segment,
00222                controller->video_mode_ptr.offset );
00223 
00224         /* Calculate length of mode list */
00225         video_mode_ptr = real_to_user ( controller->video_mode_ptr.segment,
00226                                         controller->video_mode_ptr.offset );
00227         len = 0;
00228         do {
00229                 copy_from_user ( &mode_number, video_mode_ptr, len,
00230                                  sizeof ( mode_number ) );
00231                 len += sizeof ( mode_number );
00232         } while ( mode_number != VBE_MODE_END );
00233 
00234         /* Allocate and fill mode list */
00235         *mode_numbers = malloc ( len );
00236         if ( ! *mode_numbers )
00237                 return -ENOMEM;
00238         copy_from_user ( *mode_numbers, video_mode_ptr, 0, len );
00239 
00240         return 0;
00241 }
00242 
00243 /**
00244  * Get video mode information
00245  *
00246  * @v mode_number       Mode number
00247  * @ret rc              Return status code
00248  */
00249 static int vesafb_mode_info ( unsigned int mode_number ) {
00250         struct vbe_mode_info *mode = &vbe_buf.mode;
00251         uint16_t status;
00252         int rc;
00253 
00254         /* Get mode information */
00255         __asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
00256                                : "=a" ( status )
00257                                : "a" ( VBE_MODE_INFO ),
00258                                  "c" ( mode_number ),
00259                                  "D" ( __from_data16 ( mode ) )
00260                                : "memory" );
00261         if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
00262                 DBGC ( &vbe_buf, "VESAFB could not get mode %04x information: "
00263                        "[%04x] %s\n", mode_number, status, strerror ( rc ) );
00264                 return rc;
00265         }
00266         DBGC ( &vbe_buf, "VESAFB mode %04x %dx%d %dbpp(%d:%d:%d:%d) model "
00267                "%02x [x%d]%s%s%s%s%s\n", mode_number, mode->x_resolution,
00268                mode->y_resolution, mode->bits_per_pixel, mode->rsvd_mask_size,
00269                mode->red_mask_size, mode->green_mask_size, mode->blue_mask_size,
00270                mode->memory_model, ( mode->number_of_image_pages + 1 ),
00271                ( ( mode->mode_attributes & VBE_MODE_ATTR_SUPPORTED ) ?
00272                  "" : " [unsupported]" ),
00273                ( ( mode->mode_attributes & VBE_MODE_ATTR_TTY ) ?
00274                  " [tty]" : "" ),
00275                ( ( mode->mode_attributes & VBE_MODE_ATTR_GRAPHICS ) ?
00276                  "" : " [text]" ),
00277                ( ( mode->mode_attributes & VBE_MODE_ATTR_LINEAR ) ?
00278                  "" : " [nonlinear]" ),
00279                ( ( mode->mode_attributes & VBE_MODE_ATTR_TRIPLE_BUF ) ?
00280                  " [buf]" : "" ) );
00281 
00282         return 0;
00283 }
00284 
00285 /**
00286  * Set video mode
00287  *
00288  * @v mode_number       Mode number
00289  * @ret rc              Return status code
00290  */
00291 static int vesafb_set_mode ( unsigned int mode_number ) {
00292         struct vbe_mode_info *mode = &vbe_buf.mode;
00293         uint16_t status;
00294         int rc;
00295 
00296         /* Get mode information */
00297         if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 )
00298                 return rc;
00299 
00300         /* Record mode parameters */
00301         vesafb.start = mode->phys_base_ptr;
00302         vesafb.pixel.width = mode->x_resolution;
00303         vesafb.pixel.height = mode->y_resolution;
00304         vesafb.pixel.len = ( ( mode->bits_per_pixel + 7 ) / 8 );
00305         vesafb.pixel.stride = mode->bytes_per_scan_line;
00306         DBGC ( &vbe_buf, "VESAFB mode %04x has frame buffer at %08x\n",
00307                mode_number, mode->phys_base_ptr );
00308 
00309         /* Initialise font colours */
00310         vesafb.map.red_scale = ( 8 - mode->red_mask_size );
00311         vesafb.map.green_scale = ( 8 - mode->green_mask_size );
00312         vesafb.map.blue_scale = ( 8 - mode->blue_mask_size );
00313         vesafb.map.red_lsb = mode->red_field_position;
00314         vesafb.map.green_lsb = mode->green_field_position;
00315         vesafb.map.blue_lsb = mode->blue_field_position;
00316 
00317         /* Select this mode */
00318         __asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
00319                                : "=a" ( status )
00320                                : "a" ( VBE_SET_MODE ),
00321                                  "b" ( mode_number ) );
00322         if ( ( rc = vesafb_rc ( status ) ) != 0 ) {
00323                 DBGC ( &vbe_buf, "VESAFB could not set mode %04x: [%04x] %s\n",
00324                        mode_number, status, strerror ( rc ) );
00325                 return rc;
00326         }
00327 
00328         return 0;
00329 }
00330 
00331 /**
00332  * Select video mode
00333  *
00334  * @v mode_numbers      Mode number list (terminated with VBE_MODE_END)
00335  * @v min_width         Minimum required width (in pixels)
00336  * @v min_height        Minimum required height (in pixels)
00337  * @v min_bpp           Minimum required colour depth (in bits per pixel)
00338  * @ret mode_number     Mode number, or negative error
00339  */
00340 static int vesafb_select_mode ( const uint16_t *mode_numbers,
00341                                 unsigned int min_width, unsigned int min_height,
00342                                 unsigned int min_bpp ) {
00343         struct vbe_mode_info *mode = &vbe_buf.mode;
00344         int best_mode_number = -ENOENT;
00345         unsigned int best_score = INT_MAX;
00346         unsigned int score;
00347         uint16_t mode_number;
00348         int rc;
00349 
00350         /* Find the first suitable mode */
00351         while ( ( mode_number = *(mode_numbers++) ) != VBE_MODE_END ) {
00352 
00353                 /* Force linear mode variant */
00354                 mode_number |= VBE_MODE_LINEAR;
00355 
00356                 /* Get mode information */
00357                 if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 )
00358                         continue;
00359 
00360                 /* Skip unusable modes */
00361                 if ( ( mode->mode_attributes & ( VBE_MODE_ATTR_SUPPORTED |
00362                                                  VBE_MODE_ATTR_GRAPHICS |
00363                                                  VBE_MODE_ATTR_LINEAR ) ) !=
00364                      ( VBE_MODE_ATTR_SUPPORTED | VBE_MODE_ATTR_GRAPHICS |
00365                        VBE_MODE_ATTR_LINEAR ) ) {
00366                         continue;
00367                 }
00368                 if ( mode->memory_model != VBE_MODE_MODEL_DIRECT_COLOUR )
00369                         continue;
00370 
00371                 /* Skip modes not meeting the requirements */
00372                 if ( ( mode->x_resolution < min_width ) ||
00373                      ( mode->y_resolution < min_height ) ||
00374                      ( mode->bits_per_pixel < min_bpp ) ) {
00375                         continue;
00376                 }
00377 
00378                 /* Select this mode if it has the best (i.e. lowest)
00379                  * score.  We choose the scoring system to favour
00380                  * modes close to the specified width and height;
00381                  * within modes of the same width and height we prefer
00382                  * a higher colour depth.
00383                  */
00384                 score = ( ( mode->x_resolution * mode->y_resolution ) -
00385                           mode->bits_per_pixel );
00386                 if ( score < best_score ) {
00387                         best_mode_number = mode_number;
00388                         best_score = score;
00389                 }
00390         }
00391 
00392         if ( best_mode_number >= 0 ) {
00393                 DBGC ( &vbe_buf, "VESAFB selected mode %04x\n",
00394                        best_mode_number );
00395         } else {
00396                 DBGC ( &vbe_buf, "VESAFB found no suitable mode\n" );
00397         }
00398 
00399         return best_mode_number;
00400 }
00401 
00402 /**
00403  * Restore video mode
00404  *
00405  */
00406 static void vesafb_restore ( void ) {
00407         uint32_t discard_a;
00408 
00409         /* Restore saved VGA mode */
00410         __asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
00411                                : "=a" ( discard_a )
00412                                : "a" ( VBE_SET_VGA_MODE | vesafb.saved_mode ) );
00413         DBGC ( &vbe_buf, "VESAFB restored VGA mode %#02x\n",
00414                vesafb.saved_mode );
00415 }
00416 
00417 /**
00418  * Initialise VESA frame buffer
00419  *
00420  * @v config            Console configuration, or NULL to reset
00421  * @ret rc              Return status code
00422  */
00423 static int vesafb_init ( struct console_configuration *config ) {
00424         uint32_t discard_b;
00425         uint16_t *mode_numbers;
00426         int mode_number;
00427         int rc;
00428 
00429         /* Record current VGA mode */
00430         __asm__ __volatile__ ( REAL_CODE ( "int $0x10" )
00431                                : "=a" ( vesafb.saved_mode ), "=b" ( discard_b )
00432                                : "a" ( VBE_GET_VGA_MODE ) );
00433         DBGC ( &vbe_buf, "VESAFB saved VGA mode %#02x\n", vesafb.saved_mode );
00434 
00435         /* Get VESA mode list */
00436         if ( ( rc = vesafb_mode_list ( &mode_numbers ) ) != 0 )
00437                 goto err_mode_list;
00438 
00439         /* Select mode */
00440         if ( ( mode_number = vesafb_select_mode ( mode_numbers, config->width,
00441                                                   config->height,
00442                                                   config->depth ) ) < 0 ) {
00443                 rc = mode_number;
00444                 goto err_select_mode;
00445         }
00446 
00447         /* Set mode */
00448         if ( ( rc = vesafb_set_mode ( mode_number ) ) != 0 )
00449                 goto err_set_mode;
00450 
00451         /* Get font data */
00452         vesafb_font();
00453 
00454         /* Initialise frame buffer console */
00455         if ( ( rc = fbcon_init ( &vesafb.fbcon, phys_to_user ( vesafb.start ),
00456                                  &vesafb.pixel, &vesafb.map, &vesafb.font,
00457                                  config ) ) != 0 )
00458                 goto err_fbcon_init;
00459 
00460         free ( mode_numbers );
00461         return 0;
00462 
00463         fbcon_fini ( &vesafb.fbcon );
00464  err_fbcon_init:
00465  err_set_mode:
00466         vesafb_restore();
00467  err_select_mode:
00468         free ( mode_numbers );
00469  err_mode_list:
00470         return rc;
00471 }
00472 
00473 /**
00474  * Finalise VESA frame buffer
00475  *
00476  */
00477 static void vesafb_fini ( void ) {
00478 
00479         /* Finalise frame buffer console */
00480         fbcon_fini ( &vesafb.fbcon );
00481 
00482         /* Restore saved VGA mode */
00483         vesafb_restore();
00484 }
00485 
00486 /**
00487  * Print a character to current cursor position
00488  *
00489  * @v character         Character
00490  */
00491 static void vesafb_putchar ( int character ) {
00492 
00493         fbcon_putchar ( &vesafb.fbcon, character );
00494 }
00495 
00496 /**
00497  * Configure console
00498  *
00499  * @v config            Console configuration, or NULL to reset
00500  * @ret rc              Return status code
00501  */
00502 static int vesafb_configure ( struct console_configuration *config ) {
00503         int rc;
00504 
00505         /* Reset console, if applicable */
00506         if ( ! vesafb_console.disabled ) {
00507                 vesafb_fini();
00508                 bios_console.disabled &= ~CONSOLE_DISABLED_OUTPUT;
00509                 ansicol_reset_magic();
00510         }
00511         vesafb_console.disabled = CONSOLE_DISABLED;
00512 
00513         /* Do nothing more unless we have a usable configuration */
00514         if ( ( config == NULL ) ||
00515              ( config->width == 0 ) || ( config->height == 0 ) ) {
00516                 return 0;
00517         }
00518 
00519         /* Initialise VESA frame buffer */
00520         if ( ( rc = vesafb_init ( config ) ) != 0 )
00521                 return rc;
00522 
00523         /* Mark console as enabled */
00524         vesafb_console.disabled = 0;
00525         bios_console.disabled |= CONSOLE_DISABLED_OUTPUT;
00526 
00527         /* Set magic colour to transparent if we have a background picture */
00528         if ( config->pixbuf )
00529                 ansicol_set_magic_transparent();
00530 
00531         return 0;
00532 }
00533 
00534 /** VESA frame buffer console driver */
00535 struct console_driver vesafb_console __console_driver = {
00536         .usage = CONSOLE_VESAFB,
00537         .putchar = vesafb_putchar,
00538         .configure = vesafb_configure,
00539         .disabled = CONSOLE_DISABLED,
00540 };