Skip to content

Commit 3b300d0

Browse files
Ben Bensonpopcornmix
authored andcommitted
Fullscreen Splash Images
Enable by adding the following to cmdline.txt: `fullscreen_logo_name=logo.tga fullscreen_logo=1` Will show the logo file present in /lib/firmware/ on the screen. This will be fullscreen and rendered early at boot. Any remaining space is filled with solid color from the image border. If TGA file is too big, image is clipped accordingly. Signed-off-by: Ben Benson <ben.benson@raspberrypi.com> Splash Screen: bug fix Prevents fullscreen logos from being drawn multiple times. With small enough logos, the image would be drawn multiple times across the screen. Signed-off-by: Ben Benson <ben.benson@raspberrypi.com> fbcon: Add defensive coding to logo loader There were various points where the loader was using uninitialised data, had the potential to run off the end of an array, or was handling core functions incorrectly. Fix these up. Also handle 24bpp and 32bpp framebuffers. Signed-off-by: Dave Stevenson <dave.stevenson@raspberrypi.com>
1 parent dcd246b commit 3b300d0

File tree

2 files changed

+286
-49
lines changed

2 files changed

+286
-49
lines changed

drivers/video/fbdev/core/fb_logo.c

Lines changed: 274 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,24 @@
22

33
#include <linux/fb.h>
44
#include <linux/linux_logo.h>
5+
#include <linux/fs.h>
6+
#include <linux/slab.h>
7+
#include <linux/string.h>
8+
#include <linux/uaccess.h>
9+
#include <linux/file.h>
10+
#include <linux/kernel.h>
11+
#include <linux/firmware.h>
512

613
#include "fb_internal.h"
714

815
bool fb_center_logo __read_mostly;
916
int fb_logo_count __read_mostly = -1;
17+
static int fullscreen_logo_enabled;
18+
static char *fullscreen_logo_path;
19+
20+
struct image_palette {
21+
u8 colors[224][3];
22+
};
1023

1124
static inline unsigned int safe_shift(unsigned int d, int n)
1225
{
@@ -79,6 +92,32 @@ static void fb_set_logo_truepalette(struct fb_info *info,
7992
}
8093
}
8194

95+
static void fb_set_logo_RGB_palette(struct fb_info *info,
96+
struct image_palette *pal_in,
97+
u32 *pal_out, int current_rows)
98+
{
99+
// Set the kernel palette from an array of RGB values
100+
static const unsigned char mask[] = {
101+
0, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe, 0xff
102+
};
103+
unsigned char redmask, greenmask, bluemask;
104+
int redshift, greenshift, blueshift;
105+
int i;
106+
107+
redmask = mask[info->var.red.length < 8 ? info->var.red.length : 8];
108+
greenmask = mask[info->var.green.length < 8 ? info->var.green.length : 8];
109+
bluemask = mask[info->var.blue.length < 8 ? info->var.blue.length : 8];
110+
redshift = info->var.red.offset - (8 - info->var.red.length);
111+
greenshift = info->var.green.offset - (8 - info->var.green.length);
112+
blueshift = info->var.blue.offset - (8 - info->var.blue.length);
113+
114+
for (i = 0; i < current_rows; i++) {
115+
pal_out[i + 32] = (safe_shift((pal_in->colors[i][0] & redmask), redshift) |
116+
safe_shift((pal_in->colors[i][1] & greenmask), greenshift) |
117+
safe_shift((pal_in->colors[i][2] & bluemask), blueshift));
118+
}
119+
}
120+
82121
static void fb_set_logo_directpalette(struct fb_info *info,
83122
const struct linux_logo *logo,
84123
u32 *palette)
@@ -275,6 +314,171 @@ static void fb_do_show_logo(struct fb_info *info, struct fb_image *image,
275314
}
276315
}
277316

