1 /*
2 * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301, USA.
18 *
19 * You can also choose to distribute this program under the terms of
20 * the Unmodified Binary Distribution Licence (as given in the file
21 * COPYING.UBDL), provided that you have satisfied its requirements.
22 */
23
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 /**
27 * @file
28 *
29 * EFI frame buffer console
30 *
31 */
32
33 #include <string.h>
34 #include <strings.h>
35 #include <ctype.h>
36 #include <errno.h>
37 #include <assert.h>
38 #include <limits.h>
39 #include <ipxe/efi/efi.h>
40 #include <ipxe/efi/Protocol/GraphicsOutput.h>
41 #include <ipxe/efi/Protocol/HiiFont.h>
42 #include <ipxe/ansicol.h>
43 #include <ipxe/fbcon.h>
44 #include <ipxe/console.h>
45 #include <ipxe/umalloc.h>
46 #include <ipxe/rotate.h>
47 #include <config/console.h>
48
49 /* Avoid dragging in EFI console if not otherwise used */
50 extern struct console_driver efi_console;
51 struct console_driver efi_console __attribute__ (( weak ));
52
53 /* Set default console usage if applicable
54 *
55 * We accept either CONSOLE_FRAMEBUFFER or CONSOLE_EFIFB.
56 */
57 #if ( defined ( CONSOLE_FRAMEBUFFER ) && ! defined ( CONSOLE_EFIFB ) )
58 #define CONSOLE_EFIFB CONSOLE_FRAMEBUFFER
59 #endif
60 #if ! ( defined ( CONSOLE_EFIFB ) && CONSOLE_EXPLICIT ( CONSOLE_EFIFB ) )
61 #undef CONSOLE_EFIFB
62 #define CONSOLE_EFIFB ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_LOG )
63 #endif
64
65 /* Forward declaration */
66 struct console_driver efifb_console __console_driver;
67
68 /** An EFI frame buffer */
69 struct efifb {
70 /** EFI graphics output protocol */
71 EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
72 /** EFI HII font protocol */
73 EFI_HII_FONT_PROTOCOL *hiifont;
74 /** Saved mode */
75 UINT32 saved_mode;
76
77 /** Frame buffer console */
78 struct fbcon fbcon;
79 /** Physical start address */
80 physaddr_t start;
81 /** Pixel geometry */
82 struct fbcon_geometry pixel;
83 /** Colour mapping */
84 struct fbcon_colour_map map;
85 /** Font definition */
86 struct fbcon_font font;
87 /** Character glyphs */
88 userptr_t glyphs;
89 };
90
91 /** The EFI frame buffer */
92 static struct efifb efifb;
93
94 /**
95 * Get character glyph
96 *
97 * @v character Character
98 * @v glyph Character glyph to fill in
99 */
efifb_glyph(unsigned int character,uint8_t * glyph)100 static void efifb_glyph ( unsigned int character, uint8_t *glyph ) {
101 size_t offset = ( character * efifb.font.height );
102
103 copy_from_user ( glyph, efifb.glyphs, offset, efifb.font.height );
104 }
105
106 /**
107 * Get character glyphs
108 *
109 * @ret rc Return status code
110 */
efifb_glyphs(void)111 static int efifb_glyphs ( void ) {
112 EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
113 EFI_IMAGE_OUTPUT *blt;
114 EFI_GRAPHICS_OUTPUT_BLT_PIXEL *pixel;
115 size_t offset;
116 size_t len;
117 uint8_t bitmask;
118 unsigned int character;
119 unsigned int x;
120 unsigned int y;
121 EFI_STATUS efirc;
122 int rc;
123
124 /* Get font height. The GetFontInfo() call nominally returns
125 * this information in an EFI_FONT_DISPLAY_INFO structure, but
126 * is known to fail on many UEFI implementations. Instead, we
127 * iterate over all printable characters to find the maximum
128 * height.
129 */
130 efifb.font.height = 0;
131 for ( character = 0 ; character < 256 ; character++ ) {
132
133 /* Skip non-printable characters */
134 if ( ! isprint ( character ) )
135 continue;
136
137 /* Get glyph */
138 blt = NULL;
139 if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont,
140 character, NULL, &blt,
141 NULL ) ) != 0 ) {
142 rc = -EEFI ( efirc );
143 DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n",
144 character, strerror ( rc ) );
145 continue;
146 }
147 assert ( blt != NULL );
148
149 /* Calculate maximum height */
150 if ( efifb.font.height < blt->Height )
151 efifb.font.height = blt->Height;
152
153 /* Free glyph */
154 bs->FreePool ( blt );
155 }
156 if ( ! efifb.font.height ) {
157 DBGC ( &efifb, "EFIFB could not get font height\n" );
158 return -ENOENT;
159 }
160
161 /* Allocate glyph data */
162 len = ( 256 * efifb.font.height * sizeof ( bitmask ) );
163 efifb.glyphs = umalloc ( len );
164 if ( ! efifb.glyphs ) {
165 rc = -ENOMEM;
166 goto err_alloc;
167 }
168 memset_user ( efifb.glyphs, 0, 0, len );
169
170 /* Get font data */
171 for ( character = 0 ; character < 256 ; character++ ) {
172
173 /* Skip non-printable characters */
174 if ( ! isprint ( character ) )
175 continue;
176
177 /* Get glyph */
178 blt = NULL;
179 if ( ( efirc = efifb.hiifont->GetGlyph ( efifb.hiifont,
180 character, NULL, &blt,
181 NULL ) ) != 0 ) {
182 rc = -EEFI ( efirc );
183 DBGC ( &efifb, "EFIFB could not get glyph %d: %s\n",
184 character, strerror ( rc ) );
185 continue;
186 }
187 assert ( blt != NULL );
188
189 /* Sanity check */
190 if ( blt->Width > 8 ) {
191 DBGC ( &efifb, "EFIFB glyph %d invalid width %d\n",
192 character, blt->Width );
193 continue;
194 }
195 if ( blt->Height > efifb.font.height ) {
196 DBGC ( &efifb, "EFIFB glyph %d invalid height %d\n",
197 character, blt->Height );
198 continue;
199 }
200
201 /* Convert glyph to bitmap */
202 pixel = blt->Image.Bitmap;
203 offset = ( character * efifb.font.height );
204 for ( y = 0 ; y < blt->Height ; y++ ) {
205 bitmask = 0;
206 for ( x = 0 ; x < blt->Width ; x++ ) {
207 bitmask = rol8 ( bitmask, 1 );
208 if ( pixel->Blue || pixel->Green || pixel->Red )
209 bitmask |= 0x01;
210 pixel++;
211 }
212 copy_to_user ( efifb.glyphs, offset++, &bitmask,
213 sizeof ( bitmask ) );
214 }
215
216 /* Free glyph */
217 bs->FreePool ( blt );
218 }
219
220 efifb.font.glyph = efifb_glyph;
221 return 0;
222
223 ufree ( efifb.glyphs );
224 err_alloc:
225 return rc;
226 }
227
228 /**
229 * Generate colour mapping for a single colour component
230 *
231 * @v mask Mask value
232 * @v scale Scale value to fill in
233 * @v lsb LSB value to fill in
234 * @ret rc Return status code
235 */
efifb_colour_map_mask(uint32_t mask,uint8_t * scale,uint8_t * lsb)236 static int efifb_colour_map_mask ( uint32_t mask, uint8_t *scale,
237 uint8_t *lsb ) {
238 uint32_t check;
239
240 /* Fill in LSB and scale */
241 *lsb = ( mask ? ( ffs ( mask ) - 1 ) : 0 );
242 *scale = ( mask ? ( 8 - ( fls ( mask ) - *lsb ) ) : 8 );
243
244 /* Check that original mask was contiguous */
245 check = ( ( 0xff >> *scale ) << *lsb );
246 if ( check != mask )
247 return -ENOTSUP;
248
249 return 0;
250 }
251
252 /**
253 * Generate colour mapping
254 *
255 * @v info EFI mode information
256 * @v map Colour mapping to fill in
257 * @ret bpp Number of bits per pixel, or negative error
258 */
efifb_colour_map(EFI_GRAPHICS_OUTPUT_MODE_INFORMATION * info,struct fbcon_colour_map * map)259 static int efifb_colour_map ( EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info,
260 struct fbcon_colour_map *map ) {
261 static EFI_PIXEL_BITMASK rgb_mask = {
262 0x000000ffUL, 0x0000ff00UL, 0x00ff0000UL, 0xff000000UL
263 };
264 static EFI_PIXEL_BITMASK bgr_mask = {
265 0x00ff0000UL, 0x0000ff00UL, 0x000000ffUL, 0xff000000UL
266 };
267 EFI_PIXEL_BITMASK *mask;
268 uint8_t reserved_scale;
269 uint8_t reserved_lsb;
270 int rc;
271
272 /* Determine applicable mask */
273 switch ( info->PixelFormat ) {
274 case PixelRedGreenBlueReserved8BitPerColor:
275 mask = &rgb_mask;
276 break;
277 case PixelBlueGreenRedReserved8BitPerColor:
278 mask = &bgr_mask;
279 break;
280 case PixelBitMask:
281 mask = &info->PixelInformation;
282 break;
283 default:
284 DBGC ( &efifb, "EFIFB unrecognised pixel format %d\n",
285 info->PixelFormat );
286 return -ENOTSUP;
287 }
288
289 /* Map each colour component */
290 if ( ( rc = efifb_colour_map_mask ( mask->RedMask, &map->red_scale,
291 &map->red_lsb ) ) != 0 )
292 return rc;
293 if ( ( rc = efifb_colour_map_mask ( mask->GreenMask, &map->green_scale,
294 &map->green_lsb ) ) != 0 )
295 return rc;
296 if ( ( rc = efifb_colour_map_mask ( mask->BlueMask, &map->blue_scale,
297 &map->blue_lsb ) ) != 0 )
298 return rc;
299 if ( ( rc = efifb_colour_map_mask ( mask->ReservedMask, &reserved_scale,
300 &reserved_lsb ) ) != 0 )
301 return rc;
302
303 /* Calculate total number of bits per pixel */
304 return ( 32 - ( reserved_scale + map->red_scale + map->green_scale +
305 map->blue_scale ) );
306 }
307
308 /**
309 * Select video mode
310 *
311 * @v min_width Minimum required width (in pixels)
312 * @v min_height Minimum required height (in pixels)
313 * @v min_bpp Minimum required colour depth (in bits per pixel)
314 * @ret mode_number Mode number, or negative error
315 */
efifb_select_mode(unsigned int min_width,unsigned int min_height,unsigned int min_bpp)316 static int efifb_select_mode ( unsigned int min_width, unsigned int min_height,
317 unsigned int min_bpp ) {
318 EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
319 struct fbcon_colour_map map;
320 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
321 int best_mode_number = -ENOENT;
322 unsigned int best_score = INT_MAX;
323 unsigned int score;
324 unsigned int mode;
325 int bpp;
326 UINTN size;
327 EFI_STATUS efirc;
328 int rc;
329
330 /* Find the best mode */
331 for ( mode = 0 ; mode < efifb.gop->Mode->MaxMode ; mode++ ) {
332
333 /* Get mode information */
334 if ( ( efirc = efifb.gop->QueryMode ( efifb.gop, mode, &size,
335 &info ) ) != 0 ) {
336 rc = -EEFI ( efirc );
337 DBGC ( &efifb, "EFIFB could not get mode %d "
338 "information: %s\n", mode, strerror ( rc ) );
339 goto err_query;
340 }
341
342 /* Skip unusable modes */
343 bpp = efifb_colour_map ( info, &map );
344 if ( bpp < 0 ) {
345 rc = bpp;
346 DBGC ( &efifb, "EFIFB could not build colour map for "
347 "mode %d: %s\n", mode, strerror ( rc ) );
348 goto err_map;
349 }
350
351 /* Skip modes not meeting the requirements */
352 if ( ( info->HorizontalResolution < min_width ) ||
353 ( info->VerticalResolution < min_height ) ||
354 ( ( ( unsigned int ) bpp ) < min_bpp ) ) {
355 goto err_requirements;
356 }
357
358 /* Select this mode if it has the best (i.e. lowest)
359 * score. We choose the scoring system to favour
360 * modes close to the specified width and height;
361 * within modes of the same width and height we prefer
362 * a higher colour depth.
363 */
364 score = ( ( info->HorizontalResolution *
365 info->VerticalResolution ) - bpp );
366 if ( score < best_score ) {
367 best_mode_number = mode;
368 best_score = score;
369 }
370
371 err_requirements:
372 err_map:
373 bs->FreePool ( info );
374 err_query:
375 continue;
376 }
377
378 if ( best_mode_number < 0 )
379 DBGC ( &efifb, "EFIFB found no suitable mode\n" );
380 return best_mode_number;
381 }
382
383 /**
384 * Restore video mode
385 *
386 * @v rc Return status code
387 */
efifb_restore(void)388 static int efifb_restore ( void ) {
389 EFI_STATUS efirc;
390 int rc;
391
392 /* Restore original mode */
393 if ( ( efirc = efifb.gop->SetMode ( efifb.gop,
394 efifb.saved_mode ) ) != 0 ) {
395 rc = -EEFI ( efirc );
396 DBGC ( &efifb, "EFIFB could not restore mode %d: %s\n",
397 efifb.saved_mode, strerror ( rc ) );
398 return rc;
399 }
400
401 return 0;
402 }
403
404 /**
405 * Initialise EFI frame buffer
406 *
407 * @v config Console configuration, or NULL to reset
408 * @ret rc Return status code
409 */
efifb_init(struct console_configuration * config)410 static int efifb_init ( struct console_configuration *config ) {
411 EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
412 EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
413 void *interface;
414 int mode;
415 int bpp;
416 EFI_STATUS efirc;
417 int rc;
418
419 /* Locate graphics output protocol */
420 if ( ( efirc = bs->LocateProtocol ( &efi_graphics_output_protocol_guid,
421 NULL, &interface ) ) != 0 ) {
422 rc = -EEFI ( efirc );
423 DBGC ( &efifb, "EFIFB could not locate graphics output "
424 "protocol: %s\n", strerror ( rc ) );
425 goto err_locate_gop;
426 }
427 efifb.gop = interface;
428
429 /* Locate HII font protocol */
430 if ( ( efirc = bs->LocateProtocol ( &efi_hii_font_protocol_guid,
431 NULL, &interface ) ) != 0 ) {
432 rc = -EEFI ( efirc );
433 DBGC ( &efifb, "EFIFB could not locate HII font protocol: %s\n",
434 strerror ( rc ) );
435 goto err_locate_hiifont;
436 }
437 efifb.hiifont = interface;
438
439 /* Locate glyphs */
440 if ( ( rc = efifb_glyphs() ) != 0 )
441 goto err_glyphs;
442
443 /* Save original mode */
444 efifb.saved_mode = efifb.gop->Mode->Mode;
445
446 /* Select mode */
447 if ( ( mode = efifb_select_mode ( config->width, config->height,
448 config->depth ) ) < 0 ) {
449 rc = mode;
450 goto err_select_mode;
451 }
452
453 /* Set mode */
454 if ( ( efirc = efifb.gop->SetMode ( efifb.gop, mode ) ) != 0 ) {
455 rc = -EEFI ( efirc );
456 DBGC ( &efifb, "EFIFB could not set mode %d: %s\n",
457 mode, strerror ( rc ) );
458 goto err_set_mode;
459 }
460 info = efifb.gop->Mode->Info;
461
462 /* Populate colour map */
463 bpp = efifb_colour_map ( info, &efifb.map );
464 if ( bpp < 0 ) {
465 rc = bpp;
466 DBGC ( &efifb, "EFIFB could not build colour map for "
467 "mode %d: %s\n", mode, strerror ( rc ) );
468 goto err_map;
469 }
470
471 /* Populate pixel geometry */
472 efifb.pixel.width = info->HorizontalResolution;
473 efifb.pixel.height = info->VerticalResolution;
474 efifb.pixel.len = ( ( bpp + 7 ) / 8 );
475 efifb.pixel.stride = ( efifb.pixel.len * info->PixelsPerScanLine );
476
477 /* Populate frame buffer address */
478 efifb.start = efifb.gop->Mode->FrameBufferBase;
479 DBGC ( &efifb, "EFIFB using mode %d (%dx%d %dbpp at %#08lx)\n",
480 mode, efifb.pixel.width, efifb.pixel.height, bpp, efifb.start );
481
482 /* Initialise frame buffer console */
483 if ( ( rc = fbcon_init ( &efifb.fbcon, phys_to_user ( efifb.start ),
484 &efifb.pixel, &efifb.map, &efifb.font,
485 config ) ) != 0 )
486 goto err_fbcon_init;
487
488 return 0;
489
490 fbcon_fini ( &efifb.fbcon );
491 err_fbcon_init:
492 err_map:
493 efifb_restore();
494 err_set_mode:
495 err_select_mode:
496 ufree ( efifb.glyphs );
497 err_glyphs:
498 err_locate_hiifont:
499 err_locate_gop:
500 return rc;
501 }
502
503 /**
504 * Finalise EFI frame buffer
505 *
506 */
efifb_fini(void)507 static void efifb_fini ( void ) {
508
509 /* Finalise frame buffer console */
510 fbcon_fini ( &efifb.fbcon );
511
512 /* Restore saved mode */
513 efifb_restore();
514
515 /* Free glyphs */
516 ufree ( efifb.glyphs );
517 }
518
519 /**
520 * Print a character to current cursor position
521 *
522 * @v character Character
523 */
efifb_putchar(int character)524 static void efifb_putchar ( int character ) {
525
526 fbcon_putchar ( &efifb.fbcon, character );
527 }
528
529 /**
530 * Configure console
531 *
532 * @v config Console configuration, or NULL to reset
533 * @ret rc Return status code
534 */
efifb_configure(struct console_configuration * config)535 static int efifb_configure ( struct console_configuration *config ) {
536 int rc;
537
538 /* Reset console, if applicable */
539 if ( ! efifb_console.disabled ) {
540 efifb_fini();
541 efi_console.disabled &= ~CONSOLE_DISABLED_OUTPUT;
542 ansicol_reset_magic();
543 }
544 efifb_console.disabled = CONSOLE_DISABLED;
545
546 /* Do nothing more unless we have a usable configuration */
547 if ( ( config == NULL ) ||
548 ( config->width == 0 ) || ( config->height == 0 ) ) {
549 return 0;
550 }
551
552 /* Initialise EFI frame buffer */
553 if ( ( rc = efifb_init ( config ) ) != 0 )
554 return rc;
555
556 /* Mark console as enabled */
557 efifb_console.disabled = 0;
558 efi_console.disabled |= CONSOLE_DISABLED_OUTPUT;
559
560 /* Set magic colour to transparent if we have a background picture */
561 if ( config->pixbuf )
562 ansicol_set_magic_transparent();
563
564 return 0;
565 }
566
567 /** EFI graphics output protocol console driver */
568 struct console_driver efifb_console __console_driver = {
569 .usage = CONSOLE_EFIFB,
570 .putchar = efifb_putchar,
571 .configure = efifb_configure,
572 .disabled = CONSOLE_DISABLED,
573 };
574