/* SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include #include #include #include #include #include "render_bmp.h" #define MAX_SPLASH_TEXT_WIDTH 32 static bool is_bmp_image_valid(struct bmp_image_header *header) { /* Check if the BMP Header Signature is valid */ if (header->CharB != 'B' || header->CharM != 'M') return false; /* Check if the BMP Image Header Length is valid */ if (!header->PixelHeight || !header->PixelWidth) return false; if (header->Size < header->ImageOffset) return false; if (header->ImageOffset < sizeof(struct bmp_image_header)) return false; return true; } static bool is_bmp_image_compressed(struct bmp_image_header *header) { return header->CompressionType != 0; } static bool is_bitmap_format_supported(struct bmp_image_header *header) { /* * Check BITMAP format is supported * BMP_IMAGE_HEADER = BITMAP_FILE_HEADER + BITMAP_INFO_HEADER */ if (header->HeaderSize != sizeof(struct bmp_image_header) - OFFSET_OF(struct bmp_image_header, HeaderSize)) return false; return true; } static bool do_bmp_image_authentication(struct bmp_image_header *header) { if (!is_bmp_image_valid(header)) { printk(BIOS_ERR, "%s: BMP Image Header is invalid.\n", __func__); return false; } /* * BMP image compression is unsupported by FSP implementation, * hence, exit if the BMP image is compressed. */ if (is_bmp_image_compressed(header)) { printk(BIOS_ERR, "%s: BMP Image Compression is unsupported.\n", __func__); return false; } if (!is_bitmap_format_supported(header)) { printk(BIOS_ERR, "%s: BmpHeader Header Size (0x%x) is not as expected.\n", __func__, header->HeaderSize); return false; } return true; } static uint32_t calculate_blt_buffer_size(struct bmp_image_header *header) { uint32_t blt_buffer_size; /* Calculate the size required for BLT buffer */ blt_buffer_size = header->PixelWidth * header->PixelHeight * sizeof(struct blt_pixel); if (!blt_buffer_size) return 0; return blt_buffer_size; } static int get_color_map_num(struct bmp_image_header *header) { int col_map_number; switch (header->BitPerPixel) { case 1: col_map_number = 2; break; case 4: col_map_number = 16; break; case 8: col_map_number = 256; break; default: /* * For other bit depths (e.g., 24-bit and 32-bit) that doesn't have * a standard palette, col_map_number remains 0. */ col_map_number = 0; break; } /* * At times BMP file may have padding data between its header section and the * data section. */ if (header->ImageOffset - sizeof(struct bmp_image_header) < sizeof(struct bmp_color_map) * col_map_number) return -1; return col_map_number; } /* * Visual Representation of the Flipping: * * Original BMP Image: * * 0 -----------------------------------------PixelWidth-1 * | | * | | * | | * PixelHeight-1------------------------------| * * Blitting Region: * * (x, y) ----------------------------------- (x + blt_width, y) * | | * | | * (x, y + blt_height) -------------------- --(x + blt_width, y + blt_height) * * Flipped Coordinates: * * flipped_x: Represents the horizontal coordinate from the `right` edge of the BMP, * accounting for the blit width. * * flipped_y: Represents the vertical coordinate from the `bottom` edge of the BMP, * accounting for the blit height. * * Example (Orientation: LB_FB_ORIENTATION_BOTTOM_UP): * * If blt_width is small, flipped_x will be near PixelWidth. * If blt_height is small, flipped_y will be near PixelHeight. * * The blit then starts at (flipped_x, blt_height), effectively flipping the * horizontal position and using the original blit height. * * This allows for blitting from the bottom-left corner of the display panel. */ static bool calculate_adj_height_width(const struct bmp_image_header *header, size_t *adjusted_x, size_t *adjusted_y, enum lb_fb_orientation orientation, int blt_width, int blt_height) { size_t flipped_x = header->PixelWidth - blt_width - 1; size_t flipped_y = header->PixelHeight - blt_height - 1; switch (orientation) { case LB_FB_ORIENTATION_LEFT_UP: *adjusted_x = flipped_y; *adjusted_y = flipped_x; break; case LB_FB_ORIENTATION_BOTTOM_UP: *adjusted_x = flipped_x; *adjusted_y = blt_height; break; case LB_FB_ORIENTATION_RIGHT_UP: *adjusted_x = blt_height; *adjusted_y = blt_width; break; case LB_FB_ORIENTATION_NORMAL: default: *adjusted_x = blt_width; *adjusted_y = flipped_y; break; } return true; } /* * Visual Representation of gop_width and calculate linear offset into the GOP buffer: * * GOP Display Buffer: * * +------------------------------------------------+ * | 0 | * | |--> gop_width pixels in this row | * +------------------------------------------------+ * | gop_width | * | | * | gop_width | * | ... | * +------------------------------------------------+ * | gop_width | * +------------------------------------------------+ * * gop_width: Represents the width of a row in the GOP buffer. * - It is determined by the `orientation` of the image. * - For `LEFT_UP` and `RIGHT_UP`, the GOP width is the BMP image's `PixelHeight`. * - For `BOTTOM_UP` and `NORMAL`, the GOP width is the BMP image's `PixelWidth`. * * gop_x, gop_y: Coordinates within the GOP buffer where the blit will start. * - These are calculated by `calculate_adj_height_width` based on the `orientation`, * `blt_width`, and `blt_height`. * * Pointer to the calculated pixel in the GOP blit buffer, or NULL on error. * - Calculate `gop_blt_offset`as `gop_y * gop_width + gop_x` to determine the offset * to access the correct pixel within `gop_blt_buffer`. * - `gop_y * gop_width` calculates the offset of the row. * - `gop_x` calculates the offset within that row. */ static struct blt_pixel *get_gop_blt_pixel( struct blt_pixel *gop_blt_buffer, const struct bmp_image_header *header, int blt_width, int blt_height, enum lb_fb_orientation orientation) { size_t gop_x; size_t gop_y; size_t gop_width; switch (orientation) { case LB_FB_ORIENTATION_LEFT_UP: case LB_FB_ORIENTATION_RIGHT_UP: gop_width = header->PixelHeight; break; case LB_FB_ORIENTATION_BOTTOM_UP: case LB_FB_ORIENTATION_NORMAL: default: gop_width = header->PixelWidth; break; } if (!calculate_adj_height_width(header, &gop_x, &gop_y, orientation, blt_width, blt_height)) return NULL; /* * Calculated pixel in the Graphics Output Protocol (GOP) blit buffer. * This offset represents the starting position where the blitted region will be placed * in the framebuffer. */ return &gop_blt_buffer[gop_y * gop_width + gop_x]; } /* * Fill BMP image into BLT buffer format with optional orientation * * +------------------------------------------------------------------+ * | BMP Image (header: bmp_image_header) | * | (PixelWidth, PixelHeight) | * +------------------------------------------------------------------+ * | * | (blt_width, blt_height) : Region to blit * V * +------------------------------------------------------------------+ * | calculate_adj_height_width(header, adjusted_x, adjusted_y, | * | orientation, blt_width, blt_height)| * +------------------------------------------------------------------+ * | * | Calculates adjusted coordinates based on orientation: * | - flipped_x = header->PixelWidth - blt_width - 1 * | - flipped_y = header->PixelHeight - blt_height - 1 * | - Depending on 'orientation', adjusted_x and adjusted_y are set. * V * +-------------------------------------------------------------------+ * | get_gop_blt_pixel(gop_blt_buffer, header, blt_width, blt_height, | * | orientation) | * +-------------------------------------------------------------------+ * | * | 1. Determines gop_width based on orientation: * | - LEFT_UP/RIGHT_UP: gop_width = header->PixelHeight, * | - BOTTOM_UP/NORMAL: gop_width = header->PixelWidth, * | 2. Calls calculate_adj_height_width to get gop_x and gop_y. * | 3. GOP BLT offset: calculates linear offset into the GOP buffer * | - gop_blt_offset = gop_y * gop_width + gop_x * | 4. GOP BLT offset is used to access the correct regions in: * V * +------------------------------------------------------------------+ * | GOP Blit Buffer (Framebuffer) | * +------------------------------------------------------------------+ */ static void *fill_blt_buffer(struct bmp_image_header *header, uintptr_t logo, size_t blt_buffer_size, enum lb_fb_orientation orientation) { struct blt_pixel *gop_blt_buffer; struct blt_pixel *gop_blt_ptr; struct blt_pixel *gop_blt; uint8_t *bmp_image; uint8_t *bmp_header; struct bmp_color_map *color_map; size_t image_index; gop_blt_ptr = malloc(sizeof(blt_buffer_size)); if (!gop_blt_ptr) die("%s: out of memory. Consider increasing the `CONFIG_HEAP_SIZE`\n", __func__); bmp_image = ((uint8_t *)logo) + header->ImageOffset; bmp_header = bmp_image; gop_blt_buffer = gop_blt_ptr; color_map = (struct bmp_color_map *)(logo + sizeof(struct bmp_image_header)); for (size_t height = 0; height < header->PixelHeight; height++) { for (size_t width = 0; width < header->PixelWidth; width++, bmp_image++) { size_t index = 0; gop_blt = get_gop_blt_pixel(gop_blt_buffer, header, width, height, orientation); if (!gop_blt) { free(gop_blt_ptr); return NULL; } switch (header->BitPerPixel) { /* Translate 1-bit (2 colors) BMP to 24-bit color */ case 1: for (index = 0; index < 8 && width < header->PixelWidth; index++) { uint8_t bit = ((*bmp_image) >> (7 - index)) & 0x1; gop_blt->Red = color_map[bit].Red; gop_blt->Green = color_map[bit].Green; gop_blt->Blue = color_map[bit].Blue; width++; gop_blt = get_gop_blt_pixel(gop_blt_buffer, header, width, height, orientation); if (!gop_blt) { free(gop_blt_ptr); return NULL; } } width--; break; /* Translate 4-bit (16 colors) BMP Palette to 24-bit color */ case 4: index = (*bmp_image) >> 4; gop_blt->Red = color_map[index].Red; gop_blt->Green = color_map[index].Green; gop_blt->Blue = color_map[index].Blue; if (width < (header->PixelWidth - 1)) { width++; gop_blt = get_gop_blt_pixel(gop_blt_buffer, header, width, height, orientation); if (!gop_blt) { free(gop_blt_ptr); return NULL; } index = (*bmp_image) & 0x0f; gop_blt->Red = color_map[index].Red; gop_blt->Green = color_map[index].Green; gop_blt->Blue = color_map[index].Blue; } break; /* Translate 8-bit (256 colors) BMP Palette to 24-bit color */ case 8: gop_blt->Red = color_map[*bmp_image].Red; gop_blt->Green = color_map[*bmp_image].Green; gop_blt->Blue = color_map[*bmp_image].Blue; break; /* For 24-bit BMP */ case 24: gop_blt->Blue = *bmp_image++; gop_blt->Green = *bmp_image++; gop_blt->Red = *bmp_image; break; /* Convert 32 bit to 24bit bmp - just ignore the final byte of each pixel */ case 32: gop_blt->Blue = *bmp_image++; gop_blt->Green = *bmp_image++; gop_blt->Red = *bmp_image++; break; /* Other bit format of BMP is not supported. */ default: free(gop_blt_ptr); gop_blt_ptr = NULL; printk(BIOS_ERR, "%s, BMP Bit format not supported. 0x%X\n", __func__, header->BitPerPixel); return NULL; } } image_index = (uintptr_t)bmp_image - (uintptr_t)bmp_header; /* Each row in BMP Image should be 4-byte align */ if ((image_index % 4) != 0) bmp_image = bmp_image + (4 - (image_index % 4)); } return gop_blt_ptr; } /* Helper function to perform the common BMP to GOP BLT conversion logic */ static bool convert_bmp_to_gop_blt_common(uintptr_t logo, size_t logo_size, uintptr_t *blt, size_t *blt_size, uint32_t *pixel_height, uint32_t *pixel_width, enum lb_fb_orientation orientation) { size_t blt_buffer_size; struct bmp_image_header *bmp_header; bmp_header = (struct bmp_image_header *)logo; /* Authenticate BMP header and validate size against provided logo_size */ if (!do_bmp_image_authentication(bmp_header) || (bmp_header->Size != logo_size)) return false; blt_buffer_size = calculate_blt_buffer_size(bmp_header); if (!blt_buffer_size) return false; if (get_color_map_num(bmp_header) < 0) return false; bool is_standard_orientation = (orientation == LB_FB_ORIENTATION_NORMAL || orientation == LB_FB_ORIENTATION_BOTTOM_UP); *blt_size = blt_buffer_size; *pixel_height = is_standard_orientation ? bmp_header->PixelHeight : bmp_header->PixelWidth; *pixel_width = is_standard_orientation ? bmp_header->PixelWidth : bmp_header->PixelHeight; *blt = (uintptr_t)fill_blt_buffer(bmp_header, logo, blt_buffer_size, orientation); return true; } /* Convert a *.BMP graphics image to a blt buffer */ void load_and_convert_bmp_to_blt(uintptr_t *logo, size_t *logo_size, uintptr_t *blt, size_t *blt_size, uint32_t *pixel_height, uint32_t *pixel_width, enum lb_fb_orientation orientation) { uintptr_t bmp; size_t bmp_size; if (!logo || !logo_size || !blt || !blt_size || !pixel_height || !pixel_width) return; bmp = (uintptr_t)bmp_load_logo(&bmp_size); if (!bmp || bmp_size < sizeof(struct bmp_image_header)) return; *logo = bmp; *logo_size = bmp_size; convert_bmp_to_gop_blt_common(*logo, *logo_size, blt, blt_size, pixel_height, pixel_width, orientation); } /* Convert a *.BMP graphics image (as per input `logo`) to a GOP blt buffer */ void convert_bmp_to_blt(uintptr_t logo, size_t logo_size, uintptr_t *blt, size_t *blt_size, uint32_t *pixel_height, uint32_t *pixel_width, enum lb_fb_orientation orientation) { if (!blt || !blt_size || !pixel_height || !pixel_width) return; if (!logo || logo_size < sizeof(struct bmp_image_header)) return; convert_bmp_to_gop_blt_common(logo, logo_size, blt, blt_size, pixel_height, pixel_width, orientation); } /* * Calculates the destination coordinates for the logo based on both horizontal and * vertical alignment settings. * * horizontal_resolution: The horizontal resolution of the display panel. * vertical_resolution: The vertical resolution of the display panel. * logo_width: The width of the logo bitmap. * logo_height: The height of the logo bitmap. * halignment: The horizontal alignment setting. Use FW_SPLASH_HALIGNMENT_NONE * if only vertical alignment is specified. * valignment: The vertical alignment setting. Use FW_SPLASH_VALIGNMENT_NONE * if only horizontal alignment is specified. * * Returning `struct logo_coordinates` that contains the calculated x and y coordinates * for rendering the logo. */ struct logo_coordinates calculate_logo_coordinates( uint32_t horizontal_resolution, uint32_t vertical_resolution, uint32_t logo_width, uint32_t logo_height, enum fw_splash_horizontal_alignment halignment, enum fw_splash_vertical_alignment valignment) { struct logo_coordinates coords; /* Calculate X coordinate */ switch (halignment) { case FW_SPLASH_HALIGNMENT_LEFT: coords.x = 0; break; case FW_SPLASH_HALIGNMENT_RIGHT: coords.x = horizontal_resolution - logo_width; break; default: /* FW_SPLASH_HALIGNMENT_CENTER (default) */ coords.x = (horizontal_resolution - logo_width) / 2; break; } /* Calculate Y coordinate */ switch (valignment) { case FW_SPLASH_VALIGNMENT_MIDDLE: coords.y = vertical_resolution / 2; break; case FW_SPLASH_VALIGNMENT_TOP: coords.y = 0; break; case FW_SPLASH_VALIGNMENT_BOTTOM: coords.y = vertical_resolution - logo_height; break; default: /* FW_SPLASH_VALIGNMENT_CENTER (default) */ coords.y = (vertical_resolution - logo_height) / 2; break; } return coords; } /* * Copies the logo to the framebuffer. * * framebuffer_base: The base address of the framebuffer. * bytes_per_scanline: The number of bytes per scanline in the framebuffer. * logo_buffer: The address of the logo data in BLT format. * logo_width: The width of the logo bitmap. * logo_height: The height of the logo bitmap. * dest_x: The destination x-coordinate in the framebuffer for rendering the logo. * dest_y: The destination y-coordinate in the framebuffer for rendering the logo. */ static void copy_logo_to_framebuffer( uintptr_t framebuffer_base, uint32_t bytes_per_scanline, uintptr_t logo_buffer, uint32_t logo_width, uint32_t logo_height, uint32_t dest_x, uint32_t dest_y) { size_t pixel_size = sizeof(struct blt_pixel); size_t bytes_per_logo_line = logo_width * pixel_size; uint8_t *framebuffer_offset = (uint8_t *)framebuffer_base + dest_y * bytes_per_scanline + dest_x * pixel_size; uint8_t *dst_row_address = framebuffer_offset; uint8_t *src_row_address = (uint8_t *)logo_buffer; for (uint32_t i = 0; i < logo_height; i++) { memcpy(dst_row_address, src_row_address, bytes_per_logo_line); dst_row_address += bytes_per_scanline; src_row_address += bytes_per_logo_line; } } /* * Adjust logo layout based on the panel orientation. * * logo_type: Logo type. * config: Logo configuration information. * logo_halignment: Resultant horizontal alignment setting. * logo_valignment: Resultant vertical alignment setting. * logo_bottom_margin: Resultant bottom margin. */ static void get_logo_layout( enum bootsplash_type logo_type, struct logo_config *config, enum fw_splash_horizontal_alignment *logo_halignment, enum fw_splash_vertical_alignment *logo_valignment, uint8_t *logo_bottom_margin ) { if (!config || !logo_halignment || !logo_valignment || !logo_bottom_margin) return; if (logo_type == BOOTSPLASH_LOW_BATTERY || logo_type == BOOTSPLASH_CENTER || logo_type == BOOTSPLASH_OFF_MODE_CHARGING) { *logo_halignment = config->halignment; *logo_valignment = config->valignment; /* Override logo alignment if the default screen orientation is not normal */ if (config->panel_orientation != LB_FB_ORIENTATION_NORMAL) *logo_valignment = FW_SPLASH_VALIGNMENT_CENTER; *logo_bottom_margin = 0; } else if (logo_type == BOOTSPLASH_FOOTER) { *logo_halignment = FW_SPLASH_HALIGNMENT_CENTER; *logo_valignment = FW_SPLASH_VALIGNMENT_CENTER; switch (config->panel_orientation) { case LB_FB_ORIENTATION_RIGHT_UP: *logo_halignment = FW_SPLASH_HALIGNMENT_LEFT; break; case LB_FB_ORIENTATION_LEFT_UP: *logo_halignment = FW_SPLASH_HALIGNMENT_RIGHT; break; case LB_FB_ORIENTATION_BOTTOM_UP: *logo_valignment = FW_SPLASH_VALIGNMENT_TOP; break; default: /* LB_FB_ORIENTATION_NORMAL (default) */ *logo_valignment = FW_SPLASH_VALIGNMENT_BOTTOM; break; } *logo_bottom_margin = config->logo_bottom_margin; } else { // Default values *logo_halignment = FW_SPLASH_HALIGNMENT_CENTER; *logo_valignment = FW_SPLASH_VALIGNMENT_CENTER; *logo_bottom_margin = 0; } } /* * Loads, converts, and renders a BMP logo to the framebuffer. * * logo_type: Logo type. * config: Logo configuration information. * * Returns 0 on success, -1 on failure. */ static int load_and_render_logo_to_framebuffer( enum bootsplash_type logo_type, struct logo_config *config ) { uintptr_t logo; size_t logo_size; size_t blt_size; uintptr_t blt_buffer; uint32_t logo_height, logo_width; struct logo_coordinates logo_coords; enum fw_splash_horizontal_alignment halignment; enum fw_splash_vertical_alignment valignment; uint8_t logo_bottom_margin; if (!config) return -1; logo = (uintptr_t)bmp_load_logo_by_type(logo_type, &logo_size); if (!logo || logo_size < sizeof(struct bmp_image_header)) { printk(BIOS_ERR, "%s: BMP image (%zu) is less than expected minimum size (%zu).\n", __func__, logo_size, sizeof(struct bmp_image_header)); return -1; } convert_bmp_to_blt(logo, logo_size, &blt_buffer, &blt_size, &logo_height, &logo_width, config->panel_orientation); get_logo_layout(logo_type, config, &halignment, &valignment, &logo_bottom_margin); logo_coords = calculate_logo_coordinates(config->horizontal_resolution, config->vertical_resolution, logo_width, logo_height, halignment, valignment); if (logo_bottom_margin) { switch (config->panel_orientation) { case LB_FB_ORIENTATION_RIGHT_UP: logo_coords.x = logo_bottom_margin; break; case LB_FB_ORIENTATION_LEFT_UP: logo_coords.x -= logo_bottom_margin; break; case LB_FB_ORIENTATION_BOTTOM_UP: logo_coords.y = logo_bottom_margin; break; default: /* LB_FB_ORIENTATION_NORMAL (default) */ logo_coords.y -= logo_bottom_margin; break; } } copy_logo_to_framebuffer(config->framebuffer_base, config->bytes_per_scanline, blt_buffer, logo_width, logo_height, logo_coords.x, logo_coords.y); bmp_release_logo(); return 0; } /* * Loads, converts, and renders a BMP logo to the framebuffer. * The logo type (primary or low battery etc.) is seletcted dynamically. * * config: Logo configuration information. * */ void render_logo_to_framebuffer(struct logo_config *config) { if (!config) return; if (config->framebuffer_base == 0) { /* Try to load from already populated framebuffer information */ const struct lb_framebuffer *fb = get_lb_framebuffer(); /* Exit if framebuffer is still not available */ if (!fb) return; config->framebuffer_base = fb->physical_address; config->horizontal_resolution = fb->x_resolution; config->vertical_resolution = fb->y_resolution; config->bytes_per_scanline = fb->bytes_per_line; } /* * If the device is in low-battery mode and the low-battery splash screen is being * displayed, prevent further operation and bail out early. */ if (platform_is_low_battery_shutdown_needed()) { if (load_and_render_logo_to_framebuffer(BOOTSPLASH_LOW_BATTERY, config) != 0) { printk(BIOS_ERR, "%s: Failed to render low-battery logo.\n", __func__); } /* Display Text message at the footer of splash screen if supported */ if (CONFIG(FRAMEBUFFER_SPLASH_TEXT)) { char msg[MAX_SPLASH_TEXT_WIDTH]; if (platform_get_splash_text(BOOTSPLASH_LOW_BATTERY, msg, MAX_SPLASH_TEXT_WIDTH)) render_text_to_framebuffer(config, msg, BOOTSPLASH_TEXT_FOOTER); } return; } /* * If the device has booted due to cable power insertion aka off-mode then display * off-mode charging user notification and bail out early. */ if (platform_is_off_mode_charging_active()) { if (load_and_render_logo_to_framebuffer(BOOTSPLASH_OFF_MODE_CHARGING, config) != 0) printk(BIOS_ERR, "%s: Failed to render off-mode charging logo.\n", __func__); /* Display Text message at the footer of splash screen if supported */ if (CONFIG(FRAMEBUFFER_SPLASH_TEXT)) { char msg[MAX_SPLASH_TEXT_WIDTH]; if (platform_get_splash_text(BOOTSPLASH_OFF_MODE_CHARGING, msg, MAX_SPLASH_TEXT_WIDTH)) render_text_to_framebuffer(config, msg, BOOTSPLASH_TEXT_FOOTER); } return; } /* Render the main logo */ if (load_and_render_logo_to_framebuffer(BOOTSPLASH_CENTER, config) != 0) { printk(BIOS_ERR, "%s: Failed to render main splash screen logo.\n", __func__); } else if (CONFIG(SPLASH_SCREEN_FOOTER)) { /* Render the footer logo */ if (load_and_render_logo_to_framebuffer(BOOTSPLASH_FOOTER, config) != 0) printk(BIOS_ERR, "%s: Failed to render footer logo.\n", __func__); } } static void release_logo(void *arg_unused) { bmp_release_logo(); } BOOT_STATE_INIT_ENTRY(BS_PAYLOAD_LOAD, BS_ON_EXIT, release_logo, NULL);