Added PinePhone Pro Sources

This commit is contained in:
Gerben Jan Dijkman 2022-01-14 17:01:30 +01:00
parent 0dbaf84341
commit 185ddcbfd1
27 changed files with 12447 additions and 0 deletions

View File

@ -0,0 +1 @@
DIST pinephone-pro-sources-5.16.0.tar.gz 203401938 BLAKE2B 209806c5e3a648024d897727097186d5ea15c0a9fddef00d31b244ad8436c45a3064448ce196ac0e3a4f9597abae41baa0a4aa6af339222097fc25d335ed4202 SHA512 83b9416ee4a9789919f6b630a01b2b847b3d413fabcee31380919a50b330cbee658f5f7f196a87e0854cfe97bdc87c9b9c5256588ff59aaadb26cf38e9f7b11c

View File

@ -0,0 +1,40 @@
### Generic CI Template for chrootbuild
#
#
# Usage: chrootbuild [options]
#
# -b <branch> Branch to use:
# (unstable/testing/stable-staging/stable;
# arm-unstable/arm-testing/arm-stable)
# default: unstable / arm-unstable
# -c Start with clean chroot fs
# -h This help
# -i <pkg> Install package(s) to chroot fs
# (for multiple packages repeat -i flag)
# -l <list> List(s) to build
# (for multiple lists repeat -l flag)
# -n Install built pkg to chroot fs
# -p <pkg> Package(s) to build
# (for multiple packages repeat -p flag)
# -r Remove previously built packages in $PKGDEST
# -s Sign package(s)
build-package:
tags:
# change the tag to your gitlab-runner
- bigbuilds
- aarch64
- fosshost
- xlarge
script:
# build pkg via chrootbuild
- export PKG=${PWD##*/}
- cd ..
- sudo chrootbuild -b arm-unstable -cp $PKG
- mv -v ./*.pkg.tar.* $PKG
#- sudo pacman -Syy && makepkg -scr --noconfirm
artifacts:
paths:
- ./*.pkg.tar.*
expire_in: 1 week

View File

@ -0,0 +1,746 @@
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 <mstaudt@suse.de>
+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 <ast@kernel.org>
M: Daniel Borkmann <daniel@iogearbox.net>
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 <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#define pr_fmt(fmt) "bootsplash: " fmt
+
+
+#include <linux/atomic.h>
+#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
+ * 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 <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#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>
+
+
+/*
+ * 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 <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#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(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 <mstaudt@suse.de>
+ *
+ * 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 <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_GPL(fbcon_set_dummyops);
+
+MODULE_AUTHOR("Max Staudt <mstaudt@suse.de>");
+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 <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,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 <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#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

View File

@ -0,0 +1,669 @@
diff --git a/MAINTAINERS b/MAINTAINERS
index b5633b56391e..5c237445761e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2712,6 +2712,7 @@ S: Maintained
F: drivers/video/fbdev/core/bootsplash*.*
F: drivers/video/fbdev/core/dummycon.c
F: include/linux/bootsplash.h
+F: include/uapi/linux/bootsplash_file.h
BPF (Safe dynamic programs and tools)
M: Alexei Starovoitov <ast@kernel.org>
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 e449755af268..843c5400fefc 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -32,6 +32,7 @@
#include <linux/workqueue.h>
#include "bootsplash_internal.h"
+#include "uapi/linux/bootsplash_file.h"
/*
@@ -102,10 +103,17 @@ static bool is_fb_compatible(const struct fb_info *info)
*/
void bootsplash_render_full(struct fb_info *info)
{
+ mutex_lock(&splash_state.data_lock);
+
if (!is_fb_compatible(info))
- return;
+ goto out;
+
+ bootsplash_do_render_background(info, splash_state.file);
+
+ bootsplash_do_render_pictures(info, splash_state.file);
- bootsplash_do_render_background(info);
+out:
+ mutex_unlock(&splash_state.data_lock);
}
@@ -116,6 +124,7 @@ bool bootsplash_would_render_now(void)
{
return !oops_in_progress
&& !console_blanked
+ && splash_state.file
&& bootsplash_is_enabled();
}
@@ -252,6 +261,7 @@ static struct platform_driver splash_driver = {
void bootsplash_init(void)
{
int ret;
+ struct splash_file_priv *fp;
/* Initialized already? */
if (splash_state.splash_device)
@@ -280,8 +290,26 @@ void bootsplash_init(void)
}
+ mutex_init(&splash_state.data_lock);
+ set_bit(0, &splash_state.enabled);
+
INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc);
+
+ if (!splash_state.bootfile || !strlen(splash_state.bootfile))
+ return;
+
+ fp = bootsplash_load_firmware(&splash_state.splash_device->dev,
+ splash_state.bootfile);
+
+ if (!fp)
+ goto err;
+
+ mutex_lock(&splash_state.data_lock);
+ splash_state.splash_fb = NULL;
+ splash_state.file = fp;
+ mutex_unlock(&splash_state.data_lock);
+
return;
err_device:
@@ -292,3 +320,7 @@ void bootsplash_init(void)
err:
pr_err("Failed to initialize.\n");
}
+
+
+module_param_named(bootfile, splash_state.bootfile, charp, 0444);
+MODULE_PARM_DESC(bootfile, "Bootsplash file to load on boot");
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
index b11da5cb90bf..71e2a27ac0b8 100644
--- a/drivers/video/fbdev/core/bootsplash_internal.h
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -15,15 +15,43 @@
#include <linux/types.h>
#include <linux/fb.h>
+#include <linux/firmware.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
+#include "uapi/linux/bootsplash_file.h"
+
/*
* Runtime types
*/
+struct splash_blob_priv {
+ struct splash_blob_header *blob_header;
+ const void *data;
+};
+
+
+struct splash_pic_priv {
+ const struct splash_pic_header *pic_header;
+
+ struct splash_blob_priv *blobs;
+ u16 blobs_loaded;
+};
+
+
+struct splash_file_priv {
+ const struct firmware *fw;
+ const struct splash_file_header *header;
+
+ struct splash_pic_priv *pics;
+};
+
+
struct splash_priv {
+ /* Bootup and runtime state */
+ char *bootfile;
+
/*
* Enabled/disabled state, to be used with atomic bit operations.
* Bit 0: 0 = Splash hidden
@@ -43,6 +71,13 @@ struct splash_priv {
struct platform_device *splash_device;
struct work_struct work_redraw_vc;
+
+ /* Splash data structures including lock for everything below */
+ struct mutex data_lock;
+
+ struct fb_info *splash_fb;
+
+ struct splash_file_priv *file;
};
@@ -50,6 +85,14 @@ struct splash_priv {
/*
* Rendering functions
*/
-void bootsplash_do_render_background(struct fb_info *info);
+void bootsplash_do_render_background(struct fb_info *info,
+ const struct splash_file_priv *fp);
+void bootsplash_do_render_pictures(struct fb_info *info,
+ const struct splash_file_priv *fp);
+
+
+void bootsplash_free_file(struct splash_file_priv *fp);
+struct splash_file_priv *bootsplash_load_firmware(struct device *device,
+ const char *path);
#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..fd807571ab7d
--- /dev/null
+++ b/drivers/video/fbdev/core/bootsplash_load.c
@@ -0,0 +1,225 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Loading and freeing functions)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#define pr_fmt(fmt) "bootsplash: " fmt
+
+
+#include <linux/bootsplash.h>
+#include <linux/fb.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/types.h>
+#include <linux/vmalloc.h>
+
+#include "bootsplash_internal.h"
+#include "uapi/linux/bootsplash_file.h"
+
+
+
+
+/*
+ * Free all vmalloc()'d resources describing a splash file.
+ */
+void bootsplash_free_file(struct splash_file_priv *fp)
+{
+ if (!fp)
+ return;
+
+ if (fp->pics) {
+ unsigned int i;
+
+ for (i = 0; i < fp->header->num_pics; i++) {
+ struct splash_pic_priv *pp = &fp->pics[i];
+
+ if (pp->blobs)
+ vfree(pp->blobs);
+ }
+
+ vfree(fp->pics);
+ }
+
+ release_firmware(fp->fw);
+ vfree(fp);
+}
+
+
+
+
+/*
+ * Load a splash screen from a "firmware" file.
+ *
+ * Parsing, and sanity checks.
+ */
+#ifdef __BIG_ENDIAN
+ #define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_BE
+#else
+ #define BOOTSPLASH_MAGIC BOOTSPLASH_MAGIC_LE
+#endif
+
+struct splash_file_priv *bootsplash_load_firmware(struct device *device,
+ const char *path)
+{
+ const struct firmware *fw;
+ struct splash_file_priv *fp;
+ unsigned int i;
+ const u8 *walker;
+
+ if (request_firmware(&fw, path, device))
+ return NULL;
+
+ if (fw->size < sizeof(struct splash_file_header)
+ || memcmp(fw->data, BOOTSPLASH_MAGIC, sizeof(fp->header->id))) {
+ pr_err("Not a bootsplash file.\n");
+
+ release_firmware(fw);
+ return NULL;
+ }
+
+ fp = vzalloc(sizeof(struct splash_file_priv));
+ if (!fp) {
+ release_firmware(fw);
+ return NULL;
+ }
+
+ pr_info("Loading splash file (%li bytes)\n", fw->size);
+
+ fp->fw = fw;
+ fp->header = (struct splash_file_header *)fw->data;
+
+ /* Sanity checks */
+ if (fp->header->version != BOOTSPLASH_VERSION) {
+ pr_err("Loaded v%d file, but we only support version %d\n",
+ fp->header->version,
+ BOOTSPLASH_VERSION);
+
+ goto err;
+ }
+
+ if (fw->size < sizeof(struct splash_file_header)
+ + fp->header->num_pics
+ * sizeof(struct splash_pic_header)
+ + fp->header->num_blobs
+ * sizeof(struct splash_blob_header)) {
+ pr_err("File incomplete.\n");
+
+ goto err;
+ }
+
+ /* Read picture headers */
+ if (fp->header->num_pics) {
+ fp->pics = vzalloc(fp->header->num_pics
+ * sizeof(struct splash_pic_priv));
+ if (!fp->pics)
+ goto err;
+ }
+
+ walker = fw->data + sizeof(struct splash_file_header);
+ for (i = 0; i < fp->header->num_pics; i++) {
+ struct splash_pic_priv *pp = &fp->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)
+ goto err;
+
+ walker += sizeof(struct splash_pic_header);
+ }
+
+ /* Read blob headers */
+ for (i = 0; i < fp->header->num_blobs; i++) {
+ struct splash_blob_header *bh = (void *)walker;
+ struct splash_pic_priv *pp;
+
+ if (walker + sizeof(struct splash_blob_header)
+ > fw->data + fw->size)
+ goto err;
+
+ walker += sizeof(struct splash_blob_header);
+
+ if (walker + bh->length > fw->data + fw->size)
+ goto err;
+
+ if (bh->picture_id >= fp->header->num_pics)
+ goto nextblob;
+
+ pp = &fp->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 != fw->data + fw->size)
+ pr_warn("Trailing data in splash file.\n");
+
+ /* Walk over pictures and ensure all blob slots are filled */
+ for (i = 0; i < fp->header->num_pics; i++) {
+ struct splash_pic_priv *pp = &fp->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;
+ }
+ }
+
+ pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n",
+ fw->size,
+ fp->header->num_pics,
+ fp->header->num_blobs);
+
+ return fp;
+
+
+err:
+ bootsplash_free_file(fp);
+ return NULL;
+}
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 4d7e0117f653..2ae36949d0e3 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -19,6 +19,7 @@
#include <linux/types.h>
#include "bootsplash_internal.h"
+#include "uapi/linux/bootsplash_file.h"
@@ -70,16 +71,69 @@ static inline u32 pack_pixel(const struct fb_var_screeninfo *dst_var,
}
-void bootsplash_do_render_background(struct fb_info *info)
+/*
+ * 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,
+ const struct fb_var_screeninfo *dst_var,
+ unsigned int dst_stride,
+ unsigned int dst_xoff,
+ unsigned int dst_yoff,
+ const 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++) {
+ const 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,
+ const struct splash_file_priv *fp)
{
unsigned int x, y;
u32 dstpix;
u32 dst_octpp = info->var.bits_per_pixel / 8;
dstpix = pack_pixel(&info->var,
- 0,
- 0,
- 0);
+ fp->header->bg_red,
+ fp->header->bg_green,
+ fp->header->bg_blue);
for (y = 0; y < info->var.yres_virtual; y++) {
u8 *dstline = info->screen_buffer + (y * info->fix.line_length);
@@ -91,3 +145,44 @@ void bootsplash_do_render_background(struct fb_info *info)
}
}
}
+
+
+void bootsplash_do_render_pictures(struct fb_info *info,
+ const struct splash_file_priv *fp)
+{
+ unsigned int i;
+
+ for (i = 0; i < fp->header->num_pics; i++) {
+ struct splash_blob_priv *bp;
+ struct splash_pic_priv *pp = &fp->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_info_once("Picture %u is out of bounds at current resolution: %dx%d\n"
+ "(this will only be printed once every reboot)\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);
+ }
+}
diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
new file mode 100644
index 000000000000..89dc9cca8f0c
--- /dev/null
+++ b/include/uapi/linux/bootsplash_file.h
@@ -0,0 +1,118 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (File format)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ */
+
+#ifndef __BOOTSPLASH_FILE_H
+#define __BOOTSPLASH_FILE_H
+
+
+#define BOOTSPLASH_VERSION 55561
+
+
+#include <linux/kernel.h>
+#include <linux/types.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 in the native format
+ * expected by the system the file will be used on.
+ */
+#define BOOTSPLASH_MAGIC_BE "Linux bootsplash"
+#define BOOTSPLASH_MAGIC_LE "hsalpstoob xuniL"
+
+struct splash_file_header {
+ uint8_t id[16]; /* "Linux bootsplash" (no trailing NUL) */
+
+ /* Splash file format version to avoid clashes */
+ uint16_t version;
+
+ /* The background color */
+ uint8_t bg_red;
+ uint8_t bg_green;
+ uint8_t bg_blue;
+ uint8_t bg_reserved;
+
+ /*
+ * Number of pic/blobs so we can allocate memory for internal
+ * structures ahead of time when reading the file
+ */
+ uint16_t num_blobs;
+ uint8_t num_pics;
+
+ uint8_t padding[103];
+} __attribute__((__packed__));
+
+
+struct splash_pic_header {
+ uint16_t width;
+ uint16_t height;
+
+ /*
+ * Number of data packages associated with this picture.
+ * Currently, the only use for more than 1 is for animations.
+ */
+ uint8_t num_blobs;
+
+ uint8_t padding[27];
+} __attribute__((__packed__));
+
+
+struct splash_blob_header {
+ /* Length of the data block in bytes. */
+ uint32_t length;
+
+ /*
+ * Type of the contents.
+ * 0 - Raw RGB data.
+ */
+ uint16_t type;
+
+ /*
+ * Picture this blob is associated with.
+ * Blobs will be added to a picture in the order they are
+ * found in the file.
+ */
+ uint8_t picture_id;
+
+ uint8_t padding[9];
+} __attribute__((__packed__));
+
+#endif

View File

