/* * Copyright (C) 2013 Michael Brown . * * 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 * * VESA frame buffer console * */ #include #include #include #include #include #include #include #include #include #include /* Avoid dragging in BIOS console if not otherwise used */ extern struct console_driver bios_console; struct console_driver bios_console __attribute__ (( weak )); /* Disambiguate the various error causes */ #define EIO_FAILED __einfo_error ( EINFO_EIO_FAILED ) #define EINFO_EIO_FAILED \ __einfo_uniqify ( EINFO_EIO, 0x01, \ "Function call failed" ) #define EIO_HARDWARE __einfo_error ( EINFO_EIO_HARDWARE ) #define EINFO_EIO_HARDWARE \ __einfo_uniqify ( EINFO_EIO, 0x02, \ "Not supported in current configuration" ) #define EIO_MODE __einfo_error ( EINFO_EIO_MODE ) #define EINFO_EIO_MODE \ __einfo_uniqify ( EINFO_EIO, 0x03, \ "Invalid in current video mode" ) #define EIO_VBE( code ) \ EUNIQ ( EINFO_EIO, (code), EIO_FAILED, EIO_HARDWARE, EIO_MODE ) /* Set default console usage if applicable * * We accept either CONSOLE_FRAMEBUFFER or CONSOLE_VESAFB. */ #if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_VESAFB ) ) #define CONSOLE_VESAFB CONSOLE_FRAMEBUFFER #endif #if ! ( defined ( CONSOLE_VESAFB ) && CONSOLE_EXPLICIT ( CONSOLE_VESAFB ) ) #undef CONSOLE_VESAFB #define CONSOLE_VESAFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG ) #endif /** Character height */ #define VESAFB_CHAR_HEIGHT 16 /** Font corresponding to selected character width and height */ #define VESAFB_FONT VBE_FONT_8x16 /** Number of ASCII glyphs within the font */ #define VESAFB_ASCII 128 /** Glyph to render for non-ASCII characters * * We choose to use one of the box-drawing glyphs. */ #define VESAFB_UNKNOWN 0xfe /* Forward declaration */ struct console_driver vesafb_console __console_driver; /** A VESA frame buffer */ struct vesafb { /** 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 */ struct segoff glyphs; /** Saved VGA mode */ uint8_t saved_mode; }; /** The VESA frame buffer */ static struct vesafb vesafb; /** Base memory buffer used for VBE calls */ union vbe_buffer { /** VBE controller information block */ struct vbe_controller_info controller; /** VBE mode information block */ struct vbe_mode_info mode; }; static union vbe_buffer __bss16 ( vbe_buf ); #define vbe_buf __use_data16 ( vbe_buf ) /** * Convert VBE status code to iPXE status code * * @v status VBE status code * @ret rc Return status code */ static int vesafb_rc ( unsigned int status ) { unsigned int code; if ( ( status & 0xff ) != 0x4f ) return -ENOTSUP; code = ( ( status >> 8 ) & 0xff ); return ( code ? -EIO_VBE ( code ) : 0 ); } /** * Get character glyph * * @v character Unicode character * @v glyph Character glyph to fill in */ static void vesafb_glyph ( unsigned int character, uint8_t *glyph ) { unsigned int index; size_t offset; /* Identify glyph */ if ( character < VESAFB_ASCII ) { /* ASCII character: use corresponding glyph */ index = character; } else { switch ( character ) { /* above ASCII */ case 0xc7: index = 0x80; break; case 0xfc: index = 0x81; break; case 0xe9: index = 0x82; break; case 0xe2: index = 0x83; break; case 0xe4: index = 0x84; break; case 0xe0: index = 0x85; break; case 0xe5: index = 0x86; break; case 0xe7: index = 0x87; break; case 0xea: index = 0x88; break; case 0xeb: index = 0x89; break; case 0xe8: index = 0x8a; break; case 0xef: index = 0x8b; break; case 0xee: index = 0x8c; break; case 0xec: index = 0x8d; break; case 0xc4: index = 0x8e; break; case 0xc5: index = 0x8f; break; case 0xc9: index = 0x90; break; case 0xe6: index = 0x91; break; case 0xc6: index = 0x92; break; case 0xf4: index = 0x93; break; case 0xf6: index = 0x94; break; case 0xf2: index = 0x95; break; case 0xfb: index = 0x96; break; case 0xf9: index = 0x97; break; case 0xff: index = 0x98; break; case 0xd6: index = 0x99; break; case 0xdc: index = 0x9a; break; case 0xa2: index = 0x9b; break; case 0xa3: index = 0x9c; break; case 0xa5: index = 0x9d; break; case 0x20a7: index = 0x9e; break; case 0x192: index = 0x9f; break; case 0xe1: index = 0xa0; break; case 0xed: index = 0xa1; break; case 0xf3: index = 0xa2; break; case 0xfa: index = 0xa3; break; case 0xf1: index = 0xa4; break; case 0xd1: index = 0xa5; break; case 0xaa: index = 0xa6; break; case 0xba: index = 0xa7; break; case 0xbf: index = 0xa8; break; case 0x2310: index = 0xa9; break; case 0xac: index = 0xaa; break; case 0xbd: index = 0xab; break; case 0xbc: index = 0xac; break; case 0xa1: index = 0xad; break; case 0xab: index = 0xae; break; case 0xbb: index = 0xaf; break; case 0x2591: index = 0xb0; break; case 0x2592: index = 0xb1; break; case 0x2593: index = 0xb2; break; case 0x2502: index = 0xb3; break; case 0x2524: index = 0xb4; break; case 0x2561: index = 0xb5; break; case 0x2562: index = 0xb6; break; case 0x2556: index = 0xb7; break; case 0x2555: index = 0xb8; break; case 0x2563: index = 0xb9; break; case 0x2551: index = 0xba; break; case 0x2557: index = 0xbb; break; case 0x255d: index = 0xbc; break; case 0x255c: index = 0xbd; break; case 0x255b: index = 0xbe; break; case 0x2510: index = 0xbf; break; case 0x2514: index = 0xc0; break; case 0x2534: index = 0xc1; break; case 0x252c: index = 0xc2; break; case 0x251c: index = 0xc3; break; case 0x2500: index = 0xc4; break; case 0x253c: index = 0xc5; break; case 0x255e: index = 0xc6; break; case 0x255f: index = 0xc7; break; case 0x255a: index = 0xc8; break; case 0x2554: index = 0xc9; break; case 0x2569: index = 0xca; break; case 0x2566: index = 0xcb; break; case 0x2560: index = 0xcc; break; case 0x2550: index = 0xcd; break; case 0x256c: index = 0xce; break; case 0x2567: index = 0xcf; break; case 0x2568: index = 0xd0; break; case 0x2564: index = 0xd1; break; case 0x2565: index = 0xd2; break; case 0x2559: index = 0xd3; break; case 0x2558: index = 0xd4; break; case 0x2552: index = 0xd5; break; case 0x2553: index = 0xd6; break; case 0x256b: index = 0xd7; break; case 0x256a: index = 0xd8; break; case 0x2518: index = 0xd9; break; case 0x250c: index = 0xda; break; case 0x2588: index = 0xdb; break; case 0x2584: index = 0xdc; break; case 0x258c: index = 0xdd; break; case 0x2590: index = 0xde; break; case 0x2580: index = 0xdf; break; case 0x3b1: index = 0xe0; break; case 0xdf: index = 0xe1; break; case 0x393: index = 0xe2; break; case 0x3c0: index = 0xe3; break; case 0x3a3: index = 0xe4; break; case 0x3c3: index = 0xe5; break; case 0xb5: index = 0xe6; break; case 0x3c4: index = 0xe7; break; case 0x3a6: index = 0xe8; break; case 0x398: index = 0xe9; break; case 0x3a9: index = 0xea; break; case 0x3b4: index = 0xeb; break; case 0x221e: index = 0xec; break; case 0x3c6: index = 0xed; break; case 0x3b5: index = 0xee; break; case 0x2229: index = 0xef; break; case 0x2261: index = 0xf0; break; case 0xb1: index = 0xf1; break; case 0x2265: index = 0xf2; break; case 0x2264: index = 0xf3; break; case 0x2320: index = 0xf4; break; case 0x2321: index = 0xf5; break; case 0xf7: index = 0xf6; break; case 0x2248: index = 0xf7; break; case 0xb0: index = 0xf8; break; case 0x2219: index = 0xf9; break; case 0xb7: index = 0xfa; break; case 0x221a: index = 0xfb; break; case 0x207f: index = 0xfc; break; case 0xb2: index = 0xfd; break; case 0x25a0: index = 0xfe; break; case 0xa0: index = 0xff; break; default: /* Non-CP437 character: use "unknown" glyph */ index = VESAFB_UNKNOWN; } } /* Copy glyph from BIOS font table */ offset = ( index * VESAFB_CHAR_HEIGHT ); copy_from_real ( glyph, vesafb.glyphs.segment, ( vesafb.glyphs.offset + offset ), VESAFB_CHAR_HEIGHT); } /** * Get font definition * */ static void vesafb_font ( void ) { /* Get font information * * Working around gcc bugs is icky here. The value we want is * returned in %ebp, but there's no way to specify %ebp in an * output constraint. We can't put %ebp in the clobber list, * because this tends to cause random build failures on some * gcc versions. We can't manually push/pop %ebp and return * the value via a generic register output constraint, because * gcc might choose to use %ebp to satisfy that constraint * (and we have no way to prevent it from so doing). * * Work around this hideous mess by using %ecx and %edx as the * output registers, since they get clobbered anyway. */ __asm__ __volatile__ ( REAL_CODE ( "pushw %%bp\n\t" /* gcc bug */ "int $0x10\n\t" "movw %%es, %%cx\n\t" "movw %%bp, %%dx\n\t" "popw %%bp\n\t" /* gcc bug */ ) : "=c" ( vesafb.glyphs.segment ), "=d" ( vesafb.glyphs.offset ) : "a" ( VBE_GET_FONT ), "b" ( VESAFB_FONT ) ); DBGC ( &vbe_buf, "VESAFB has font %04x at %04x:%04x\n", VESAFB_FONT, vesafb.glyphs.segment, vesafb.glyphs.offset ); vesafb.font.height = VESAFB_CHAR_HEIGHT; vesafb.font.glyph = vesafb_glyph; } /** * Get VBE mode list * * @ret mode_numbers Mode number list (terminated with VBE_MODE_END) * @ret rc Return status code * * The caller is responsible for eventually freeing the mode list. */ static int vesafb_mode_list ( uint16_t **mode_numbers ) { struct vbe_controller_info *controller = &vbe_buf.controller; userptr_t video_mode_ptr; uint16_t mode_number; uint16_t status; size_t len; int rc; /* Avoid returning uninitialised data on error */ *mode_numbers = NULL; /* Get controller information block */ controller->vbe_signature = 0; __asm__ __volatile__ ( REAL_CODE ( "int $0x10" ) : "=a" ( status ) : "a" ( VBE_CONTROLLER_INFO ), "D" ( __from_data16 ( controller ) ) : "memory", "ebx", "edx" ); if ( ( rc = vesafb_rc ( status ) ) != 0 ) { DBGC ( &vbe_buf, "VESAFB could not get controller information: " "[%04x] %s\n", status, strerror ( rc ) ); return rc; } if ( controller->vbe_signature != VBE_CONTROLLER_SIGNATURE ) { DBGC ( &vbe_buf, "VESAFB invalid controller signature " "\"%c%c%c%c\"\n", ( controller->vbe_signature >> 0 ), ( controller->vbe_signature >> 8 ), ( controller->vbe_signature >> 16 ), ( controller->vbe_signature >> 24 ) ); DBGC_HDA ( &vbe_buf, 0, controller, sizeof ( *controller ) ); return -EINVAL; } DBGC ( &vbe_buf, "VESAFB found VBE version %d.%d with mode list at " "%04x:%04x\n", controller->vbe_major_version, controller->vbe_minor_version, controller->video_mode_ptr.segment, controller->video_mode_ptr.offset ); /* Calculate length of mode list */ video_mode_ptr = real_to_user ( controller->video_mode_ptr.segment, controller->video_mode_ptr.offset ); len = 0; do { copy_from_user ( &mode_number, video_mode_ptr, len, sizeof ( mode_number ) ); len += sizeof ( mode_number ); } while ( mode_number != VBE_MODE_END ); /* Allocate and fill mode list */ *mode_numbers = malloc ( len ); if ( ! *mode_numbers ) return -ENOMEM; copy_from_user ( *mode_numbers, video_mode_ptr, 0, len ); return 0; } /** * Get video mode information * * @v mode_number Mode number * @ret rc Return status code */ static int vesafb_mode_info ( unsigned int mode_number ) { struct vbe_mode_info *mode = &vbe_buf.mode; uint16_t status; int rc; /* Get mode information */ __asm__ __volatile__ ( REAL_CODE ( "int $0x10" ) : "=a" ( status ) : "a" ( VBE_MODE_INFO ), "c" ( mode_number ), "D" ( __from_data16 ( mode ) ) : "memory" ); if ( ( rc = vesafb_rc ( status ) ) != 0 ) { DBGC ( &vbe_buf, "VESAFB could not get mode %04x information: " "[%04x] %s\n", mode_number, status, strerror ( rc ) ); return rc; } DBGC ( &vbe_buf, "VESAFB mode %04x %dx%d %dbpp(%d:%d:%d:%d) model " "%02x [x%d]%s%s%s%s%s\n", mode_number, mode->x_resolution, mode->y_resolution, mode->bits_per_pixel, mode->rsvd_mask_size, mode->red_mask_size, mode->green_mask_size, mode->blue_mask_size, mode->memory_model, ( mode->number_of_image_pages + 1 ), ( ( mode->mode_attributes & VBE_MODE_ATTR_SUPPORTED ) ? "" : " [unsupported]" ), ( ( mode->mode_attributes & VBE_MODE_ATTR_TTY ) ? " [tty]" : "" ), ( ( mode->mode_attributes & VBE_MODE_ATTR_GRAPHICS ) ? "" : " [text]" ), ( ( mode->mode_attributes & VBE_MODE_ATTR_LINEAR ) ? "" : " [nonlinear]" ), ( ( mode->mode_attributes & VBE_MODE_ATTR_TRIPLE_BUF ) ? " [buf]" : "" ) ); return 0; } /** * Set video mode * * @v mode_number Mode number * @ret rc Return status code */ static int vesafb_set_mode ( unsigned int mode_number ) { struct vbe_mode_info *mode = &vbe_buf.mode; uint16_t status; int rc; /* Get mode information */ if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 ) return rc; /* Record mode parameters */ vesafb.start = mode->phys_base_ptr; vesafb.pixel.width = mode->x_resolution; vesafb.pixel.height = mode->y_resolution; vesafb.pixel.len = ( ( mode->bits_per_pixel + 7 ) / 8 ); vesafb.pixel.stride = mode->bytes_per_scan_line; DBGC ( &vbe_buf, "VESAFB mode %04x has frame buffer at %08x\n", mode_number, mode->phys_base_ptr ); /* Initialise font colours */ vesafb.map.red_scale = ( 8 - mode->red_mask_size ); vesafb.map.green_scale = ( 8 - mode->green_mask_size ); vesafb.map.blue_scale = ( 8 - mode->blue_mask_size ); vesafb.map.red_lsb = mode->red_field_position; vesafb.map.green_lsb = mode->green_field_position; vesafb.map.blue_lsb = mode->blue_field_position; /* Select this mode */ __asm__ __volatile__ ( REAL_CODE ( "int $0x10" ) : "=a" ( status ) : "a" ( VBE_SET_MODE ), "b" ( mode_number ) ); if ( ( rc = vesafb_rc ( status ) ) != 0 ) { DBGC ( &vbe_buf, "VESAFB could not set mode %04x: [%04x] %s\n", mode_number, status, strerror ( rc ) ); return rc; } return 0; } /** * Select video mode * * @v mode_numbers Mode number list (terminated with VBE_MODE_END) * @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 vesafb_select_mode ( const uint16_t *mode_numbers, unsigned int min_width, unsigned int min_height, unsigned int min_bpp ) { struct vbe_mode_info *mode = &vbe_buf.mode; int best_mode_number = -ENOENT; unsigned int best_score = INT_MAX; unsigned int score; uint16_t mode_number; int rc; /* Find the first suitable mode */ while ( ( mode_number = *(mode_numbers++) ) != VBE_MODE_END ) { /* Force linear mode variant */ mode_number |= VBE_MODE_LINEAR; /* Get mode information */ if ( ( rc = vesafb_mode_info ( mode_number ) ) != 0 ) continue; /* Skip unusable modes */ if ( ( mode->mode_attributes & ( VBE_MODE_ATTR_SUPPORTED | VBE_MODE_ATTR_GRAPHICS | VBE_MODE_ATTR_LINEAR ) ) != ( VBE_MODE_ATTR_SUPPORTED | VBE_MODE_ATTR_GRAPHICS | VBE_MODE_ATTR_LINEAR ) ) { continue; } if ( mode->memory_model != VBE_MODE_MODEL_DIRECT_COLOUR ) continue; /* Skip modes not meeting the requirements */ if ( ( mode->x_resolution < min_width ) || ( mode->y_resolution < min_height ) || ( mode->bits_per_pixel < min_bpp ) ) { continue; } /* 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 = ( ( mode->x_resolution * mode->y_resolution ) - mode->bits_per_pixel ); if ( score < best_score ) { best_mode_number = mode_number; best_score = score; } } if ( best_mode_number >= 0 ) { DBGC ( &vbe_buf, "VESAFB selected mode %04x\n", best_mode_number ); } else { DBGC ( &vbe_buf, "VESAFB found no suitable mode\n" ); } return best_mode_number; } /** * Restore video mode * */ static void vesafb_restore ( void ) { uint32_t discard_a; /* Restore saved VGA mode */ __asm__ __volatile__ ( REAL_CODE ( "int $0x10" ) : "=a" ( discard_a ) : "a" ( VBE_SET_VGA_MODE | vesafb.saved_mode ) ); DBGC ( &vbe_buf, "VESAFB restored VGA mode %#02x\n", vesafb.saved_mode ); } /** * Initialise VESA frame buffer * * @v config Console configuration, or NULL to reset * @ret rc Return status code */ static int vesafb_init ( struct console_configuration *config ) { uint32_t discard_b; uint16_t *mode_numbers = NULL; int mode_number; int rc; if ( ! config->lazy_update ) { /* Record current VGA mode */ __asm__ __volatile__ ( REAL_CODE ( "int $0x10" ) : "=a" ( vesafb.saved_mode ), "=b" ( discard_b ) : "a" ( VBE_GET_VGA_MODE ) ); DBGC ( &vbe_buf, "VESAFB saved VGA mode %#02x\n", vesafb.saved_mode ); /* Get VESA mode list */ if ( ( rc = vesafb_mode_list ( &mode_numbers ) ) != 0 ) goto err_mode_list; /* Select mode */ if ( ( mode_number = vesafb_select_mode ( mode_numbers, config->width, config->height, config->depth ) ) < 0 ) { rc = mode_number; goto err_select_mode; } /* Set mode */ if ( ( rc = vesafb_set_mode ( mode_number ) ) != 0 ) goto err_set_mode; /* Get font data */ vesafb_font(); } /* Initialise frame buffer console */ if ( ( rc = fbcon_init ( &vesafb.fbcon, phys_to_user ( vesafb.start ), &vesafb.pixel, &vesafb.map, &vesafb.font, config ) ) != 0 ) goto err_fbcon_init; free ( mode_numbers ); return 0; fbcon_fini ( &vesafb.fbcon ); err_fbcon_init: err_set_mode: vesafb_restore(); err_select_mode: free ( mode_numbers ); err_mode_list: return rc; } /** * Finalise VESA frame buffer * */ static void vesafb_fini ( void ) { /* Finalise frame buffer console */ fbcon_fini ( &vesafb.fbcon ); /* Restore saved VGA mode */ vesafb_restore(); } /** * Print a character to current cursor position * * @v character Character */ static void vesafb_putchar ( int character ) { fbcon_putchar ( &vesafb.fbcon, character ); } /** * Configure console * * @v config Console configuration, or NULL to reset * @ret rc Return status code */ static int vesafb_configure ( struct console_configuration *config ) { int rc; if ( config && config->lazy_update ) { if ( vesafb_console.disabled ) { /* --update mode with disabled console -> nothing to do */ return 0; } /* No width/height given, use current so we can update the border */ if ( ( config->width == 0 ) || ( config->height == 0 ) ) { if ( ( vesafb.pixel.width == 0 ) || ( vesafb.pixel.height == 0 ) ) { return -EINVAL; } config->width = vesafb.pixel.width; config->height = vesafb.pixel.height; } else { /* Otherwise make sure the new dimensions match the old ones */ if ( ( vesafb.pixel.width != config->width ) || ( vesafb.pixel.height != config->height ) ) { return -EINVAL; } } } else { /* Reset console, if applicable */ if ( ! vesafb_console.disabled ) { vesafb_fini(); bios_console.disabled &= ~CONSOLE_DISABLED_OUTPUT; ansicol_reset_magic(); } vesafb_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 VESA frame buffer */ if ( ( rc = vesafb_init ( config ) ) != 0 ) return rc; if ( ! config->lazy_update ) { /* Mark console as enabled */ vesafb_console.disabled = 0; bios_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; } /** VESA frame buffer console driver */ struct console_driver vesafb_console __console_driver = { .usage = CONSOLE_VESAFB, .putchar = vesafb_putchar, .configure = vesafb_configure, .disabled = CONSOLE_DISABLED, };