[RFC 08/14] bootsplash: Add file reading and picture rendering

From: Max Staudt
Date: Wed Oct 25 2017 - 08:48:38 EST


Load logo(s) from a file and render them in the center of the screen.

This removes the "black screen" functionality, which can now be emulated
by providing a splash file with no pictures and a black background.

Signed-off-by: Max Staudt <mstaudt@xxxxxxx>
Reviewed-by: Oliver Neukum <oneukum@xxxxxxxx>
---
drivers/video/console/Kconfig | 8 +
drivers/video/fbdev/core/Makefile | 2 +-
drivers/video/fbdev/core/bootsplash.c | 57 +++++-
drivers/video/fbdev/core/bootsplash_file.h | 112 ++++++++++++
drivers/video/fbdev/core/bootsplash_internal.h | 36 ++++
drivers/video/fbdev/core/bootsplash_load.c | 242 +++++++++++++++++++++++++
drivers/video/fbdev/core/bootsplash_render.c | 106 ++++++++++-
7 files changed, 557 insertions(+), 6 deletions(-)
create mode 100644 drivers/video/fbdev/core/bootsplash_file.h
create mode 100644 drivers/video/fbdev/core/bootsplash_load.c

diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index a6617c07229a..c3496c5ac2bb 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -175,6 +175,14 @@ config BOOTSPLASH
If unsure, say N.
This is typically used by distributors and system integrators.

+config BOOTSPLASH_FILE
+ string "Default full path to bootsplash file"
+ depends on BOOTSPLASH
+ default "/bootsplash"
+ help
+ This file will be looked for in the initramfs and, if found, loaded
+ and used as a bootsplash.
+
config STI_CONSOLE
bool "STI text console"
depends on PARISC
diff --git a/drivers/video/fbdev/core/Makefile b/drivers/video/fbdev/core/Makefile
index 66895321928e..6a8d1bab8a01 100644
--- a/drivers/video/fbdev/core/Makefile
+++ b/drivers/video/fbdev/core/Makefile
@@ -31,4 +31,4 @@ obj-$(CONFIG_FB_SVGALIB) += svgalib.o
obj-$(CONFIG_FB_DDC) += fb_ddc.o

obj-$(CONFIG_BOOTSPLASH) += bootsplash.o bootsplash_render.o \
- dummyblit.o
+ bootsplash_load.o dummyblit.o
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index e98c05dd8bc0..3253506d26a7 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -32,6 +32,7 @@
#include <linux/vt_kern.h>
#include <linux/workqueue.h>

+#include "bootsplash_file.h"
#include "bootsplash_internal.h"


@@ -116,12 +117,19 @@ static bool is_fb_compatible(struct fb_info *info)
*/
void bootsplash_render_full(struct fb_info *info)
{
+ mutex_lock(&splash_global.data_lock);
+
if (!is_fb_compatible(info))
- return;
+ goto out;

bootsplash_do_render_background(info);

+ bootsplash_do_render_pictures(info);
+
bootsplash_do_render_flush(info);
+
+out:
+ mutex_unlock(&splash_global.data_lock);
}


@@ -135,6 +143,7 @@ bool bootsplash_would_render_now(void)
{
return !oops_in_progress
&& !console_blanked
+ && splash_global.filebuf
&& bootsplash_is_enabled();
}

@@ -231,11 +240,33 @@ static ssize_t splash_store_enabled(struct device *device,
return count;
}

+static ssize_t splash_store_drop_splash(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ if (!buf || !count || !splash_global.filebuf)
+ return count;
+
+ /* Stop the splash first so we get our text console
+ * if we're currently on one.
+ */
+ mutex_lock(&splash_global.data_lock);
+
+ bootsplash_disable();
+ bootsplash_free_locked();
+
+ mutex_unlock(&splash_global.data_lock);
+
+ return count;
+}
+
static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled);
+static DEVICE_ATTR(drop_splash, 0200, NULL, splash_store_drop_splash);


static struct attribute *splash_dev_attrs[] = {
&dev_attr_enabled.attr,
+ &dev_attr_drop_splash.attr,
NULL
};

