diff --git a/drivers/video/fbdev/core/fb_logo.c b/drivers/video/fbdev/core/fb_logo.c index 0bab8352b684..b68e912c369c 100644 --- a/drivers/video/fbdev/core/fb_logo.c +++ b/drivers/video/fbdev/core/fb_logo.c @@ -2,11 +2,24 @@ #include #include +#include +#include +#include +#include +#include +#include +#include #include "fb_internal.h" bool fb_center_logo __read_mostly; int fb_logo_count __read_mostly = -1; +static int fullscreen_logo_enabled; +static char *fullscreen_logo_path; + +struct image_palette { + u8 colors[224][3]; +}; static inline unsigned int safe_shift(unsigned int d, int n) { @@ -79,6 +92,22 @@ static void fb_set_logo_truepalette(struct fb_info *info, } } +static void fb_set_logo_RGB_palette(struct image_palette *palette, + u32 *palette_to_write, int current_rows) +{ + // Set the kernel palette from an array of RGB values + uint32_t color_code; + int i; + + // Color format is RGB565, remove LSB 3 bits, and move to correct position + for (i = 0; i < current_rows; i++) { + color_code = ((((uint16_t)palette->colors[i][0]) >> 3) << 11) | + ((((uint16_t)palette->colors[i][1]) >> 2) << 5) | + (((uint16_t)palette->colors[i][2]) >> 3); + palette_to_write[i+32] = color_code; + } +} + static void fb_set_logo_directpalette(struct fb_info *info, const struct linux_logo *logo, u32 *palette) @@ -275,6 +304,162 @@ static void fb_do_show_logo(struct fb_info *info, struct fb_image *image, } } +static int __init fb_fullscreen_logo_setup(char *str) +{ + fullscreen_logo_enabled = 1; + fullscreen_logo_path = str; + pr_info("Fullscreen splash enabled, using image path: %s", fullscreen_logo_path); + return 1; +} + +__setup("fullscreen_logo_name=", fb_fullscreen_logo_setup); + +static bool fb_palette_contains_entry(struct image_palette *palette, int num_existing_rows, + u8 *entry_to_add, int cols, int *index) +{ + for (int i = 0; i < num_existing_rows; i++) { + bool match = true; + + for (int j = 0; j < cols; j++) { + if (palette->colors[i][j] != entry_to_add[j]) { + match = false; + break; + } + } + if (match) { + *index = i; // Update the index + return true; // Found a duplicate + } + } + return false; // No duplicate found +} + +static void fb_set_logo_from_file(struct fb_info *info, const char *filepath, + struct fb_image *image, u32 *palette) +{ + int current_rows = 0, palette_index = 0, actual_row, skip_x = 0, skip_y = 0, ret; + unsigned char *read_logo = NULL, *header; + const char *file_content = NULL; + const struct firmware *fw; + struct image_palette image_palette; + const char *current_ptr, *end_ptr; + long width = 0, height = 0; + bool top_to_bottom; + u8 B, G, R; + u8 entry[3]; + ssize_t len; + + ret = request_firmware(&fw, filepath, info->device); + if (ret) { + pr_info("Failed to load logo file '%s': %d\n", filepath, ret); + goto cleanup; + } + len = fw->size; + file_content = fw->data; + + if (len > 0) { + current_ptr = file_content; + end_ptr = file_content + len; + if (len < 18) { + pr_err("Invalid logo file: TGA file too small for header\n"); + goto cleanup; + } + header = (unsigned char *)file_content; + + // Skip color map info (bytes 3-7) + // Skip image origin (bytes 8-11) + width = header[12] | (header[13] << 8); + height = header[14] | (header[15] << 8); + + // Only supports uncompressed true-color images (type 2) with 24-bit depth + if (header[2] != 2 || header[16] != 24) { + pr_err("Unsupported TGA logo format: Type=%d, Depth=%d (only support Type=2, Depth=24)\n", + header[2], header[16]); + goto cleanup; + } + // Skip header + ID field + current_ptr = file_content + 18 + header[0]; + + read_logo = kmalloc_array(width, height, GFP_KERNEL); + if (!read_logo) + goto cleanup; + + image->data = read_logo; + + // TGA pixels are stored bottom-to-top by default, unless bit 5 of + // image_descriptor is set + top_to_bottom = (header[17] & 0x20) != 0; + skip_x = 0; + skip_y = 0; + + if (image->width > info->var.xres) { + pr_info("Logo is larger than screen, clipping horizontally"); + skip_x = (image->width - info->var.xres) / 2; + } + if (image->height > info->var.yres) { + pr_info("Logo is larger than screen, clipping vertically"); + skip_y = (image->height - info->var.yres) / 2; + } + current_ptr += skip_y * width * 3 + skip_x * 3; + // Parse pixel data (BGR format in TGA) + for (int i = 0; i < height - 2 * skip_y; i++) { + for (int j = 0; j < width - 2 * skip_x; j++) { + if (current_ptr + 3 > end_ptr) { + pr_info("TGA: Unexpected end of file\n"); + goto cleanup; + } + B = (unsigned char)*current_ptr++; + G = (unsigned char)*current_ptr++; + R = (unsigned char)*current_ptr++; + entry[0] = R; + entry[1] = G; + entry[2] = B; + palette_index = 0; + + if (!fb_palette_contains_entry(&image_palette, current_rows, + entry, 3, &palette_index)) { + for (int k = 0; k < 3; k++) + image_palette.colors[current_rows][k] = entry[k]; + palette_index = current_rows; + current_rows++; + } + actual_row = top_to_bottom ? i : (height - 1 - i); + + read_logo[actual_row * (width - 2 * skip_x) + j] = + palette_index + 32; + } + current_ptr += skip_x * 3 * 2; + } + + // Set logo palette + palette = kmalloc(256 * 4, GFP_KERNEL); + if (palette == NULL) + goto cleanup; + fb_set_logo_RGB_palette(&image_palette, palette, current_rows); + info->pseudo_palette = palette; + + } else { + pr_err("Error: logo TGA file is empty. Not drawing fullscreen logo.\n"); + } + + image->width = min_t(unsigned int, width, info->var.xres); + image->height = min_t(unsigned int, height, info->var.yres); + image->dx = 0; + image->dy = 0; + image->depth = 8; + + if (image->height < info->var.yres) + image->dy = (info->var.yres - image->height) / 2; + if (image->width < info->var.xres) + image->dx = (info->var.xres - image->width) / 2; + +cleanup: + kfree(read_logo); + if (file_content) + kvfree(file_content); +} + + static int fb_show_logo_line(struct fb_info *info, int rotate, const struct linux_logo *logo, int y, unsigned int n) @@ -288,66 +473,87 @@ static int fb_show_logo_line(struct fb_info *info, int rotate, info->fbops->owner) return 0; - image.depth = 8; - image.data = logo->data; - - if (fb_logo.needs_cmapreset) - fb_set_logocmap(info, logo); - - if (fb_logo.needs_truepalette || - fb_logo.needs_directpalette) { - palette = kmalloc(256 * 4, GFP_KERNEL); - if (palette == NULL) - return 0; - - if (fb_logo.needs_truepalette) - fb_set_logo_truepalette(info, logo, palette); - else - fb_set_logo_directpalette(info, logo, palette); - - saved_pseudo_palette = info->pseudo_palette; - info->pseudo_palette = palette; - } - - if (fb_logo.depth <= 4) { - logo_new = kmalloc_array(logo->width, logo->height, - GFP_KERNEL); - if (logo_new == NULL) { - kfree(palette); - if (saved_pseudo_palette) - info->pseudo_palette = saved_pseudo_palette; - return 0; - } - image.data = logo_new; - fb_set_logo(info, logo, logo_new, fb_logo.depth); - } - - if (fb_center_logo) { - int xres = info->var.xres; - int yres = info->var.yres; - - if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) { - xres = info->var.yres; - yres = info->var.xres; - } - - while (n && (n * (logo->width + 8) - 8 > xres)) - --n; - image.dx = (xres - (n * (logo->width + 8) - 8)) / 2; - image.dy = y ?: (yres - logo->height) / 2; + if (fullscreen_logo_enabled) { + fb_set_logo_from_file(info, fullscreen_logo_path, + &image, palette); } else { - image.dx = 0; - image.dy = y; + image.depth = 8; + image.data = logo->data; + + if (fb_logo.needs_cmapreset) + fb_set_logocmap(info, logo); + + if (fb_logo.needs_truepalette || + fb_logo.needs_directpalette) { + palette = kmalloc(256 * 4, GFP_KERNEL); + if (palette == NULL) + return 0; + + if (fb_logo.needs_truepalette) + fb_set_logo_truepalette(info, logo, palette); + else + fb_set_logo_directpalette(info, logo, palette); + + saved_pseudo_palette = info->pseudo_palette; + info->pseudo_palette = palette; + } + + if (fb_logo.depth <= 4) { + logo_new = kmalloc_array(logo->width, logo->height, + GFP_KERNEL); + if (logo_new == NULL) { + kfree(palette); + if (saved_pseudo_palette) + info->pseudo_palette = saved_pseudo_palette; + return 0; + } + image.data = logo_new; + fb_set_logo(info, logo, logo_new, fb_logo.depth); + } + + if (fb_center_logo) { + int xres = info->var.xres; + int yres = info->var.yres; + + if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) { + xres = info->var.yres; + yres = info->var.xres; + } + + while (n && (n * (logo->width + 8) - 8 > xres)) + --n; + image.dx = (xres - (n * (logo->width + 8) - 8)) / 2; + image.dy = y ?: (yres - logo->height) / 2; + } else { + image.dx = 0; + image.dy = y; + } + + image.width = logo->width; + image.height = logo->height; + + if (rotate) { + logo_rotate = kmalloc_array(logo->width, logo->height, + GFP_KERNEL); + if (logo_rotate) + fb_rotate_logo(info, logo_rotate, &image, rotate); + } } + if (fullscreen_logo_enabled) { + // Fullscreen logo data may not fill screen + // Fill remainder of screen with border color of logo for continuous feel + u32 fill_color = image.data[0]; + struct fb_fillrect region; - image.width = logo->width; - image.height = logo->height; - - if (rotate) { - logo_rotate = kmalloc_array(logo->width, logo->height, - GFP_KERNEL); - if (logo_rotate) - fb_rotate_logo(info, logo_rotate, &image, rotate); + region.color = fill_color; + region.dx = 0; + region.dy = 0; + region.width = info->var.xres; + region.height = info->var.yres; + region.rop = ROP_COPY; + info->fbops->fb_fillrect(info, ®ion); + // Enforce only one draw of the logo + n = 1; } fb_do_show_logo(info, &image, rotate, n); diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c index 9bd3c3814b5c..d4b56ee50e9d 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -108,6 +108,7 @@ static struct fbcon_display fb_display[MAX_NR_CONSOLES]; static struct fb_info *fbcon_registered_fb[FB_MAX]; static int fbcon_num_registered_fb; +static int fullscreen_logo_enabled; #define fbcon_for_each_registered_fb(i) \ for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++) \ @@ -514,6 +515,15 @@ static int __init fb_console_setup(char *this_opt) } __setup("fbcon=", fb_console_setup); + +static int __init fullscreen_logo_setup(char *str) +{ + pr_info("Fullscreen Logo Enabled"); + fullscreen_logo_enabled = 1; + return 1; +} + +__setup("fullscreen_logo=", fullscreen_logo_setup); #endif static int search_fb_in_map(int idx) @@ -597,6 +607,8 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info, if (fb_get_color_depth(&info->var, &info->fix) == 1) erase &= ~0x400; logo_height = fb_prepare_logo(info, ops->rotate); + if (fullscreen_logo_enabled) + logo_height = info->var.yres - vc->vc_font.height; logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height); q = (unsigned short *) (vc->vc_origin + vc->vc_size_row * rows);