[RFC 01/14] bootsplash: Initial implementation showing black screen

From: Max Staudt
Date: Wed Oct 25 2017 - 08:49:35 EST


This is the initial prototype for a lean Linux kernel bootsplash.

It works by replacing fbcon's FB manipulation routines (such as
bitblit, tileblit) with dummy functions, effectively disabling text
output, and drawing the splash directly onto the FB device.

As it is now, it will show a black screen rather than a logo, and
only if manually enabled via the kernel cmdline:

bootsplash.enable=1

The reasons for implementing a bootsplash in kernel space are:

- Quieting things more and nicer than with the quiet boot option:
Currently the 'quiet' boot option does not remove the blinking cursor
and errors are still printed. There are use cases where this is not
desirable (such as embedded and desktop systems, digital signage,
etc.) and a vendor logo is preferable.

- Showing graphics, and never text, when the GUI crashes:
This is an extension of the above use case, where recovery is meant
to happen as invisibly to the user as possible. A system integrator
needs the flexibility to hide "scary text" from users in all cases
other than a panic.
This is especially desirable in embedded systems such as digital
signage.

- Racy VT API:
Userspace bootsplashes and GUIs (e.g. plymouth and X) tend to kick
each other out via the non-exclusive KDSETMODE ioctl. This can
result in situations such as the user being stuck in X with chvt
and Ctrl-Alt-Fx no longer working.

- Mode switching from FB to KMS:
We cannot switch from a generic framebuffer (vesafb, efifb) to a
KMS driver while a userspace splash keeps /dev/fb0 open. The device
will vanish, but the address space is still busy, so the KMS driver
cannot reserve its VRAM.

- Simplification of userspace integration:
Right now, hooking up a splash screen in userspace is quite complex.
Having it in the kernel makes this a breeze, as hooks for
switch_root, remounting r/w, etc. become obsolete.

Signed-off-by: Max Staudt <mstaudt@xxxxxxx>
Reviewed-by: Oliver Neukum <oneukum@xxxxxxxx>
---
drivers/video/console/Kconfig | 24 +++
drivers/video/fbdev/core/Makefile | 3 +
drivers/video/fbdev/core/bootsplash.c | 230 +++++++++++++++++++++++++
drivers/video/fbdev/core/bootsplash_internal.h | 68 ++++++++
drivers/video/fbdev/core/bootsplash_render.c | 93 ++++++++++
drivers/video/fbdev/core/dummyblit.c | 89 ++++++++++
drivers/video/fbdev/core/fbcon.c | 12 ++
drivers/video/fbdev/core/fbcon.h | 5 +
include/linux/bootsplash.h | 45 +++++
9 files changed, 569 insertions(+)
create mode 100644 drivers/video/fbdev/core/bootsplash.c
create mode 100644 drivers/video/fbdev/core/bootsplash_internal.h
create mode 100644 drivers/video/fbdev/core/bootsplash_render.c
create mode 100644 drivers/video/fbdev/core/dummyblit.c
create mode 100644 include/linux/bootsplash.h

diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig
index 7f1f1fbcef9e..a6617c07229a 100644
--- a/drivers/video/console/Kconfig
+++ b/drivers/video/console/Kconfig
@@ -151,6 +151,30 @@ config FRAMEBUFFER_CONSOLE_ROTATION
such that other users of the framebuffer will remain normally
oriented.