@@ -279,8 +310,11 @@ static struct platform_driver splash_driver = {

void bootsplash_init(void)
{
+ loff_t len;
+ void *mem;
+
/* Initialized already? */
- if (splash_global.wq)
+ if (splash_global.filebuf)
return;


@@ -311,6 +345,7 @@ void bootsplash_init(void)
}

spin_lock_init(&splash_global.state_lock);
+ mutex_init(&splash_global.data_lock);
if (!splash_global.wq)
splash_global.wq = alloc_workqueue("bootsplash",
WQ_UNBOUND | WQ_FREEZABLE,
@@ -320,6 +355,21 @@ void bootsplash_init(void)
goto err;
}

+ /* Default splash file to load */
+ if (!splash_global.filename)
+ splash_global.filename = CONFIG_BOOTSPLASH_FILE
+ "." __stringify(BOOTSPLASH_VERSION);
+ // TODO: Remove the version suffix when upstreaming
+
+ /* Load splash from file in initramfs */
+ if (kernel_read_file_from_path(splash_global.filename, &mem,
+ &len, 2*1024*1024, READING_UNKNOWN)) {
+ pr_err("Failed to read file: %s\n", splash_global.filename);
+ goto err;
+ }
+
+ bootsplash_activate_buf(mem, len);
+
return;

err_device:
@@ -335,3 +385,6 @@ void bootsplash_init(void)

module_param_named(enable, splash_global.enabled, bool, 0444);
MODULE_PARM_DESC(enable, "Enable/disable kernel bootsplash");
+
+module_param_named(filename, splash_global.filename, charp, 0444);
+MODULE_PARM_DESC(filename, "Bootsplash file");
diff --git a/drivers/video/fbdev/core/bootsplash_file.h b/drivers/video/fbdev/core/bootsplash_file.h
new file mode 100644
index 000000000000..f33577e062ca
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_file.h
@@ -0,0 +1,112 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * Authors:
+ * Max Staudt <mstaudt@xxxxxxxx>
+ *
+ *
+ * Very loosely based on the old kernel splash project at
+ * http://www.bootsplash.org/ and SUSE improvements.
+ */
+
+#ifndef __BOOTSPLASH_FILE_H
+#define __BOOTSPLASH_FILE_H
+
+
+#define BOOTSPLASH_VERSION 55559
+
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+
+
+/*
+ * On-disk types
+ *
+ * A splash file consists of:
+ * - One single 'struct splash_file_header'
+ * - An array of 'struct splash_pic_header'
+ * - An array of raw data blocks, each padded to 16 bytes and
+ * preceded by a 'struct splash_blob_header'
+ *
+ * A single-frame splash may look like this:
+ *
+ * +--------------------+
+ * | |
+ * | splash_file_header |
+ * | -> num_blobs = 1 |
+ * | -> num_pics = 1 |
+ * | |
+ * +--------------------+
+ * | |
+ * | splash_pic_header |
+ * | |
+ * +--------------------+
+ * | |
+ * | splash_blob_header |
+ * | -> type = 0 |
+ * | -> picture_id = 0 |
+ * | |
+ * | (raw RGB data) |
+ * | (pad to 16 bytes) |
+ * | |
+ * +--------------------+
+ *
+ * All multi-byte values are stored on disk as big endian, and
+ * will be converted to CPU endianness in-memory during loading.
+ */
+
+struct splash_file_header {
+ u8 id[16]; /* "Linux bootsplash" (no trailing NUL) */
+
+ /* Splash file format version to avoid clashes */
+ u16 version;
+
+ /* The background color */
+ u8 bg_red;
+ u8 bg_green;
+ u8 bg_blue;
+ u8 bg_reserved;
+
+ /* Number of pic/blobs so we can allocate memory for internal
+ * structures ahead of time when reading the file
+ */
+ u16 num_blobs;
+ u8 num_pics;
+
+ u8 padding[103];
+} __attribute__((__packed__));
+
+
+struct splash_pic_header {
+ u16 width;
+ u16 height;
+
+ /* Number of data packages associated with this picture.
+ * Currently, the only use for more than 1 is for animations.
+ */
+ u8 num_blobs;
+
+ u8 padding[11];
+} __attribute__((__packed__));
+
+
+struct splash_blob_header {
+ /* Length of the data block in bytes. */
+ u32 length;
+
+ /* Type of the contents.
+ * 0 - Raw RGB data.
+ */
+ u16 type;
+
+ /* Picture this blob is associated with.
+ * Blobs will be added to a picture in the order they are
+ * found in the file.
+ */
+ u8 picture_id;
+
+ u8 padding[9];
+} __attribute__((__packed__));
+
+#endif
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
index c0653dd7807b..791591d10d6b 100644
--- a/drivers/video/fbdev/core/bootsplash_internal.h
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -22,12 +22,31 @@
#include <linux/spinlock.h>
#include <linux/workqueue.h>

+#include "bootsplash_file.h"
+

/*
* Runtime types
*/

+struct splash_blob_priv {
+ struct splash_blob_header *blob_header;
+ void *data;
+};
+
+
+struct splash_pic_priv {
+ struct splash_pic_header *pic_header;
+
+ struct splash_blob_priv *blobs;
+ u16 blobs_loaded;
+};
+
+
struct splash_priv {
+ /* Bootup and runtime state */
+ char *filename;
+
/* This lock only synchronizes the enabled/disabled state
*
* Note: fbcon.c uses this twice, by calling
@@ -49,6 +68,18 @@ struct splash_priv {
* during suspend.
*/
struct workqueue_struct *wq;
+
+ /* Splash data structures including lock for everything below */
+ struct mutex data_lock;
+
+ struct fb_info *splash_fb;
+
+ union {
+ u8 *filebuf;
+ struct splash_file_header *header;
+ };
+
+ struct splash_pic_priv *pics;
};


@@ -67,6 +98,11 @@ extern struct splash_priv splash_global;
*/

void bootsplash_do_render_background(struct fb_info *info);
+void bootsplash_do_render_pictures(struct fb_info *info);
void bootsplash_do_render_flush(struct fb_info *info);

+
+void bootsplash_free_locked(void);
+int bootsplash_activate_buf(char *buf, long buflen);
+
#endif
diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c
new file mode 100644
index 000000000000..2f983a74664c
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_load.c
@@ -0,0 +1,242 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Loading and freeing functions)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@xxxxxxxx>
+ *
+ *
+ * Very loosely based on the old kernel splash project at
+ * http://www.bootsplash.org/ and SUSE improvements.
+ */
+
+#define pr_fmt(fmt) "bootsplash: " fmt
+
+
+#include <linux/bootsplash.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+
+#include "bootsplash_file.h"
+#include "bootsplash_internal.h"
+
+
+
+
+/* Free all vmalloc()'d resources.
+ *
+ * To be called with the bootsplash data_lock held.
+ */
+void bootsplash_free_locked(void)
+{
+ if (splash_global.filebuf && splash_global.pics) {
+ unsigned int i;
+
+ for (i = 0; i < splash_global.header->num_pics; i++) {
+ struct splash_pic_priv *pp = &splash_global.pics[i];
+
+ vfree(pp->blobs);
+ }
+
+ vfree(splash_global.pics);
+ splash_global.pics = NULL;
+ }
+
+ if (splash_global.filebuf) {
+ vfree(splash_global.filebuf);
+ splash_global.filebuf = NULL;
+ }
+}
+
+
+
+
+/*
+ * Load a splash screen from a memory buffer.
+ *
+ * Parsing, and sanity checks.
+ *
+ * Once a vmalloc'd buffer is passed to this function, the bootsplash
+ * code is responsible for freeing it - even in an error case.
+ */
+
+int bootsplash_activate_buf(char *buf, long buflen)
+{
+ unsigned int i;
+ char *walker;
+
+ if (splash_global.filebuf) {
+ pr_err("Won't load splash screen on top of another one.\n");
+
+ vfree(buf);
+ return -ENOMEM;
+ }
+
+
+ if (buflen < sizeof(struct splash_file_header)
+#ifdef __BIG_ENDIAN
+ || memcmp(buf, "Linux bootsplash",
+#else
+ || memcmp(buf, "hsalpstoob xuniL",
+#endif
+ sizeof(splash_global.header->id))) {
+ pr_err("Not a bootsplash file.\n");
+
+ vfree(buf);
+ return -EINVAL;
+ }
+
+ pr_info("Loading splash file (%li bytes)\n", buflen);
+
+
+ mutex_lock(&splash_global.data_lock);
+
+ splash_global.filebuf = buf;
+
+
+ /* Sanity checks */
+ if (splash_global.header->version != BOOTSPLASH_VERSION) {
+ pr_err("Loaded v%d file, but we only support version %d\n",
+ splash_global.header->version,
+ BOOTSPLASH_VERSION);
+
+ goto err;
+ }
+
+ if (buflen < sizeof(struct splash_file_header)
+ + splash_global.header->num_pics
+ * sizeof(struct splash_pic_header)
+ + splash_global.header->num_blobs
+ * sizeof(struct splash_blob_header)) {
+ pr_err("File incomplete.\n");
+
+ goto err;
+ }
+
+
+ /* Read picture headers */
+
+ if (splash_global.header->num_pics) {
+ splash_global.pics = vzalloc(splash_global.header->num_pics
+ * sizeof(struct splash_pic_priv));
+ if (!splash_global.pics)
+ goto err;
+ }
+
+ walker = splash_global.filebuf + sizeof(struct splash_file_header);
+ for (i = 0; i < splash_global.header->num_pics; i++) {
+ struct splash_pic_priv *pp = &splash_global.pics[i];
+ struct splash_pic_header *ph = (void *)walker;
+
+ pr_debug("Picture %u: Size %ux%u\n", i, ph->width, ph->height);
+
+ if (ph->num_blobs < 1) {
+ pr_err("Picture %u: Zero blobs? Aborting load.\n", i);
+
+ goto err;
+ }
+
+ pp->pic_header = ph;
+ pp->blobs = vzalloc(ph->num_blobs
+ * sizeof(struct splash_blob_priv));
+ if (!pp->blobs) {
+ pr_err("Could not allocate pointer array for picture %d.\n",
+ i);
+
+ ph->num_blobs = 0;
+ }
+
+ walker += sizeof(struct splash_pic_header);
+ }
+
+
+
+ /* Read blob headers */
+ for (i = 0; i < splash_global.header->num_blobs; i++) {
+ struct splash_blob_header *bh = (void *)walker;
+ struct splash_pic_priv *pp;
+
+ if (walker + sizeof(struct splash_blob_header) > buf + buflen)
+ goto err;
+
+ walker += sizeof(struct splash_blob_header);
+
+ if (walker + bh->length > buf + buflen)
+ goto err;
+
+ if (bh->picture_id >= splash_global.header->num_pics)
+ goto nextblob;
+
+ pp = &splash_global.pics[bh->picture_id];
+
+ pr_debug("Blob %u, pic %u, blobs_loaded %u, num_blobs %u.\n",
+ i, bh->picture_id,
+ pp->blobs_loaded, pp->pic_header->num_blobs);
+
+ if (pp->blobs_loaded >= pp->pic_header->num_blobs)
+ goto nextblob;
+
+ switch (bh->type) {
+ case 0:
+ /* Raw 24-bit packed pixels */
+ if (bh->length != pp->pic_header->width
+ * pp->pic_header->height * 3) {
+ pr_err("Blob %u, type 1: Length doesn't match picture.\n",
+ i);
+
+ goto err;
+ }
+ break;
+ default:
+ pr_warn("Blob %u, unknown type %u.\n", i, bh->type);
+ goto nextblob;
+ }
+
+ pp->blobs[pp->blobs_loaded].blob_header = bh;
+ pp->blobs[pp->blobs_loaded].data = walker;
+ pp->blobs_loaded++;
+
+nextblob:
+ walker += bh->length;
+ if (bh->length % 16)
+ walker += 16 - (bh->length % 16);
+ }
+
+ if (walker != buf + buflen)
+ pr_warn("Trailing data in splash file.\n");
+
+ /* Walk over pictures and ensure all blob slots are filled */
+ for (i = 0; i < splash_global.header->num_pics; i++) {
+ struct splash_pic_priv *pp = &splash_global.pics[i];
+
+ if (pp->blobs_loaded != pp->pic_header->num_blobs) {
+ pr_err("Picture %u doesn't have all blob slots filled.\n",
+ i);
+
+ goto err;
+ }
+ }
+
+
+ /* Force a full redraw when the splash is re-activated */
+ splash_global.splash_fb = NULL;
+
+ pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n",
+ buflen, splash_global.header->num_pics,
+ splash_global.header->num_blobs);
+
+ mutex_unlock(&splash_global.data_lock);
+
+ return 0;
+
+
+err:
+ bootsplash_free_locked();
+ mutex_unlock(&splash_global.data_lock);
+ return -EINVAL;
+}
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 460ae0168cb0..72d9867c4656 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -20,6 +20,7 @@
#include <linux/printk.h>
#include <linux/types.h>

