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 <msta...@suse.de>
Reviewed-by: Oliver Neukum <oneu...@suse.com>
---
 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 <msta...@suse.com>
+ *
+ *
+ * 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 <msta...@suse.com>
+ *
+ *
+ * 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

Reply via email to