+config BOOTSPLASH
+ bool "Bootup splash screen"
+ depends on FRAMEBUFFER_CONSOLE=y
+ ---help---
+ This option enables the Linux bootsplash screen.
+
+ The bootsplash is a full-screen logo or animation indicating a
+ booting system. It replaces the classic scrolling text with a
+ graphical alternative, similar to other systems.
+
+ Since this is technically implemented as a hook on top of fbcon,
+ it can only work if the FRAMEBUFFER_CONSOLE is enabled and a
+ framebuffer driver is active. Thus, to get a text-free boot,
+ the system needs to boot with vesafb, efifb, or similar.
+
+ Once built into the kernel, the bootsplash needs to be enabled
+ with bootsplash.enabled=1 and a splash file needs to be supplied.
+
+ Further documentation can be found in:
+ Documentation/fb/bootsplash.txt
+
+ If unsure, say N.
+ This is typically used by distributors and system integrators.
+
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 73493bbd7a15..66895321928e 100644
--- a/drivers/video/fbdev/core/Makefile
+++ b/drivers/video/fbdev/core/Makefile
@@ -29,3 +29,6 @@ obj-$(CONFIG_FB_SYS_IMAGEBLIT) += sysimgblt.o
obj-$(CONFIG_FB_SYS_FOPS) += fb_sys_fops.o
obj-$(CONFIG_FB_SVGALIB) += svgalib.o
obj-$(CONFIG_FB_DDC) += fb_ddc.o
+
+obj-$(CONFIG_BOOTSPLASH) += bootsplash.o bootsplash_render.o \
+ dummyblit.o
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
new file mode 100644
index 000000000000..7a6fd6f8076a
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -0,0 +1,230 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Main file: Glue code, workers, timer, PM, kernel and userland API)
+ *
+ * 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/console.h>
+#include <linux/device.h> /* dev_warn() */
+#include <linux/fb.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/selection.h> /* console_blanked */
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+#include <linux/vt_kern.h>
+#include <linux/workqueue.h>
+
+#include "bootsplash_internal.h"
+
+
+
+/* We only have one splash screen, so let's keep a single
+ * global instance of the internal state.
+ */
+struct splash_priv splash_global;
+
+
+
+
+
+/*
+ * Worker callbacks
+ */
+
+static void splash_callback_redraw_vc(struct work_struct *ignored)
+{
+ if (console_blanked)
+ return;
+
+ console_lock();
+ if (vc_cons[fg_console].d)
+ update_screen(vc_cons[fg_console].d);
+ console_unlock();
+}
+
+static DECLARE_WORK(splash_work_redraw_vc, splash_callback_redraw_vc);
+
+
+
+
+/*
+ * Rendering: External API
+ */
+
+static bool is_fb_compatible(struct fb_info *info)
+{
+ if (!(info->flags & FBINFO_BE_MATH) != !fb_be_math(info)) {
+ dev_warn(info->device,
+ "Can't draw on foreign endianness framebuffer.\n");
+
+ return false;
+ }
+
+ if (info->flags & FBINFO_MISC_TILEBLITTING) {
+ dev_warn(info->device,
+ "Can't draw splash on tiling framebuffer.\n");
+
+ return false;
+ }
+
+ if (info->fix.type != FB_TYPE_PACKED_PIXELS
+ || (info->fix.visual != FB_VISUAL_TRUECOLOR
+ && info->fix.visual != FB_VISUAL_DIRECTCOLOR)) {
+ dev_warn(info->device,
+ "Can't draw splash on non-packed or non-truecolor framebuffer.\n");
+
+ dev_warn(info->device,
+ " type: %u visual: %u\n",
+ info->fix.type, info->fix.visual);
+
+ return false;
+ }
+
+ if (info->var.bits_per_pixel != 16
+ && info->var.bits_per_pixel != 24
+ && info->var.bits_per_pixel != 32) {
+ dev_warn(info->device,
+ "We only support drawing on framebuffers with 16, 24, or 32 bpp, not %d.\n",
+ info->var.bits_per_pixel);
+
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Called when an fbcon instance is activated or refreshed.
+ */
+void bootsplash_render_full(struct fb_info *info)
+{
+ if (!is_fb_compatible(info))
+ return;
+
+ bootsplash_do_render_background(info);
+}
+
+
+
+
+/*
+ * External status enquiry and on/off switch
+ */
+
+bool bootsplash_would_render_now(void)
+{
+ return !oops_in_progress
+ && !console_blanked
+ && bootsplash_is_enabled();
+}
+
+bool bootsplash_is_enabled(void)
+{
+ unsigned long flags;
+ bool was_enabled;
+
+ /* Take the lock as a memory barrier around splash_global.enabled */
+ spin_lock_irqsave(&splash_global.state_lock, flags);
+ was_enabled = splash_global.enabled;
+ spin_unlock_irqrestore(&splash_global.state_lock, flags);
+
+ return was_enabled;
+}
+
+void bootsplash_disable(void)
+{
+ unsigned long flags;
+ bool was_enabled;
+
+ spin_lock_irqsave(&splash_global.state_lock, flags);
+
+ was_enabled = splash_global.enabled;
+ splash_global.enabled = false;
+
+ spin_unlock_irqrestore(&splash_global.state_lock, flags);
+
+
+ if (was_enabled) {
+ if (oops_in_progress) {
+ /* Redraw screen now so we can see a panic */
+ if (vc_cons[fg_console].d)
+ update_screen(vc_cons[fg_console].d);
+ } else {
+ /* No urgency, redraw at next opportunity */
+ queue_work(splash_global.wq, &splash_work_redraw_vc);
+ }
+ }
+}
+
+void bootsplash_enable(void)
+{
+ unsigned long flags;
+ bool was_enabled;
+
+ if (oops_in_progress)
+ return;
+
+ spin_lock_irqsave(&splash_global.state_lock, flags);
+
+ was_enabled = splash_global.enabled;
+ splash_global.enabled = true;
+
+ spin_unlock_irqrestore(&splash_global.state_lock, flags);
+
+
+ if (!was_enabled)
+ queue_work(splash_global.wq, &splash_work_redraw_vc);
+}
+
+
+
+
+/*
+ * Main init/exit functions
+ */
+
+void bootsplash_init(void)
+{
+ /* Initialized already? */
+ if (splash_global.wq)
+ return;
+
+
+ spin_lock_init(&splash_global.state_lock);
+ if (!splash_global.wq)
+ splash_global.wq = alloc_workqueue("bootsplash",
+ WQ_UNBOUND | WQ_FREEZABLE,
+ 1);
+ if (!splash_global.wq) {
+ pr_err("Failed to allocate work queue.\n");
+ goto err;
+ }
+
+ return;
+
+err:
+ pr_err("Failed to initialize.\n");
+}
+
+
+
+module_param_named(enable, splash_global.enabled, bool, 0444);
+MODULE_PARM_DESC(enable, "Enable/disable kernel bootsplash");
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
new file mode 100644
index 000000000000..4cec02774652
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -0,0 +1,68 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Internal data structures used at runtime)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@xxxxxxxx>
+ *
+ *
+ * Very loosely based on the old kernel splash project at
+ * http://www.bootsplash.org/ and SUSE improvements.
+ */
+
+#ifndef __BOOTSPLASH_INTERNAL_H
+#define __BOOTSPLASH_INTERNAL_H
+
+
+#include <linux/types.h>
+#include <linux/fb.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+
+
+/*
+ * Runtime types
+ */
+
+struct splash_priv {
+ /* This lock only synchronizes the enabled/disabled state
+ *
+ * Note: fbcon.c uses this twice, by calling
+ * bootsplash_would_render_now() in set_blitting_type() and
+ * in fbcon_switch().
+ * This is racy, but eventually consistent: Turning the
+ * splash on/off will cause a redraw, which calls
+ * fbcon_switch(), which calls set_blitting_type().
+ * So the last on/off toggle will make things consistent.
+ */
+ spinlock_t state_lock;
+ bool enabled;
+
+ /* We use our own workqueue so we don't have to worry about blocking
+ * on console_lock() while animating and thus freezing the system
+ * during suspend.
+ */
+ struct workqueue_struct *wq;
+};
+
+
+
+
+/*
+ * Internal global variables
+ */
+extern struct splash_priv splash_global;
+
+
+
+
+/*
+ * Rendering functions
+ */
+
+void bootsplash_do_render_background(struct fb_info *info);
+
+#endif
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
new file mode 100644
index 000000000000..ceb19c34fd61
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -0,0 +1,93 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Rendering 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/printk.h>
+#include <linux/types.h>
+
+#include "bootsplash_internal.h"
+
+
+
+
+/*
+ * Rendering: Internal drawing routines
+ */
+
+
+/* Pack pixel into target format and do Big/Little Endian handling.
+ * This would be a good place to handle endianness conversion if necessary.
+ */
+static inline u32 pack_pixel(struct fb_var_screeninfo *dst_var,
+ u8 red, u8 green, u8 blue)
+{
+ u32 dstpix;
+
+ /* Quantize pixel */
+ red = red >> (8 - dst_var->red.length);
+ green = green >> (8 - dst_var->green.length);
+ blue = blue >> (8 - dst_var->blue.length);
+
+ /* Pack pixel */
+ dstpix = red << (dst_var->red.offset)
+ | green << (dst_var->green.offset)
+ | blue << (dst_var->blue.offset);
+
+ /* Move packed pixel to the beginning of the memory cell,
+ * so we can memcpy() it out easily
+ */
+#ifdef __BIG_ENDIAN
+ switch (dst_var->bits_per_pixel) {
+ case 16:
+ dstpix <<= 16;
+ break;
+ case 24:
+ dstpix <<= 8;
+ break;
+ case 32:
+ break;
+ }
+#else
+ /* This is intrinsically unnecessary on Little Endian */
+#endif
+
+ return dstpix;
+}
+
+
+void bootsplash_do_render_background(struct fb_info *info)
+{
+ unsigned int x, y;
+ u32 dstpix;
+ u32 dst_octpp = info->var.bits_per_pixel / 8;
+
+ dstpix = pack_pixel(&info->var,
+ 0,
+ 0,
+ 0);
+
+ for (y = 0; y < info->var.yres_virtual; y++) {
+ u8 *dstline = info->screen_buffer + (y * info->fix.line_length);
+
+ for (x = 0; x < info->var.xres_virtual; x++) {
+ memcpy(dstline, &dstpix, dst_octpp);
+
+ dstline += dst_octpp;
+ }
+ }
+}
diff --git a/drivers/video/fbdev/core/dummyblit.c b/drivers/video/fbdev/core/dummyblit.c
new file mode 100644
index 000000000000..1c574caa94e4
--- /dev/null
+++ b/drivers/video/fbdev/core/dummyblit.c
@@ -0,0 +1,89 @@
+/*
+ * linux/drivers/video/fbdev/core/dummyblit.c -- Dummy Blitting Operation
+ *
+ * Copyright (C) 2017 Max Staudt <mstaudt@xxxxxxx>
+ *
+ * These functions are used in place of blitblit/tileblit to suppress
+ * fbcon's text output while a splash is shown.
+ *
+ * Only suppressing actual rendering keeps the text buffer in the VC layer
+ * intact and makes it easy to switch back from the bootsplash to a full
+ * text console with a simple redraw (with the original functions in place).
+ *
+ * Based on linux/drivers/video/fbdev/core/bitblit.c
+ * and linux/drivers/video/fbdev/core/tileblit.c
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ */
+
+#include <linux/module.h>
+#include <linux/fb.h>
+#include <linux/vt_kern.h>
+#include <linux/console.h>
+#include <asm/types.h>
+#include "fbcon.h"
+
+static void dummy_bmove(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int dy, int dx, int height, int width)
+{
+ ;
+}
+
+static void dummy_clear(struct vc_data *vc, struct fb_info *info, int sy,
+ int sx, int height, int width)
+{
+ ;
+}
+
+static void dummy_putcs(struct vc_data *vc, struct fb_info *info,
+ const unsigned short *s, int count, int yy, int xx,
+ int fg, int bg)
+{
+ ;
+}
+
+static void dummy_clear_margins(struct vc_data *vc, struct fb_info *info,
+ int color, int bottom_only)
+{
+ ;
+}
+
+static void dummy_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
+{
+ ;
+}
+
+static int dummy_update_start(struct fb_info *info)
+{
+ /* Copied from bitblit.c and tileblit.c
+ *
+ * As of Linux 4.12, nobody seems to care about our return value.
+ */
+ struct fbcon_ops *ops = info->fbcon_par;
+ int err;
+
+ err = fb_pan_display(info, &ops->var);
+ ops->var.xoffset = info->var.xoffset;
+ ops->var.yoffset = info->var.yoffset;
+ ops->var.vmode = info->var.vmode;
+ return err;
+}
+
+void fbcon_set_dummyops(struct fbcon_ops *ops)
+{
+ ops->bmove = dummy_bmove;
+ ops->clear = dummy_clear;
+ ops->putcs = dummy_putcs;
+ ops->clear_margins = dummy_clear_margins;
+ ops->cursor = dummy_cursor;
+ ops->update_start = dummy_update_start;
+ ops->rotate_font = NULL;
+}
+EXPORT_SYMBOL(fbcon_set_dummyops);
+
+MODULE_AUTHOR("Max Staudt <mstaudt@xxxxxxx>");
+MODULE_DESCRIPTION("Dummy Blitting Operation");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 04612f938bab..4ac1a33be418 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -80,6 +80,7 @@
#include <asm/irq.h>

#include "fbcon.h"
+#include <linux/bootsplash.h>

#ifdef FBCONDEBUG
# define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __func__ , ## args)
@@ -542,6 +543,8 @@ static int do_fbcon_takeover(int show_logo)
for (i = first_fb_vc; i <= last_fb_vc; i++)
con2fb_map[i] = info_idx;

+ bootsplash_init();
+
err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc,
fbcon_is_default);