+#include "bootsplash_file.h"
#include "bootsplash_internal.h"


@@ -70,16 +71,73 @@ static inline u32 pack_pixel(struct fb_var_screeninfo *dst_var,
}


+/*
+ * Copy from source and blend into the destination picture.
+ * Currently assumes that the source picture is 24bpp.
+ * Currently assumes that the destination is <= 32bpp.
+ */
+static int splash_convert_to_fb(u8 *dst,
+ struct fb_var_screeninfo *dst_var,
+ unsigned int dst_stride,
+ unsigned int dst_xoff,
+ unsigned int dst_yoff,
+ u8 *src,
+ unsigned int src_width,
+ unsigned int src_height)
+{
+ unsigned int x, y;
+ unsigned int src_stride = 3 * src_width; /* Assume 24bpp packed */
+ u32 dst_octpp = dst_var->bits_per_pixel / 8;
+
+ dst_xoff += dst_var->xoffset;
+ dst_yoff += dst_var->yoffset;
+
+ /* Copy with stride and pixel size adjustment */
+ for (y = 0;
+ y < src_height && y + dst_yoff < dst_var->yres_virtual;
+ y++) {
+ u8 *srcline = src
+ + (y * src_stride);
+ u8 *dstline = dst
+ + ((y + dst_yoff) * dst_stride)
+ + (dst_xoff * dst_octpp);
+
+ for (x = 0;
+ x < src_width && x + dst_xoff < dst_var->xres_virtual;
+ x++) {
+ u8 red, green, blue;
+ u32 dstpix;
+
+ /* Read pixel */
+ red = *srcline++;
+ green = *srcline++;
+ blue = *srcline++;
+
+ /* Write pixel */
+ dstpix = pack_pixel(dst_var, red, green, blue);
+ memcpy(dstline, &dstpix, dst_octpp);
+
+ dstline += dst_octpp;
+ }
+ }
+
+ return 0;
+}
+
+
void bootsplash_do_render_background(struct fb_info *info)
{
unsigned int x, y;
u32 dstpix;
u32 dst_octpp = info->var.bits_per_pixel / 8;

+ if (!splash_global.filebuf)
+ return;
+
dstpix = pack_pixel(&info->var,
- 0,
- 0,
- 0);
+ splash_global.header->bg_red,
+ splash_global.header->bg_green,
+ splash_global.header->bg_blue);