@ -0,0 +1,150 @@
--- b/drivers/video/fbdev/core/bitblit.c
+++ a/drivers/video/fbdev/core/bitblit.c
@@ -234,7 +234,7 @@
}
static void bit_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
- int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_ops *ops = info->fbcon_par;
@@ -247,6 +247,15 @@
cursor.set = 0;
+ if (softback_lines) {
+ if (y + softback_lines >= vc->vc_rows) {
+ mode = CM_ERASE;
+ ops->cursor_flash = 0;
+ return;
+ } else
+ y += softback_lines;
+ }
+
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = vc->vc_font.data + ((c & charmask) * (w * vc->vc_font.height));
--- b/drivers/video/fbdev/core/fbcon.c
+++ a/drivers/video/fbdev/core/fbcon.c
@@ -394,7 +394,7 @@
c = scr_readw((u16 *) vc->vc_pos);
mode = (!ops->cursor_flash || ops->cursor_state.enable) ?
CM_ERASE : CM_DRAW;
+ ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
- ops->cursor(vc, info, mode, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
console_unlock();
}
@@ -1345,7 +1345,7 @@
ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1;
+ ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
- ops->cursor(vc, info, mode, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
}
--- b/drivers/video/fbdev/core/fbcon.h
+++ a/drivers/video/fbdev/core/fbcon.h
@@ -62,7 +62,7 @@
void (*clear_margins)(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only);
void (*cursor)(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg);
- int fg, int bg);
int (*update_start)(struct fb_info *info);
int (*rotate_font)(struct fb_info *info, struct vc_data *vc);
struct fb_var_screeninfo var; /* copy of the current fb_var_screeninfo */
--- b/drivers/video/fbdev/core/fbcon_ccw.c
+++ a/drivers/video/fbdev/core/fbcon_ccw.c
@@ -219,7 +219,7 @@
}
static void ccw_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
- int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_ops *ops = info->fbcon_par;
@@ -236,6 +236,15 @@
cursor.set = 0;
+ if (softback_lines) {
+ if (y + softback_lines >= vc->vc_rows) {
+ mode = CM_ERASE;
+ ops->cursor_flash = 0;
+ return;
+ } else
+ y += softback_lines;
+ }
+
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
--- b/drivers/video/fbdev/core/fbcon_cw.c
+++ a/drivers/video/fbdev/core/fbcon_cw.c
@@ -202,7 +202,7 @@
}
static void cw_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
- int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_ops *ops = info->fbcon_par;
@@ -219,6 +219,15 @@
cursor.set = 0;
+ if (softback_lines) {
+ if (y + softback_lines >= vc->vc_rows) {
+ mode = CM_ERASE;
+ ops->cursor_flash = 0;
+ return;
+ } else
+ y += softback_lines;
+ }
+
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));
--- b/drivers/video/fbdev/core/fbcon_ud.c
+++ a/drivers/video/fbdev/core/fbcon_ud.c
@@ -249,7 +249,7 @@
}
static void ud_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
- int fg, int bg)
{
struct fb_cursor cursor;
struct fbcon_ops *ops = info->fbcon_par;
@@ -267,6 +267,15 @@
cursor.set = 0;
+ if (softback_lines) {
+ if (y + softback_lines >= vc->vc_rows) {
+ mode = CM_ERASE;
+ ops->cursor_flash = 0;
+ return;
+ } else
+ y += softback_lines;
+ }
+
c = scr_readw((u16 *) vc->vc_pos);
attribute = get_attribute(info, c);
src = ops->fontbuffer + ((c & charmask) * (w * vc->vc_font.height));
--- b/drivers/video/fbdev/core/tileblit.c
+++ a/drivers/video/fbdev/core/tileblit.c
@@ -80,7 +80,7 @@
}
static void tile_cursor(struct vc_data *vc, struct fb_info *info, int mode,
+ int softback_lines, int fg, int bg)
- int fg, int bg)
{
struct fb_tilecursor cursor;
int use_sw = (vc->vc_cursor_type & 0x10);

View File

@ -0,0 +1,66 @@
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index 843c5400fefc..815b007f81ca 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -112,6 +112,8 @@ void bootsplash_render_full(struct fb_info *info)
bootsplash_do_render_pictures(info, splash_state.file);
+ bootsplash_do_render_flush(info);
+
out:
mutex_unlock(&splash_state.data_lock);
}
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
index 71e2a27ac0b8..0acb383aa4e3 100644
--- a/drivers/video/fbdev/core/bootsplash_internal.h
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -89,6 +89,7 @@ void bootsplash_do_render_background(struct fb_info *info,
const struct splash_file_priv *fp);
void bootsplash_do_render_pictures(struct fb_info *info,
const struct splash_file_priv *fp);
+void bootsplash_do_render_flush(struct fb_info *info);
void bootsplash_free_file(struct splash_file_priv *fp);
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 2ae36949d0e3..8c09c306ff67 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -186,3 +186,36 @@ void bootsplash_do_render_pictures(struct fb_info *info,
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
+ * screen after modifying its contents. When the FB is mmap()ed
+ * from userspace, this happens via a dirty pages callback, but
+ * when modifying the FB from the kernel, there is no such thing.
+ *
+ * So let's issue a fake fb_copyarea (copying the FB onto itself)
+ * to trick the FB driver into syncing the screen.
+ *
+ * A few DRM drivers' FB implementations are broken by not using
+ * deferred_io when they really should - we match on the known
+ * bad ones manually for now.
+ */
+ if (info->fbdefio
+ || !strcmp(info->fix.id, "astdrmfb")
+ || !strcmp(info->fix.id, "cirrusdrmfb")
+ || !strcmp(info->fix.id, "mgadrmfb")) {
+ struct fb_copyarea area;
+
+ area.dx = 0;
+ area.dy = 0;
+ area.width = info->var.xres;
+ area.height = info->var.yres;
+ area.sx = 0;
+ area.sy = 0;
+
+ info->fbops->fb_copyarea(info, &area);
+ }
+}

View File