317+
static int __init fb_fullscreen_logo_setup(char *str)
318+
{
319+
fullscreen_logo_enabled = 1;
320+
fullscreen_logo_path = str;
321+
pr_info("Fullscreen splash enabled, using image path: %s", fullscreen_logo_path);
322+
return 1;
323+
}
324+
325+
__setup("fullscreen_logo_name=", fb_fullscreen_logo_setup);
326+
327+
static bool fb_palette_contains_entry(struct image_palette *palette, int num_existing_rows,
328+
u8 *entry_to_add, int cols, int *index)
329+
{
330+
for (int i = 0; i < num_existing_rows; i++) {
331+
bool match = true;
332+
333+
for (int j = 0; j < cols; j++) {
334+
if (palette->colors[i][j] != entry_to_add[j]) {
335+
match = false;
336+
break;
337+
}
338+
}
339+
if (match) {
340+
*index = i; // Update the index
341+
return true; // Found a duplicate
342+
}
343+
}
344+
return false; // No duplicate found
345+
}
346+
347+
static void fb_set_logo_from_file(struct fb_info *info, const char *filepath,
348+
struct fb_image *image, u32 *palette)
349+
{
350+
int current_rows = 0, palette_index = 0, actual_row, skip_x = 0, skip_y = 0, ret;
351+
unsigned char *read_logo = NULL, *header;
352+
const char *file_content;
353+
const struct firmware *fw = NULL;
354+
struct image_palette image_palette;
355+
const char *current_ptr, *end_ptr;
356+
long width = 0, height = 0;
357+
bool top_to_bottom;
358+
u8 B, G, R;
359+
u8 entry[3];
360+
ssize_t len;
361+
362+
ret = firmware_request_nowarn(&fw, filepath, info->device);
363+
if (ret) {
364+
pr_info("Failed to load logo file '%s': %d\n", filepath, ret);
365+
goto cleanup;
366+
}
367+
len = fw->size;
368+
file_content = fw->data;
369+
370+
if (!len) {
371+
pr_err("Error: logo TGA file is empty. Not drawing fullscreen logo.\n");
372+
goto cleanup;
373+
}
374+
375+
current_ptr = file_content;
376+
end_ptr = file_content + len;
377+
if (len < 18) {
378+
pr_err("Invalid logo file: TGA file too small for header\n");
379+
goto cleanup;
380+
}
381+
header = (unsigned char *)file_content;
382+
383+
// Skip color map info (bytes 3-7)
384+
// Skip image origin (bytes 8-11)
385+
width = header[12] | (header[13] << 8);
386+
height = header[14] | (header[15] << 8);
387+
388+
// Only supports uncompressed true-color images (type 2) with 24-bit depth
389+
if (header[2] != 2 || header[16] != 24) {
390+
pr_err("Unsupported TGA logo format: Type=%d, Depth=%d (only support Type=2, Depth=24)\n",
391+
header[2], header[16]);
392+
goto cleanup;
393+
}
394+
// Skip header + ID field
395+
current_ptr = file_content + 18 + header[0];
396+
397+
read_logo = kmalloc_array(width, height, GFP_KERNEL);
398+
if (!read_logo)
399+
goto cleanup;
400+
401+
image->data = read_logo;
402+
403+
// TGA pixels are stored bottom-to-top by default, unless bit 5 of
404+
// image_descriptor is set
405+
top_to_bottom = (header[17] & 0x20) != 0;
406+
skip_x = 0;
407+
skip_y = 0;
408+
409+
if (width > info->var.xres) {
410+
pr_info("Logo is larger than screen (%lu vs %u), clipping horizontally\n",
411+
width, info->var.xres);
412+
skip_x = (width - info->var.xres) / 2;
413+
}
414+
if (height > info->var.yres) {
415+
pr_info("Logo is larger than screen (%lu vs %u), clipping vertically\n",
416+
height, info->var.yres);
417+
skip_y = (height - info->var.yres) / 2;
418+
}
419+
current_ptr += skip_y * width * 3 + skip_x * 3;
420+
// Parse pixel data (BGR format in TGA)
421+
for (int i = 0; i < height - 2 * skip_y; i++) {
422+
for (int j = 0; j < width - 2 * skip_x; j++) {
423+
if (current_ptr + 3 > end_ptr) {
424+
pr_info("TGA: Unexpected end of file\n");
425+
goto cleanup;
426+
}
427+
B = (unsigned char)*current_ptr++;
428+
G = (unsigned char)*current_ptr++;
429+
R = (unsigned char)*current_ptr++;
430+
entry[0] = R;
431+
entry[1] = G;
432+
entry[2] = B;
433+
palette_index = 0;
434+
435+
if (!fb_palette_contains_entry(&image_palette, current_rows,
436+
entry, 3, &palette_index)) {
437+
if (current_rows < 224) {
438+
for (int k = 0; k < 3; k++)
439+
image_palette.colors[current_rows][k] =
440+
entry[k];
441+
palette_index = current_rows;
442+
}
443+
current_rows++;
444+
}
445+
actual_row = top_to_bottom ? i : (height - 1 - i);
446+
447+
read_logo[actual_row * (width - 2 * skip_x) + j] =
448+
palette_index + 32;
449+
}
450+
current_ptr += skip_x * 3 * 2;
451+
}
452+
453+
if (current_rows >= 224)
454+
pr_err("Palette overflow. Entries clipped\n");
455+
456+
// Set logo palette
457+
palette = kzalloc(256 * 4, GFP_KERNEL);
458+
if (!palette)
459+
goto cleanup;
460+
fb_set_logo_RGB_palette(info, &image_palette, palette, current_rows);
461+
if (info->pseudo_palette)
462+
memcpy(palette, info->pseudo_palette, 32 * sizeof(uint32_t));
463+
info->pseudo_palette = palette;
464+
465+
image->width = min_t(unsigned int, width, info->var.xres);
466+
image->height = min_t(unsigned int, height, info->var.yres);
467+
image->dx = 0;
468+
image->dy = 0;
469+
image->depth = 8;
470+
471+
if (image->height < info->var.yres)
472+
image->dy = (info->var.yres - image->height) / 2;
473+
if (image->width < info->var.xres)
474+
image->dx = (info->var.xres - image->width) / 2;
475+
476+
cleanup:
477+
kfree(read_logo);
478+
release_firmware(fw);
479+
}
480+
481+
278482
static int fb_show_logo_line(struct fb_info *info, int rotate,
279483
const struct linux_logo *logo, int y,
280484
unsigned int n)
@@ -288,66 +492,87 @@ static int fb_show_logo_line(struct fb_info *info, int rotate,
288492
info->fbops->owner)
289493
return 0;
290494

