diff --git a/MAINTAINERS b/MAINTAINERS index a74227ad082e..b5633b56391e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2705,6 +2705,14 @@ S: Supported F: drivers/net/bonding/ F: include/uapi/linux/if_bonding.h +BOOTSPLASH +M: Max Staudt +L: linux-fbdev@vger.kernel.org +S: Maintained +F: drivers/video/fbdev/core/bootsplash*.* +F: drivers/video/fbdev/core/dummycon.c +F: include/linux/bootsplash.h + BPF (Safe dynamic programs and tools) M: Alexei Starovoitov M: Daniel Borkmann diff --git a/drivers/video/console/Kconfig b/drivers/video/console/Kconfig index 7f1f1fbcef9e..f3ff976266fe 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 + ---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..e449755af268 --- /dev/null +++ b/drivers/video/fbdev/core/bootsplash.c @@ -0,0 +1,294 @@ +/* + * Kernel based bootsplash. + * + * (Main file: Glue code, workers, timer, PM, kernel and userland API) + * + * Authors: + * Max Staudt + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#define pr_fmt(fmt) "bootsplash: " fmt + + +#include +#include +#include +#include /* dev_warn() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include /* console_blanked */ +#include +#include +#include +#include +#include + +#include "bootsplash_internal.h" + + +/* + * We only have one splash screen, so let's keep a single + * instance of the internal state. + */ +static struct splash_priv splash_state; + + +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 bool is_fb_compatible(const struct fb_info *info) +{ + if (!(info->flags & FBINFO_BE_MATH) + != !fb_be_math((struct fb_info *)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 by fbcon_switch() when an 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) +{ + bool was_enabled; + + /* Make sure we have the newest state */ + smp_rmb(); + + was_enabled = test_bit(0, &splash_state.enabled); + + return was_enabled; +} + +void bootsplash_disable(void) +{ + int was_enabled; + + was_enabled = test_and_clear_bit(0, &splash_state.enabled); + + 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 */ + schedule_work(&splash_state.work_redraw_vc); + } + } +} + +void bootsplash_enable(void) +{ + bool was_enabled; + + if (oops_in_progress) + return; + + was_enabled = test_and_set_bit(0, &splash_state.enabled); + + if (!was_enabled) + schedule_work(&splash_state.work_redraw_vc); +} + + +/* + * Userland API via platform device in sysfs + */ +static ssize_t splash_show_enabled(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", bootsplash_is_enabled()); +} + +static ssize_t splash_store_enabled(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + bool enable; + int err; + + if (!buf || !count) + return -EFAULT; + + err = kstrtobool(buf, &enable); + if (err) + return err; + + if (enable) + bootsplash_enable(); + else + bootsplash_disable(); + + return count; +} + +static DEVICE_ATTR(enabled, 0644, splash_show_enabled, splash_store_enabled); + + +static struct attribute *splash_dev_attrs[] = { + &dev_attr_enabled.attr, + NULL +}; + +ATTRIBUTE_GROUPS(splash_dev); + + + + +/* + * Power management fixup via platform device + * + * When the system is woken from sleep or restored after hibernating, we + * cannot expect the screen contents to still be present in video RAM. + * Thus, we have to redraw the splash if we're currently active. + */ +static int splash_resume(struct device *device) +{ + if (bootsplash_would_render_now()) + schedule_work(&splash_state.work_redraw_vc); + + return 0; +} + +static int splash_suspend(struct device *device) +{ + cancel_work_sync(&splash_state.work_redraw_vc); + + return 0; +} + + +static const struct dev_pm_ops splash_pm_ops = { + .thaw = splash_resume, + .restore = splash_resume, + .resume = splash_resume, + .suspend = splash_suspend, + .freeze = splash_suspend, +}; + +static struct platform_driver splash_driver = { + .driver = { + .name = "bootsplash", + .pm = &splash_pm_ops, + }, +}; + + +/* + * Main init + */ +void bootsplash_init(void) +{ + int ret; + + /* Initialized already? */ + if (splash_state.splash_device) + return; + + + /* Register platform device to export user API */ + ret = platform_driver_register(&splash_driver); + if (ret) { + pr_err("platform_driver_register() failed: %d\n", ret); + goto err; + } + + splash_state.splash_device + = platform_device_alloc("bootsplash", 0); + + if (!splash_state.splash_device) + goto err_driver; + + splash_state.splash_device->dev.groups = splash_dev_groups; + + ret = platform_device_add(splash_state.splash_device); + if (ret) { + pr_err("platform_device_add() failed: %d\n", ret); + goto err_device; + } + + + INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc); + + return; + +err_device: + platform_device_put(splash_state.splash_device); + splash_state.splash_device = NULL; +err_driver: + platform_driver_unregister(&splash_driver); +err: + pr_err("Failed to initialize.\n"); +} diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h new file mode 100644 index 000000000000..b11da5cb90bf --- /dev/null +++ b/drivers/video/fbdev/core/bootsplash_internal.h @@ -0,0 +1,55 @@ +/* + * Kernel based bootsplash. + * + * (Internal data structures used at runtime) + * + * Authors: + * Max Staudt + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef __BOOTSPLASH_INTERNAL_H +#define __BOOTSPLASH_INTERNAL_H + + +#include +#include +#include +#include +#include + + +/* + * Runtime types + */ +struct splash_priv { + /* + * Enabled/disabled state, to be used with atomic bit operations. + * Bit 0: 0 = Splash hidden + * 1 = Splash shown + * + * 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. + */ + unsigned long enabled; + + /* Our gateway to userland via sysfs */ + struct platform_device *splash_device; + + struct work_struct work_redraw_vc; +}; + + + +/* + * 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..4d7e0117f653 --- /dev/null +++ b/drivers/video/fbdev/core/bootsplash_render.c @@ -0,0 +1,93 @@ +/* + * Kernel based bootsplash. + * + * (Rendering functions) + * + * Authors: + * Max Staudt + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#define pr_fmt(fmt) "bootsplash: " fmt + + +#include +#include +#include +#include +#include + +#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(const 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..8c22ff92ce24 --- /dev/null +++ b/drivers/video/fbdev/core/dummyblit.c @@ -0,0 +1,89 @@ +/* + * linux/drivers/video/fbdev/core/dummyblit.c -- Dummy Blitting Operation + * + * Authors: + * Max Staudt + * + * 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 + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#include +#include +#include +#include +#include +#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_GPL(fbcon_set_dummyops); + +MODULE_AUTHOR("Max Staudt "); +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..9a39a6fcfe98 100644 --- a/drivers/video/fbdev/core/fbcon.c +++ b/drivers/video/fbdev/core/fbcon.c @@ -80,6 +80,7 @@ #include #include "fbcon.h" +#include #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,19 @@ 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); + + /* + * Note: + * This is *eventually correct*. + * Setting the fbcon operations and drawing the splash happen at + * different points in time. If the splash is enabled/disabled + * in between, then bootsplash_{en,dis}able will schedule a + * redraw, which will again render the splash (or not) and set + * the correct fbcon ops. + * The last run will then be the right one. + */ + if (bootsplash_would_render_now()) + fbcon_set_dummyops(ops); } static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount) @@ -2184,6 +2203,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..c6dd0b43180d --- /dev/null +++ b/include/linux/bootsplash.h @@ -0,0 +1,43 @@ +/* + * Kernel based bootsplash. + * + * Authors: + * Max Staudt + * + * SPDX-License-Identifier: GPL-2.0 + */ + +#ifndef __LINUX_BOOTSPLASH_H +#define __LINUX_BOOTSPLASH_H + +#include + + +#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