From patchwork Wed Oct 25 12:45:56 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: 'Max Staudt X-Patchwork-Id: 10026653 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 7B1F0601E8 for ; Wed, 25 Oct 2017 12:48:55 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 77A9C28B61 for ; Wed, 25 Oct 2017 12:48:55 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 6CA6728B74; Wed, 25 Oct 2017 12:48:55 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00,RCVD_IN_DNSWL_HI autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 4D99628B61 for ; Wed, 25 Oct 2017 12:48:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752088AbdJYMr4 (ORCPT ); Wed, 25 Oct 2017 08:47:56 -0400 Received: from mx2.suse.de ([195.135.220.15]:36154 "EHLO mx2.suse.de" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1751803AbdJYMqb (ORCPT ); Wed, 25 Oct 2017 08:46:31 -0400 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (charybdis-ext.suse.de [195.135.220.254]) by mx2.suse.de (Postfix) with ESMTP id 72F5FAD4D; Wed, 25 Oct 2017 12:46:28 +0000 (UTC) From: Max Staudt To: b.zolnierkie@samsung.com, linux-fbdev@vger.kernel.org Cc: mstaudt@suse.de, tiwai@suse.com, oneukum@suse.com, msrb@suse.com, sndirsch@suse.com, michal@markovi.net, linux-kernel@vger.kernel.org Subject: [RFC 08/14] bootsplash: Add file reading and picture rendering Date: Wed, 25 Oct 2017 14:45:56 +0200 Message-Id: <20171025124602.28292-9-mstaudt@suse.de> X-Mailer: git-send-email 2.12.3 In-Reply-To: <20171025124602.28292-1-mstaudt@suse.de> References: <20171025124602.28292-1-mstaudt@suse.de> Sender: linux-fbdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fbdev@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 Reviewed-by: Oliver Neukum --- 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 #include +#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 + * + * + * 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 +#include + + +/* + * 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 #include +#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 + * + * + * Very loosely based on the old kernel splash project at + * http://www.bootsplash.org/ and SUSE improvements. + */ + +#define pr_fmt(fmt) "bootsplash: " fmt + + +#include +#include +#include +#include +#include +#include +#include + +#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 #include +#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