@@ -661,6 +664,9 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
else {
fbcon_set_rotation(info);
fbcon_set_bitops(ops);
+
+ if (bootsplash_would_render_now())
+ fbcon_set_dummyops(ops);
}
}

@@ -683,6 +689,9 @@ static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
ops->p = &fb_display[vc->vc_num];
fbcon_set_rotation(info);
fbcon_set_bitops(ops);
+
+ if (bootsplash_would_render_now())
+ fbcon_set_dummyops(ops);
}

static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
@@ -2184,6 +2193,9 @@ static int fbcon_switch(struct vc_data *vc)
info = registered_fb[con2fb_map[vc->vc_num]];
ops = info->fbcon_par;

+ if (bootsplash_would_render_now())
+ bootsplash_render_full(info);
+
if (softback_top) {
if (softback_lines)
fbcon_set_origin(vc);
diff --git a/drivers/video/fbdev/core/fbcon.h b/drivers/video/fbdev/core/fbcon.h
index 18f3ac144237..45f94347fe5e 100644
--- a/drivers/video/fbdev/core/fbcon.h
+++ b/drivers/video/fbdev/core/fbcon.h
@@ -214,6 +214,11 @@ static inline int attr_col_ec(int shift, struct vc_data *vc,
#define SCROLL_REDRAW 0x004
#define SCROLL_PAN_REDRAW 0x005

+#ifdef CONFIG_BOOTSPLASH
+extern void fbcon_set_dummyops(struct fbcon_ops *ops);
+#else /* CONFIG_BOOTSPLASH */
+#define fbcon_set_dummyops(x)
+#endif /* CONFIG_BOOTSPLASH */
#ifdef CONFIG_FB_TILEBLITTING
extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info);
#endif
diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h
new file mode 100644
index 000000000000..101e70818363
--- /dev/null
+++ b/include/linux/bootsplash.h
@@ -0,0 +1,45 @@
+/*
+ * 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 __LINUX_BOOTSPLASH_H
+#define __LINUX_BOOTSPLASH_H
+
+#include <linux/fb.h>
+
+
+#ifdef CONFIG_BOOTSPLASH
+
+extern void bootsplash_render_full(struct fb_info *info);
+
+extern bool bootsplash_would_render_now(void);
+
+extern bool bootsplash_is_enabled(void);
+extern void bootsplash_disable(void);
+extern void bootsplash_enable(void);
+
+extern void bootsplash_init(void);
+
+#else /* CONFIG_BOOTSPLASH */
+
+#define bootsplash_render_full(x)
+
+#define bootsplash_would_render_now() (false)
+
+#define bootsplash_is_enabled() (false)
+#define bootsplash_disable()
+#define bootsplash_enable()
+
+#define bootsplash_init()
+
+#endif /* CONFIG_BOOTSPLASH */
+
+
+#endif
--
2.12.3