@ -0,0 +1,31 @@
--- b/drivers/video/fbdev/core/fbcon.c
+++ a/drivers/video/fbdev/core/fbcon.c
@@ -163,6 +163,8 @@
#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
+static int fbcon_set_origin(struct vc_data *);
+
static int fbcon_cursor_noblink;
#define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1)
@@ -2633,6 +2635,11 @@
}
}
+static int fbcon_set_origin(struct vc_data *vc)
+{
+ return 0;
+}
+
void fbcon_suspended(struct fb_info *info)
{
struct vc_data *vc = NULL;
@@ -3103,6 +3110,7 @@
.con_font_default = fbcon_set_def_font,
.con_font_copy = fbcon_copy_font,
.con_set_palette = fbcon_set_palette,
+ .con_set_origin = fbcon_set_origin,
.con_invert_region = fbcon_invert_region,
.con_screen_pos = fbcon_screen_pos,
.con_getxy = fbcon_getxy,

View File

@ -0,0 +1,215 @@
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 8c09c306ff67..07e3a4eab811 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -155,6 +155,7 @@ void bootsplash_do_render_pictures(struct fb_info *info,
for (i = 0; i < fp->header->num_pics; i++) {
struct splash_blob_priv *bp;
struct splash_pic_priv *pp = &fp->pics[i];
+ const struct splash_pic_header *ph = pp->pic_header;
long dst_xoff, dst_yoff;
if (pp->blobs_loaded < 1)
@@ -165,8 +166,139 @@ void bootsplash_do_render_pictures(struct fb_info *info,
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;
+ switch (ph->position) {
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_LEFT:
+ dst_xoff = 0;
+ dst_yoff = 0;
+
+ dst_xoff += ph->position_offset;
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = 0;
+
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_TOP_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_yoff = 0;
+
+ dst_xoff -= ph->position_offset;
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff -= ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+
+ dst_xoff -= ph->position_offset;
+ dst_yoff -= ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+
+ dst_yoff -= ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_BOTTOM_LEFT:
+ dst_xoff = 0 + ph->position_offset;
+ dst_yoff = info->var.yres - pp->pic_header->height
+ - ph->position_offset;
+ break;
+ case SPLASH_POS_FLAG_CORNER | SPLASH_CORNER_LEFT:
+ dst_xoff = 0;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff += ph->position_offset;
+ break;
+
+ case SPLASH_CORNER_TOP_LEFT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff -= ph->position_offset;
+ dst_yoff -= ph->position_offset;
+ break;
+ case SPLASH_CORNER_TOP:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_yoff -= ph->position_offset;
+ break;
+ case SPLASH_CORNER_TOP_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff += ph->position_offset;
+ dst_yoff -= ph->position_offset;
+ break;
+ case SPLASH_CORNER_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff += ph->position_offset;
+ break;
+ case SPLASH_CORNER_BOTTOM_RIGHT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff += ph->position_offset;
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_CORNER_BOTTOM:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_CORNER_BOTTOM_LEFT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff -= ph->position_offset;
+ dst_yoff += ph->position_offset;
+ break;
+ case SPLASH_CORNER_LEFT:
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+
+ dst_xoff -= ph->position_offset;
+ break;
+
+ default:
+ /* As a fallback, center the picture. */
+ dst_xoff = info->var.xres - pp->pic_header->width;
+ dst_xoff /= 2;
+ dst_yoff = info->var.yres - pp->pic_header->height;
+ dst_yoff /= 2;
+ break;
+ }
if (dst_xoff < 0
|| dst_yoff < 0
diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
index 89dc9cca8f0c..71cedcc68933 100644
--- a/include/uapi/linux/bootsplash_file.h
+++ b/include/uapi/linux/bootsplash_file.h
@@ -91,7 +91,32 @@ struct splash_pic_header {
*/
uint8_t num_blobs;
- uint8_t padding[27];
+ /*
+ * Corner to move the picture to / from.
+ * 0x00 - Top left
+ * 0x01 - Top
+ * 0x02 - Top right
+ * 0x03 - Right
+ * 0x04 - Bottom right
+ * 0x05 - Bottom
+ * 0x06 - Bottom left
+ * 0x07 - Left
+ *
+ * Flags:
+ * 0x10 - Calculate offset from the corner towards the center,
+ * rather than from the center towards the corner
+ */
+ uint8_t position;
+
+ /*
+ * Pixel offset from the selected position.
+ * Example: If the picture is in the top right corner, it will
+ * be placed position_offset pixels from the top and
+ * position_offset pixels from the right margin.
+ */
+ uint16_t position_offset;
+
+ uint8_t padding[24];
} __attribute__((__packed__));
@@ -115,4 +140,22 @@ struct splash_blob_header {
uint8_t padding[9];
} __attribute__((__packed__));
+
+
+
+/*
+ * Enums for on-disk types
+ */
+enum splash_position {
+ SPLASH_CORNER_TOP_LEFT = 0,
+ SPLASH_CORNER_TOP = 1,
+ SPLASH_CORNER_TOP_RIGHT = 2,
+ SPLASH_CORNER_RIGHT = 3,
+ SPLASH_CORNER_BOTTOM_RIGHT = 4,
+ SPLASH_CORNER_BOTTOM = 5,
+ SPLASH_CORNER_BOTTOM_LEFT = 6,
+ SPLASH_CORNER_LEFT = 7,
+ SPLASH_POS_FLAG_CORNER = 0x10,
+};
+
#endif

View File

@ -0,0 +1,500 @@
--- b/drivers/video/fbdev/core/fbcon.c
+++ a/drivers/video/fbdev/core/fbcon.c
@@ -122,6 +122,12 @@
/* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO
enums. */
static int logo_shown = FBCON_LOGO_CANSHOW;
+/* Software scrollback */
+static int fbcon_softback_size = 32768;
+static unsigned long softback_buf, softback_curr;
+static unsigned long softback_in;
+static unsigned long softback_top, softback_end;
+static int softback_lines;
/* console mappings */
static int first_fb_vc;
static int last_fb_vc = MAX_NR_CONSOLES - 1;
@@ -161,6 +167,8 @@
static const struct consw fb_con;
+#define CM_SOFTBACK (8)
+
#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
static int fbcon_set_origin(struct vc_data *);
@@ -365,6 +373,18 @@
return color;
}
+static void fbcon_update_softback(struct vc_data *vc)
+{
+ int l = fbcon_softback_size / vc->vc_size_row;
+
+ if (l > 5)
+ softback_end = softback_buf + l * vc->vc_size_row;
+ else
+ /* Smaller scrollback makes no sense, and 0 would screw
+ the operation totally */
+ softback_top = 0;
+}
+
static void fb_flashcursor(struct work_struct *work)
{
struct fb_info *info = container_of(work, struct fb_info, queue);
@@ -394,7 +414,7 @@
c = scr_readw((u16 *) vc->vc_pos);
mode = (!ops->cursor_flash || ops->cursor_state.enable) ?
CM_ERASE : CM_DRAW;
+ ops->cursor(vc, info, mode, softback_lines, get_color(vc, info, c, 1),
- ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
console_unlock();
}
@@ -451,7 +471,13 @@
}
if (!strncmp(options, "scrollback:", 11)) {
+ options += 11;
+ if (*options) {
+ fbcon_softback_size = simple_strtoul(options, &options, 0);
+ if (*options == 'k' || *options == 'K') {
+ fbcon_softback_size *= 1024;
+ }
+ }
- pr_warn("Ignoring scrollback size option\n");
continue;
}
@@ -996,6 +1022,31 @@
set_blitting_type(vc, info);
+ if (info->fix.type != FB_TYPE_TEXT) {
+ if (fbcon_softback_size) {
+ if (!softback_buf) {
+ softback_buf =
+ (unsigned long)
+ kvmalloc(fbcon_softback_size,
+ GFP_KERNEL);
+ if (!softback_buf) {
+ fbcon_softback_size = 0;
+ softback_top = 0;
+ }
+ }
+ } else {
+ if (softback_buf) {
+ kvfree((void *) softback_buf);
+ softback_buf = 0;
+ softback_top = 0;
+ }
+ }
+ if (softback_buf)
+ softback_in = softback_top = softback_curr =
+ softback_buf;
+ softback_lines = 0;
+ }
+
/* Setup default font */
if (!p->fontdata && !vc->vc_font.data) {
if (!fontname[0] || !(font = find_font(fontname)))
@@ -1169,6 +1220,9 @@
if (logo)
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
+ if (vc == svc && softback_buf)
+ fbcon_update_softback(vc);
+
if (ops->rotate_font && ops->rotate_font(info, vc)) {
ops->rotate = FB_ROTATE_UR;
set_blitting_type(vc, info);
@@ -1331,6 +1385,7 @@
{
struct fb_info *info = registered_fb[con2fb_map[vc->vc_num]];
struct fbcon_ops *ops = info->fbcon_par;
+ int y;
int c = scr_readw((u16 *) vc->vc_pos);
ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
@@ -1334,11 +1389,19 @@ static void fbcon_cursor(struct vc_data
fbcon_add_cursor_timer(info);
ops->cursor_flash = (mode == CM_ERASE) ? 0 : 1;
+ if (mode & CM_SOFTBACK) {
+ mode &= ~CM_SOFTBACK;
+ y = softback_lines;
+ } else {
+ if (softback_lines)
+ fbcon_set_origin(vc);
+ y = 0;
+ }
if (!ops->cursor)
return;
- ops->cursor(vc, info, mode, 0, get_color(vc, info, c, 1),
+ ops->cursor(vc, info, mode, y, get_color(vc, info, c, 1),
get_color(vc, info, c, 0));
}
@@ -1416,6 +1479,8 @@
if (con_is_visible(vc)) {
update_screen(vc);
+ if (softback_buf)
+ fbcon_update_softback(vc);
}
}
@@ -1553,6 +1618,99 @@
scrollback_current = 0;
}
+static void fbcon_redraw_softback(struct vc_data *vc, struct fbcon_display *p,
+ long delta)
+{
+ int count = vc->vc_rows;
+ unsigned short *d, *s;
+ unsigned long n;
+ int line = 0;
+
+ d = (u16 *) softback_curr;
+ if (d == (u16 *) softback_in)
+ d = (u16 *) vc->vc_origin;
+ n = softback_curr + delta * vc->vc_size_row;
+ softback_lines -= delta;
+ if (delta < 0) {
+ if (softback_curr < softback_top && n < softback_buf) {
+ n += softback_end - softback_buf;
+ if (n < softback_top) {
+ softback_lines -=
+ (softback_top - n) / vc->vc_size_row;
+ n = softback_top;
+ }
+ } else if (softback_curr >= softback_top
+ && n < softback_top) {
+ softback_lines -=
+ (softback_top - n) / vc->vc_size_row;
+ n = softback_top;
+ }
+ } else {
+ if (softback_curr > softback_in && n >= softback_end) {
+ n += softback_buf - softback_end;
+ if (n > softback_in) {
+ n = softback_in;
+ softback_lines = 0;
+ }
+ } else if (softback_curr <= softback_in && n > softback_in) {
+ n = softback_in;
+ softback_lines = 0;
+ }
+ }
+ if (n == softback_curr)
+ return;
+ softback_curr = n;
+ s = (u16 *) softback_curr;
+ if (s == (u16 *) softback_in)
+ s = (u16 *) vc->vc_origin;
+ while (count--) {
+ unsigned short *start;
+ unsigned short *le;
+ unsigned short c;
+ int x = 0;
+ unsigned short attr = 1;
+
+ start = s;
+ le = advance_row(s, 1);
+ do {
+ c = scr_readw(s);
+ if (attr != (c & 0xff00)) {
+ attr = c & 0xff00;
+ if (s > start) {
+ fbcon_putcs(vc, start, s - start,
+ line, x);
+ x += s - start;
+ start = s;
+ }
+ }
+ if (c == scr_readw(d)) {
+ if (s > start) {
+ fbcon_putcs(vc, start, s - start,
+ line, x);
+ x += s - start + 1;
+ start = s + 1;
+ } else {
+ x++;
+ start++;
+ }
+ }
+ s++;
+ d++;
+ } while (s < le);
+ if (s > start)
+ fbcon_putcs(vc, start, s - start, line, x);
+ line++;
+ if (d == (u16 *) softback_end)
+ d = (u16 *) softback_buf;
+ if (d == (u16 *) softback_in)
+ d = (u16 *) vc->vc_origin;
+ if (s == (u16 *) softback_end)
+ s = (u16 *) softback_buf;
+ if (s == (u16 *) softback_in)
+ s = (u16 *) vc->vc_origin;
+ }
+}
+
static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
int line, int count, int dy)
{
@@ -1692,6 +1850,31 @@
}
}
+static inline void fbcon_softback_note(struct vc_data *vc, int t,
+ int count)
+{
+ unsigned short *p;
+
+ if (vc->vc_num != fg_console)
+ return;
+ p = (unsigned short *) (vc->vc_origin + t * vc->vc_size_row);
+
+ while (count) {
+ scr_memcpyw((u16 *) softback_in, p, vc->vc_size_row);
+ count--;
+ p = advance_row(p, 1);
+ softback_in += vc->vc_size_row;
+ if (softback_in == softback_end)
+ softback_in = softback_buf;
+ if (softback_in == softback_top) {
+ softback_top += vc->vc_size_row;
+ if (softback_top == softback_end)
+ softback_top = softback_buf;
+ }
+ }
+ softback_curr = softback_in;
+}
+
static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
enum con_scroll dir, unsigned int count)
{
@@ -1714,6 +1897,8 @@
case SM_UP:
if (count > vc->vc_rows) /* Maximum realistic size */
count = vc->vc_rows;
+ if (softback_top)
+ fbcon_softback_note(vc, t, count);
if (logo_shown >= 0)
goto redraw_up;
switch (p->scrollmode) {
@@ -2084,6 +2269,14 @@
info = registered_fb[con2fb_map[vc->vc_num]];
ops = info->fbcon_par;
+ if (softback_top) {
+ if (softback_lines)
+ fbcon_set_origin(vc);
+ softback_top = softback_curr = softback_in = softback_buf;
+ softback_lines = 0;
+ fbcon_update_softback(vc);
+ }
+
if (logo_shown >= 0) {
struct vc_data *conp2 = vc_cons[logo_shown].d;
@@ -2407,6 +2600,9 @@
int cnt;
char *old_data = NULL;
+ if (con_is_visible(vc) && softback_lines)
+ fbcon_set_origin(vc);
+
resize = (w != vc->vc_font.width) || (h != vc->vc_font.height);
if (p->userfont)
old_data = vc->vc_font.data;
@@ -2432,6 +2628,8 @@
cols /= w;
rows /= h;
vc_resize(vc, cols, rows);
+ if (con_is_visible(vc) && softback_buf)
+ fbcon_update_softback(vc);
} else if (con_is_visible(vc)
&& vc->vc_mode == KD_TEXT) {
fbcon_clear_margins(vc, 0);
@@ -2590,7 +2788,19 @@
static u16 *fbcon_screen_pos(struct vc_data *vc, int offset)
{
+ unsigned long p;
+ int line;
+
+ if (vc->vc_num != fg_console || !softback_lines)
+ return (u16 *) (vc->vc_origin + offset);
+ line = offset / vc->vc_size_row;
+ if (line >= softback_lines)
+ return (u16 *) (vc->vc_origin + offset -
+ softback_lines * vc->vc_size_row);
+ p = softback_curr + offset;
+ if (p >= softback_end)
+ p += softback_buf - softback_end;
+ return (u16 *) p;
- return (u16 *) (vc->vc_origin + offset);
}
static unsigned long fbcon_getxy(struct vc_data *vc, unsigned long pos,
@@ -2604,7 +2814,22 @@
x = offset % vc->vc_cols;
y = offset / vc->vc_cols;
+ if (vc->vc_num == fg_console)
+ y += softback_lines;
ret = pos + (vc->vc_cols - x) * 2;
+ } else if (vc->vc_num == fg_console && softback_lines) {
+ unsigned long offset = pos - softback_curr;
+
+ if (pos < softback_curr)
+ offset += softback_end - softback_buf;
+ offset /= 2;
+ x = offset % vc->vc_cols;
+ y = offset / vc->vc_cols;
+ ret = pos + (vc->vc_cols - x) * 2;
+ if (ret == softback_end)
+ ret = softback_buf;
+ if (ret == softback_in)
+ ret = vc->vc_origin;
} else {
/* Should not happen */
x = y = 0;
@@ -2632,11 +2857,106 @@
a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) |
(((a) & 0x0700) << 4);
scr_writew(a, p++);
+ if (p == (u16 *) softback_end)
+ p = (u16 *) softback_buf;
+ if (p == (u16 *) softback_in)
+ p = (u16 *) vc->vc_origin;
+ }
+}
+
+static void fbcon_scrolldelta(struct vc_data *vc, int lines)
+{
+ struct fb_info *info = registered_fb[con2fb_map[fg_console]];
+ struct fbcon_ops *ops = info->fbcon_par;
+ struct fbcon_display *disp = &fb_display[fg_console];
+ int offset, limit, scrollback_old;
+
+ if (softback_top) {
+ if (vc->vc_num != fg_console)
+ return;
+ if (vc->vc_mode != KD_TEXT || !lines)
+ return;
+ if (logo_shown >= 0) {
+ struct vc_data *conp2 = vc_cons[logo_shown].d;
+
+ if (conp2->vc_top == logo_lines
+ && conp2->vc_bottom == conp2->vc_rows)
+ conp2->vc_top = 0;
+ if (logo_shown == vc->vc_num) {
+ unsigned long p, q;
+ int i;
+
+ p = softback_in;
+ q = vc->vc_origin +
+ logo_lines * vc->vc_size_row;
+ for (i = 0; i < logo_lines; i++) {
+ if (p == softback_top)
+ break;
+ if (p == softback_buf)
+ p = softback_end;
+ p -= vc->vc_size_row;
+ q -= vc->vc_size_row;
+ scr_memcpyw((u16 *) q, (u16 *) p,
+ vc->vc_size_row);
+ }
+ softback_in = softback_curr = p;
+ update_region(vc, vc->vc_origin,
+ logo_lines * vc->vc_cols);
+ }
+ logo_shown = FBCON_LOGO_CANSHOW;
+ }
+ fbcon_cursor(vc, CM_ERASE | CM_SOFTBACK);
+ fbcon_redraw_softback(vc, disp, lines);
+ fbcon_cursor(vc, CM_DRAW | CM_SOFTBACK);
+ return;
}
+
+ if (!scrollback_phys_max)
+ return;
+
+ scrollback_old = scrollback_current;
+ scrollback_current -= lines;
+ if (scrollback_current < 0)
+ scrollback_current = 0;
+ else if (scrollback_current > scrollback_max)
+ scrollback_current = scrollback_max;
+ if (scrollback_current == scrollback_old)
+ return;
+
+ if (fbcon_is_inactive(vc, info))
+ return;
+
+ fbcon_cursor(vc, CM_ERASE);
+
+ offset = disp->yscroll - scrollback_current;
+ limit = disp->vrows;
+ switch (disp->scrollmode) {
+ case SCROLL_WRAP_MOVE:
+ info->var.vmode |= FB_VMODE_YWRAP;
+ break;
+ case SCROLL_PAN_MOVE:
+ case SCROLL_PAN_REDRAW:
+ limit -= vc->vc_rows;
+ info->var.vmode &= ~FB_VMODE_YWRAP;
+ break;
+ }
+ if (offset < 0)
+ offset += limit;
+ else if (offset >= limit)
+ offset -= limit;
+
+ ops->var.xoffset = 0;
+ ops->var.yoffset = offset * vc->vc_font.height;
+ ops->update_start(info);
+
+ if (!scrollback_current)
+ fbcon_cursor(vc, CM_DRAW);
}
static int fbcon_set_origin(struct vc_data *vc)
{
+ if (softback_lines)
+ fbcon_scrolldelta(vc, softback_lines);
return 0;
}
@@ -2700,6 +3020,8 @@
fbcon_set_palette(vc, color_table);
update_screen(vc);
+ if (softback_buf)
+ fbcon_update_softback(vc);
}
}
@@ -3110,6 +3432,7 @@
.con_font_default = fbcon_set_def_font,
.con_font_copy = fbcon_copy_font,
.con_set_palette = fbcon_set_palette,
+ .con_scrolldelta = fbcon_scrolldelta,
.con_set_origin = fbcon_set_origin,
.con_invert_region = fbcon_invert_region,
.con_screen_pos = fbcon_screen_pos,
@@ -3344,6 +3667,9 @@
}
#endif
+ kvfree((void *)softback_buf);
+ softback_buf = 0UL;
+
for_each_registered_fb(i) {
int pending = 0;

View File

@ -0,0 +1,327 @@
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index 815b007f81ca..c8642142cfea 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -53,6 +53,14 @@ static void splash_callback_redraw_vc(struct work_struct *ignored)
console_unlock();
}
+static void splash_callback_animation(struct work_struct *ignored)
+{
+ if (bootsplash_would_render_now()) {
+ /* This will also re-schedule this delayed worker */
+ splash_callback_redraw_vc(ignored);
+ }
+}
+
static bool is_fb_compatible(const struct fb_info *info)
{
@@ -103,17 +111,44 @@ static bool is_fb_compatible(const struct fb_info *info)
*/
void bootsplash_render_full(struct fb_info *info)
{
+ bool is_update = false;
+
mutex_lock(&splash_state.data_lock);
- if (!is_fb_compatible(info))
- goto out;
+ /*
+ * If we've painted on this FB recently, we don't have to do
+ * the sanity checks and background drawing again.
+ */
+ if (splash_state.splash_fb == info)
+ is_update = true;
+
+
+ if (!is_update) {
+ /* Check whether we actually support this FB. */
+ splash_state.splash_fb = NULL;
+
+ if (!is_fb_compatible(info))
+ goto out;
+
+ /* Draw the background only once */
+ bootsplash_do_render_background(info, splash_state.file);
- bootsplash_do_render_background(info, splash_state.file);
+ /* Mark this FB as last seen */
+ splash_state.splash_fb = info;
+ }
- bootsplash_do_render_pictures(info, splash_state.file);
+ bootsplash_do_render_pictures(info, splash_state.file, is_update);
bootsplash_do_render_flush(info);
+ bootsplash_do_step_animations(splash_state.file);
+
+ /* Schedule update for animated splash screens */
+ if (splash_state.file->frame_ms > 0)
+ schedule_delayed_work(&splash_state.dwork_animation,
+ msecs_to_jiffies(
+ splash_state.file->frame_ms));
+
out:
mutex_unlock(&splash_state.data_lock);
}
@@ -169,8 +204,14 @@ void bootsplash_enable(void)
was_enabled = test_and_set_bit(0, &splash_state.enabled);
- if (!was_enabled)
+ if (!was_enabled) {
+ /* Force a full redraw when the splash is re-activated */
+ mutex_lock(&splash_state.data_lock);
+ splash_state.splash_fb = NULL;
+ mutex_unlock(&splash_state.data_lock);
+
schedule_work(&splash_state.work_redraw_vc);
+ }
}
@@ -227,6 +268,14 @@ ATTRIBUTE_GROUPS(splash_dev);
*/
static int splash_resume(struct device *device)
{
+ /*
+ * Force full redraw on resume since we've probably lost the
+ * framebuffer's contents meanwhile
+ */
+ mutex_lock(&splash_state.data_lock);
+ splash_state.splash_fb = NULL;
+ mutex_unlock(&splash_state.data_lock);
+
if (bootsplash_would_render_now())
schedule_work(&splash_state.work_redraw_vc);
@@ -235,6 +284,7 @@ static int splash_resume(struct device *device)
static int splash_suspend(struct device *device)
{
+ cancel_delayed_work_sync(&splash_state.dwork_animation);
cancel_work_sync(&splash_state.work_redraw_vc);
return 0;
@@ -296,6 +346,8 @@ void bootsplash_init(void)
set_bit(0, &splash_state.enabled);
INIT_WORK(&splash_state.work_redraw_vc, splash_callback_redraw_vc);
+ INIT_DELAYED_WORK(&splash_state.dwork_animation,
+ splash_callback_animation);
if (!splash_state.bootfile || !strlen(splash_state.bootfile))
diff --git a/drivers/video/fbdev/core/bootsplash_internal.h b/drivers/video/fbdev/core/bootsplash_internal.h
index 0acb383aa4e3..b3a74835d90f 100644
--- a/drivers/video/fbdev/core/bootsplash_internal.h
+++ b/drivers/video/fbdev/core/bootsplash_internal.h
@@ -37,6 +37,8 @@ struct splash_pic_priv {
struct splash_blob_priv *blobs;
u16 blobs_loaded;
+
+ u16 anim_nextframe;
};
@@ -45,6 +47,12 @@ struct splash_file_priv {
const struct splash_file_header *header;
struct splash_pic_priv *pics;
+
+ /*
+ * A local copy of the frame delay in the header.
+ * We modify it to keep the code simple.
+ */
+ u16 frame_ms;
};
@@ -71,6 +79,7 @@ struct splash_priv {
struct platform_device *splash_device;
struct work_struct work_redraw_vc;
+ struct delayed_work dwork_animation;
/* Splash data structures including lock for everything below */
struct mutex data_lock;
@@ -88,8 +97,10 @@ struct splash_priv {
void bootsplash_do_render_background(struct fb_info *info,
const struct splash_file_priv *fp);
void bootsplash_do_render_pictures(struct fb_info *info,
- const struct splash_file_priv *fp);
+ const struct splash_file_priv *fp,
+ bool is_update);
void bootsplash_do_render_flush(struct fb_info *info);
+void bootsplash_do_step_animations(struct splash_file_priv *fp);
void bootsplash_free_file(struct splash_file_priv *fp);
diff --git a/drivers/video/fbdev/core/bootsplash_load.c b/drivers/video/fbdev/core/bootsplash_load.c
index fd807571ab7d..1f661b2d4cc9 100644
--- a/drivers/video/fbdev/core/bootsplash_load.c
+++ b/drivers/video/fbdev/core/bootsplash_load.c
@@ -71,6 +71,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
{
const struct firmware *fw;
struct splash_file_priv *fp;
+ bool have_anim = false;
unsigned int i;
const u8 *walker;
@@ -135,6 +136,13 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
goto err;
}
+ if (ph->anim_type > SPLASH_ANIM_LOOP_FORWARD) {
+ pr_warn("Picture %u: Unsupported animation type %u.\n",
+ i, ph->anim_type);
+
+ ph->anim_type = SPLASH_ANIM_NONE;
+ }
+
pp->pic_header = ph;
pp->blobs = vzalloc(ph->num_blobs
* sizeof(struct splash_blob_priv));
@@ -202,6 +210,7 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
/* Walk over pictures and ensure all blob slots are filled */
for (i = 0; i < fp->header->num_pics; i++) {
struct splash_pic_priv *pp = &fp->pics[i];
+ const struct splash_pic_header *ph = pp->pic_header;
if (pp->blobs_loaded != pp->pic_header->num_blobs) {
pr_err("Picture %u doesn't have all blob slots filled.\n",
@@ -209,8 +218,20 @@ struct splash_file_priv *bootsplash_load_firmware(struct device *device,
goto err;
}
+
+ if (ph->anim_type
+ && ph->num_blobs > 1
+ && ph->anim_loop < pp->blobs_loaded)
+ have_anim = true;
}
+ if (!have_anim)
+ /* Disable animation timer if there is nothing to animate */
+ fp->frame_ms = 0;
+ else
+ /* Enforce minimum delay between frames */
+ fp->frame_ms = max((u16)20, fp->header->frame_ms);
+
pr_info("Loaded (%ld bytes, %u pics, %u blobs).\n",
fw->size,
fp->header->num_pics,
diff --git a/drivers/video/fbdev/core/bootsplash_render.c b/drivers/video/fbdev/core/bootsplash_render.c
index 07e3a4eab811..76033606ca8a 100644
--- a/drivers/video/fbdev/core/bootsplash_render.c
+++ b/drivers/video/fbdev/core/bootsplash_render.c
@@ -148,7 +148,8 @@ void bootsplash_do_render_background(struct fb_info *info,
void bootsplash_do_render_pictures(struct fb_info *info,
- const struct splash_file_priv *fp)
+ const struct splash_file_priv *fp,
+ bool is_update)
{
unsigned int i;
@@ -161,7 +162,11 @@ void bootsplash_do_render_pictures(struct fb_info *info,
if (pp->blobs_loaded < 1)
continue;
- bp = &pp->blobs[0];
+ /* Skip static pictures when refreshing animations */
+ if (ph->anim_type == SPLASH_ANIM_NONE && is_update)
+ continue;
+
+ bp = &pp->blobs[pp->anim_nextframe];
if (!bp || bp->blob_header->type != 0)
continue;
@@ -351,3 +356,24 @@ void bootsplash_do_render_flush(struct fb_info *info)
info->fbops->fb_copyarea(info, &area);
}
}
+
+
+void bootsplash_do_step_animations(struct splash_file_priv *fp)
+{
+ unsigned int i;
+
+ /* Step every animation once */
+ for (i = 0; i < fp->header->num_pics; i++) {
+ struct splash_pic_priv *pp = &fp->pics[i];
+
+ if (pp->blobs_loaded < 2
+ || pp->pic_header->anim_loop > pp->blobs_loaded)
+ continue;
+
+ if (pp->pic_header->anim_type == SPLASH_ANIM_LOOP_FORWARD) {
+ pp->anim_nextframe++;
+ if (pp->anim_nextframe >= pp->pic_header->num_blobs)
+ pp->anim_nextframe = pp->pic_header->anim_loop;
+ }
+ }
+}
diff --git a/include/uapi/linux/bootsplash_file.h b/include/uapi/linux/bootsplash_file.h
index 71cedcc68933..b3af0a3c6487 100644
--- a/include/uapi/linux/bootsplash_file.h
+++ b/include/uapi/linux/bootsplash_file.h
@@ -77,7 +77,17 @@ struct splash_file_header {
uint16_t num_blobs;
uint8_t num_pics;
- uint8_t padding[103];
+ uint8_t unused_1;
+
+ /*
+ * Milliseconds to wait before painting the next frame in
+ * an animation.
+ * This is actually a minimum, as the system is allowed to
+ * stall for longer between frames.
+ */
+ uint16_t frame_ms;
+
+ uint8_t padding[100];
} __attribute__((__packed__));
@@ -116,7 +126,23 @@ struct splash_pic_header {
*/
uint16_t position_offset;
- uint8_t padding[24];
+ /*
+ * Animation type.
+ * 0 - off
+ * 1 - forward loop
+ */
+ uint8_t anim_type;
+
+ /*
+ * Animation loop point.
+ * Actual meaning depends on animation type:
+ * Type 0 - Unused
+ * 1 - Frame at which to restart the forward loop
+ * (allowing for "intro" frames)
+ */
+ uint8_t anim_loop;
+
+ uint8_t padding[22];
} __attribute__((__packed__));
@@ -158,4 +184,9 @@ enum splash_position {
SPLASH_POS_FLAG_CORNER = 0x10,
};
+enum splash_anim_type {
+ SPLASH_ANIM_NONE = 0,
+ SPLASH_ANIM_LOOP_FORWARD = 1,
+};
+
#endif

View File

@ -0,0 +1,82 @@
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 2ebaba16f785..416735ab6dc1 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -105,6 +105,7 @@
#include <linux/ctype.h>
#include <linux/bsearch.h>
#include <linux/gcd.h>
+#include <linux/bootsplash.h>
#define MAX_NR_CON_DRIVER 16
@@ -4235,6 +4236,7 @@ void do_unblank_screen(int leaving_gfx)
}
console_blanked = 0;
+ bootsplash_mark_dirty();
if (vc->vc_sw->con_blank(vc, 0, leaving_gfx))
/* Low-level driver cannot restore -> do it ourselves */
update_screen(vc);
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index c8642142cfea..13fcaabbc2ca 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -165,6 +165,13 @@ bool bootsplash_would_render_now(void)
&& bootsplash_is_enabled();
}
+void bootsplash_mark_dirty(void)
+{
+ mutex_lock(&splash_state.data_lock);
+ splash_state.splash_fb = NULL;
+ mutex_unlock(&splash_state.data_lock);
+}
+
bool bootsplash_is_enabled(void)
{
bool was_enabled;
@@ -206,9 +213,7 @@ void bootsplash_enable(void)
if (!was_enabled) {
/* Force a full redraw when the splash is re-activated */
- mutex_lock(&splash_state.data_lock);
- splash_state.splash_fb = NULL;
- mutex_unlock(&splash_state.data_lock);
+ bootsplash_mark_dirty();
schedule_work(&splash_state.work_redraw_vc);
}
@@ -272,9 +277,7 @@ static int splash_resume(struct device *device)
* Force full redraw on resume since we've probably lost the
* framebuffer's contents meanwhile
*/
- mutex_lock(&splash_state.data_lock);
- splash_state.splash_fb = NULL;
- mutex_unlock(&splash_state.data_lock);
+ bootsplash_mark_dirty();
if (bootsplash_would_render_now())
schedule_work(&splash_state.work_redraw_vc);
diff --git a/include/linux/bootsplash.h b/include/linux/bootsplash.h
index c6dd0b43180d..4075098aaadd 100644
--- a/include/linux/bootsplash.h
+++ b/include/linux/bootsplash.h
@@ -19,6 +19,8 @@ extern void bootsplash_render_full(struct fb_info *info);
extern bool bootsplash_would_render_now(void);
+extern void bootsplash_mark_dirty(void);
+
extern bool bootsplash_is_enabled(void);
extern void bootsplash_disable(void);
extern void bootsplash_enable(void);
@@ -31,6 +33,8 @@ extern void bootsplash_init(void);
#define bootsplash_would_render_now() (false)
+#define bootsplash_mark_dirty()
+
#define bootsplash_is_enabled() (false)
#define bootsplash_disable()
#define bootsplash_enable()

View File

@ -0,0 +1,42 @@
diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c
index f4166263bb3a..a248429194bb 100644
--- a/drivers/tty/vt/keyboard.c
+++ b/drivers/tty/vt/keyboard.c
@@ -49,6 +49,8 @@
#include <asm/irq_regs.h>
+#include <linux/bootsplash.h>
+
/*
* Exported functions/variables
*/
@@ -1413,6 +1415,28 @@ static void kbd_keycode(unsigned int key
}
#endif
+ /* Trap keys when bootsplash is shown */
+ if (bootsplash_would_render_now()) {
+ /* Deactivate bootsplash on ESC or Alt+Fxx VT switch */
+ if (keycode >= KEY_F1 && keycode <= KEY_F12) {
+ bootsplash_disable();
+
+ /*
+ * No return here since we want to actually
+ * perform the VT switch.
+ */
+ } else {
+ if (keycode == KEY_ESC)
+ bootsplash_disable();
+
+ /*
+ * Just drop any other keys.
+ * Their effect would be hidden by the splash.
+ */
+ return;
+ }
+ }
+
if (kbd->kbdmode == VC_MEDIUMRAW) {
/*
* This is extended medium raw mode, with keys above 127

View File

@ -0,0 +1,21 @@
diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index 3ffc1ce29023..bc6a24c9dfa8 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -49,6 +49,7 @@
#include <linux/syscalls.h>
#include <linux/of.h>
#include <linux/rcupdate.h>
+#include <linux/bootsplash.h>
#include <asm/ptrace.h>
#include <asm/irq_regs.h>
@@ -104,6 +105,8 @@ static void sysrq_handle_SAK(int key)
{
struct work_struct *SAK_work = &vc_cons[fg_console].SAK_work;
schedule_work(SAK_work);
+
+ bootsplash_disable();
}
static struct sysrq_key_op sysrq_SAK_op = {
.handler = sysrq_handle_SAK,

View File

@ -0,0 +1,21 @@
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 9a39a6fcfe98..8a9c67e1c5d8 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -1343,6 +1343,16 @@ static void fbcon_cursor(struct vc_data *vc, int mode)
int y;
int c = scr_readw((u16 *) vc->vc_pos);
+ /*
+ * Disable the splash here so we don't have to hook into
+ * vt_console_print() in drivers/tty/vt/vt.c
+ *
+ * We'd disable the splash just before the call to
+ * hide_cursor() anyway, so this spot is just fine.
+ */
+ if (oops_in_progress)
+ bootsplash_disable();
+
ops->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
if (fbcon_is_inactive(vc, info) || vc->vc_deccm != 1)

View File

@ -0,0 +1,321 @@
diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash
new file mode 100644
index 000000000000..742c7b035ded
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-platform-bootsplash
@@ -0,0 +1,11 @@
+What: /sys/devices/platform/bootsplash.0/enabled
+Date: Oct 2017
+KernelVersion: 4.14
+Contact: Max Staudt <mstaudt@suse.de>
+Description:
+ Can be set and read.
+
+ 0: Splash is disabled.
+ 1: Splash is shown whenever fbcon would show a text console
+ (i.e. no graphical application is running), and a splash
+ file is loaded.
diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst
new file mode 100644
index 000000000000..611f0c558925
--- /dev/null
+++ b/Documentation/bootsplash.rst
@@ -0,0 +1,285 @@
+====================
+The Linux bootsplash
+====================
+
+:Date: November, 2017
+:Author: Max Staudt <mstaudt@suse.de>
+
+
+The Linux bootsplash is a graphical replacement for the '``quiet``' boot
+option, typically showing a logo and a spinner animation as the system starts.
+
+Currently, it is a part of the Framebuffer Console support, and can be found
+as ``CONFIG_BOOTSPLASH`` in the kernel configuration. This means that as long
+as it is enabled, it hijacks fbcon's output and draws a splash screen instead.
+
+Purely compiling in the bootsplash will not render it functional - to actually
+render a splash, you will also need a splash theme file. See the example
+utility and script in ``tools/bootsplash`` for a live demo.
+
+
+
+Motivation
+==========
+
+- The '``quiet``' boot option only suppresses most messages during boot, but
+ errors are still shown.
+
+- A user space implementation can only show a logo once user space has been
+ initialized far enough to allow this. A kernel splash can display a splash
+ immediately as soon as fbcon can be displayed.
+
+- Implementing a splash screen in user space (e.g. Plymouth) is problematic
+ due to resource conflicts.
+
+ For example, if Plymouth is keeping ``/dev/fb0`` (provided via vesafb/efifb)
+ open, then most DRM drivers can't replace it because the address space is
+ still busy - thus leading to a VRAM reservation error.
+
+ See: https://bugzilla.opensuse.org/show_bug.cgi?id=980750
+
+
+
+Command line arguments
+======================
+
+``bootsplash.bootfile``
+ Which file in the initramfs to load.
+
+ The splash theme is loaded via request_firmware(), thus to load
+ ``/lib/firmware/bootsplash/mytheme`` pass the command line:
+
+ ``bootsplash.bootfile=bootsplash/mytheme``
+
+ Note: The splash file *has to be* in the initramfs, as it needs to be
+ available when the splash is initialized early on.
+
+ Default: none, i.e. a non-functional splash, falling back to showing text.
+
+
+
+sysfs run-time configuration
+============================
+
+``/sys/devices/platform/bootsplash.0/enabled``
+ Enable/disable the bootsplash.
+ The system boots with this set to 1, but will not show a splash unless
+ a splash theme file is also loaded.
+
+
+
+Kconfig
+=======
+
+``BOOTSPLASH``
+ Whether to compile in bootsplash support
+ (depends on fbcon compiled in, i.e. ``FRAMEBUFFER_CONSOLE=y``)
+
+
+
+Bootsplash file format
+======================
+
+A file specified in the kernel configuration as ``CONFIG_BOOTSPLASH_FILE``
+or specified on the command line as ``bootsplash.bootfile`` will be loaded
+and displayed as soon as fbcon is initialized.
+
+
+Main blocks
+-----------
+
+There are 3 main blocks in each file:
+
+ - one File header
+ - n Picture headers
+ - m (Blob header + payload) blocks
+
+
+Structures
+----------
+
+The on-disk structures are defined in
+``drivers/video/fbdev/core/bootsplash_file.h`` and represent these blocks:
+
+ - ``struct splash_file_header``
+
+ Represents the file header, with splash-wide information including:
+
+ - The magic string "``Linux bootsplash``" on big-endian platforms
+ (the reverse on little endian)
+ - The file format version (for incompatible updates, hopefully never)
+ - The background color
+ - Number of picture and blob blocks
+ - Animation speed (we only allow one delay for all animations)
+
+ The file header is followed by the first picture header.
+
+
+ - ``struct splash_picture_header``
+
+ Represents an object (picture) drawn on screen, including its immutable
+ properties:
+ - Width, height
+ - Positioning relative to screen corners or in the center
+ - Animation, if any
+ - Animation type
+ - Number of blobs
+
+ The picture header is followed by another picture header, up until n
+ picture headers (as defined in the file header) have been read. Then,
+ the (blob header, payload) pairs follow.
+
+
+ - ``struct splash_blob_header``
+ (followed by payload)
+
+ Represents one raw data stream. So far, only picture data is defined.
+
+ The blob header is followed by a payload, then padding to n*16 bytes,
+ then (if further blobs are defined in the file header) a further blob
+ header.
+
+
+Alignment
+---------
+
+The bootsplash file is designed to be loaded into memory as-is.
+
+All structures are a multiple of 16 bytes long, all elements therein are
+aligned to multiples of their length, and the payloads are always padded
+up to multiples of 16 bytes. This is to allow aligned accesses in all
+cases while still simply mapping the structures over an in-memory copy of
+the bootsplash file.
+
+
+Further information
+-------------------
+
+Please see ``drivers/video/fbdev/core/bootsplash_file.h`` for further
+details and possible values in the file.
+
+
+
+Hooks - how the bootsplash is integrated
+========================================
+
+``drivers/video/fbdev/core/fbcon.c``
+ ``fbcon_init()`` calls ``bootsplash_init()``, which loads the default
+ bootsplash file or the one specified on the kernel command line.
+
+ ``fbcon_switch()`` draws the bootsplash when it's active, and is also
+ one of the callers of ``set_blitting_type()``.
+
+ ``set_blitting_type()`` calls ``fbcon_set_dummyops()`` when the
+ bootsplash is active, overriding the text rendering functions.
+
+ ``fbcon_cursor()`` will call ``bootsplash_disable()`` when an oops is
+ being printed in order to make a kernel panic visible.
+
+``drivers/video/fbdev/core/dummyblit.c``
+ This contains the dummy text rendering functions used to suppress text
+ output while the bootsplash is shown.
+
+``drivers/tty/vt/keyboard.c``
+ ``kbd_keycode()`` can call ``bootsplash_disable()`` when the user
+ presses ESC or F1-F12 (changing VT). This is to provide a built-in way
+ of disabling the splash manually at any time.
+
+
+
+FAQ: Frequently Asked Questions
+===============================
+
+I want to see the log! How do I show the log?
+---------------------------------------------
+
+Press ESC while the splash is shown, or remove the ``bootsplash.bootfile``
+parameter from the kernel cmdline. Without that parameter, the bootsplash
+will boot disabled.
+
+
+Why use FB instead of modern DRM/KMS?
+-------------------------------------
+
+This is a semantic problem:
+ - What memory to draw the splash to?
+ - And what mode will the screen be set to?
+
+Using the fbdev emulation solves these issues.
+
+Let's start from a bare KMS system, without fbcon, and without fbdev
+emulation. In this case, as long as userspace doesn't open the KMS
+device, the state of the screen is undefined. No framebuffer is
+allocated in video RAM, and no particular mode is set.
+
+In this case, we'd have to allocate a framebuffer to show the splash,
+and set our mode ourselves. This either wastes a screenful of video RAM
+if the splash is to co-exist with the userspace program's own allocated
+framebuffer, or there is a flicker as we deactivate and delete the
+bootsplash's framebuffer and hand control over to userspace. Since we
+may set a different mode than userspace, we'd also have flicker due
+to mode switching.
+
+This logic is already contained in every KMS driver that performs fbdev
+emulation. So we might as well use that. And the correct API to do so is
+fbdev. Plus, we get compatibility with old, pure fbdev drivers for free.
+With the fbdev emulation, there is *always* a well-defined framebuffer
+to draw on. And the selection of mode has already been done by the
+graphics driver, so we don't need to reinvent that wheel, either.
+Finally, if userspace decides to use /dev/fbX, we don't have to worry
+about wasting video RAM, either.
+
+
+Why is the bootsplash integrated in fbcon?
+------------------------------------------
+
+Right now, the bootsplash is drawn from within fbcon, as this allows us
+to easily know *when* to draw - i.e. when we're safe from fbcon and
+userspace drawing all over our beautiful splash logo.
+
+Separating them is not easy - see the to-do list below.
+
+
+
+TO DO list for future development
+=================================
+
+Second enable/disable switch for the system
+-------------------------------------------
+
+It may be helpful to differentiate between the system and the user
+switching off the bootsplash. Thus, the system may make it disappear and
+reappear e.g. for a password prompt, yet once the user has pressed ESC,
+it could stay gone.
+
+
+Fix buggy DRM/KMS drivers
+-------------------------
+
+Currently, the splash code manually checks for fbdev emulation provided by
+the ast, cirrus, and mgag200 DRM/KMS drivers.
+These drivers use a manual mechanism similar to deferred I/O for their FB
+emulation, and thus need to be manually flushed onto the screen in the same
+way.
+
+This may be improved upon in several ways:
+
+1. Changing these drivers to expose the fbdev BO's memory directly, like
+ bochsdrmfb does.
+2. Creating a new fb_ops->fb_flush() API to allow the kernel to flush the
+ framebuffer once the bootsplash has been drawn into it.
+
+
+Separating from fbcon
+---------------------
+
+Separating these two components would yield independence from fbcon being
+compiled into the kernel, and thus lowering code size in embedded
+applications.
+
+To do this cleanly will involve a clean separation of users of an FB device
+within the kernel, i.e. fbcon, bootsplash, and userspace. Right now, the
+legacy fbcon code and VT code co-operate to switch between fbcon and
+userspace (by setting the VT into KD_GRAPHICS mode). Installing a muxer
+between these components ensues refactoring of old code and checking for
+correct locking.
diff --git a/MAINTAINERS b/MAINTAINERS
index 5c237445761e..7ffac272434e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2709,6 +2709,8 @@ BOOTSPLASH
M: Max Staudt <mstaudt@suse.de>
L: linux-fbdev@vger.kernel.org
S: Maintained
+F: Documentation/ABI/testing/sysfs-platform-bootsplash
+F: Documentation/bootsplash.rst
F: drivers/video/fbdev/core/bootsplash*.*
F: drivers/video/fbdev/core/dummycon.c
F: include/linux/bootsplash.h

View File

@ -0,0 +1,129 @@
diff --git a/Documentation/ABI/testing/sysfs-platform-bootsplash b/Documentation/ABI/testing/sysfs-platform-bootsplash
index 742c7b035ded..f8f4b259220e 100644
--- a/Documentation/ABI/testing/sysfs-platform-bootsplash
+++ b/Documentation/ABI/testing/sysfs-platform-bootsplash
@@ -9,3 +9,35 @@ Description:
1: Splash is shown whenever fbcon would show a text console
(i.e. no graphical application is running), and a splash
file is loaded.
+
+What: /sys/devices/platform/bootsplash.0/drop_splash
+Date: Oct 2017
+KernelVersion: 4.14
+Contact: Max Staudt <mstaudt@suse.de>
+Description:
+ Can only be set.
+
+ Any value written will cause the current splash theme file
+ to be unloaded and the text console to be redrawn.
+
+What: /sys/devices/platform/bootsplash.0/load_file
+Date: Oct 2017
+KernelVersion: 4.14
+Contact: Max Staudt <mstaudt@suse.de>
+Description:
+ Can only be set.
+
+ Any value written will cause the splash to be disabled and
+ internal memory structures to be freed.
+
+ A firmware path written will cause a new theme file to be
+ loaded and the current bootsplash to be replaced.
+ The current enabled/disabled status is not touched.
+ If the splash is already active, it will be redrawn.
+
+ The path has to be a path in /lib/firmware since
+ request_firmware() is used to fetch the data.
+
+ When setting the splash from the shell, echo -n has to be
+ used as any trailing '\n' newline will be interpreted as
+ part of the path.
diff --git a/Documentation/bootsplash.rst b/Documentation/bootsplash.rst
index 611f0c558925..b35aba5093e8 100644
--- a/Documentation/bootsplash.rst
+++ b/Documentation/bootsplash.rst
@@ -67,6 +67,14 @@ sysfs run-time configuration
a splash theme file is also loaded.
+``/sys/devices/platform/bootsplash.0/drop_splash``
+ Unload splash data and free memory.
+
+``/sys/devices/platform/bootsplash.0/load_file``
+ Load a splash file from ``/lib/firmware/``.
+ Note that trailing newlines will be interpreted as part of the file name.
+
+
Kconfig
=======
diff --git a/drivers/video/fbdev/core/bootsplash.c b/drivers/video/fbdev/core/bootsplash.c
index 13fcaabbc2ca..16cb0493629d 100644
--- a/drivers/video/fbdev/core/bootsplash.c
+++ b/drivers/video/fbdev/core/bootsplash.c
@@ -251,11 +251,65 @@ 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)
+{
+ struct splash_file_priv *fp;
+
+ if (!buf || !count || !splash_state.file)
+ return count;
+
+ mutex_lock(&splash_state.data_lock);
+ fp = splash_state.file;
+ splash_state.file = NULL;
+ mutex_unlock(&splash_state.data_lock);
+
+ /* Redraw the text console */
+ schedule_work(&splash_state.work_redraw_vc);
+
+ bootsplash_free_file(fp);
+
+ return count;
+}
+
+static ssize_t splash_store_load_file(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct splash_file_priv *fp, *fp_old;
+
+ if (!count)
+ return 0;
+
+ fp = bootsplash_load_firmware(&splash_state.splash_device->dev,
+ buf);
+
+ if (!fp)
+ return -ENXIO;
+
+ mutex_lock(&splash_state.data_lock);
+ fp_old = splash_state.file;
+ splash_state.splash_fb = NULL;
+ splash_state.file = fp;
+ mutex_unlock(&splash_state.data_lock);
+
+ /* Update the splash or text console */
+ schedule_work(&splash_state.work_redraw_vc);
+
+ bootsplash_free_file(fp_old);
+ 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 DEVICE_ATTR(load_file, 0200, NULL, splash_store_load_file);
static struct attribute *splash_dev_attrs[] = {
&dev_attr_enabled.attr,
+ &dev_attr_drop_splash.attr,
+ &dev_attr_load_file.attr,
NULL
};

View File

@ -0,0 +1,511 @@
diff --git a/MAINTAINERS b/MAINTAINERS
index 7ffac272434e..ddff07cd794c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2715,6 +2715,7 @@ F: drivers/video/fbdev/core/bootsplash*.*
F: drivers/video/fbdev/core/dummycon.c
F: include/linux/bootsplash.h
F: include/uapi/linux/bootsplash_file.h
+F: tools/bootsplash/*
BPF (Safe dynamic programs and tools)
M: Alexei Starovoitov <ast@kernel.org>
diff --git a/tools/bootsplash/.gitignore b/tools/bootsplash/.gitignore
new file mode 100644
index 000000000000..091b99a17567
--- /dev/null
+++ b/tools/bootsplash/.gitignore
@@ -0,0 +1 @@
+bootsplash-packer
diff --git a/tools/bootsplash/Makefile b/tools/bootsplash/Makefile
new file mode 100644
index 000000000000..0ad8e8a84942
--- /dev/null
+++ b/tools/bootsplash/Makefile
@@ -0,0 +1,9 @@
+CC := $(CROSS_COMPILE)gcc
+CFLAGS := -I../../usr/include
+
+PROGS := bootsplash-packer
+
+all: $(PROGS)
+
+clean:
+ rm -fr $(PROGS)
diff --git a/tools/bootsplash/bootsplash-packer.c b/tools/bootsplash/bootsplash-packer.c
new file mode 100644
index 000000000000..ffb6a8b69885
--- /dev/null
+++ b/tools/bootsplash/bootsplash-packer.c
@@ -0,0 +1,471 @@
+/*
+ * Kernel based bootsplash.
+ *
+ * (Splash file packer tool)
+ *
+ * Authors:
+ * Max Staudt <mstaudt@suse.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <endian.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/bootsplash_file.h>
+
+
+static void print_help(char *progname)
+{
+ printf("Usage: %s [OPTIONS] outfile\n", progname);
+ printf("\n"
+ "Options, executed in order given:\n"
+ " -h, --help Print this help message\n"
+ "\n"
+ " --bg_red <u8> Background color (red part)\n"
+ " --bg_green <u8> Background color (green part)\n"
+ " --bg_blue <u8> Background color (blue part)\n"
+ " --bg_reserved <u8> (do not use)\n"
+ " --frame_ms <u16> Minimum milliseconds between animation steps\n"
+ "\n"
+ " --picture Start describing the next picture\n"
+ " --pic_width <u16> Picture width in pixels\n"
+ " --pic_height <u16> Picture height in pixels\n"
+ " --pic_position <u8> Coarse picture placement:\n"
+ " 0x00 - Top left\n"
+ " 0x01 - Top\n"
+ " 0x02 - Top right\n"
+ " 0x03 - Right\n"
+ " 0x04 - Bottom right\n"
+ " 0x05 - Bottom\n"
+ " 0x06 - Bottom left\n"
+ " 0x07 - Left\n"
+ "\n"
+ " Flags:\n"
+ " 0x10 - Calculate offset from corner towards center,\n"
+ " rather than from center towards corner\n"
+ " --pic_position_offset <u16> Distance from base position in pixels\n"
+ " --pic_anim_type <u8> Animation type:\n"
+ " 0 - None\n"
+ " 1 - Forward loop\n"
+ " --pic_anim_loop <u8> Loop point for animation\n"
+ "\n"
+ " --blob <filename> Include next data stream\n"
+ " --blob_type <u16> Type of data\n"
+ " --blob_picture_id <u8> Picture to associate this blob with, starting at 0\n"
+ " (default: number of last --picture)\n"
+ "\n");
+ printf("This tool will write %s files.\n\n",
+#if __BYTE_ORDER == __BIG_ENDIAN
+ "Big Endian (BE)");
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ "Little Endian (LE)");
+#else
+#error
+#endif
+}
+
+
+struct blob_entry {
+ struct blob_entry *next;
+
+ char *fn;
+
+ struct splash_blob_header header;
+};
+
+
+static void dump_file_header(struct splash_file_header *h)
+{
+ printf(" --- File header ---\n");
+ printf("\n");
+ printf(" version: %5u\n", h->version);
+ printf("\n");
+ printf(" bg_red: %5u\n", h->bg_red);
+ printf(" bg_green: %5u\n", h->bg_green);
+ printf(" bg_blue: %5u\n", h->bg_blue);
+ printf(" bg_reserved: %5u\n", h->bg_reserved);
+ printf("\n");
+ printf(" num_blobs: %5u\n", h->num_blobs);
+ printf(" num_pics: %5u\n", h->num_pics);
+ printf("\n");
+ printf(" frame_ms: %5u\n", h->frame_ms);
+ printf("\n");
+}
+
+static void dump_pic_header(struct splash_pic_header *ph)
+{
+ printf(" --- Picture header ---\n");
+ printf("\n");
+ printf(" width: %5u\n", ph->width);
+ printf(" height: %5u\n", ph->height);
+ printf("\n");
+ printf(" num_blobs: %5u\n", ph->num_blobs);
+ printf("\n");
+ printf(" position: %0x3x\n", ph->position);
+ printf(" position_offset: %5u\n", ph->position_offset);
+ printf("\n");
+ printf(" anim_type: %5u\n", ph->anim_type);
+ printf(" anim_loop: %5u\n", ph->anim_loop);
+ printf("\n");
+}
+
+static void dump_blob(struct blob_entry *b)
+{
+ printf(" --- Blob header ---\n");
+ printf("\n");
+ printf(" length: %7u\n", b->header.length);
+ printf(" type: %7u\n", b->header.type);
+ printf("\n");
+ printf(" picture_id: %7u\n", b->header.picture_id);
+ printf("\n");
+}
+
+
+#define OPT_MAX(var, max) \
+ do { \
+ if ((var) > max) { \
+ fprintf(stderr, "--%s: Invalid value\n", \
+ long_options[option_index].name); \
+ break; \
+ } \
+ } while (0)
+
+static struct option long_options[] = {
+ {"help", 0, 0, 'h'},
+ {"bg_red", 1, 0, 10001},
+ {"bg_green", 1, 0, 10002},
+ {"bg_blue", 1, 0, 10003},
+ {"bg_reserved", 1, 0, 10004},
+ {"frame_ms", 1, 0, 10005},
+ {"picture", 0, 0, 20000},
+ {"pic_width", 1, 0, 20001},
+ {"pic_height", 1, 0, 20002},
+ {"pic_position", 1, 0, 20003},
+ {"pic_position_offset", 1, 0, 20004},
+ {"pic_anim_type", 1, 0, 20005},
+ {"pic_anim_loop", 1, 0, 20006},
+ {"blob", 1, 0, 30000},
+ {"blob_type", 1, 0, 30001},
+ {"blob_picture_id", 1, 0, 30002},
+ {NULL, 0, NULL, 0}
+};
+
+
+int main(int argc, char **argv)
+{
+ FILE *of;
+ char *ofn;
+ int c;
+ int option_index = 0;
+
+ unsigned long ul;
+ struct splash_file_header fh = {};
+ struct splash_pic_header ph[255];
+ struct blob_entry *blob_first = NULL;
+ struct blob_entry *blob_last = NULL;
+ struct blob_entry *blob_cur = NULL;
+
+ if (argc < 2) {
+ print_help(argv[0]);
+ return EXIT_FAILURE;
+ }
+
+
+ /* Parse and and execute user commands */
+ while ((c = getopt_long(argc, argv, "h",
+ long_options, &option_index)) != -1) {
+ switch (c) {
+ case 10001: /* bg_red */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ fh.bg_red = ul;
+ break;
+ case 10002: /* bg_green */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ fh.bg_green = ul;
+ break;
+ case 10003: /* bg_blue */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ fh.bg_blue = ul;
+ break;
+ case 10004: /* bg_reserved */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ fh.bg_reserved = ul;
+ break;
+ case 10005: /* frame_ms */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 65535);
+ fh.frame_ms = ul;
+ break;
+
+
+ case 20000: /* picture */
+ if (fh.num_pics >= 255) {
+ fprintf(stderr, "--%s: Picture array full\n",
+ long_options[option_index].name);
+ break;
+ }
+
+ fh.num_pics++;
+ break;
+
+ case 20001: /* pic_width */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 65535);
+ ph[fh.num_pics - 1].width = ul;
+ break;
+
+ case 20002: /* pic_height */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 65535);
+ ph[fh.num_pics - 1].height = ul;
+ break;
+
+ case 20003: /* pic_position */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ ph[fh.num_pics - 1].position = ul;
+ break;
+
+ case 20004: /* pic_position_offset */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ ph[fh.num_pics - 1].position_offset = ul;
+ break;
+
+ case 20005: /* pic_anim_type */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ ph[fh.num_pics - 1].anim_type = ul;
+ break;
+
+ case 20006: /* pic_anim_loop */
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ ph[fh.num_pics - 1].anim_loop = ul;
+ break;
+
+
+ case 30000: /* blob */
+ if (fh.num_blobs >= 65535) {
+ fprintf(stderr, "--%s: Blob array full\n",
+ long_options[option_index].name);
+ break;
+ }
+
+ blob_cur = calloc(1, sizeof(struct blob_entry));
+ if (!blob_cur) {
+ fprintf(stderr, "--%s: Out of memory\n",
+ long_options[option_index].name);
+ break;
+ }
+
+ blob_cur->fn = optarg;
+ if (fh.num_pics)
+ blob_cur->header.picture_id = fh.num_pics - 1;
+
+ if (!blob_first)
+ blob_first = blob_cur;
+ if (blob_last)
+ blob_last->next = blob_cur;
+ blob_last = blob_cur;
+ fh.num_blobs++;
+ break;
+
+ case 30001: /* blob_type */
+ if (!blob_cur) {
+ fprintf(stderr, "--%s: No blob selected\n",
+ long_options[option_index].name);
+ break;
+ }
+
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ blob_cur->header.type = ul;
+ break;
+
+ case 30002: /* blob_picture_id */
+ if (!blob_cur) {
+ fprintf(stderr, "--%s: No blob selected\n",
+ long_options[option_index].name);
+ break;
+ }
+
+ ul = strtoul(optarg, NULL, 0);
+ OPT_MAX(ul, 255);
+ blob_cur->header.picture_id = ul;
+ break;
+
+
+
+ case 'h':
+ case '?':
+ default:
+ print_help(argv[0]);
+ goto EXIT;
+ } /* switch (c) */
+ } /* while ((c = getopt_long(...)) != -1) */
+
+ /* Consume and drop lone arguments */
+ while (optind < argc) {
+ ofn = argv[optind];
+ optind++;
+ }
+
+
+ /* Read file lengths */
+ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
+ FILE *f;
+ long pos;
+ int i;
+
+ if (!blob_cur->fn)
+ continue;
+
+ f = fopen(blob_cur->fn, "rb");
+ if (!f)
+ goto ERR_FILE_LEN;
+
+ if (fseek(f, 0, SEEK_END))
+ goto ERR_FILE_LEN;
+
+ pos = ftell(f);
+ if (pos < 0 || pos > (1 << 30))
+ goto ERR_FILE_LEN;
+
+ blob_cur->header.length = pos;
+
+ fclose(f);
+ continue;
+
+ERR_FILE_LEN:
+ fprintf(stderr, "Error getting file length (or too long): %s\n",
+ blob_cur->fn);
+ if (f)
+ fclose(f);
+ continue;
+ }
+
+
+ /* Set magic headers */
+#if __BYTE_ORDER == __BIG_ENDIAN
+ memcpy(&fh.id[0], BOOTSPLASH_MAGIC_BE, 16);
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ memcpy(&fh.id[0], BOOTSPLASH_MAGIC_LE, 16);
+#else
+#error
+#endif
+ fh.version = BOOTSPLASH_VERSION;
+
+ /* Set blob counts */
+ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next) {
+ if (blob_cur->header.picture_id < fh.num_pics)
+ ph[blob_cur->header.picture_id].num_blobs++;
+ }
+
+
+ /* Dump structs */
+ dump_file_header(&fh);
+
+ for (ul = 0; ul < fh.num_pics; ul++)
+ dump_pic_header(&ph[ul]);
+
+ for (blob_cur = blob_first; blob_cur; blob_cur = blob_cur->next)
+ dump_blob(blob_cur);
+
+
+ /* Write to file */
+ printf("Writing splash to file: %s\n", ofn);
+ of = fopen(ofn, "wb");
+ if (!of)
+ goto ERR_WRITING;
+
+ if (fwrite(&fh, sizeof(struct splash_file_header), 1, of) != 1)
+ goto ERR_WRITING;
+
+ for (ul = 0; ul < fh.num_pics; ul++) {
+ if (fwrite(&ph[ul], sizeof(struct splash_pic_header), 1, of)
+ != 1)
+ goto ERR_WRITING;
+ }
+
+ blob_cur = blob_first;
+ while (blob_cur) {
+ struct blob_entry *blob_old = blob_cur;
+ FILE *f;
+ char *buf[256];
+ uint32_t left;
+
+ if (fwrite(&blob_cur->header,
+ sizeof(struct splash_blob_header), 1, of) != 1)
+ goto ERR_WRITING;
+
+ if (!blob_cur->header.length || !blob_cur->fn)
+ continue;
+
+ f = fopen(blob_cur->fn, "rb");
+ if (!f)
+ goto ERR_FILE_COPY;
+
+ left = blob_cur->header.length;
+ while (left >= sizeof(buf)) {
+ if (fread(buf, sizeof(buf), 1, f) != 1)
+ goto ERR_FILE_COPY;
+ if (fwrite(buf, sizeof(buf), 1, of) != 1)
+ goto ERR_FILE_COPY;
+ left -= sizeof(buf);
+ }
+ if (left) {
+ if (fread(buf, left, 1, f) != 1)
+ goto ERR_FILE_COPY;
+ if (fwrite(buf, left, 1, of) != 1)
+ goto ERR_FILE_COPY;
+ }
+
+ /* Pad data stream to 16 bytes */
+ if (left % 16) {
+ if (fwrite("\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
+ 16 - (left % 16), 1, of) != 1)
+ goto ERR_FILE_COPY;
+ }
+
+ fclose(f);
+ blob_cur = blob_cur->next;
+ free(blob_old);
+ continue;
+
+ERR_FILE_COPY:
+ if (f)
+ fclose(f);
+ goto ERR_WRITING;
+ }
+
+ fclose(of);
+
+EXIT:
+ return EXIT_SUCCESS;
+
+
+ERR_WRITING:
+ fprintf(stderr, "Error writing splash.\n");
+ fprintf(stderr, "The output file is probably corrupt.\n");
+ if (of)
+ fclose(of);
+
+ while (blob_cur) {
+ struct blob_entry *blob_old = blob_cur;
+
+ blob_cur = blob_cur->next;
+ free(blob_old);
+ }
+
+ return EXIT_FAILURE;
+}

View File

@ -0,0 +1,172 @@
From 2423aac2d6f5db55da99e11fd799ee66fe6f54c6 Mon Sep 17 00:00:00 2001
From: Samuel Holland <samuel@sholland.org>
Date: Mon, 9 Aug 2021 19:30:18 -0500
Subject: [PATCH] Input: kb151 - Add support for the FN layer
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
.../dts/allwinner/sun50i-a64-pinephone.dtsi | 34 +++++++++++++++++--
drivers/input/keyboard/kb151.c | 33 ++++++++++--------
2 files changed, 51 insertions(+), 16 deletions(-)
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
index 0bdc6eceec6099..68f5730cf164c7 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
@@ -557,7 +557,7 @@
reg = <0x15>;
interrupt-parent = <&r_pio>;
interrupts = <0 12 IRQ_TYPE_EDGE_FALLING>; /* PL12 */
- keypad,num-rows = <6>;
+ keypad,num-rows = <12>;
keypad,num-columns = <12>;
linux,keymap = <MATRIX_KEY(0, 0, KEY_ESC)
MATRIX_KEY(0, 1, KEY_1)
@@ -612,7 +612,37 @@
MATRIX_KEY(4, 9, KEY_LEFTBRACE)
MATRIX_KEY(5, 2, KEY_FN)
MATRIX_KEY(5, 3, KEY_LEFTALT)
- MATRIX_KEY(5, 5, KEY_RIGHTALT)>;
+ MATRIX_KEY(5, 5, KEY_RIGHTALT)
+
+ /* FN layer */
+ MATRIX_KEY(6, 1, KEY_BACKSLASH)
+ MATRIX_KEY(6, 2, KEY_BACKSLASH)
+ MATRIX_KEY(6, 3, KEY_DOLLAR)
+ MATRIX_KEY(6, 4, KEY_EURO)
+ MATRIX_KEY(6, 5, KEY_GRAVE)
+ MATRIX_KEY(6, 6, KEY_GRAVE)
+ MATRIX_KEY(6, 7, KEY_MINUS)
+ MATRIX_KEY(6, 8, KEY_EQUAL)
+ MATRIX_KEY(6, 9, KEY_MINUS)
+ MATRIX_KEY(6, 10, KEY_EQUAL)
+ MATRIX_KEY(6, 11, KEY_DELETE)
+
+ MATRIX_KEY(8, 0, KEY_SYSRQ)
+ MATRIX_KEY(8, 10, KEY_INSERT)
+
+ MATRIX_KEY(9, 0, KEY_LEFTSHIFT)
+ MATRIX_KEY(9, 8, KEY_HOME)
+ MATRIX_KEY(9, 9, KEY_UP)
+ MATRIX_KEY(9, 10, KEY_END)
+
+ MATRIX_KEY(10, 1, KEY_LEFTCTRL)
+ MATRIX_KEY(10, 6, KEY_LEFT)
+ MATRIX_KEY(10, 8, KEY_RIGHT)
+ MATRIX_KEY(10, 9, KEY_DOWN)
+
+ MATRIX_KEY(11, 2, KEY_FN)
+ MATRIX_KEY(11, 3, KEY_LEFTALT)
+ MATRIX_KEY(11, 5, KEY_RIGHTALT)>;
wakeup-source;
};
};
diff --git a/drivers/input/keyboard/kb151.c b/drivers/input/keyboard/kb151.c
index 595275d4f9d96f..bb6250efe93419 100644
--- a/drivers/input/keyboard/kb151.c
+++ b/drivers/input/keyboard/kb151.c
@@ -29,6 +29,7 @@ struct kb151 {
u8 row_shift;
u8 rows;
u8 cols;
+ u8 fn_state;
u8 buf_swap;
u8 buf[];
};
@@ -55,7 +56,7 @@ static void kb151_update(struct i2c_client *client)
return;
}
- dev_info(dev, "%02x | %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ dev_dbg(dev, "%02x | %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
new_buf[0], new_buf[1], new_buf[2], new_buf[3], new_buf[4], new_buf[5],
new_buf[6], new_buf[7], new_buf[8], new_buf[9], new_buf[10], new_buf[11],
new_buf[12]);
@@ -65,8 +66,6 @@ static void kb151_update(struct i2c_client *client)
crc, new_buf[0]);
return;
}
- dev_info(dev, "Good scan data (%02x == %02x)\n",
- crc, new_buf[0]);
for (col = 0; col < kb151->cols; ++col) {
u8 old = *(++old_buf);
@@ -74,14 +73,20 @@ static void kb151_update(struct i2c_client *client)
u8 changed = old ^ new;
for (row = 0; row < kb151->rows; ++row) {
- int code = MATRIX_SCAN_CODE(row, col, kb151->row_shift);
u8 pressed = new & BIT(row);
+ u8 map_row = row + (kb151->fn_state ? kb151->rows : 0);
+ int code = MATRIX_SCAN_CODE(map_row, col, kb151->row_shift);
if (!(changed & BIT(row)))
continue;
dev_dbg(&client->dev, "row %u col %u %sed\n",
- row, col, pressed ? "press" : "releas");
+ map_row, col, pressed ? "press" : "releas");
+ if (keymap[code] == KEY_FN) {
+ dev_dbg(&client->dev, "FN is now %s\n",
+ pressed ? "pressed" : "released");
+ kb151->fn_state = pressed;
+ } else
input_report_key(kb151->input, keymap[code], pressed);
}
}
@@ -151,7 +156,7 @@ static int kb151_probe(struct i2c_client *client)
struct device *dev = &client->dev;
u8 info[KB151_MATRIX_SIZE + 1];
unsigned int kb_rows, kb_cols;
- unsigned int rows, cols;
+ unsigned int map_rows, map_cols;
struct kb151 *kb151;
int ret;
@@ -168,20 +173,20 @@ static int kb151_probe(struct i2c_client *client)
info[KB151_FW_REVISION] & 0xf,
info[KB151_FW_FEATURES]);
- ret = matrix_keypad_parse_properties(dev, &rows, &cols);
+ ret = matrix_keypad_parse_properties(dev, &map_rows, &map_cols);
if (ret)
return ret;
kb_rows = info[KB151_MATRIX_SIZE] & 0xf;
kb_cols = info[KB151_MATRIX_SIZE] >> 4;
- if (rows > kb_rows || cols != kb_cols) {
+ if (map_rows != 2 * kb_rows || map_cols != kb_cols) {
dev_err(dev, "Keyboard matrix is %ux%u, but key map is %ux%u\n",
- kb_rows, kb_cols, rows, cols);
+ kb_rows, kb_cols, map_rows, map_cols);
return -EINVAL;
}
/* Allocate two buffers, and include space for the CRC. */
- kb151 = devm_kzalloc(dev, struct_size(kb151, buf, 2 * (cols + 1)), GFP_KERNEL);
+ kb151 = devm_kzalloc(dev, struct_size(kb151, buf, 2 * (kb_cols + 1)), GFP_KERNEL);
if (!kb151)
return -ENOMEM;
@@ -189,9 +194,9 @@ static int kb151_probe(struct i2c_client *client)
crc8_populate_msb(kb151->crc_table, KB151_CRC8_POLYNOMIAL);
- kb151->row_shift = get_count_order(cols);
- kb151->rows = rows;
- kb151->cols = cols;
+ kb151->row_shift = get_count_order(kb_cols);
+ kb151->rows = kb_rows;
+ kb151->cols = kb_cols;
kb151->input = devm_input_allocate_device(dev);
if (!kb151->input)
@@ -207,7 +212,7 @@ static int kb151_probe(struct i2c_client *client)
__set_bit(EV_REP, kb151->input->evbit);
- ret = matrix_keypad_build_keymap(NULL, NULL, rows, cols,
+ ret = matrix_keypad_build_keymap(NULL, NULL, map_rows, map_cols,
NULL, kb151->input);
if (ret)
return dev_err_probe(dev, ret, "Failed to build keymap\n");

View File

@ -0,0 +1,12 @@
[Trigger]
Type = File
Operation = Install
Operation = Upgrade
Operation = Remove
Target = usr/lib/modules/%KERNVER%/*
Target = usr/lib/modules/%EXTRAMODULES%/*
[Action]
Description = Updating %PKGBASE% module dependencies...
When = PostTransaction
Exec = /usr/bin/depmod %KERNVER%

View File

@ -0,0 +1,11 @@
[Trigger]
Type = File
Operation = Install
Operation = Upgrade
Target = boot/Image
Target = usr/lib/initcpio/*
[Action]
Description = Updating %PKGBASE% initcpios...
When = PostTransaction
Exec = /usr/bin/mkinitcpio -p %PKGBASE%

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,377 @@
From d1d849cae12db71aa81ceedaedc1b17a34790367 Mon Sep 17 00:00:00 2001
From: Samuel Holland <samuel@sholland.org>
Date: Sat, 19 Jun 2021 18:36:05 -0500
Subject: [PATCH] Input: kb151 - Add a driver for the KB151 keyboard
This keyboard is found in the official Pine64 PinePhone keyboard case.
It is connected over I2C and runs a libre firmware.
Signed-off-by: Samuel Holland <samuel@sholland.org>
---
.../dts/allwinner/sun50i-a64-pinephone.dtsi | 64 +++++
drivers/input/keyboard/Kconfig | 10 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/kb151.c | 246 ++++++++++++++++++
4 files changed, 321 insertions(+)
create mode 100644 drivers/input/keyboard/kb151.c
diff --git a/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi b/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
index 4ede9fe66020c9..0bdc6eceec6099 100644
--- a/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
+++ b/arch/arm64/boot/dts/allwinner/sun50i-a64-pinephone.dtsi
@@ -551,6 +551,70 @@
/* Connected to pogo pins (external spring based pinheader for user addons) */
&i2c2 {
status = "okay";
+
+ keyboard@15 {
+ compatible = "pine64,kb151";
+ reg = <0x15>;
+ interrupt-parent = <&r_pio>;
+ interrupts = <0 12 IRQ_TYPE_EDGE_FALLING>; /* PL12 */
+ keypad,num-rows = <6>;
+ keypad,num-columns = <12>;
+ linux,keymap = <MATRIX_KEY(0, 0, KEY_ESC)
+ MATRIX_KEY(0, 1, KEY_1)
+ MATRIX_KEY(0, 2, KEY_2)
+ MATRIX_KEY(0, 3, KEY_3)
+ MATRIX_KEY(0, 4, KEY_4)
+ MATRIX_KEY(0, 5, KEY_5)
+ MATRIX_KEY(0, 6, KEY_6)
+ MATRIX_KEY(0, 7, KEY_7)
+ MATRIX_KEY(0, 8, KEY_8)
+ MATRIX_KEY(0, 9, KEY_9)
+ MATRIX_KEY(0, 10, KEY_0)
+ MATRIX_KEY(0, 11, KEY_BACKSPACE)
+ MATRIX_KEY(1, 0, KEY_TAB)
+ MATRIX_KEY(1, 1, KEY_Q)
+ MATRIX_KEY(1, 2, KEY_W)
+ MATRIX_KEY(1, 3, KEY_E)
+ MATRIX_KEY(1, 4, KEY_R)
+ MATRIX_KEY(1, 5, KEY_T)
+ MATRIX_KEY(1, 6, KEY_Y)
+ MATRIX_KEY(1, 7, KEY_U)
+ MATRIX_KEY(1, 8, KEY_I)
+ MATRIX_KEY(1, 9, KEY_O)
+ MATRIX_KEY(1, 10, KEY_P)
+ MATRIX_KEY(1, 11, KEY_ENTER)
+ MATRIX_KEY(2, 0, KEY_LEFTMETA)
+ MATRIX_KEY(2, 1, KEY_A)
+ MATRIX_KEY(2, 2, KEY_S)
+ MATRIX_KEY(2, 3, KEY_D)
+ MATRIX_KEY(2, 4, KEY_F)
+ MATRIX_KEY(2, 5, KEY_G)
+ MATRIX_KEY(2, 6, KEY_H)
+ MATRIX_KEY(2, 7, KEY_J)
+ MATRIX_KEY(2, 8, KEY_K)
+ MATRIX_KEY(2, 9, KEY_L)
+ MATRIX_KEY(2, 10, KEY_SEMICOLON)
+ MATRIX_KEY(3, 0, KEY_LEFTSHIFT)
+ MATRIX_KEY(3, 1, KEY_Z)
+ MATRIX_KEY(3, 2, KEY_X)
+ MATRIX_KEY(3, 3, KEY_C)
+ MATRIX_KEY(3, 4, KEY_V)
+ MATRIX_KEY(3, 5, KEY_B)
+ MATRIX_KEY(3, 6, KEY_N)
+ MATRIX_KEY(3, 7, KEY_M)
+ MATRIX_KEY(3, 8, KEY_COMMA)
+ MATRIX_KEY(3, 9, KEY_DOT)
+ MATRIX_KEY(3, 10, KEY_SLASH)
+ MATRIX_KEY(4, 1, KEY_LEFTCTRL)
+ MATRIX_KEY(4, 4, KEY_SPACE)
+ MATRIX_KEY(4, 6, KEY_APOSTROPHE)
+ MATRIX_KEY(4, 8, KEY_RIGHTBRACE)
+ MATRIX_KEY(4, 9, KEY_LEFTBRACE)
+ MATRIX_KEY(5, 2, KEY_FN)
+ MATRIX_KEY(5, 3, KEY_LEFTALT)
+ MATRIX_KEY(5, 5, KEY_RIGHTALT)>;
+ wakeup-source;
+ };
};
&i2s2 {
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 40a070a2e7f5b7..0259e9133f4692 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -353,6 +353,16 @@ config KEYBOARD_HP7XX
To compile this driver as a module, choose M here: the
module will be called jornada720_kbd.
+config KEYBOARD_KB151
+ tristate "Pine64 KB151 Keyboard"
+ depends on I2C
+ select CRC8
+ select INPUT_MATRIXKMAP
+ help
+ Say Y here to enable support for the KB151 keyboard used in the
+ Pine64 PinePhone keyboard case. This driver supports the FLOSS
+ firmware available at https://megous.com/git/pinephone-keyboard/
+
config KEYBOARD_LM8323
tristate "LM8323 keypad chip"
depends on I2C
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 1d689fdd5c00f9..87fda7b961913a 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_KEYBOARD_IMX) += imx_keypad.o
obj-$(CONFIG_KEYBOARD_IMX_SC_KEY) += imx_sc_key.o
obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o
obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o
+obj-$(CONFIG_KEYBOARD_KB151) += kb151.o
obj-$(CONFIG_KEYBOARD_LKKBD) += lkkbd.o
obj-$(CONFIG_KEYBOARD_LM8323) += lm8323.o
obj-$(CONFIG_KEYBOARD_LM8333) += lm8333.o
diff --git a/drivers/input/keyboard/kb151.c b/drivers/input/keyboard/kb151.c
new file mode 100644
index 00000000000000..595275d4f9d96f
--- /dev/null
+++ b/drivers/input/keyboard/kb151.c
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-2.0-only
+//
+// Copyright (C) 2021 Samuel Holland <samuel@sholland.org>
+
+#include <linux/crc8.h>
+#include <linux/i2c.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm_wakeirq.h>
+
+#define KB151_CRC8_POLYNOMIAL 0x07
+
+#define KB151_DEVICE_ID_HI 0x00
+#define KB151_DEVICE_ID_HI_VALUE 0x4b
+#define KB151_DEVICE_ID_LO 0x01
+#define KB151_DEVICE_ID_LO_VALUE 0x42
+#define KB151_FW_REVISION 0x02
+#define KB151_FW_FEATURES 0x03
+#define KB151_MATRIX_SIZE 0x06
+#define KB151_SCAN_CRC 0x07
+#define KB151_SCAN_DATA 0x08
+#define KB151_SYS_CONFIG 0x20
+#define KB151_SYS_CONFIG_DISABLE_SCAN BIT(0)
+
+struct kb151 {
+ struct input_dev *input;
+ u8 crc_table[CRC8_TABLE_SIZE];
+ u8 row_shift;
+ u8 rows;
+ u8 cols;
+ u8 buf_swap;
+ u8 buf[];
+};
+
+static void kb151_update(struct i2c_client *client)
+{
+ struct kb151 *kb151 = i2c_get_clientdata(client);
+ unsigned short *keymap = kb151->input->keycode;
+ struct device *dev = &client->dev;
+ size_t buf_len = kb151->cols + 1;
+ u8 *old_buf = kb151->buf;
+ u8 *new_buf = kb151->buf;
+ int col, crc, ret, row;
+
+ if (kb151->buf_swap)
+ old_buf += buf_len;
+ else
+ new_buf += buf_len;
+
+ ret = i2c_smbus_read_i2c_block_data(client, KB151_SCAN_CRC,
+ buf_len, new_buf);
+ if (ret != buf_len) {
+ dev_err(dev, "Failed to read scan data: %d\n", ret);
+ return;
+ }
+
+ dev_info(dev, "%02x | %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
+ new_buf[0], new_buf[1], new_buf[2], new_buf[3], new_buf[4], new_buf[5],
+ new_buf[6], new_buf[7], new_buf[8], new_buf[9], new_buf[10], new_buf[11],
+ new_buf[12]);
+ crc = crc8(kb151->crc_table, new_buf + 1, kb151->cols, CRC8_INIT_VALUE);
+ if (crc != new_buf[0]) {
+ dev_err(dev, "Bad scan data (%02x != %02x)\n",
+ crc, new_buf[0]);
+ return;
+ }
+ dev_info(dev, "Good scan data (%02x == %02x)\n",
+ crc, new_buf[0]);
+
+ for (col = 0; col < kb151->cols; ++col) {
+ u8 old = *(++old_buf);
+ u8 new = *(++new_buf);
+ u8 changed = old ^ new;
+
+ for (row = 0; row < kb151->rows; ++row) {
+ int code = MATRIX_SCAN_CODE(row, col, kb151->row_shift);
+ u8 pressed = new & BIT(row);
+
+ if (!(changed & BIT(row)))
+ continue;
+
+ dev_dbg(&client->dev, "row %u col %u %sed\n",
+ row, col, pressed ? "press" : "releas");
+ input_report_key(kb151->input, keymap[code], pressed);
+ }
+ }
+ input_sync(kb151->input);
+
+ kb151->buf_swap = !kb151->buf_swap;
+}
+
+static int kb151_open(struct input_dev *input)
+{
+ struct i2c_client *client = input_get_drvdata(input);
+ struct device *dev = &client->dev;
+ int ret, val;
+
+ ret = i2c_smbus_read_byte_data(client, KB151_SYS_CONFIG);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read config: %d\n", ret);
+ return ret;
+ }
+
+ val = ret & ~KB151_SYS_CONFIG_DISABLE_SCAN;
+ ret = i2c_smbus_write_byte_data(client, KB151_SYS_CONFIG, val);
+ if (ret) {
+ dev_err(dev, "Failed to write config: %d\n", ret);
+ return ret;
+ }
+
+ kb151_update(client);
+
+ enable_irq(client->irq);
+
+ return 0;
+}
+
+static void kb151_close(struct input_dev *input)
+{
+ struct i2c_client *client = input_get_drvdata(input);
+ struct device *dev = &client->dev;
+ int ret, val;
+
+ disable_irq(client->irq);
+
+ ret = i2c_smbus_read_byte_data(client, KB151_SYS_CONFIG);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read config: %d\n", ret);
+ return;
+ }
+
+ val = ret | KB151_SYS_CONFIG_DISABLE_SCAN;
+ ret = i2c_smbus_write_byte_data(client, KB151_SYS_CONFIG, val);
+ if (ret) {
+ dev_err(dev, "Failed to write config: %d\n", ret);
+ }
+}
+
+static irqreturn_t kb151_irq_thread(int irq, void *data)
+{
+ struct i2c_client *client = data;
+
+ kb151_update(client);
+
+ return IRQ_HANDLED;
+}
+
+static int kb151_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ u8 info[KB151_MATRIX_SIZE + 1];
+ unsigned int kb_rows, kb_cols;
+ unsigned int rows, cols;
+ struct kb151 *kb151;
+ int ret;
+
+ ret = i2c_smbus_read_i2c_block_data(client, 0, sizeof(info), info);
+ if (ret != sizeof(info))
+ return ret;
+
+ if (info[KB151_DEVICE_ID_HI] != KB151_DEVICE_ID_HI_VALUE ||
+ info[KB151_DEVICE_ID_LO] != KB151_DEVICE_ID_LO_VALUE)
+ return -ENODEV;
+
+ dev_info(dev, "Found KB151 with firmware %d.%d (features=%#x)\n",
+ info[KB151_FW_REVISION] >> 4,
+ info[KB151_FW_REVISION] & 0xf,
+ info[KB151_FW_FEATURES]);
+
+ ret = matrix_keypad_parse_properties(dev, &rows, &cols);
+ if (ret)
+ return ret;
+
+ kb_rows = info[KB151_MATRIX_SIZE] & 0xf;
+ kb_cols = info[KB151_MATRIX_SIZE] >> 4;
+ if (rows > kb_rows || cols != kb_cols) {
+ dev_err(dev, "Keyboard matrix is %ux%u, but key map is %ux%u\n",
+ kb_rows, kb_cols, rows, cols);
+ return -EINVAL;
+ }
+
+ /* Allocate two buffers, and include space for the CRC. */
+ kb151 = devm_kzalloc(dev, struct_size(kb151, buf, 2 * (cols + 1)), GFP_KERNEL);
+ if (!kb151)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, kb151);
+
+ crc8_populate_msb(kb151->crc_table, KB151_CRC8_POLYNOMIAL);
+
+ kb151->row_shift = get_count_order(cols);
+ kb151->rows = rows;
+ kb151->cols = cols;
+
+ kb151->input = devm_input_allocate_device(dev);
+ if (!kb151->input)
+ return -ENOMEM;
+
+ input_set_drvdata(kb151->input, client);
+
+ kb151->input->name = client->name;
+ kb151->input->phys = "kb151/input0";
+ kb151->input->id.bustype = BUS_I2C;
+ kb151->input->open = kb151_open;
+ kb151->input->close = kb151_close;
+
+ __set_bit(EV_REP, kb151->input->evbit);
+
+ ret = matrix_keypad_build_keymap(NULL, NULL, rows, cols,
+ NULL, kb151->input);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to build keymap\n");
+
+ ret = devm_request_threaded_irq(dev, client->irq,
+ NULL, kb151_irq_thread,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ client->name, client);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request IRQ\n");
+
+ ret = input_register_device(kb151->input);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to register input\n");
+
+ return 0;
+}
+
+static const struct of_device_id kb151_of_match[] = {
+ { .compatible = "pine64,kb151" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, kb151_of_match);
+
+static struct i2c_driver kb151_driver = {
+ .probe_new = kb151_probe,
+ .driver = {
+ .name = "kb151",
+ .of_match_table = kb151_of_match,
+ },
+};
+module_i2c_driver(kb151_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Pine64 KB151 keyboard driver");
+MODULE_LICENSE("GPL");

View File

@ -0,0 +1,10 @@
# mkinitcpio preset file for the '%PKGBASE%' package
ALL_config="/etc/mkinitcpio.conf"
ALL_kver="%KERNVER%"
PRESETS=('default')
#default_config="/etc/mkinitcpio.conf"
default_image="/boot/initramfs-linux.img"
#default_options=""

View File

@ -0,0 +1,143 @@
commit 4f4db49e9fc5a3c7b7db1b87f9c815e376a05314
Author: Zhaofeng Li <hello@zhaofeng.li>
Date: Wed Jan 12 21:52:18 2022 -0800
arm64: dts: rk3399-pinephone-pro: Add keyboard accessory
The pogo pins are connected to i2c5.
diff --git a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
index e6c738e02..873d70bc6 100644
--- a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
+++ b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
@@ -6,6 +6,7 @@
/dts-v1/;
#include <dt-bindings/input/gpio-keys.h>
+#include <dt-bindings/input/input.h>
#include <dt-bindings/input/linux-event-codes.h>
#include <dt-bindings/usb/pd.h>
#include <dt-bindings/leds/common.h>
@@ -981,6 +982,109 @@ ak09911: compass@c {
};
};
+&i2c5 {
+ clock-frequency = <400000>;
+ i2c-scl-rising-time-ns = <450>;
+ i2c-scl-falling-time-ns = <15>;
+ status = "okay";
+
+ keyboard@15 {
+ compatible = "pine64,kb151";
+ reg = <0x15>;
+ interrupt-parent = <&gpio3>;
+ interrupts = <RK_PA0 IRQ_TYPE_EDGE_FALLING>; /* FIXME */
+ pinctrl-names = "default";
+ pinctrl-0 = <&pogo_int>;
+ keypad,num-rows = <12>;
+ keypad,num-columns = <12>;
+ linux,keymap = <MATRIX_KEY(0, 0, KEY_ESC)
+ MATRIX_KEY(0, 1, KEY_1)
+ MATRIX_KEY(0, 2, KEY_2)
+ MATRIX_KEY(0, 3, KEY_3)
+ MATRIX_KEY(0, 4, KEY_4)
+ MATRIX_KEY(0, 5, KEY_5)
+ MATRIX_KEY(0, 6, KEY_6)
+ MATRIX_KEY(0, 7, KEY_7)
+ MATRIX_KEY(0, 8, KEY_8)
+ MATRIX_KEY(0, 9, KEY_9)
+ MATRIX_KEY(0, 10, KEY_0)
+ MATRIX_KEY(0, 11, KEY_BACKSPACE)
+ MATRIX_KEY(1, 0, KEY_TAB)
+ MATRIX_KEY(1, 1, KEY_Q)
+ MATRIX_KEY(1, 2, KEY_W)
+ MATRIX_KEY(1, 3, KEY_E)
+ MATRIX_KEY(1, 4, KEY_R)
+ MATRIX_KEY(1, 5, KEY_T)
+ MATRIX_KEY(1, 6, KEY_Y)
+ MATRIX_KEY(1, 7, KEY_U)
+ MATRIX_KEY(1, 8, KEY_I)
+ MATRIX_KEY(1, 9, KEY_O)
+ MATRIX_KEY(1, 10, KEY_P)
+ MATRIX_KEY(1, 11, KEY_ENTER)
+ MATRIX_KEY(2, 0, KEY_LEFTMETA)
+ MATRIX_KEY(2, 1, KEY_A)
+ MATRIX_KEY(2, 2, KEY_S)
+ MATRIX_KEY(2, 3, KEY_D)
+ MATRIX_KEY(2, 4, KEY_F)
+ MATRIX_KEY(2, 5, KEY_G)
+ MATRIX_KEY(2, 6, KEY_H)
+ MATRIX_KEY(2, 7, KEY_J)
+ MATRIX_KEY(2, 8, KEY_K)
+ MATRIX_KEY(2, 9, KEY_L)
+ MATRIX_KEY(2, 10, KEY_SEMICOLON)
+ MATRIX_KEY(3, 0, KEY_LEFTSHIFT)
+ MATRIX_KEY(3, 1, KEY_Z)
+ MATRIX_KEY(3, 2, KEY_X)
+ MATRIX_KEY(3, 3, KEY_C)
+ MATRIX_KEY(3, 4, KEY_V)
+ MATRIX_KEY(3, 5, KEY_B)
+ MATRIX_KEY(3, 6, KEY_N)
+ MATRIX_KEY(3, 7, KEY_M)
+ MATRIX_KEY(3, 8, KEY_COMMA)
+ MATRIX_KEY(3, 9, KEY_DOT)
+ MATRIX_KEY(3, 10, KEY_SLASH)
+ MATRIX_KEY(4, 1, KEY_LEFTCTRL)
+ MATRIX_KEY(4, 4, KEY_SPACE)
+ MATRIX_KEY(4, 6, KEY_APOSTROPHE)
+ MATRIX_KEY(4, 8, KEY_RIGHTBRACE)
+ MATRIX_KEY(4, 9, KEY_LEFTBRACE)
+ MATRIX_KEY(5, 2, KEY_FN)
+ MATRIX_KEY(5, 3, KEY_LEFTALT)
+ MATRIX_KEY(5, 5, KEY_RIGHTALT)
+
+ /* FN layer */
+ MATRIX_KEY(6, 1, KEY_BACKSLASH)
+ MATRIX_KEY(6, 2, KEY_BACKSLASH)
+ MATRIX_KEY(6, 3, KEY_DOLLAR)
+ MATRIX_KEY(6, 4, KEY_EURO)
+ MATRIX_KEY(6, 5, KEY_GRAVE)
+ MATRIX_KEY(6, 6, KEY_GRAVE)
+ MATRIX_KEY(6, 7, KEY_MINUS)
+ MATRIX_KEY(6, 8, KEY_EQUAL)
+ MATRIX_KEY(6, 9, KEY_MINUS)
+ MATRIX_KEY(6, 10, KEY_EQUAL)
+ MATRIX_KEY(6, 11, KEY_DELETE)
+
+ MATRIX_KEY(8, 0, KEY_SYSRQ)
+ MATRIX_KEY(8, 10, KEY_INSERT)
+
+ MATRIX_KEY(9, 0, KEY_LEFTSHIFT)
+ MATRIX_KEY(9, 8, KEY_HOME)
+ MATRIX_KEY(9, 9, KEY_UP)
+ MATRIX_KEY(9, 10, KEY_END)
+
+ MATRIX_KEY(10, 1, KEY_LEFTCTRL)
+ MATRIX_KEY(10, 6, KEY_LEFT)
+ MATRIX_KEY(10, 8, KEY_RIGHT)
+ MATRIX_KEY(10, 9, KEY_DOWN)
+
+ MATRIX_KEY(11, 2, KEY_FN)
+ MATRIX_KEY(11, 3, KEY_LEFTALT)
+ MATRIX_KEY(11, 5, KEY_RIGHTALT)>;
+ wakeup-source;
+ };
+};
+
&i2s0 {
rockchip,playback-channels = <2>;
rockchip,capture-channels = <2>;
@@ -1193,6 +1297,12 @@ spk_en: spk-en {
rockchip,pins = <0 RK_PB3 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
+
+ keyboard {
+ pogo_int: pogo-int {
+ rockchip,pins = <3 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
+ };
+ };
};
&pwm0 {

View File

@ -0,0 +1,54 @@
--- b/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
+++ a/arch/arm64/boot/dts/rockchip/rk3399-pinephone-pro.dts
@@ -260,6 +260,7 @@
pinctrl-names = "default";
pinctrl-0 = <&vcc_4g_5v_en>;
regulator-name = "vcc_4g_5v";
+ regulator-always-on;
regulator-min-microvolt = <5000000>;
regulator-max-microvolt = <5000000>;
vin-supply = <&vcc5v0_sys>;
@@ -272,6 +273,7 @@
pinctrl-names = "default";
pinctrl-0 = <&vcc_4g_en>;
regulator-name = "vcc_4g";
+ regulator-always-on;
regulator-min-microvolt = <3800000>;
regulator-max-microvolt = <3800000>;
vin-supply = <&vcc_sysin>;
@@ -959,35 +961,6 @@
};
};
-&uart3 {
- status = "okay";
-
- modem {
- compatible = "quectel,eg25";
- char-device-name = "modem-power";
-
- pinctrl-names = "default";
- pinctrl-0 = <&modem_control_pins>;
-
- power-supply = <&vcc_4g>;
- vbus-supply = <&vcc_4g_5v>;
-
- enable-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>; // W_DISABLE#
- reset-gpios = <&gpio3 RK_PB0 GPIO_ACTIVE_HIGH>;
- status-gpios = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>;
- pwrkey-gpios = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;
-
- host-ready-gpios = <&gpio0 RK_PB4 GPIO_ACTIVE_HIGH>; // apready
- wakeup-gpios = <&gpio0 RK_PA1 GPIO_ACTIVE_HIGH>; // ri
-
- dtr-gpios = <&gpio0 RK_PA3 GPIO_ACTIVE_HIGH>;
- cts-gpios = <&gpio3 RK_PC0 GPIO_ACTIVE_HIGH>;
- rts-gpios = <&gpio3 RK_PC1 GPIO_ACTIVE_HIGH>;
-
- quectel,qdai = "3,0,0,4,0,0,1,1";
- };
-};
-
&pmu_io_domains {
pmu1830-supply = <&vcc_1v8>;
status = "okay";

View File

@ -0,0 +1,67 @@
# Copyright 1999-2022 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2
EAPI="8"
K_NOUSENAME="yes"
K_NOSETEXTRAVERSION="yes"
K_SECURITY_UNSUPPORTED="1"
ETYPE="sources"
inherit kernel-2
detect_version
KEYWORDS="~arm64"
DEPEND="${RDEPEND}
>=sys-devel/patch-2.7.5"
DESCRIPTION="Full sources for the Linux kernel, with megi's patch for Pinephone Pro"
MEGI_PATCH_COMMIT="382509700d5a3ea447b44f002e9d928fd0a14ad5"
SRC_URI="https://github.com/megous/linux/archive/${MEGI_PATCH_COMMIT}.tar.gz -> ${P}.tar.gz"
PATCHES=(
${FILESDIR}/0001-bootsplash.patch
${FILESDIR}/0001-revert-garbage-collect-fbdev-scrolling-acceleration.patch
${FILESDIR}/0002-bootsplash.patch
${FILESDIR}/0002-revert-fbcon-remove-now-unusued-softback_lines-cursor-argument.patch
${FILESDIR}/0003-bootsplash.patch
${FILESDIR}/0003-revert-fbcon-remove-no-op-fbcon_set_origin.patch
${FILESDIR}/0004-bootsplash.patch
${FILESDIR}/0004-revert-fbcon-remove-soft-scrollback-code.patch
${FILESDIR}/0005-bootsplash.patch
${FILESDIR}/0006-bootsplash.patch
${FILESDIR}/0007-bootsplash.patch
${FILESDIR}/0008-bootsplash.patch
${FILESDIR}/0009-bootsplash.patch
${FILESDIR}/0010-bootsplash.patch
${FILESDIR}/0011-bootsplash.patch
${FILESDIR}/0012-bootsplash.patch
${FILESDIR}/ppp-keyboard.patch
${FILESDIR}/2423aac2d6f5db55da99e11fd799ee66fe6f54c6.patch
${FILESDIR}/d1d849cae12db71aa81ceedaedc1b17a34790367.patch
)
src_prepare() {
default
eapply_user
}
pkg_postinst() {
kernel-2_pkg_postinst
einfo "For more info on this patchset, and how to report problems, see:"
einfo "${HOMEPAGE}"
einfo "To build the kernel use the following command:"
einfo "make Image Image.gz modules"
einfo "make DTC_FLAGS="-@" dtbs"
einfo "make install; make modules_intall; make dtbs_install"
einfo "If you use kernel config coming with this ebuild, don't forget to also copy dracut-pp.conf to /etc/dracut.conf.d/"
einfo "to make sure proper kernel modules are loaded into initramfs"
einfo "if you want to cross compile pinephone kernel on amd64 host, follow the https://wiki.gentoo.org/wiki/Cross_build_environment"
einfo "to setup cross toolchain environment, then create a xmake wrapper like the following, and replace make with xmake in above commands"
einfo "#!/bin/sh"
einfo "exec make ARCH='arm64' CROSS_COMPILE='aarch64-unknown-linux-gnu-' INSTALL_MOD_PATH='${SYSROOT}' '$@'"
}
pkg_postrm() {
kernel-2_pkg_postrm
}