From: Max Pedraza <maximpedraza@gmail.com>
To: Helge Deller <deller@gmx.de>,
Thomas Zimmermann <tzimmermann@suse.de>,
Simona Vetter <simona@ffwll.ch>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Maxime Ripard <mripard@kernel.org>
Cc: linux-fbdev@vger.kernel.org, devicetree@vger.kernel.org,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
Max Pedraza <maximpedraza@gmail.com>
Subject: [PATCH v3 3/7] video: logo: allow the boot logo to come from the device tree
Date: Wed, 23 Sep 2026 22:10:31 +0200 [thread overview]
Message-ID: <20260923201035.51007-4-maximpedraza@gmail.com> (raw)
In-Reply-To: <20260923201035.51007-1-maximpedraza@gmail.com>
Add CONFIG_LOGO_DT_CLUT224, which makes fb_find_logo() look for a node
compatible with "boot-logo-clut224" under /chosen before falling
back to the logos built into the kernel image.
The image is validated before it is used: the palette must have at most
224 entries, the pixel data length must match the geometry, and every
pixel must reference an entry that exists. A malformed node is reported
and ignored rather than drawn, so a bad device tree cannot take the
display down with it.
The image is copied out of the device tree so that the 32 entry offset
the frame buffer layer reserves for the console can be applied to the
pixels. The pixels are allocated with kvmalloc(), since a full screen
image is larger than kmalloc() will comfortably serve.
Unlike the built-in logos the copy is never freed. Those are initdata and
go away in free_initmem(), which runs after async_synchronize_full();
anything this code could hook into runs before that, so releasing the image
here would pull it out from under a display driver whose probe is still in
flight. It is a modest allocation and it lives as long as the device tree
it came from, which also means fb_find_logo() can still hand it out after
the built-in logos are gone.
The node lives under /chosen because a logo is configuration handed over
by firmware rather than a description of the hardware, which is also
where simple-framebuffer nodes live for the same reason.
The lookup is guarded with IS_ENABLED() rather than wrapped in an #ifdef,
so that the code is compile checked whatever the configuration and the
compiler drops it when the option is off. Built for x86_64 with CONFIG_OF=n
the object is left with no unresolved of_* symbol and none of the data.
Signed-off-by: Max Pedraza <maximpedraza@gmail.com>
---
drivers/video/logo/Kconfig | 12 +++
drivers/video/logo/logo.c | 154 ++++++++++++++++++++++++++++++++++++-
include/linux/linux_logo.h | 3 +
3 files changed, 168 insertions(+), 1 deletion(-)
diff --git a/drivers/video/logo/Kconfig b/drivers/video/logo/Kconfig
index cda15b9589..cce89948f1 100644
--- a/drivers/video/logo/Kconfig
+++ b/drivers/video/logo/Kconfig
@@ -76,4 +76,16 @@ config LOGO_LINUX_CLUT224_FILE
magick source_image -compress none -colors 224 destination.ppm
+config LOGO_DT_CLUT224
+ bool "224-color logo supplied by the device tree"
+ depends on OF
+ help
+ Look for a boot logo in the device tree, in a node compatible with
+ "boot-logo-clut224" under /chosen, instead of using one of
+ the logos built into the kernel image. This allows a single kernel
+ image to be used by several products that only differ in branding.
+
+ If no such node is present, or it is disabled, the built-in logo
+ selected above is used, so saying Y here is safe.
+
endif # LOGO
diff --git a/drivers/video/logo/logo.c b/drivers/video/logo/logo.c
index 91535f8848..84afd5b337 100644
--- a/drivers/video/logo/logo.c
+++ b/drivers/video/logo/logo.c
@@ -11,6 +11,9 @@
*/
#include <linux/linux_logo.h>
+#include <linux/of.h>
+#include <linux/sizes.h>
+#include <linux/slab.h>
#include <linux/stddef.h>
#include <linux/module.h>
@@ -22,6 +25,139 @@ static bool nologo;
module_param(nologo, bool, 0);
MODULE_PARM_DESC(nologo, "Disables startup logo");
+/* Boot logo supplied by the device tree */
+
+#define LOGO_DT_MAX_CLUT 224
+/*
+ * The first 32 palette entries are reserved for the console, so the logo
+ * colours start at index 32. That is an implementation detail of the frame
+ * buffer layer rather than a property of the image, so the device tree stores
+ * plain indices and the offset is applied here.
+ */
+#define LOGO_DT_CLUT_OFFSET 32
+/*
+ * Sanity limit on the image size, a device tree is not a good place for more:
+ * a 4K screen is 8.3M pixels, and the copy is kept for the life of the kernel.
+ * One byte per pixel, so this is a byte count as well.
+ */
+#define LOGO_DT_MAX_PIXELS SZ_16M
+
+static struct linux_logo logo_dt_clut224 = {
+ .type = LINUX_LOGO_CLUT224,
+};
+
+static unsigned char *logo_dt_clut;
+static unsigned char *logo_dt_data;
+
+static int logo_dt_parse(struct device_node *np)
+{
+ unsigned int clutsize, npixels, i;
+ unsigned char *clut, *data;
+ u32 width, height;
+ int len, ret;
+
+ ret = of_property_read_u32(np, "width", &width);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u32(np, "height", &height);
+ if (ret)
+ return ret;
+
+ if (!width || !height || (u64)width * height > LOGO_DT_MAX_PIXELS)
+ return -EINVAL;
+
+ npixels = width * height;
+
+ len = of_property_count_u8_elems(np, "clut");
+ if (len < 3 || len % 3)
+ return -EINVAL;
+
+ clutsize = len / 3;
+ if (clutsize > LOGO_DT_MAX_CLUT)
+ return -EINVAL;
+
+ ret = of_property_count_u8_elems(np, "data");
+ if (ret < 0)
+ return ret;
+ if ((unsigned int)ret != npixels)
+ return -EINVAL;
+
+ clut = kmalloc(len, GFP_KERNEL);
+ if (!clut)
+ return -ENOMEM;
+
+ /* The palette is at most 672 bytes, the pixels can be megabytes */
+ data = kvmalloc(npixels, GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_free_clut;
+ }
+
+ ret = of_property_read_u8_array(np, "clut", clut, len);
+ if (ret)
+ goto err_free_data;
+
+ ret = of_property_read_u8_array(np, "data", data, npixels);
+ if (ret)
+ goto err_free_data;
+
+ for (i = 0; i < npixels; i++) {
+ if (data[i] >= clutsize) {
+ ret = -ERANGE;
+ goto err_free_data;
+ }
+ data[i] += LOGO_DT_CLUT_OFFSET;
+ }
+
+ logo_dt_clut = clut;
+ logo_dt_data = data;
+
+ logo_dt_clut224.width = width;
+ logo_dt_clut224.height = height;
+ logo_dt_clut224.clutsize = clutsize;
+ logo_dt_clut224.clut = clut;
+ logo_dt_clut224.data = data;
+
+ return 0;
+
+err_free_data:
+ kvfree(data);
+err_free_clut:
+ kfree(clut);
+ return ret;
+}
+
+static const struct linux_logo *logo_dt_find(void)
+{
+ static bool probed;
+ struct device_node *np;
+ int ret;
+
+ if (!IS_ENABLED(CONFIG_LOGO_DT_CLUT224))
+ return NULL;
+
+ if (probed)
+ return logo_dt_data ? &logo_dt_clut224 : NULL;
+
+ probed = true;
+
+ np = of_get_compatible_child(of_chosen, LOGO_DT_COMPATIBLE);
+ if (!np)
+ return NULL;
+
+ if (of_device_is_available(np)) {
+ ret = logo_dt_parse(np);
+ if (ret)
+ pr_warn("logo: ignoring malformed %pOF node (%d)\n",
+ np, ret);
+ }
+
+ of_node_put(np);
+
+ return logo_dt_data ? &logo_dt_clut224 : NULL;
+}
+
/*
* Logos are located in the initdata, and will be freed in kernel_init.
* Use late_init to mark the logos as freed to prevent any further use.
@@ -45,7 +181,23 @@ const struct linux_logo * __ref fb_find_logo(int depth)
{
const struct linux_logo *logo = NULL;
- if (nologo || logos_freed)
+ if (nologo)
+ return NULL;
+
+ /*
+ * A logo supplied by the device tree wins over the built-in ones. It
+ * is an ordinary allocation rather than initdata, so unlike them it
+ * stays valid for the life of the kernel, and is still there for a
+ * display driver whose probe finishes after the built-in logos have
+ * gone.
+ */
+ if (depth >= 8) {
+ logo = logo_dt_find();
+ if (logo)
+ return logo;
+ }
+
+ if (logos_freed)
return NULL;
#ifdef CONFIG_LOGO_LINUX_MONO
diff --git a/include/linux/linux_logo.h b/include/linux/linux_logo.h
index b3b15d3800..1e7e9db6dd 100644
--- a/include/linux/linux_logo.h
+++ b/include/linux/linux_logo.h
@@ -22,6 +22,9 @@
#define LINUX_LOGO_CLUT224 3 /* 224 colors */
#define LINUX_LOGO_GRAY256 4 /* 256 levels grayscale */
+/* Compatible of the /chosen child describing a device tree supplied logo */
+#define LOGO_DT_COMPATIBLE "boot-logo-clut224"
+
struct linux_logo {
int type; /* one of LINUX_LOGO_* */
--
2.39.5
next prev parent reply other threads:[~2026-09-23 20:11 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-23 20:10 [PATCH v3 0/7] Boot logo supplied by " Max Pedraza
2026-09-23 20:10 ` [PATCH v3 1/7] fbdev: describe where the boot logo goes in one place Max Pedraza
2026-09-24 12:05 ` Thomas Zimmermann
2026-09-23 20:10 ` [PATCH v3 2/7] dt-bindings: display: add a device tree supplied boot logo Max Pedraza
2026-09-24 11:49 ` Rob Herring (Arm)
2026-09-23 20:10 ` Max Pedraza [this message]
2026-09-23 20:10 ` [PATCH v3 4/7] fbdev: honour the device tree boot logo placement properties Max Pedraza
2026-09-23 20:10 ` [PATCH v3 5/7] dt-bindings: display: allow the boot logo in a reserved memory region Max Pedraza
2026-09-23 20:10 ` [PATCH v3 6/7] video: logo: allow the boot logo to come from " Max Pedraza
2026-09-23 20:10 ` [PATCH v3 7/7] video: logo: add ppmtodtlogo host tool Max Pedraza
2026-09-24 12:21 ` [PATCH v3 0/7] Boot logo supplied by the device tree Thomas Zimmermann
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260923201035.51007-4-maximpedraza@gmail.com \
--to=maximpedraza@gmail.com \
--cc=conor+dt@kernel.org \
--cc=deller@gmx.de \
--cc=devicetree@vger.kernel.org \
--cc=dri-devel@lists.freedesktop.org \
--cc=krzk+dt@kernel.org \
--cc=linux-fbdev@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mripard@kernel.org \
--cc=robh@kernel.org \
--cc=simona@ffwll.ch \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®