291-
image.depth = 8;
292-
image.data = logo->data;
495+
if (fullscreen_logo_enabled) {
496+
fb_set_logo_from_file(info, fullscreen_logo_path,
497+
&image, palette);
498+
} else {
499+
image.depth = 8;
500+
image.data = logo->data;
293501

294-
if (fb_logo.needs_cmapreset)
295-
fb_set_logocmap(info, logo);
502+
if (fb_logo.needs_cmapreset)
503+
fb_set_logocmap(info, logo);
296504

297-
if (fb_logo.needs_truepalette ||
298-
fb_logo.needs_directpalette) {
299-
palette = kmalloc(256 * 4, GFP_KERNEL);
300-
if (palette == NULL)
301-
return 0;
505+
if (fb_logo.needs_truepalette ||
506+
fb_logo.needs_directpalette) {
507+
palette = kmalloc(256 * 4, GFP_KERNEL);
508+
if (palette == NULL)
509+
return 0;
302510

303-
if (fb_logo.needs_truepalette)
304-
fb_set_logo_truepalette(info, logo, palette);
305-
else
306-
fb_set_logo_directpalette(info, logo, palette);
511+
if (fb_logo.needs_truepalette)
512+
fb_set_logo_truepalette(info, logo, palette);
513+
else
514+
fb_set_logo_directpalette(info, logo, palette);
307515

308-
saved_pseudo_palette = info->pseudo_palette;
309-
info->pseudo_palette = palette;
310-
}
516+
saved_pseudo_palette = info->pseudo_palette;
517+
info->pseudo_palette = palette;
518+
}
311519

312-
if (fb_logo.depth <= 4) {
313-
logo_new = kmalloc_array(logo->width, logo->height,
314-
GFP_KERNEL);
315-
if (logo_new == NULL) {
316-
kfree(palette);
317-
if (saved_pseudo_palette)
318-
info->pseudo_palette = saved_pseudo_palette;
319-
return 0;
520+
if (fb_logo.depth <= 4) {
521+
logo_new = kmalloc_array(logo->width, logo->height,
522+
GFP_KERNEL);
523+
if (logo_new == NULL) {
524+
kfree(palette);
525+
if (saved_pseudo_palette)
526+
info->pseudo_palette = saved_pseudo_palette;
527+
return 0;
528+
}
529+
image.data = logo_new;
530+
fb_set_logo(info, logo, logo_new, fb_logo.depth);
320531
}
321-
image.data = logo_new;
322-
fb_set_logo(info, logo, logo_new, fb_logo.depth);
323-
}
324532

325-
if (fb_center_logo) {
326-
int xres = info->var.xres;
327-
int yres = info->var.yres;
533+
if (fb_center_logo) {
534+
int xres = info->var.xres;
535+
int yres = info->var.yres;
328536

329-
if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
330-
xres = info->var.yres;
331-
yres = info->var.xres;
332-
}
537+
if (rotate == FB_ROTATE_CW || rotate == FB_ROTATE_CCW) {
538+
xres = info->var.yres;
539+
yres = info->var.xres;
540+
}
333541

334-
while (n && (n * (logo->width + 8) - 8 > xres))
335-
--n;
336-
image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
337-
image.dy = y ?: (yres - logo->height) / 2;
338-
} else {
339-
image.dx = 0;
340-
image.dy = y;
341-
}
542+
while (n && (n * (logo->width + 8) - 8 > xres))
543+
--n;
544+
image.dx = (xres - (n * (logo->width + 8) - 8)) / 2;
545+
image.dy = y ?: (yres - logo->height) / 2;
546+
} else {
547+
image.dx = 0;
548+
image.dy = y;
549+
}
342550

343-
image.width = logo->width;
344-
image.height = logo->height;
551+
image.width = logo->width;
552+
image.height = logo->height;
345553

346-
if (rotate) {
347-
logo_rotate = kmalloc_array(logo->width, logo->height,
348-
GFP_KERNEL);
349-
if (logo_rotate)
350-
fb_rotate_logo(info, logo_rotate, &image, rotate);
554+
if (rotate) {
555+
logo_rotate = kmalloc_array(logo->width, logo->height,
556+
GFP_KERNEL);
557+
if (logo_rotate)
558+
fb_rotate_logo(info, logo_rotate, &image, rotate);
559+
}
560+
}
561+
if (fullscreen_logo_enabled) {
562+
// Fullscreen logo data may not fill screen
563+
// Fill remainder of screen with border color of logo for continuous feel
564+
u32 fill_color = image.data[0];
565+
struct fb_fillrect region;
566+
567+
region.color = fill_color;
568+
region.dx = 0;
569+
region.dy = 0;
570+
region.width = info->var.xres;
571+
region.height = info->var.yres;
572+
region.rop = ROP_COPY;
573+
info->fbops->fb_fillrect(info, &region);
574+
// Enforce only one draw of the logo
575+
n = 1;
351576
}
352577

353578
fb_do_show_logo(info, &image, rotate, n);

drivers/video/fbdev/core/fbcon.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ static struct fbcon_display fb_display[MAX_NR_CONSOLES];
110110

111111
static struct fb_info *fbcon_registered_fb[FB_MAX];
112112
static int fbcon_num_registered_fb;
113+
static int fullscreen_logo_enabled;
113114

114115
#define fbcon_for_each_registered_fb(i) \
115116
for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++) \
@@ -516,6 +517,15 @@ static int __init fb_console_setup(char *this_opt)
516517
}
517518

518519
__setup("fbcon=", fb_console_setup);
520+
521+
static int __init fullscreen_logo_setup(char *str)
522+
{
523+
pr_info("Fullscreen Logo Enabled");
524+
fullscreen_logo_enabled = 1;
525+
return 1;
526+
}
527+
528+
__setup("fullscreen_logo=", fullscreen_logo_setup);
519529
#endif
520530

521531
static int search_fb_in_map(int idx)
@@ -599,6 +609,8 @@ static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
599609
if (fb_get_color_depth(&info->var, &info->fix) == 1)
600610
erase &= ~0x400;
601611
logo_height = fb_prepare_logo(info, ops->rotate);
612+
if (fullscreen_logo_enabled)
613+
logo_height = info->var.yres - vc->vc_font.height;
602614
logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
603615
q = (unsigned short *) (vc->vc_origin +
604616
vc->vc_size_row * rows);

0 commit comments

Comments
 (0)