for (y = 0; y < info->var.yres_virtual; y++) {
u8 *dstline = info->screen_buffer + (y * info->fix.line_length);
@@ -93,6 +151,48 @@ void bootsplash_do_render_background(struct fb_info *info)
}


+void bootsplash_do_render_pictures(struct fb_info *info)
+{
+ unsigned int i;
+
+ if (!splash_global.filebuf)
+ return;
+
+ for (i = 0; i < splash_global.header->num_pics; i++) {
+ struct splash_blob_priv *bp;
+ struct splash_pic_priv *pp = &splash_global.pics[i];
+ long dst_xoff, dst_yoff;
+
+ if (pp->blobs_loaded < 1)
+ continue;
+
+ bp = &pp->blobs[0];
+
+ if (!bp || bp->blob_header->type != 0)
+ continue;
+
+ dst_xoff = (info->var.xres - pp->pic_header->width) / 2;
+ dst_yoff = (info->var.yres - pp->pic_header->height) / 2;
+
+ if (dst_xoff < 0
+ || dst_yoff < 0
+ || dst_xoff + pp->pic_header->width > info->var.xres
+ || dst_yoff + pp->pic_header->height > info->var.yres) {
+ pr_warn("Picture %u is out of bounds at current resolution: %dx%d\n",
+ i, info->var.xres, info->var.yres);
+
+ continue;
+ }
+
+ /* Draw next splash frame */
+ splash_convert_to_fb(info->screen_buffer, &info->var,
+ info->fix.line_length, dst_xoff, dst_yoff,
+ bp->data,
+ pp->pic_header->width, pp->pic_header->height);
+ }
+}
+
+
void bootsplash_do_render_flush(struct fb_info *info)
{
/* FB drivers using deferred_io (such as Xen) need to sync the
--
2.12.3