* [PATCH v5 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 02/13] efi: bgrt: add accessors for the boot graphics Màxim Pedraza Padilla
` (11 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
The body of DRM_CLIENT_DEFAULT is indented with spaces, unlike every
other entry in the file. Use tabs, so that the next client added to the
list does not have to pick one or the other.
No functional change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 6096c623d9d5..8d8bc37ec9f2 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -114,10 +114,10 @@ config DRM_CLIENT_DEFAULT_LOG
endchoice
config DRM_CLIENT_DEFAULT
- string
- depends on DRM_CLIENT
- default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
- default "log" if DRM_CLIENT_DEFAULT_LOG
- default ""
+ string
+ depends on DRM_CLIENT
+ default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
+ default "log" if DRM_CLIENT_DEFAULT_LOG
+ default ""
endmenu
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 02/13] efi: bgrt: add accessors for the boot graphics
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 03/13] drm: client: add splash client Màxim Pedraza Padilla
` (10 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
The DRM splash client shows the boot graphics the firmware left behind,
and needs the image address and size, its offsets and the status bits.
Reading them from bgrt_tab and bgrt_image_size works with drm_client_lib
built in, but it can be a module, and neither is exported:
ERROR: modpost: drivers/gpu/drm/clients/drm_client_lib.ko: symbol 'bgrt_tab' undefined!
ERROR: modpost: drivers/gpu/drm/clients/drm_client_lib.ko: symbol 'bgrt_image_size' undefined!
Rather than export the data, add an accessor for each of them, exported
GPL only, with stubs in efi-bgrt.h for CONFIG_ACPI_BGRT=n so that users
need no #ifdef of their own. The variables stay global for
drivers/acpi/bgrt.c, and are only written by efi_bgrt_init(): mark them
__ro_after_init.
Reported-by: Mario Limonciello <mario.limonciello@amd.com>
Closes: https://lore.kernel.org/dri-devel/5d7067de-97b7-4232-9cf6-e4b978696482@amd.com/
Suggested-by: Jani Nikula <jani.nikula@linux.intel.com>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/firmware/efi/efi-bgrt.c | 74 ++++++++++++++++++++++++++++++++-
include/linux/efi-bgrt.h | 39 ++++++++++++++++-
2 files changed, 110 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c
index 1da451582812..01425c4d2e70 100644
--- a/drivers/firmware/efi/efi-bgrt.c
+++ b/drivers/firmware/efi/efi-bgrt.c
@@ -16,8 +16,8 @@
#include <linux/efi.h>
#include <linux/efi-bgrt.h>
-struct acpi_table_bgrt bgrt_tab;
-size_t bgrt_image_size;
+struct acpi_table_bgrt bgrt_tab __ro_after_init;
+size_t bgrt_image_size __ro_after_init;
struct bmp_header {
u16 id;
@@ -89,3 +89,73 @@ void __init efi_bgrt_init(struct acpi_table_header *table)
out:
memset(bgrt, 0, sizeof(bgrt_tab));
}
+
+/**
+ * efi_bgrt_has_image() - Whether the firmware left a boot graphic
+ *
+ * Return: true if the BGRT points at a BMP image, which efi_bgrt_init()
+ * checked and reserved.
+ */
+bool efi_bgrt_has_image(void)
+{
+ return bgrt_tab.image_address != 0;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_has_image);
+
+/**
+ * efi_bgrt_image_address() - Physical address of the boot graphic
+ *
+ * Return: the address of the BMP image, or 0 if there is none.
+ */
+phys_addr_t efi_bgrt_image_address(void)
+{
+ return bgrt_tab.image_address;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_image_address);
+
+/**
+ * efi_bgrt_image_size() - Size of the boot graphic
+ *
+ * Return: the size of the BMP image, as its header gives it, or 0 if there
+ * is none.
+ */
+size_t efi_bgrt_image_size(void)
+{
+ return bgrt_image_size;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_image_size);
+
+/**
+ * efi_bgrt_status() - Status field of the BGRT
+ *
+ * Return: the status byte. Bit 0 tells whether the image is still on
+ * screen and, since ACPI 6.2, bits 1-2 how far it has to be turned to show
+ * upright (ACPI_BGRT_ORIENTATION_OFFSET).
+ */
+u8 efi_bgrt_status(void)
+{
+ return bgrt_tab.status;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_status);
+
+/**
+ * efi_bgrt_image_offset_x() - Horizontal position of the boot graphic
+ *
+ * Return: the X offset, in pixels, at which the firmware drew the image.
+ */
+u32 efi_bgrt_image_offset_x(void)
+{
+ return bgrt_tab.image_offset_x;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_image_offset_x);
+
+/**
+ * efi_bgrt_image_offset_y() - Vertical position of the boot graphic
+ *
+ * Return: the Y offset, in pixels, at which the firmware drew the image.
+ */
+u32 efi_bgrt_image_offset_y(void)
+{
+ return bgrt_tab.image_offset_y;
+}
+EXPORT_SYMBOL_GPL(efi_bgrt_image_offset_y);
diff --git a/include/linux/efi-bgrt.h b/include/linux/efi-bgrt.h
index e6cd51005633..1c287e5fd388 100644
--- a/include/linux/efi-bgrt.h
+++ b/include/linux/efi-bgrt.h
@@ -9,7 +9,14 @@
void efi_bgrt_init(struct acpi_table_header *table);
int __init acpi_parse_bgrt(struct acpi_table_header *table);
-/* The BGRT data itself; only valid if bgrt_image != NULL. */
+bool efi_bgrt_has_image(void);
+phys_addr_t efi_bgrt_image_address(void);
+size_t efi_bgrt_image_size(void);
+u8 efi_bgrt_status(void);
+u32 efi_bgrt_image_offset_x(void);
+u32 efi_bgrt_image_offset_y(void);
+
+/* The BGRT data itself; only valid if efi_bgrt_has_image(). */
extern size_t bgrt_image_size;
extern struct acpi_table_bgrt bgrt_tab;
@@ -21,6 +28,36 @@ static inline int __init acpi_parse_bgrt(struct acpi_table_header *table)
return 0;
}
+static inline bool efi_bgrt_has_image(void)
+{
+ return false;
+}
+
+static inline phys_addr_t efi_bgrt_image_address(void)
+{
+ return 0;
+}
+
+static inline size_t efi_bgrt_image_size(void)
+{
+ return 0;
+}
+
+static inline u8 efi_bgrt_status(void)
+{
+ return 0;
+}
+
+static inline u32 efi_bgrt_image_offset_x(void)
+{
+ return 0;
+}
+
+static inline u32 efi_bgrt_image_offset_y(void)
+{
+ return 0;
+}
+
#endif /* !CONFIG_ACPI_BGRT */
#endif /* _LINUX_EFI_BGRT_H */
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 03/13] drm: client: add splash client
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 01/13] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 02/13] efi: bgrt: add accessors for the boot graphics Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:43 ` sashiko-bot
2026-10-13 9:32 ` [PATCH v5 04/13] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
` (9 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Add a DRM client that draws a simple splash, with possibility to show:
- the logo provided by EFI BGRT;
- a static BMP image (loaded as firmware);
- a colored background.
The client is not meant to replace a full-featured bootsplash, but
rather to remove some complexity (and hopefully boot time) on small
embedded platforms or on systems with a limited scope (e.g: recovery
or manufacturing images).
The background color can be set either at build time from a dedicated
config option or at runtime through the drm_client_lib.splash_color
command line parameter. Any color in RGB888 format can be used.
If enabled, the static BMP image is loaded using the kernel firmware
infrastructure; a valid BMP image with 24bpp color and no compression
is expected. The name of the image can be set through the
drm_client_lib.splash_bmp kernel command line parameter, with the
default being 'drm_splash.bmp'.
Just like the existing DRM clients, the splash can be enabled from the
kernel command line using drm_client_lib.active=splash.
Signed-off-by: Francesco Valla <francesco@valla.it>
[Màxim: fix the render thread never exiting when the BMP firmware is
missing, a lost wake-up, and stopping a thread that had exited, never
existed or failed to start; fix buffers going to the wrong modesets
and tiled groups in drm_splash_init_client(), and take modeset_mutex
there; don't keep an ERR_PTR as a buffer; unmap the image only once;
fix use-after-frees on unbind and in the firmware callback; check the
BMP size without overflowing; indent the Kconfig entry with tabs;
clear the modesets' framebuffers when the scanouts are freed, and
don't restore without any; free the scanouts when setting up fails;
release a firmware that lands after the render thread is done; read
the BGRT through the efi_bgrt_*() accessors; drop "static inline"
from the .c file]
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 63 +-
drivers/gpu/drm/clients/Makefile | 1 +
drivers/gpu/drm/clients/drm_client_internal.h | 9 +
drivers/gpu/drm/clients/drm_client_setup.c | 8 +
drivers/gpu/drm/clients/drm_splash.c | 1031 +++++++++++++++++
5 files changed, 1111 insertions(+), 1 deletion(-)
create mode 100644 drivers/gpu/drm/clients/drm_splash.c
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 8d8bc37ec9f2..cbe271c3a23f 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -12,6 +12,7 @@ config DRM_CLIENT_LIB
config DRM_CLIENT_SELECTION
tristate
depends on DRM
+ select DRM_CLIENT_LIB if DRM_CLIENT_SPLASH
select DRM_CLIENT_LIB if DRM_CLIENT_LOG
select DRM_CLIENT_LIB if DRM_FBDEV_EMULATION
help
@@ -85,10 +86,63 @@ config DRM_CLIENT_LOG
If you only need logs, but no terminal, or if you prefer userspace
terminal, say "Y".
+config DRM_CLIENT_SPLASH
+ bool "Display graphic splash"
+ depends on DRM_CLIENT_SELECTION
+ select DRM_CLIENT
+ select DRM_CLIENT_SETUP
+ select DRM_DRAW
+ help
+ This enables a splash drm client, able to display either a plain
+ color or a static image until the userspace is ready to take over.
+ The splash will be displayed on all screens available at boot, if
+ any, or on the ones part of the first hotplug event.
+
+config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
+ hex "Splash background color"
+ depends on DRM_CLIENT_SPLASH
+ default 0x000000
+ help
+ The default splash background color, in RGB888 format.
+
+ The color can be overridden through the drm_client_lib.splash_color
+ kernel command line parameter.
+
+config DRM_CLIENT_SPLASH_SRC_BGRT
+ bool "EFI BGRT as splash source"
+ depends on DRM_CLIENT_SPLASH
+ depends on ACPI_BGRT
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ help
+ Use the BGRT image provided by the EFI bootloader. If the image is
+ smaller than the display(s), it will be centered and the color
+ specified through the DRM_CLIENT_SPLASH_BACKGROUND_COLOR config
+ option will be used as background.
+
+config DRM_CLIENT_SPLASH_SRC_BMP
+ bool "BMP image as splash source"
+ depends on DRM_CLIENT_SPLASH
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ select FW_LOADER
+ help
+ Use a BMP (bitmap) image as splash. If the image is smaller than the
+ display(s), it will be centered and the color specified through the
+ DRM_CLIENT_SPLASH_BACKGROUND_COLOR config option will be used as
+ background.
+
+ The image will be loaded using the firmware loading facility the
+ kernel provides; it shall use 24 bits per pixel and shall not be
+ compressed. The name of the file can be set through the
+ drm_client_lib.splash_bmp command line parameter, with the default
+ being 'drm_splash.bmp'.
+
+config DRM_CLIENT_SPLASH_BMP_SUPPORT
+ bool
+
choice
prompt "Default DRM Client"
depends on DRM_CLIENT_SELECTION
- depends on DRM_FBDEV_EMULATION || DRM_CLIENT_LOG
+ depends on DRM_FBDEV_EMULATION || DRM_CLIENT_LOG || DRM_CLIENT_SPLASH
default DRM_CLIENT_DEFAULT_FBDEV
help
Selects the default drm client.
@@ -111,6 +165,12 @@ config DRM_CLIENT_DEFAULT_LOG
screen, but doesn't implement a full terminal. For that you will need
a userspace terminal using drm/kms.
+config DRM_CLIENT_DEFAULT_SPLASH
+ bool "splash"
+ depends on DRM_CLIENT_SPLASH
+ help
+ Use splash as default drm client.
+
endchoice
config DRM_CLIENT_DEFAULT
@@ -118,6 +178,7 @@ config DRM_CLIENT_DEFAULT
depends on DRM_CLIENT
default "fbdev" if DRM_CLIENT_DEFAULT_FBDEV
default "log" if DRM_CLIENT_DEFAULT_LOG
+ default "splash" if DRM_CLIENT_DEFAULT_SPLASH
default ""
endmenu
diff --git a/drivers/gpu/drm/clients/Makefile b/drivers/gpu/drm/clients/Makefile
index 1304ff358509..b43b1e86fe8e 100644
--- a/drivers/gpu/drm/clients/Makefile
+++ b/drivers/gpu/drm/clients/Makefile
@@ -6,4 +6,5 @@ drm_client_lib-y := drm_client_setup.o
drm_client_lib-$(CONFIG_DRM_CLIENT_LOG) += drm_log.o
drm_client_lib-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_client.o \
drm_fbdev_helper.o
+drm_client_lib-$(CONFIG_DRM_CLIENT_SPLASH) += drm_splash.o
obj-$(CONFIG_DRM_CLIENT_LIB) += drm_client_lib.o
diff --git a/drivers/gpu/drm/clients/drm_client_internal.h b/drivers/gpu/drm/clients/drm_client_internal.h
index 6dc078bf6503..48ee0c1c2529 100644
--- a/drivers/gpu/drm/clients/drm_client_internal.h
+++ b/drivers/gpu/drm/clients/drm_client_internal.h
@@ -22,4 +22,13 @@ void drm_log_register(struct drm_device *dev);
static inline void drm_log_register(struct drm_device *dev) {}
#endif
+#ifdef CONFIG_DRM_CLIENT_SPLASH
+void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format);
+#else
+static inline void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format)
+{}
+#endif
+
#endif
diff --git a/drivers/gpu/drm/clients/drm_client_setup.c b/drivers/gpu/drm/clients/drm_client_setup.c
index 515aceac22b1..c19498938ee3 100644
--- a/drivers/gpu/drm/clients/drm_client_setup.c
+++ b/drivers/gpu/drm/clients/drm_client_setup.c
@@ -56,6 +56,14 @@ void drm_client_setup(struct drm_device *dev, const struct drm_format_info *form
return;
}
#endif
+
+#ifdef CONFIG_DRM_CLIENT_SPLASH
+ if (!strcmp(drm_client_default, "splash")) {
+ drm_splash_register(dev, format);
+ return;
+ }
+#endif
+
if (strcmp(drm_client_default, ""))
drm_warn(dev, "Unknown DRM client %s\n", drm_client_default);
}
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
new file mode 100644
index 000000000000..a7d627954158
--- /dev/null
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -0,0 +1,1031 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+/*
+ * Copyright (c) 2025-2026 Francesco Valla <francesco@valla.it>
+ */
+
+#include <linux/atomic.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/efi-bgrt.h>
+#include <linux/firmware.h>
+#include <linux/init.h>
+#include <linux/iosys-map.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+
+#include <acpi/actbl1.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "drm_client_internal.h"
+#include "drm_draw_internal.h"
+#include "drm_internal.h"
+
+/**
+ * DOC: overview
+ *
+ * This is a simple graphic bootsplash, able to display either a plain color or
+ * a static image.
+ */
+
+static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+module_param(splash_color, uint, 0400);
+MODULE_PARM_DESC(splash_color, "Splash background color (RGB888)");
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
+#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
+static char *splash_bmp = DEFAULT_SPLASH_BMP;
+module_param(splash_bmp, charp, 0400);
+MODULE_PARM_DESC(splash_bmp, "Name of splash image (default: \"" DEFAULT_SPLASH_BMP "\")");
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+
+#define BMP_FILE_MAGIC_ID 0x4d42
+
+/* BMP header structures copied from drivers/video/fbdev/efifb.c */
+struct bmp_file_header {
+ __le16 id;
+ __le32 file_size;
+ __le32 reserved;
+ __le32 bitmap_offset;
+} __packed;
+
+struct bmp_dib_header {
+ __le32 dib_header_size;
+ __le32 width;
+ __le32 height;
+ __le16 planes;
+ __le16 bpp;
+ __le32 compression;
+ __le32 bitmap_size;
+ __le32 horz_resolution;
+ __le32 vert_resolution;
+ __le32 colors_used;
+ __le32 colors_important;
+} __packed;
+
+struct drm_splash_scanout {
+ int id;
+ /* Modesets scanning this buffer out, by position in the list */
+ u32 modesets;
+ u32 format;
+ unsigned int width;
+ unsigned int height;
+ struct drm_client_buffer *buffer;
+ bool bg_drawn;
+ bool img_drawn;
+};
+
+struct drm_splash {
+ struct drm_client_dev client;
+ u32 preferred_format;
+ struct device dev;
+
+ struct task_struct *thread;
+ atomic_t pending;
+ /* A firmware request is in flight, so an image may still arrive */
+ bool fw_pending;
+ /* A request was made, and fw_done tells when its callback is over */
+ bool fw_requested;
+ struct completion fw_done;
+
+ /* Serialises hotplug events: the first one sets the modesets up */
+ struct mutex hotplug_lock;
+ bool initialized;
+
+ u32 n_scanout;
+ struct drm_splash_scanout *scanout;
+
+ /* Hands fw over from the firmware callback to the render thread */
+ spinlock_t fw_lock;
+ const struct firmware *fw;
+ /* The render thread is gone: a late firmware is released, not kept */
+ bool render_done;
+ void *map_data;
+
+ bool use_bgrt;
+};
+
+static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
+{
+ return container_of_const(client, struct drm_splash, client);
+}
+
+static struct drm_splash_scanout *
+get_scanout_from_tile_group(struct drm_splash *splash, int id)
+{
+ int j;
+
+ for (j = 0; j < splash->n_scanout; j++)
+ if (splash->scanout[j].id == id)
+ return &splash->scanout[j];
+
+ return NULL;
+}
+
+static void drm_splash_wake_render_thread(struct drm_splash *splash)
+{
+ wake_up_process(splash->thread);
+}
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
+static int drm_splash_fw_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ const struct firmware *fw;
+
+ scoped_guard(spinlock, &splash->fw_lock)
+ fw = splash->fw;
+
+ if (!fw)
+ return -ENOENT;
+
+ *data = fw->data;
+ *size = fw->size;
+
+ return 0;
+}
+
+static void drm_splash_fw_callback(const struct firmware *fw, void *context)
+{
+ struct drm_splash *splash = context;
+ struct drm_client_dev *client = &splash->client;
+
+ if (!fw || !fw->data) {
+ drm_err(client->dev, "splash: no firmware");
+ release_firmware(fw);
+ fw = NULL;
+ }
+
+ scoped_guard(spinlock, &splash->fw_lock) {
+ if (!splash->render_done) {
+ splash->fw = fw;
+ fw = NULL;
+ }
+ }
+ /* Nobody is left to draw it */
+ release_firmware(fw);
+
+ /*
+ * Whether it came or not, nothing else is coming: the thread must be
+ * told either way, or it waits for good. Release, so that a thread
+ * that sees the flag clear also sees the firmware stored above.
+ */
+ smp_store_release(&splash->fw_pending, false);
+
+ /* Wake the render thread */
+ drm_dbg(client->dev, "splash: firmware request done, wake up drawing thread");
+ atomic_set(&splash->pending, 1);
+ drm_splash_wake_render_thread(splash);
+
+ /* Last: once this is seen, unregister may free the splash */
+ complete(&splash->fw_done);
+}
+
+static int drm_splash_fw_request_bmp(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+
+ drm_info(client->dev, "splash: request %s as firmware", splash_bmp);
+
+ return request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ splash_bmp, client->dev->dev, GFP_KERNEL,
+ splash, drm_splash_fw_callback);
+}
+#else
+static int drm_splash_fw_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ return -EOPNOTSUPP;
+}
+
+static int drm_splash_fw_request_bmp(struct drm_splash *splash)
+{
+ return -EOPNOTSUPP;
+}
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+
+static bool drm_splash_bgrt_available(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+
+ if (!IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BGRT))
+ return false;
+
+ if (!efi_bgrt_has_image()) {
+ drm_info(client->dev, "splash: no BGRT found");
+ return false;
+ }
+
+ if (efi_bgrt_status() & ACPI_BGRT_ORIENTATION_OFFSET) {
+ drm_info(client->dev, "splash: BGRT rotation bits set, skipping");
+ return false;
+ }
+
+ return true;
+}
+
+static int drm_splash_bgrt_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ struct drm_client_dev *client = &splash->client;
+ phys_addr_t addr = efi_bgrt_image_address();
+ size_t len = efi_bgrt_image_size();
+
+ if (!drm_splash_bgrt_available(splash))
+ return -ENOENT;
+
+ drm_dbg(client->dev, "splash: BGRT image is at %pa, size=%zX",
+ &addr, len);
+
+ splash->map_data = memremap(addr, len, MEMREMAP_WB);
+ if (!splash->map_data) {
+ drm_warn(client->dev, "splash: failed to map BGRT image memory");
+ return -ENOMEM;
+ }
+
+ *data = splash->map_data;
+ *size = len;
+
+ return 0;
+}
+
+static u32 drm_splash_find_usable_format(struct drm_plane *plane,
+ u32 preferred_format)
+{
+ int i;
+
+ /* Check if the preferred format can be used */
+ for (i = 0; i < plane->format_count; i++)
+ if (plane->format_types[i] == preferred_format)
+ return preferred_format;
+
+ /* Otherwise, find the first format that can be converted from XRGB8888 */
+ for (i = 0; i < plane->format_count; i++)
+ if (drm_draw_can_convert_from_xrgb8888(plane->format_types[i]))
+ return plane->format_types[i];
+
+ return DRM_FORMAT_INVALID;
+}
+
+static void drm_splash_fill(struct iosys_map *map, unsigned int dst_pitch,
+ unsigned int height, unsigned int width,
+ u32 px_width, u32 color)
+{
+ switch (px_width) {
+ case 2:
+ drm_draw_fill16(map, dst_pitch, height, width, color);
+ break;
+ case 3:
+ drm_draw_fill24(map, dst_pitch, height, width, color);
+ break;
+ case 4:
+ drm_draw_fill32(map, dst_pitch, height, width, color);
+ break;
+ default:
+ WARN_ONCE(1, "Can't fill with pixel width %d", px_width);
+ }
+}
+
+static int drm_splash_fill_solid_color(struct drm_client_buffer *buffer,
+ u32 color)
+{
+ struct drm_client_dev *client = buffer->client;
+ struct drm_framebuffer *fb = buffer->fb;
+ struct drm_rect r = DRM_RECT_INIT(0, 0, fb->width, fb->height);
+ u32 px_width = fb->format->cpp[0];
+ struct iosys_map map;
+ int ret;
+
+ ret = drm_client_buffer_vmap_local(buffer, &map);
+ if (ret) {
+ drm_err(client->dev, "splash: cannot vmap buffer: %d", ret);
+ return ret;
+ }
+
+ drm_splash_fill(&map, fb->pitches[0], drm_rect_height(&r),
+ drm_rect_width(&r), px_width, color);
+
+ drm_client_buffer_vunmap_local(buffer);
+
+ return drm_client_buffer_flush(buffer, &r);
+}
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * sizeof(u16);
+
+ iosys_map_wr(map, wr_off, u16, dcolor);
+ }
+ }
+}
+
+static void drm_splash_blit_pix24(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * 3;
+
+ iosys_map_wr(map, wr_off, u8, (dcolor & 0x000000FF) >> 0);
+ iosys_map_wr(map, wr_off + 1, u8, (dcolor & 0x0000FF00) >> 8);
+ iosys_map_wr(map, wr_off + 2, u8, (dcolor & 0x00FF0000) >> 16);
+ }
+ }
+}
+
+static void drm_splash_blit_pix32(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, u32 format)
+{
+ unsigned int x, y, src_offset, dst_offset;
+ u32 scolor, dcolor, wr_off;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch;
+
+ for (x = 0; x < width; x++) {
+ scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ dcolor = drm_draw_color_from_xrgb8888(scolor, format);
+ wr_off = dst_offset + (x_pad + x) * sizeof(u32);
+
+ iosys_map_wr(map, wr_off, u32, dcolor);
+ }
+ }
+}
+
+static void drm_splash_blit_rgb888(struct iosys_map *map, unsigned int dpitch,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y)
+{
+ unsigned int y, src_offset, dst_offset;
+
+ for (y = 0; y < height; y++) {
+ src_offset = (invert_y ? (height - y - 1) : y) * spitch;
+ dst_offset = (y_pad + y) * dpitch + x_pad * 3;
+
+ iosys_map_memcpy_to(map, dst_offset, &sbuf8[src_offset], width * 3);
+ }
+}
+
+static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
+ struct drm_splash_scanout *scanout,
+ const u8 *data, size_t data_len)
+
+{
+ struct drm_client_buffer *buffer = scanout->buffer;
+ struct drm_client_dev *client = buffer->client;
+ struct drm_framebuffer *fb = buffer->fb;
+ u32 px_width = fb->format->cpp[0];
+ const struct bmp_file_header *file_header;
+ const struct bmp_dib_header *dib_header;
+ u32 dib_header_size;
+ u16 bmp_id, bmp_bpp, bmp_planes;
+ u32 bmp_compression, bmp_pitch;
+ s32 bmp_width, bmp_height;
+ bool bmp_invert_y;
+ u32 bitmap_offset;
+ size_t row_len, pixels_len, end;
+ unsigned int x_pad, y_pad;
+ const u8 *image_data;
+ struct iosys_map map;
+ struct drm_rect r;
+ int ret;
+
+ if (data_len < (sizeof(*file_header) + sizeof(*dib_header))) {
+ drm_err(client->dev, "splash: BMP file too short");
+ return -EINVAL;
+ }
+
+ file_header = (const struct bmp_file_header *)data;
+
+ bmp_id = get_unaligned_le16(&file_header->id);
+ if (bmp_id != BMP_FILE_MAGIC_ID) {
+ drm_err(client->dev, "splash: invalid BMP magic 0x%04X", bmp_id);
+ return -EINVAL;
+ }
+
+ bitmap_offset = get_unaligned_le32(&file_header->bitmap_offset);
+
+ dib_header = (const struct bmp_dib_header *)(data + sizeof(*file_header));
+
+ dib_header_size = get_unaligned_le32(&dib_header->dib_header_size);
+
+ bmp_width = (s32)get_unaligned_le32(&dib_header->width);
+ bmp_height = (s32)get_unaligned_le32(&dib_header->height);
+ bmp_planes = get_unaligned_le16(&dib_header->planes);
+ bmp_bpp = get_unaligned_le16(&dib_header->bpp);
+ bmp_compression = get_unaligned_le32(&dib_header->compression);
+
+ /* Restrict supported format to uncompressed, 24bit RGB888 */
+ if (dib_header_size != 40 || bmp_width <= 0 || bmp_height == 0 ||
+ bmp_height == S32_MIN || bmp_planes != 1 ||
+ bmp_compression != 0 || bmp_bpp != 24) {
+ drm_err(client->dev, "splash: invalid BMP format");
+ return -EINVAL;
+ }
+
+ /* A positive height means bottom-to-top scan direction */
+ bmp_invert_y = (bmp_height > 0);
+ bmp_height = abs(bmp_height);
+
+ /*
+ * Every field comes from the image, which the firmware or the device
+ * tree supplied, so check the arithmetic before trusting the result:
+ * rows are padded to four bytes, and the pixels must end within the
+ * data.
+ */
+ if (check_mul_overflow((size_t)bmp_width, (size_t)3, &row_len) ||
+ check_add_overflow(row_len, (size_t)3, &row_len) ||
+ check_mul_overflow(row_len & ~(size_t)3, (size_t)bmp_height,
+ &pixels_len) ||
+ check_add_overflow((size_t)bitmap_offset, pixels_len, &end) ||
+ end > data_len) {
+ drm_err(client->dev, "splash: invalid BMP size");
+ return -EINVAL;
+ }
+ bmp_pitch = row_len & ~(size_t)3;
+
+ if (bmp_width > scanout->width || bmp_height > scanout->height) {
+ drm_err(client->dev, "splash: BMP image is too big for the screen");
+ return -EINVAL;
+ }
+
+ if (splash->use_bgrt) {
+ x_pad = efi_bgrt_image_offset_x();
+ y_pad = efi_bgrt_image_offset_y();
+
+ if ((x_pad + bmp_width) > scanout->width ||
+ (y_pad + bmp_height) > scanout->height) {
+ drm_err(client->dev, "splash: BGRT image would overflow");
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_X86
+ /*
+ * BGRT sanity check, taken from efifb.c:
+ *
+ * On x86 some firmwares use a low non native resolution for
+ * the display when they have shown some text messages. While
+ * keeping the bgrt filled with info for the native resolution.
+ * If the bgrt image intended for the native resolution still
+ * fits, it will be displayed very close to the right edge of
+ * the display looking quite bad.
+ */
+
+ if (x_pad != (scanout->width - bmp_width) / 2) {
+ drm_err(client->dev, "splash: BGRT sanity check failed");
+ return -EINVAL;
+ }
+#endif
+ } else {
+ /* Center X and Y */
+ x_pad = (scanout->width - bmp_width) / 2;
+ y_pad = (scanout->height - bmp_height) / 2;
+ }
+
+ image_data = data + bitmap_offset;
+
+ ret = drm_client_buffer_vmap_local(buffer, &map);
+ if (ret) {
+ drm_err(client->dev, "splash: cannot vmap buffer: %d", ret);
+ return ret;
+ }
+
+ r = DRM_RECT_INIT(x_pad, y_pad, bmp_width, bmp_height);
+
+ /* In case the target format is RGB888, source data can be copied to
+ * the video buffer line by line, avoiding some overhead.
+ */
+ if (scanout->format == DRM_FORMAT_RGB888) {
+ drm_splash_blit_rgb888(&map, fb->pitches[0], x_pad, y_pad,
+ image_data, bmp_pitch, bmp_width,
+ bmp_height, bmp_invert_y);
+ } else {
+ switch (px_width) {
+ case 2:
+ drm_splash_blit_pix16(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ case 3:
+ drm_splash_blit_pix24(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ case 4:
+ drm_splash_blit_pix32(&map, fb->pitches[0], x_pad,
+ y_pad, image_data, bmp_pitch,
+ bmp_width, bmp_height,
+ bmp_invert_y, scanout->format);
+ break;
+ default:
+ drm_warn_once(client->dev,
+ "splash: can't blit with pixel width %d",
+ px_width);
+ }
+ }
+
+ drm_client_buffer_vunmap_local(buffer);
+
+ return drm_client_buffer_flush(buffer, &r);
+}
+#else
+static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
+ struct drm_splash_scanout *scanout,
+ const u8 *data, size_t data_len)
+{
+ return -EOPNOTSUPP;
+}
+#endif
+
+static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
+ size_t *img_data_len)
+{
+ int ret = 0;
+
+ if (splash->use_bgrt) {
+ ret = drm_splash_bgrt_load(splash, img_data, img_data_len);
+ if (ret)
+ splash->use_bgrt = false;
+ }
+
+ /* BGRT failed to load */
+ if (!splash->use_bgrt)
+ ret = drm_splash_fw_load(splash, img_data, img_data_len);
+
+ return ret;
+}
+
+static void drm_splash_image_cleanup(struct drm_splash *splash)
+{
+ const struct firmware *fw = NULL;
+
+ /* Called after drawing and again at unregister: unmap only once */
+ memunmap(splash->map_data);
+ splash->map_data = NULL;
+
+ scoped_guard(spinlock, &splash->fw_lock) {
+ fw = splash->fw;
+ splash->fw = NULL;
+ }
+
+ release_firmware(fw);
+}
+
+static int drm_splash_render_thread(void *data)
+{
+ struct drm_splash *splash = data;
+ struct drm_client_dev *client = &splash->client;
+
+ while (!kthread_should_stop()) {
+ unsigned int draw_count = 0;
+ const u8 *img_data = NULL;
+ size_t img_data_len = 0;
+ bool img_loaded, last;
+ int i, ret;
+
+ drm_dbg(client->dev, "splash: run render thread...");
+
+ /*
+ * Whether this is the last pass has to be known before looking
+ * for the image: if the firmware lands while this pass runs,
+ * another pass has to pick it up.
+ */
+ last = !smp_load_acquire(&splash->fw_pending);
+
+ ret = drm_splash_image_load(splash, &img_data, &img_data_len);
+ img_loaded = (ret == 0);
+
+ for (i = 0; i < splash->n_scanout; i++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[i];
+
+ if (!scanout->buffer) {
+ drm_err(client->dev,
+ "splash: no buffer for scanout %d", i);
+ continue;
+ }
+
+ if (!scanout->bg_drawn) {
+ drm_dbg(client->dev, "draw background for scanout %d", i);
+ u32 color = drm_draw_color_from_xrgb8888(splash_color,
+ scanout->format);
+ drm_splash_fill_solid_color(scanout->buffer, color);
+ scanout->bg_drawn = true;
+ }
+
+ if (img_loaded && !scanout->img_drawn) {
+ drm_dbg(client->dev, "draw image for scanout %d", i);
+ /* Ignore the return value, since the solid
+ * color has already been drawn to screen.
+ */
+ ret = drm_splash_bmp_to_scanout(splash, scanout,
+ img_data,
+ img_data_len);
+ scanout->img_drawn = (ret == 0);
+ }
+
+ draw_count++;
+ }
+
+ if (img_loaded)
+ drm_splash_image_cleanup(splash);
+
+ if (draw_count > 0) {
+ ret = drm_client_modeset_commit(client);
+ /* If commit returns EBUSY, another master showed up.
+ * This means that the splash is no more required.
+ */
+ if (ret == -EBUSY) {
+ drm_info(client->dev, "splash: not master anymore, exiting");
+ break;
+ }
+ }
+
+ /* Done, or nothing could have arrived since the image was sought */
+ if (img_loaded || last)
+ break;
+
+ /*
+ * Wait for the firmware callback. The state is set before
+ * looking at the flag, so that a wake up in between is not
+ * lost, and the wait is idle rather than uninterruptible, as
+ * nothing guarantees how long it lasts.
+ */
+ set_current_state(TASK_IDLE);
+ if (atomic_xchg(&splash->pending, 0) || kthread_should_stop() ||
+ !READ_ONCE(splash->fw_pending)) {
+ __set_current_state(TASK_RUNNING);
+ continue;
+ }
+
+ schedule();
+ }
+
+ /*
+ * Nothing is drawn from here on, for instance after another master
+ * took over: drop the image, and have a firmware that lands later
+ * released by its callback.
+ */
+ scoped_guard(spinlock, &splash->fw_lock)
+ splash->render_done = true;
+ drm_splash_image_cleanup(splash);
+
+ return 0;
+}
+
+static int drm_splash_init_client(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct drm_mode_set *modeset;
+ unsigned int fb_count = 0;
+ unsigned int i, k;
+
+ if (drm_client_modeset_probe(client, 0, 0))
+ return -EIO;
+
+ mutex_lock(&client->modeset_mutex);
+
+ /*
+ * Collect one scanout per usable modeset, or per tile group. Each
+ * scanout records the modesets it feeds by their position in the
+ * list, so that the second pass below can find them again whatever
+ * was skipped in between.
+ */
+ i = 0;
+ drm_client_for_each_modeset(modeset, client) {
+ struct drm_splash_scanout *tmp;
+ struct drm_splash_scanout *scanout;
+ unsigned int idx = i++;
+ u32 format;
+ int id = -1;
+
+ if (WARN_ON_ONCE(idx >= 32))
+ break;
+
+ /* Skip modesets without a mode */
+ if (!modeset->mode)
+ continue;
+
+ if (modeset->connectors[0]->has_tile) {
+ struct drm_splash_scanout *tiled;
+ int new_id = modeset->connectors[0]->tile_group->id;
+
+ /*
+ * Tiled modesets contribute to a single framebuffer:
+ * the first column adds rows, the first row adds
+ * columns.
+ */
+ tiled = get_scanout_from_tile_group(splash, new_id);
+ if (tiled) {
+ if (!modeset->x)
+ tiled->height += modeset->mode->vdisplay;
+ if (!modeset->y)
+ tiled->width += modeset->mode->hdisplay;
+ tiled->modesets |= BIT(idx);
+ continue;
+ }
+
+ /* New tile group, save its ID for later */
+ id = new_id;
+ }
+
+ format = drm_splash_find_usable_format(modeset->crtc->primary,
+ splash->preferred_format);
+ if (format == DRM_FORMAT_INVALID) {
+ drm_warn(client->dev,
+ "splash: can't find a usable format for modeset");
+ continue;
+ }
+
+ tmp = krealloc(splash->scanout,
+ (splash->n_scanout + 1) * sizeof(*splash->scanout),
+ GFP_KERNEL);
+ if (!tmp) {
+ drm_warn(client->dev,
+ "splash: can't reallocate the scanout array");
+ break;
+ }
+
+ splash->scanout = tmp;
+ scanout = &splash->scanout[splash->n_scanout];
+ splash->n_scanout++;
+
+ memset(scanout, 0, sizeof(*scanout));
+ scanout->id = id;
+ scanout->modesets = BIT(idx);
+ scanout->format = format;
+ scanout->width = modeset->mode->hdisplay;
+ scanout->height = modeset->mode->vdisplay;
+ }
+
+ /* Now that the sizes are known, allocate one buffer per scanout */
+ for (k = 0; k < splash->n_scanout; k++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[k];
+ struct drm_client_buffer *buffer;
+
+ buffer = drm_client_buffer_create_dumb(client, scanout->width,
+ scanout->height,
+ scanout->format);
+ if (IS_ERR(buffer)) {
+ drm_warn(client->dev,
+ "splash: can't create dumb buffer %d %d %p4cc",
+ scanout->width, scanout->height, &scanout->format);
+ continue;
+ }
+
+ drm_info(client->dev, "splash: created dumb buffer %d %d %p4cc",
+ scanout->width, scanout->height, &scanout->format);
+
+ scanout->buffer = buffer;
+ fb_count++;
+ }
+
+ /* And point every modeset at the buffer of its scanout */
+ i = 0;
+ drm_client_for_each_modeset(modeset, client) {
+ unsigned int idx = i++;
+
+ if (idx >= 32)
+ break;
+
+ for (k = 0; k < splash->n_scanout; k++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[k];
+
+ if (scanout->buffer && (scanout->modesets & BIT(idx))) {
+ modeset->fb = scanout->buffer->fb;
+ break;
+ }
+ }
+ }
+
+ mutex_unlock(&client->modeset_mutex);
+
+ return (fb_count == 0) ? -ENODEV : 0;
+}
+
+static void drm_splash_free_scanout(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ struct drm_mode_set *modeset;
+ int i;
+
+ /* The modesets must not point at the framebuffers freed below */
+ mutex_lock(&client->modeset_mutex);
+ drm_client_for_each_modeset(modeset, client)
+ modeset->fb = NULL;
+ mutex_unlock(&client->modeset_mutex);
+
+ if (splash->n_scanout) {
+ for (i = 0; i < splash->n_scanout; i++)
+ drm_client_buffer_delete(splash->scanout[i].buffer);
+
+ splash->n_scanout = 0;
+ kfree(splash->scanout);
+ splash->scanout = NULL;
+ }
+}
+
+static int drm_splash_client_hotplug(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ struct task_struct *thread;
+ int ret = 0;
+
+ guard(mutex)(&splash->hotplug_lock);
+
+ /* The modesets that get a splash are defined at first hotplug event */
+ if (splash->initialized)
+ return 0;
+
+ ret = drm_splash_init_client(splash);
+ if (ret) {
+ /* It may have set up scanouts before failing to get buffers */
+ drm_splash_free_scanout(client);
+ if (ret == -ENODEV) {
+ drm_info(client->dev, "splash: no modeset found");
+ return 0;
+ }
+ drm_err(client->dev,
+ "splash: failed to init client: %d", ret);
+ return ret;
+ }
+
+ /* Create the render thread, waken later */
+ thread = kthread_create(drm_splash_render_thread, splash,
+ "drm_splash_%s", client->dev->unique);
+ if (IS_ERR(thread)) {
+ ret = PTR_ERR(thread);
+ drm_err(client->dev,
+ "splash: failed to create render thread: %d", ret);
+ drm_splash_free_scanout(client);
+ return ret;
+ }
+
+ /*
+ * The thread exits on its own once the splash is up, so hold on to
+ * it: kthread_stop() at unregister must not find it gone.
+ */
+ get_task_struct(thread);
+ splash->thread = thread;
+
+ splash->use_bgrt = drm_splash_bgrt_available(splash);
+
+ /* If no other image has been loaded, try to load a BMP as firmware */
+ if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP) && !splash->use_bgrt) {
+ WRITE_ONCE(splash->fw_pending, true);
+ ret = drm_splash_fw_request_bmp(splash);
+ splash->fw_requested = !ret;
+ if (ret) {
+ drm_err(client->dev,
+ "splash: failed to kick image load: %d", ret);
+ kthread_stop_put(splash->thread);
+ splash->thread = NULL;
+ drm_splash_free_scanout(client);
+ return ret;
+ }
+ }
+
+ /* Wake the render thread to show initial contents */
+ drm_splash_wake_render_thread(splash);
+
+ splash->initialized = true;
+
+ return 0;
+}
+
+static int drm_splash_client_restore(struct drm_client_dev *client, bool force)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ int ret;
+
+ /* No buffers, so nothing to put back: the modesets have no fb */
+ if (!splash->n_scanout)
+ return -ENODEV;
+
+ if (force)
+ ret = drm_client_modeset_commit_locked(client);
+ else
+ ret = drm_client_modeset_commit(client);
+
+ return ret;
+}
+
+static void drm_splash_client_unregister(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+
+ /*
+ * A firmware request cannot be cancelled, and its callback uses the
+ * splash and wakes the thread: let it finish first. It is usually over
+ * already; at worst this waits for the firmware fallback timeout.
+ */
+ if (splash->fw_requested)
+ wait_for_completion(&splash->fw_done);
+
+ /* No thread if no output was usable at the first hotplug */
+ if (splash->thread)
+ kthread_stop_put(splash->thread);
+ drm_splash_free_scanout(client);
+ drm_splash_image_cleanup(splash);
+
+ /* Last: it ends up in drm_splash_client_free(), which frees splash */
+ drm_client_release(client);
+}
+
+static void drm_splash_client_free(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ struct drm_device *dev = client->dev;
+
+ mutex_destroy(&splash->hotplug_lock);
+ kfree(splash);
+
+ drm_dbg(dev, "Unregistered with drm splash");
+}
+
+static const struct drm_client_funcs drm_splash_client_funcs = {
+ .owner = THIS_MODULE,
+ .hotplug = drm_splash_client_hotplug,
+ .restore = drm_splash_client_restore,
+ .unregister = drm_splash_client_unregister,
+ .free = drm_splash_client_free,
+};
+
+/**
+ * drm_splash_register() - Register a drm device to drm_splash
+ * @dev: the drm device to register.
+ * @format: drm device preferred format.
+ */
+void drm_splash_register(struct drm_device *dev,
+ const struct drm_format_info *format)
+{
+ struct drm_splash *splash;
+
+ splash = kzalloc_obj(*splash);
+ if (!splash)
+ goto err_warn;
+
+ mutex_init(&splash->hotplug_lock);
+ spin_lock_init(&splash->fw_lock);
+ init_completion(&splash->fw_done);
+
+ if (format && format->num_planes == 1)
+ splash->preferred_format = format->format;
+ else
+ splash->preferred_format = DRM_FORMAT_RGB888;
+
+ if (drm_client_init(dev, &splash->client, "drm_splash",
+ &drm_splash_client_funcs))
+ goto err_free;
+
+ drm_client_register(&splash->client);
+ drm_dbg(dev, "Registered with drm splash");
+
+ return;
+
+err_free:
+ kfree(splash);
+err_warn:
+ drm_warn(dev, "Failed to register with drm splash");
+}
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v5 03/13] drm: client: add splash client
2026-10-13 9:32 ` [PATCH v5 03/13] drm: client: add splash client Màxim Pedraza Padilla
@ 2026-10-13 9:43 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-13 9:43 UTC (permalink / raw)
To: Màxim Pedraza Padilla; +Cc: dri-devel, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm: out-of-bounds read and unaligned access in BMP blitting
- [High] drm: integer overflow in BGRT padding validation allows OOB write
--
commit 099d44ed94a2ad19b8702756f34790d1e8ae5693
Author: Francesco Valla <francesco@valla.it>
drm: client: add splash client
This commit adds a new DRM client to draw a simple splash screen during boot.
It supports displaying an EFI BGRT logo, a static BMP image loaded via
firmware, or a plain background color. The splash can be enabled via kernel
command line parameters.
> diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/gpu/drm/clients/drm_splash.c
[ ... ]
> +static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
> + unsigned int x_pad, unsigned int y_pad,
> + const u8 *sbuf8, unsigned int spitch,
> + unsigned int width, unsigned int height,
> + bool invert_y, u32 format)
> +{
> + unsigned int x, y, src_offset, dst_offset;
> + u32 scolor, dcolor, wr_off;
> +
> + for (y = 0; y < height; y++) {
> + src_offset = (invert_y ? (height - y - 1) : y) * spitch;
> + dst_offset = (y_pad + y) * dpitch;
> +
> + for (x = 0; x < width; x++) {
> + scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
[Severity: High]
Could this 32-bit cast cause memory safety and endianness issues?
The code casts a byte pointer to a 32-bit integer pointer to read a 3-byte
(24bpp) pixel. This introduces a few potential problems.
First, it performs unaligned memory accesses, which can trigger fatal
alignment exceptions on architectures that do not support hardware unaligned
loads, such as SPARC or some older ARM platforms.
Second, for the last pixel in an image row where there are no padding bytes,
the 4-byte read extends 1 byte beyond the end of the row. On the final row of
the image, this reads 1 byte past the end of the mapped memory buffer. This
could trigger a KASAN out-of-bounds read or a fatal page fault if the read
crosses an unmapped page boundary.
Finally, on big-endian architectures, reading the 3 bytes plus an arbitrary
4th byte directly into a u32 will scramble the internal BGR color channels,
resulting in corrupted colors.
Does this same cast pattern need to be addressed in drm_splash_blit_pix24()
and drm_splash_blit_pix32() below as well?
> + dcolor = drm_draw_color_from_xrgb8888(scolor, format);
> + wr_off = dst_offset + (x_pad + x) * sizeof(u16);
> +
> + iosys_map_wr(map, wr_off, u16, dcolor);
> + }
> + }
> +}
[ ... ]
> + if (splash->use_bgrt) {
> + x_pad = efi_bgrt_image_offset_x();
> + y_pad = efi_bgrt_image_offset_y();
> +
> + if ((x_pad + bmp_width) > scanout->width ||
> + (y_pad + bmp_height) > scanout->height) {
[Severity: High]
Is this bounds check vulnerable to an integer overflow?
The x_pad and y_pad values are sourced directly from the ACPI BGRT table
via efi_bgrt_image_offset_x() and efi_bgrt_image_offset_y(), which return
32-bit unsigned integers. Because x_pad and scanout->width are unsigned int,
and bmp_width is a positive s32, the addition is evaluated as an unsigned
integer.
If an attacker provides an extremely large value for x_pad, such as
0xFFFFFFFF, in a compromised ACPI table, x_pad + bmp_width will wrap around
to a small number. This would bypass the greater-than scanout->width check.
Later during blitting, these wrapped offsets are used to calculate the write
offset. For example, calculating wr_off = dst_offset + (x_pad + x) * 3
would result in a massive out-of-bounds write and potential kernel memory
corruption.
Would it be safer to check these bounds using an overflow-safe method, or
perhaps by checking if x_pad > scanout->width - bmp_width?
> + drm_err(client->dev, "splash: BGRT image would overflow");
> + return -EINVAL;
> + }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013093243.505776-1-maximpedraza@gmail.com?part=3
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 04/13] MAINTAINERS: add entry for DRM splash client
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (2 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 03/13] drm: client: add splash client Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 05/13] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
` (8 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Add myself as maintainer for the DRM splash client.
Signed-off-by: Francesco Valla <francesco@valla.it>
[Màxim: add myself as co-maintainer]
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
MAINTAINERS | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index a08087fb6e67..321ecb9f5b5b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9168,6 +9168,14 @@ F: Documentation/gpu/drm-ras.rst
F: drivers/gpu/drm/drm_ras*
F: include/drm/drm_ras*
+DRM SPLASH
+M: Francesco Valla <francesco@valla.it>
+M: Màxim Pedraza Padilla <maximpedraza@gmail.com>
+L: dri-devel@lists.freedesktop.org
+S: Maintained
+T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F: drivers/gpu/drm/clients/drm_splash.c
+
DRM TTM SUBSYSTEM
M: Natalie Vock <nat@pixelcluster.dev>
M: Arun Pravin <arunpravin.paneerselvam@amd.com>
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 05/13] drm: docs: remove bootsplash from TODO
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (3 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 04/13] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 06/13] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
` (7 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
From: Francesco Valla <francesco@valla.it>
Now that a splash client exists, remove the bootsplash task from the
TODO list for the DRM subsystem.
Signed-off-by: Francesco Valla <francesco@valla.it>
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
Documentation/gpu/todo.rst | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst
index a437ae5c71f4..e77de5e2a24f 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -773,23 +773,6 @@ See drivers/gpu/drm/amd/display/TODO for tasks.
Contact: Harry Wentland, Alex Deucher
-Bootsplash
-==========
-
-There is support in place now for writing internal DRM clients making it
-possible to pick up the bootsplash work that was rejected because it was written
-for fbdev.
-
-- [v6,8/8] drm/client: Hack: Add bootsplash example
- https://patchwork.freedesktop.org/patch/306579/
-
-- [RFC PATCH v2 00/13] Kernel based bootsplash
- https://lore.kernel.org/r/20171213194755.3409-1-mstaudt@suse.de
-
-Contact: Sam Ravnborg
-
-Level: Advanced
-
Brightness handling on devices with multiple internal panels
============================================================
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 06/13] dt-bindings: display: add a boot logo node under /chosen
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (4 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 05/13] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 07/13] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
` (6 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
Products built on the same board often differ only in branding. On UEFI
systems the firmware hands the operating system its boot logo through
the ACPI BGRT; a device tree system has no such table, so today the logo
is built into the kernel, and each product needs its own kernel image.
With U-Boot's Falcon mode the device tree is the only thing that reaches
the kernel, so it is also the only place the logo can come from.
Add a "boot-logo" node under /chosen. The node lives there because a
logo is configuration handed over by firmware rather than a description
of the hardware, as simple-framebuffer nodes already are.
The image is a BMP with 24 bits per pixel and no compression, the
format of the BGRT image, so that one file serves the firmware splash,
a BMP loaded as firmware and this node alike. It is carried either in
an "image" property, which dtc fills in from the file with /incbin/, or
in a reserved memory region the bootloader loaded it into, named by
"memory-region". Exactly one of the two must be present.
"logo-position" takes -1 on an axis to mean centre on that axis, which
is the only way to say it when the device tree does not know the image
size, and "logo-offset" is added afterwards. "rotation" counts degrees
counter clockwise, as for panels, and turns the image rather than the
screen, so position and offset stay in screen pixels. The optional
"background-color" goes with the image, since a logo designed for one
background looks wrong on another.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
.../bindings/display/boot-logo.yaml | 151 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 152 insertions(+)
create mode 100644 Documentation/devicetree/bindings/display/boot-logo.yaml
diff --git a/Documentation/devicetree/bindings/display/boot-logo.yaml b/Documentation/devicetree/bindings/display/boot-logo.yaml
new file mode 100644
index 000000000000..bf4121f24f1b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/boot-logo.yaml
@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/boot-logo.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Boot logo supplied by the device tree
+
+maintainers:
+ - Francesco Valla <francesco@valla.it>
+ - Màxim Pedraza Padilla <maximpedraza@gmail.com>
+
+description: |
+ An image for the operating system to show on the display while it boots,
+ handed over by the firmware together with where and how to show it.
+
+ Products built on the same board often differ only in branding. Carrying
+ the logo in the device tree lets a single kernel image serve all of them,
+ each booting with its own device tree, and it works where the device tree
+ is the only thing that reaches the kernel, as with U-Boot's Falcon mode.
+ It plays the part the ACPI BGRT plays on UEFI systems.
+
+ Since a logo is configuration rather than a description of the hardware,
+ the node lives under /chosen, next to the other things firmware hands to
+ the operating system.
+
+ The image is a BMP file with a 40 byte BITMAPINFOHEADER, 24 bits per pixel
+ and no compression, the format of the BGRT image. It is carried either in
+ the node itself or in a reserved memory region the bootloader loaded it
+ into, never both.
+
+properties:
+ $nodename:
+ const: logo
+
+ compatible:
+ const: boot-logo
+
+ image:
+ $ref: /schemas/types.yaml#/definitions/uint8-array
+ description:
+ The BMP file itself, byte for byte, which dtc can fill in from the file
+ with /incbin/. The device tree stays in memory for as long as the
+ system runs, so this is meant for small images; larger ones belong in
+ a reserved memory region.
+
+ memory-region:
+ maxItems: 1
+ description: |
+ Reserved memory region the bootloader loaded the BMP file into, starting
+ at the beginning of the region. The BMP header says how much of the
+ region is image.
+
+ This keeps the image out of the device tree, so that it can be changed
+ without rebuilding the device tree, for instance by loading it from a
+ partition of its own that userspace can update.
+
+ Memory keeps its contents across a reset, and may even across a short
+ power cycle, and the operating system cannot tell a stale image from a
+ fresh one. A bootloader that loads no image into the region therefore
+ disables the node (status = "disabled") or removes it.
+
+ logo-position:
+ $ref: /schemas/types.yaml#/definitions/int32-array
+ description:
+ X and Y coordinates, in screen pixels, of the top left corner of the
+ image once it has been rotated. A value of -1 on an axis centres the
+ image on that axis instead, which is the only way to say it when the
+ device tree does not know the image size, as with a memory region.
+ Defaults to centring on both axes.
+ items:
+ - description: X coordinate, or -1 to centre horizontally
+ minimum: -1
+ maximum: 65535
+ - description: Y coordinate, or -1 to centre vertically
+ minimum: -1
+ maximum: 65535
+
+ logo-offset:
+ $ref: /schemas/types.yaml#/definitions/int32-array
+ description:
+ X and Y displacement, in screen pixels, applied after the image has been
+ placed. Mostly useful together with a centred axis, to land the image
+ somewhere other than the middle of a panel whose visible area is not
+ the middle of the mode.
+ items:
+ - description: X displacement
+ minimum: -65535
+ maximum: 65535
+ - description: Y displacement
+ minimum: -65535
+ maximum: 65535
+
+ rotation:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Rotation applied to the image before it is shown, in degrees counter
+ clockwise, as for the rotation property of panels. It turns the image
+ and not the screen, so a quarter turn swaps how much room the image
+ takes up, but logo-position and logo-offset stay in screen pixels
+ either way.
+ enum: [0, 90, 180, 270]
+ default: 0
+
+ background-color:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Colour of the rest of the screen, as 0xRRGGBB. When absent, the
+ operating system uses its own default.
+ maximum: 0xffffff
+
+required:
+ - compatible
+
+# The image lives either in the node or in a reserved memory region,
+# never both and never neither.
+oneOf:
+ - required:
+ - image
+ - required:
+ - memory-region
+
+additionalProperties: false
+
+examples:
+ - |
+ // A 2x1 image, red and blue, centred and 120 pixels below the middle
+ / {
+ compatible = "foo";
+ model = "foo";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ chosen {
+ logo {
+ compatible = "boot-logo";
+ /* Normally: image = /incbin/("logo.bmp"); */
+ image = /bits/ 8 <0x42 0x4d 0x3e 0x00 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x36 0x00 0x00 0x00 0x28 0x00
+ 0x00 0x00 0x02 0x00 0x00 0x00 0x01 0x00
+ 0x00 0x00 0x01 0x00 0x18 0x00 0x00 0x00
+ 0x00 0x00 0x08 0x00 0x00 0x00 0x13 0x0b
+ 0x00 0x00 0x13 0x0b 0x00 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0xff 0xff 0x00 0x00 0x00 0x00>;
+ logo-position = <(-1) (-1)>;
+ logo-offset = <0 120>;
+ background-color = <0x1e1e28>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 321ecb9f5b5b..0d1decaedde0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9174,6 +9174,7 @@ M: Màxim Pedraza Padilla <maximpedraza@gmail.com>
L: dri-devel@lists.freedesktop.org
S: Maintained
T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
+F: Documentation/devicetree/bindings/display/boot-logo.yaml
F: drivers/gpu/drm/clients/drm_splash.c
DRM TTM SUBSYSTEM
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 07/13] drm/client: splash: add a device tree image source
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (5 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 06/13] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:51 ` sashiko-bot
2026-10-13 9:32 ` [PATCH v5 08/13] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
` (5 subsequent siblings)
12 siblings, 1 reply; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
Embedded products often share one kernel image and one board support
package across several models that differ in branding. The splash
client can show the firmware's BGRT image or a BMP loaded as firmware,
but a device tree system has no BGRT, and a BMP loaded as firmware is
either built into the kernel, which means one kernel per logo, or read
from a filesystem that is not there yet when the display comes up. With
U-Boot's Falcon mode the SPL starts the kernel directly, and the device
tree is the only thing that reaches it.
Add a source that takes the image from a "boot-logo" node under /chosen.
The BMP either sits in the node itself, in an "image" property that dtc
fills in with /incbin/, or in a reserved memory region the bootloader
loaded it into, named by "memory-region". Exactly one of the two must be
present; a node with both or neither is ignored with a warning.
The image in the node is used where it is: the device tree stays in
memory, so there is nothing to copy or release. The region is mapped
like the BGRT image, and the BMP header is checked against its size.
A node is only there if someone put it there for that board, so the
device tree goes first, then the BGRT, then the BMP loaded as firmware.
The boolean that chose between the latter two becomes an enum naming
the one source in use.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 15 +++
drivers/gpu/drm/clients/drm_splash.c | 166 ++++++++++++++++++++++++---
2 files changed, 165 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index cbe271c3a23f..aef3781cc110 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -108,6 +108,21 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
The color can be overridden through the drm_client_lib.splash_color
kernel command line parameter.
+config DRM_CLIENT_SPLASH_SRC_DT
+ bool "Device tree as splash source"
+ depends on DRM_CLIENT_SPLASH
+ depends on OF
+ select DRM_CLIENT_SPLASH_BMP_SUPPORT
+ help
+ Use a BMP image described by a "boot-logo" node under /chosen in
+ the device tree, either carried in the node itself or placed by the
+ bootloader in a reserved memory region. This lets one kernel image
+ show a different logo on each board or product, chosen by the
+ device tree it boots with.
+
+ When present, the device tree image is preferred over the other
+ sources.
+
config DRM_CLIENT_SPLASH_SRC_BGRT
bool "EFI BGRT as splash source"
depends on DRM_CLIENT_SPLASH
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index a7d627954158..50842922a7f8 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -9,9 +9,12 @@
#include <linux/efi-bgrt.h>
#include <linux/firmware.h>
#include <linux/init.h>
+#include <linux/io.h>
#include <linux/iosys-map.h>
#include <linux/kthread.h>
#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/unaligned.h>
@@ -71,6 +74,14 @@ struct bmp_dib_header {
__le32 colors_important;
} __packed;
+/* Where the image comes from, in order of preference */
+enum drm_splash_source {
+ DRM_SPLASH_SRC_NONE,
+ DRM_SPLASH_SRC_DT,
+ DRM_SPLASH_SRC_BGRT,
+ DRM_SPLASH_SRC_BMP,
+};
+
struct drm_splash_scanout {
int id;
/* Modesets scanning this buffer out, by position in the list */
@@ -110,7 +121,7 @@ struct drm_splash {
bool render_done;
void *map_data;
- bool use_bgrt;
+ enum drm_splash_source src;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -212,6 +223,114 @@ static int drm_splash_fw_request_bmp(struct drm_splash *splash)
}
#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_DT)
+#define DRM_SPLASH_DT_COMPATIBLE "boot-logo"
+
+/*
+ * The boot logo node, if the device tree has a usable one. Only a child of
+ * /chosen is considered, not anything compatible anywhere in the tree: the
+ * image is configuration handed over by the firmware, not a device.
+ */
+static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct device_node *np;
+ bool image, region;
+
+ np = of_get_compatible_child(of_chosen, DRM_SPLASH_DT_COMPATIBLE);
+ if (!np)
+ return NULL;
+
+ if (!of_device_is_available(np))
+ goto out_put;
+
+ image = of_property_present(np, "image");
+ region = of_property_present(np, "memory-region");
+ if (image == region) {
+ drm_warn(client->dev,
+ "splash: %pOF needs exactly one of image or memory-region",
+ np);
+ goto out_put;
+ }
+
+ return np;
+
+out_put:
+ of_node_put(np);
+ return NULL;
+}
+
+static bool drm_splash_dt_available(struct drm_splash *splash)
+{
+ struct device_node *np = drm_splash_dt_node(splash);
+
+ of_node_put(np);
+
+ return np;
+}
+
+static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct device_node *np;
+ struct resource res;
+ const void *prop;
+ int len, ret = 0;
+
+ np = drm_splash_dt_node(splash);
+ if (!np)
+ return -ENOENT;
+
+ /*
+ * An image in the node is used in place: the device tree stays in
+ * memory for as long as the system runs, so there is nothing to copy
+ * and nothing to release.
+ */
+ prop = of_get_property(np, "image", &len);
+ if (prop) {
+ *data = prop;
+ *size = len;
+ goto out_put;
+ }
+
+ /* Otherwise the bootloader loaded it into a reserved region */
+ ret = of_reserved_mem_region_to_resource(np, 0, &res);
+ if (ret) {
+ drm_warn(client->dev, "splash: %pOF: no usable memory-region: %d",
+ np, ret);
+ goto out_put;
+ }
+
+ splash->map_data = memremap(res.start, resource_size(&res),
+ MEMREMAP_WB);
+ if (!splash->map_data) {
+ drm_warn(client->dev, "splash: failed to map %pR", &res);
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ /* The BMP header, checked against this size, says how much is image */
+ *data = splash->map_data;
+ *size = resource_size(&res);
+
+out_put:
+ of_node_put(np);
+ return ret;
+}
+#else
+static bool drm_splash_dt_available(struct drm_splash *splash)
+{
+ return false;
+}
+
+static int drm_splash_dt_load(struct drm_splash *splash,
+ const u8 **data, size_t *size)
+{
+ return -EOPNOTSUPP;
+}
+#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_DT
+
static bool drm_splash_bgrt_available(struct drm_splash *splash)
{
struct drm_client_dev *client = &splash->client;
@@ -489,7 +608,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return -EINVAL;
}
- if (splash->use_bgrt) {
+ if (splash->src == DRM_SPLASH_SRC_BGRT) {
x_pad = efi_bgrt_image_offset_x();
y_pad = efi_bgrt_image_offset_y();
@@ -582,19 +701,34 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
size_t *img_data_len)
{
- int ret = 0;
-
- if (splash->use_bgrt) {
- ret = drm_splash_bgrt_load(splash, img_data, img_data_len);
- if (ret)
- splash->use_bgrt = false;
+ switch (splash->src) {
+ case DRM_SPLASH_SRC_DT:
+ return drm_splash_dt_load(splash, img_data, img_data_len);
+ case DRM_SPLASH_SRC_BGRT:
+ return drm_splash_bgrt_load(splash, img_data, img_data_len);
+ case DRM_SPLASH_SRC_BMP:
+ return drm_splash_fw_load(splash, img_data, img_data_len);
+ default:
+ return -ENOENT;
}
+}
- /* BGRT failed to load */
- if (!splash->use_bgrt)
- ret = drm_splash_fw_load(splash, img_data, img_data_len);
-
- return ret;
+/*
+ * Pick the one source the image will come from. A device tree node is only
+ * there if someone put it there for this board, so it goes first; the BGRT
+ * is whatever the firmware shows, and the BMP loaded as firmware is the
+ * fallback. With none of them, only the background colour is drawn.
+ */
+static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
+{
+ if (drm_splash_dt_available(splash))
+ return DRM_SPLASH_SRC_DT;
+ if (drm_splash_bgrt_available(splash))
+ return DRM_SPLASH_SRC_BGRT;
+ if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
+ return DRM_SPLASH_SRC_BMP;
+
+ return DRM_SPLASH_SRC_NONE;
}
static void drm_splash_image_cleanup(struct drm_splash *splash)
@@ -909,10 +1043,10 @@ static int drm_splash_client_hotplug(struct drm_client_dev *client)
get_task_struct(thread);
splash->thread = thread;
- splash->use_bgrt = drm_splash_bgrt_available(splash);
+ splash->src = drm_splash_pick_source(splash);
- /* If no other image has been loaded, try to load a BMP as firmware */
- if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP) && !splash->use_bgrt) {
+ /* The BMP loaded as firmware is the only source that arrives later */
+ if (splash->src == DRM_SPLASH_SRC_BMP) {
WRITE_ONCE(splash->fw_pending, true);
ret = drm_splash_fw_request_bmp(splash);
splash->fw_requested = !ret;
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* Re: [PATCH v5 07/13] drm/client: splash: add a device tree image source
2026-10-13 9:32 ` [PATCH v5 07/13] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
@ 2026-10-13 9:51 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-13 9:51 UTC (permalink / raw)
To: Màxim Pedraza Padilla; +Cc: dri-devel, devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] of: use-after-free of DT property data due to premature of_node_put()
--
commit 98dbc79d3322b530cd1f8ff31d16dc494f5db1c5
Author: Màxim Pedraza Padilla <maximpedraza@gmail.com>
drm/client: splash: add a device tree image source
This commit adds a device tree image source for the splash client,
allowing it to load a BMP image from a boot-logo node under /chosen
in the device tree.
> diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
> index a7d6279541587..50842922a7f82 100644
> --- a/drivers/gpu/drm/clients/drm_splash.c
> +++ b/drivers/gpu/drm/clients/drm_splash.c
[ ... ]
> @@ -212,6 +223,114 @@ static int drm_splash_fw_request_bmp(struct drm_splash *splash)
[ ... ]
> +static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
> + size_t *size)
> +{
> + struct drm_client_dev *client = &splash->client;
> + struct device_node *np;
> + struct resource res;
> + const void *prop;
> + int len, ret = 0;
> +
> + np = drm_splash_dt_node(splash);
> + if (!np)
> + return -ENOENT;
> +
> + /*
> + * An image in the node is used in place: the device tree stays in
> + * memory for as long as the system runs, so there is nothing to copy
> + * and nothing to release.
> + */
> + prop = of_get_property(np, "image", &len);
> + if (prop) {
> + *data = prop;
> + *size = len;
> + goto out_put;
> + }
[ ... ]
> +out_put:
> + of_node_put(np);
> + return ret;
> +}
[Severity: High]
Does this code risk a use-after-free if the device tree node is dynamically
removed?
The comment notes the device tree stays in memory, but if device tree overlays
are used and the node is dynamically removed, its properties will be freed
when the reference count reaches zero.
Here, drm_splash_dt_load() assigns the pointer from of_get_property() to
*data, but then immediately drops the reference to the device tree node by
calling of_node_put() before returning.
Later, the asynchronous render thread could read this freed memory when parsing
the BMP header and blitting the image via drm_splash_bmp_to_scanout().
Should the node reference be kept in struct drm_splash and only released
later in drm_splash_image_cleanup()?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261013093243.505776-1-maximpedraza@gmail.com?part=7
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 08/13] drm/client: splash: place the device tree image where it asks
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (6 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 07/13] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 09/13] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
` (4 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
A logo supplied by the device tree describes a particular product, and
where it lands on the panel matters: some panels only show part of the
mode, so the middle of the mode is not the middle of what the user
sees.
Read the optional "logo-position" and "logo-offset" properties of the
node. The position is the top left corner, with -1 on an axis meaning
centre on that axis, and the offset is added afterwards, which is the
only way to say "below the centre" when the device tree does not know
the image size, as with an image in a reserved region.
Both come straight from the device tree, so the sum is done in 64 bits
and clamped, and a silly pair of values lands the image against an
edge rather than off the screen: the blitters do not clip. Every other
source keeps being centred, except the BGRT, which keeps its own
offsets.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 73 ++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 50842922a7f8..8561d00a5b96 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -122,6 +122,14 @@ struct drm_splash {
void *map_data;
enum drm_splash_source src;
+
+ /*
+ * Where the image goes, in screen pixels: a coordinate of -1 centres
+ * it on that axis, and the offset is added afterwards. Only the device
+ * tree asks for anything but the centre.
+ */
+ s32 pos_x, pos_y;
+ s32 off_x, off_y;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -260,13 +268,21 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-static bool drm_splash_dt_available(struct drm_splash *splash)
+/* The optional placement properties of the node, read once */
+static void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
{
- struct device_node *np = drm_splash_dt_node(splash);
+ u32 val[2];
- of_node_put(np);
+ if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
+ splash->pos_x = (s32)val[0];
+ splash->pos_y = (s32)val[1];
+ }
- return np;
+ if (!of_property_read_u32_array(np, "logo-offset", val, 2)) {
+ splash->off_x = (s32)val[0];
+ splash->off_y = (s32)val[1];
+ }
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -319,9 +335,14 @@ static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
return ret;
}
#else
-static bool drm_splash_dt_available(struct drm_splash *splash)
+static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
+{
+ return NULL;
+}
+
+static void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
{
- return false;
}
static int drm_splash_dt_load(struct drm_splash *splash,
@@ -438,6 +459,26 @@ static int drm_splash_fill_solid_color(struct drm_client_buffer *buffer,
}
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+/*
+ * Place the image along one axis of the screen. @pos is the coordinate asked
+ * for, or -1 to centre on that axis, and @off is added afterwards. Both may
+ * come from the device tree, so the sum is done wide enough not to wrap, and
+ * the result keeps the whole image on the screen: the blitters do not clip.
+ */
+static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
+ unsigned int size)
+{
+ s64 last = (s64)span - size;
+ s64 coord;
+
+ if (last <= 0)
+ return 0;
+
+ coord = pos == -1 ? last / 2 : pos;
+
+ return clamp_t(s64, coord + off, 0, last);
+}
+
static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
unsigned int x_pad, unsigned int y_pad,
const u8 *sbuf8, unsigned int spitch,
@@ -636,9 +677,10 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
#endif
} else {
- /* Center X and Y */
- x_pad = (scanout->width - bmp_width) / 2;
- y_pad = (scanout->height - bmp_height) / 2;
+ x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
+ scanout->width, bmp_width);
+ y_pad = drm_splash_place_axis(splash->pos_y, splash->off_y,
+ scanout->height, bmp_height);
}
image_data = data + bitmap_offset;
@@ -721,8 +763,15 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
*/
static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
{
- if (drm_splash_dt_available(splash))
+ struct device_node *np;
+
+ np = drm_splash_dt_node(splash);
+ if (np) {
+ drm_splash_dt_placement(splash, np);
+ of_node_put(np);
return DRM_SPLASH_SRC_DT;
+ }
+
if (drm_splash_bgrt_available(splash))
return DRM_SPLASH_SRC_BGRT;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
@@ -1144,6 +1193,10 @@ void drm_splash_register(struct drm_device *dev,
spin_lock_init(&splash->fw_lock);
init_completion(&splash->fw_done);
+ /* Centred unless the image source asks otherwise */
+ splash->pos_x = -1;
+ splash->pos_y = -1;
+
if (format && format->num_planes == 1)
splash->preferred_format = format->format;
else
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 09/13] drm/client: splash: turn the device tree image as it asks
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (7 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 08/13] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 10/13] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
` (3 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
The same board is often fitted with the same panel mounted in more than
one orientation. Let the device tree node ask for the image to be turned,
with a "rotation" property counting degrees counter clockwise, as panels
already do and as DRM_MODE_ROTATE_* count, which is what it is kept as.
The rotation turns the image and not the screen: "logo-position" and
"logo-offset" stay in screen pixels, and a quarter turn only swaps how
much room the image takes up. Placing it in a frame that turned with it
would make the same pair of coordinates mean different places on the
same panel, decided by a property that describes the image.
The three blitters, one per destination pixel size, become one that
reads the source pixel through the rotation and writes it according to
the pixel size. Each source pixel is read as three bytes rather than as
an unaligned u32: a BMP row is only padded as a whole, so with 3 * width
already a multiple of four the last pixel of the image was read one byte
past its end. An upright image going to an RGB888 buffer is still copied
a row at a time.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 206 ++++++++++++++-------------
1 file changed, 110 insertions(+), 96 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 8561d00a5b96..fdf398987007 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -25,6 +25,7 @@
#include <drm/drm_drv.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
+#include <drm/drm_mode.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
@@ -130,6 +131,8 @@ struct drm_splash {
*/
s32 pos_x, pos_y;
s32 off_x, off_y;
+ /* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
+ unsigned int rotation;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -283,6 +286,28 @@ static void drm_splash_dt_placement(struct drm_splash *splash,
splash->off_x = (s32)val[0];
splash->off_y = (s32)val[1];
}
+
+ /* Degrees counter clockwise, as for panels, and as DRM counts them */
+ if (!of_property_read_u32(np, "rotation", &val[0])) {
+ switch (val[0]) {
+ case 0:
+ splash->rotation = DRM_MODE_ROTATE_0;
+ break;
+ case 90:
+ splash->rotation = DRM_MODE_ROTATE_90;
+ break;
+ case 180:
+ splash->rotation = DRM_MODE_ROTATE_180;
+ break;
+ case 270:
+ splash->rotation = DRM_MODE_ROTATE_270;
+ break;
+ default:
+ drm_warn(splash->client.dev,
+ "splash: %pOF: unsupported rotation %u",
+ np, val[0]);
+ }
+ }
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -479,73 +504,68 @@ static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
return clamp_t(s64, coord + off, 0, last);
}
-static void drm_splash_blit_pix16(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
-{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
-
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
- dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * sizeof(u16);
-
- iosys_map_wr(map, wr_off, u16, dcolor);
- }
- }
-}
-
-static void drm_splash_blit_pix24(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
-{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
-
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
- dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * 3;
-
- iosys_map_wr(map, wr_off, u8, (dcolor & 0x000000FF) >> 0);
- iosys_map_wr(map, wr_off + 1, u8, (dcolor & 0x0000FF00) >> 8);
- iosys_map_wr(map, wr_off + 2, u8, (dcolor & 0x00FF0000) >> 16);
- }
- }
-}
-
-static void drm_splash_blit_pix32(struct iosys_map *map, unsigned int dpitch,
- unsigned int x_pad, unsigned int y_pad,
- const u8 *sbuf8, unsigned int spitch,
- unsigned int width, unsigned int height,
- bool invert_y, u32 format)
+/*
+ * Copy the BMP pixels into the buffer, converted to its format and turned by
+ * @rotation, one of DRM_MODE_ROTATE_*, which count counter clockwise. The
+ * rotation turns the image, not the screen: a quarter turn swaps how much
+ * room the image takes up, and @x_pad and @y_pad place that room on screen.
+ *
+ * Each source pixel is three bytes read on their own, since a BMP row is
+ * only padded to four bytes as a whole and the image need not be aligned.
+ */
+static void drm_splash_blit(struct iosys_map *map, unsigned int dpitch,
+ u32 format, unsigned int cpp,
+ unsigned int x_pad, unsigned int y_pad,
+ const u8 *sbuf8, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y, unsigned int rotation)
{
- unsigned int x, y, src_offset, dst_offset;
- u32 scolor, dcolor, wr_off;
-
- for (y = 0; y < height; y++) {
- src_offset = (invert_y ? (height - y - 1) : y) * spitch;
- dst_offset = (y_pad + y) * dpitch;
+ bool quarter = rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int dw = quarter ? height : width;
+ unsigned int dh = quarter ? width : height;
+ unsigned int x, y, sx, sy, row, off;
+ u32 scolor, dcolor;
+
+ for (y = 0; y < dh; y++) {
+ for (x = 0; x < dw; x++) {
+ switch (rotation) {
+ case DRM_MODE_ROTATE_90:
+ sx = width - 1 - y;
+ sy = x;
+ break;
+ case DRM_MODE_ROTATE_180:
+ sx = width - 1 - x;
+ sy = height - 1 - y;
+ break;
+ case DRM_MODE_ROTATE_270:
+ sx = y;
+ sy = height - 1 - x;
+ break;
+ default:
+ sx = x;
+ sy = y;
+ break;
+ }
- for (x = 0; x < width; x++) {
- scolor = *(const u32 *)(&sbuf8[src_offset + 3 * x]);
+ /* A positive BMP height stores the rows bottom up */
+ row = invert_y ? height - 1 - sy : sy;
+ scolor = get_unaligned_le24(&sbuf8[row * spitch + 3 * sx]);
dcolor = drm_draw_color_from_xrgb8888(scolor, format);
- wr_off = dst_offset + (x_pad + x) * sizeof(u32);
+ off = (y_pad + y) * dpitch + (x_pad + x) * cpp;
- iosys_map_wr(map, wr_off, u32, dcolor);
+ switch (cpp) {
+ case 2:
+ iosys_map_wr(map, off, u16, dcolor);
+ break;
+ case 3:
+ iosys_map_wr(map, off, u8, dcolor & 0xff);
+ iosys_map_wr(map, off + 1, u8, (dcolor >> 8) & 0xff);
+ iosys_map_wr(map, off + 2, u8, (dcolor >> 16) & 0xff);
+ break;
+ case 4:
+ iosys_map_wr(map, off, u32, dcolor);
+ break;
+ }
}
}
}
@@ -584,7 +604,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
bool bmp_invert_y;
u32 bitmap_offset;
size_t row_len, pixels_len, end;
- unsigned int x_pad, y_pad;
+ unsigned int x_pad, y_pad, img_w, img_h;
const u8 *image_data;
struct iosys_map map;
struct drm_rect r;
@@ -644,7 +664,16 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
bmp_pitch = row_len & ~(size_t)3;
- if (bmp_width > scanout->width || bmp_height > scanout->height) {
+ /* The room the image takes up on screen, once turned */
+ if (splash->rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) {
+ img_w = bmp_height;
+ img_h = bmp_width;
+ } else {
+ img_w = bmp_width;
+ img_h = bmp_height;
+ }
+
+ if (img_w > scanout->width || img_h > scanout->height) {
drm_err(client->dev, "splash: BMP image is too big for the screen");
return -EINVAL;
}
@@ -678,9 +707,9 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
#endif
} else {
x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
- scanout->width, bmp_width);
+ scanout->width, img_w);
y_pad = drm_splash_place_axis(splash->pos_y, splash->off_y,
- scanout->height, bmp_height);
+ scanout->height, img_h);
}
image_data = data + bitmap_offset;
@@ -691,40 +720,24 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return ret;
}
- r = DRM_RECT_INIT(x_pad, y_pad, bmp_width, bmp_height);
+ r = DRM_RECT_INIT(x_pad, y_pad, img_w, img_h);
- /* In case the target format is RGB888, source data can be copied to
- * the video buffer line by line, avoiding some overhead.
+ /*
+ * An unturned image going to an RGB888 buffer has the same byte order
+ * as the BMP, so it is copied a row at a time.
*/
- if (scanout->format == DRM_FORMAT_RGB888) {
+ if (scanout->format == DRM_FORMAT_RGB888 &&
+ splash->rotation == DRM_MODE_ROTATE_0) {
drm_splash_blit_rgb888(&map, fb->pitches[0], x_pad, y_pad,
image_data, bmp_pitch, bmp_width,
bmp_height, bmp_invert_y);
+ } else if (px_width >= 2 && px_width <= 4) {
+ drm_splash_blit(&map, fb->pitches[0], scanout->format, px_width,
+ x_pad, y_pad, image_data, bmp_pitch, bmp_width,
+ bmp_height, bmp_invert_y, splash->rotation);
} else {
- switch (px_width) {
- case 2:
- drm_splash_blit_pix16(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- case 3:
- drm_splash_blit_pix24(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- case 4:
- drm_splash_blit_pix32(&map, fb->pitches[0], x_pad,
- y_pad, image_data, bmp_pitch,
- bmp_width, bmp_height,
- bmp_invert_y, scanout->format);
- break;
- default:
- drm_warn_once(client->dev,
- "splash: can't blit with pixel width %d",
- px_width);
- }
+ drm_warn_once(client->dev, "splash: can't blit with pixel width %d",
+ px_width);
}
drm_client_buffer_vunmap_local(buffer);
@@ -1193,9 +1206,10 @@ void drm_splash_register(struct drm_device *dev,
spin_lock_init(&splash->fw_lock);
init_completion(&splash->fw_done);
- /* Centred unless the image source asks otherwise */
+ /* Centred and upright unless the image source asks otherwise */
splash->pos_x = -1;
splash->pos_y = -1;
+ splash->rotation = DRM_MODE_ROTATE_0;
if (format && format->num_planes == 1)
splash->preferred_format = format->format;
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 10/13] drm/client: splash: take the background colour from the image source
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (8 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 09/13] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 11/13] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
` (2 subsequent siblings)
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
A logo designed for a light background looks wrong framed in black, and
products that share a board support package differ in their colours as
much as in their logos. The background colour belongs with the image,
so take it from the same place:
- a device tree image uses the optional "background-color" of its node;
- the BGRT carries no colour, and firmware splashes sit on black, so it
uses the kernel's default;
- the BMP loaded as firmware is named on the command line, and so is its
colour, through drm_client_lib.splash_color, which also covers the
screen when there is no image at all.
CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR remains the default wherever
the source says nothing. The command line no longer overrides a device
tree image: a bootloader that wants another colour for a given boot,
say to flag a failed test on a production line, changes the node.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 7 ++++--
drivers/gpu/drm/clients/drm_splash.c | 36 +++++++++++++++++++++-------
2 files changed, 32 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index aef3781cc110..f471019bab87 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -105,8 +105,11 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
help
The default splash background color, in RGB888 format.
- The color can be overridden through the drm_client_lib.splash_color
- kernel command line parameter.
+ The background color comes from the same place as the image. A
+ device tree image can carry its own, and the drm_client_lib.splash_color
+ kernel command line parameter sets it for a BMP loaded as firmware,
+ or for the whole screen when there is no image. This color is used
+ otherwise, and always with the BGRT image, which carries none.
config DRM_CLIENT_SPLASH_SRC_DT
bool "Device tree as splash source"
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index fdf398987007..463aff27c747 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -42,7 +42,8 @@
static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
module_param(splash_color, uint, 0400);
-MODULE_PARM_DESC(splash_color, "Splash background color (RGB888)");
+MODULE_PARM_DESC(splash_color,
+ "Background color (RGB888) around the BMP loaded as firmware, or of the whole screen with no image");
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
@@ -133,6 +134,8 @@ struct drm_splash {
s32 off_x, off_y;
/* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
unsigned int rotation;
+ /* RGB888, from the same place as the image */
+ u32 bg_color;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -271,12 +274,18 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-/* The optional placement properties of the node, read once */
-static void drm_splash_dt_placement(struct drm_splash *splash,
- struct device_node *np)
+/* The optional properties of the node, read once */
+static void drm_splash_dt_props(struct drm_splash *splash,
+ struct device_node *np)
{
u32 val[2];
+ /* The colour travels with the image, or it is the kernel's default */
+ if (!of_property_read_u32(np, "background-color", &val[0]))
+ splash->bg_color = val[0] & 0xffffff;
+ else
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+
if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
splash->pos_x = (s32)val[0];
splash->pos_y = (s32)val[1];
@@ -365,8 +374,8 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
-static void drm_splash_dt_placement(struct drm_splash *splash,
- struct device_node *np)
+static void drm_splash_dt_props(struct drm_splash *splash,
+ struct device_node *np)
{
}
@@ -780,13 +789,22 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
np = drm_splash_dt_node(splash);
if (np) {
- drm_splash_dt_placement(splash, np);
+ drm_splash_dt_props(splash, np);
of_node_put(np);
return DRM_SPLASH_SRC_DT;
}
- if (drm_splash_bgrt_available(splash))
+ /* The BGRT carries no colour: firmware splashes sit on black */
+ if (drm_splash_bgrt_available(splash)) {
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
return DRM_SPLASH_SRC_BGRT;
+ }
+
+ /*
+ * The BMP loaded as firmware is named on the command line, and so is
+ * its colour, which also covers the screen when there is no image.
+ */
+ splash->bg_color = splash_color & 0xffffff;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
return DRM_SPLASH_SRC_BMP;
@@ -844,7 +862,7 @@ static int drm_splash_render_thread(void *data)
if (!scanout->bg_drawn) {
drm_dbg(client->dev, "draw background for scanout %d", i);
- u32 color = drm_draw_color_from_xrgb8888(splash_color,
+ u32 color = drm_draw_color_from_xrgb8888(splash->bg_color,
scanout->format);
drm_splash_fill_solid_color(scanout->buffer, color);
scanout->bg_drawn = true;
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 11/13] drm/client: splash: turn the BGRT image on panels mounted turned
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (9 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 10/13] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 12/13] drm/client: splash: prefer what the command line asks for Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 13/13] drm/client: splash: document the image sources and parameters Màxim Pedraza Padilla
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
Since ACPI 6.2, bits 1-2 of the BGRT status tell that the firmware
image is not pre-rotated and how far it has to be turned to show
upright on a panel mounted turned. The client skipped such images
altogether, so tablets and 2-in-1s with a portrait panel got no splash.
Turn the image as the bits say. The values count 90 degree steps
counter clockwise, as Plymouth, the main user of the BGRT, applies
them, and as DRM_MODE_ROTATE_* count, which is what they become.
The offsets need more care. For such an image the firmware gives them
on the upright screen, while the client draws into the buffer as it is
scanned out, which on a panel mounted turned is the upright screen
turned the other way. Check them and apply the efifb sanity check in
the upright frame, then move the image's rectangle into the buffer's
frame. An image without the bits is drawn at its offsets as before:
the buffer is the frame the firmware drew it in, so a pre-rotated image
needs none of the guessing userspace has to do for it.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 67 +++++++++++++++++++++++-----
1 file changed, 56 insertions(+), 11 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 463aff27c747..4c30de7c5c16 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -398,11 +398,6 @@ static bool drm_splash_bgrt_available(struct drm_splash *splash)
return false;
}
- if (efi_bgrt_status() & ACPI_BGRT_ORIENTATION_OFFSET) {
- drm_info(client->dev, "splash: BGRT rotation bits set, skipping");
- return false;
- }
-
return true;
}
@@ -431,6 +426,26 @@ static int drm_splash_bgrt_load(struct drm_splash *splash, const u8 **data,
return 0;
}
+/*
+ * Since ACPI 6.2, bits 1-2 of the BGRT status say that the image is not
+ * pre-rotated for a panel mounted turned, and by how much it has to be
+ * turned to show upright on it: 0, 90, 180 or 270 degrees, which firmware
+ * and userspace (Plymouth) apply counter clockwise, as DRM counts them.
+ */
+static unsigned int drm_splash_bgrt_rotation(void)
+{
+ switch ((efi_bgrt_status() & ACPI_BGRT_ORIENTATION_OFFSET) >> 1) {
+ case 1:
+ return DRM_MODE_ROTATE_90;
+ case 2:
+ return DRM_MODE_ROTATE_180;
+ case 3:
+ return DRM_MODE_ROTATE_270;
+ default:
+ return DRM_MODE_ROTATE_0;
+ }
+}
+
static u32 drm_splash_find_usable_format(struct drm_plane *plane,
u32 preferred_format)
{
@@ -688,11 +703,21 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
if (splash->src == DRM_SPLASH_SRC_BGRT) {
- x_pad = efi_bgrt_image_offset_x();
- y_pad = efi_bgrt_image_offset_y();
-
- if ((x_pad + bmp_width) > scanout->width ||
- (y_pad + bmp_height) > scanout->height) {
+ /*
+ * The BGRT offsets place the upright image on the upright
+ * screen, which for a panel mounted turned is not the frame
+ * the buffer is scanned out in. Work in that frame, then move
+ * the image's rectangle into the buffer's.
+ */
+ bool quarter = splash->rotation &
+ (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int lw = quarter ? scanout->height : scanout->width;
+ unsigned int lh = quarter ? scanout->width : scanout->height;
+ unsigned int xo = efi_bgrt_image_offset_x();
+ unsigned int yo = efi_bgrt_image_offset_y();
+
+ if (xo > lw || bmp_width > lw - xo ||
+ yo > lh || bmp_height > lh - yo) {
drm_err(client->dev, "splash: BGRT image would overflow");
return -EINVAL;
}
@@ -709,11 +734,30 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
* the display looking quite bad.
*/
- if (x_pad != (scanout->width - bmp_width) / 2) {
+ if (xo != (lw - bmp_width) / 2) {
drm_err(client->dev, "splash: BGRT sanity check failed");
return -EINVAL;
}
#endif
+
+ switch (splash->rotation) {
+ case DRM_MODE_ROTATE_90:
+ x_pad = yo;
+ y_pad = lw - xo - bmp_width;
+ break;
+ case DRM_MODE_ROTATE_180:
+ x_pad = lw - xo - bmp_width;
+ y_pad = lh - yo - bmp_height;
+ break;
+ case DRM_MODE_ROTATE_270:
+ x_pad = lh - yo - bmp_height;
+ y_pad = xo;
+ break;
+ default:
+ x_pad = xo;
+ y_pad = yo;
+ break;
+ }
} else {
x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
scanout->width, img_w);
@@ -797,6 +841,7 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
/* The BGRT carries no colour: firmware splashes sit on black */
if (drm_splash_bgrt_available(splash)) {
splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+ splash->rotation = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 12/13] drm/client: splash: prefer what the command line asks for
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (10 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 11/13] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
2026-10-13 9:32 ` [PATCH v5 13/13] drm/client: splash: document the image sources and parameters Màxim Pedraza Padilla
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
A logo or a colour given on the kernel command line was asked for this
very boot: a bootloader that picks the product variant from an EEPROM
and passes it along, a developer, or a production line that turns the
screen red when a test fails. The device tree, the BGRT and the kernel's
defaults describe what the board or the build normally wants. Let the
command line win, as console= wins over stdout-path.
Each parameter wins for what it says, and only when it is given, so
none has a default of its own:
- drm_client_lib.splash_bmp names a BMP to load as firmware, preferred
over the device tree and the BGRT. Without it the order is unchanged:
device tree, BGRT, then "drm_splash.bmp" loaded as firmware. An empty
value counts as not given. A file that is named but missing gives no
image rather than falling back, since it is what was asked for.
- drm_client_lib.splash_bmp=none asks for no image at all: only the
background is drawn, whatever the other sources have.
- drm_client_lib.splash_pos=X,Y, splash_offset=DX,DY and
splash_rotation=0|90|180|270 place and turn the image with the same
meaning as the device tree properties, whatever the image's source.
A BMP loaded as firmware has no other way to be placed or turned, and
for the others they let a placement be tried without rebuilding a
device tree. Positions take both values or are ignored with a
warning. When they place a BGRT image, it is placed in screen pixels
like any other, and neither the table offsets nor the efifb sanity
check apply.
- drm_client_lib.splash_color sets the background whatever the image.
Without it the colour comes from the source that supplied the image,
a device tree node's "background-color", and otherwise from
CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR. A device tree colour is not
used around an image named on the command line: it belongs to another
image.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 20 ++--
drivers/gpu/drm/clients/drm_splash.c | 171 +++++++++++++++++++++------
2 files changed, 142 insertions(+), 49 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index f471019bab87..2a374bea370b 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -105,11 +105,9 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
help
The default splash background color, in RGB888 format.
- The background color comes from the same place as the image. A
- device tree image can carry its own, and the drm_client_lib.splash_color
- kernel command line parameter sets it for a BMP loaded as firmware,
- or for the whole screen when there is no image. This color is used
- otherwise, and always with the BGRT image, which carries none.
+ The drm_client_lib.splash_color kernel command line parameter, when
+ given, is used whatever the image. Otherwise a device tree image can
+ carry its own color, and this one is used for everything else.
config DRM_CLIENT_SPLASH_SRC_DT
bool "Device tree as splash source"
@@ -123,8 +121,9 @@ config DRM_CLIENT_SPLASH_SRC_DT
show a different logo on each board or product, chosen by the
device tree it boots with.
- When present, the device tree image is preferred over the other
- sources.
+ When present, the device tree image is preferred over the BGRT and
+ over the default BMP loaded as firmware. A BMP named on the kernel
+ command line is preferred over it.
config DRM_CLIENT_SPLASH_SRC_BGRT
bool "EFI BGRT as splash source"
@@ -150,9 +149,10 @@ config DRM_CLIENT_SPLASH_SRC_BMP
The image will be loaded using the firmware loading facility the
kernel provides; it shall use 24 bits per pixel and shall not be
- compressed. The name of the file can be set through the
- drm_client_lib.splash_bmp command line parameter, with the default
- being 'drm_splash.bmp'.
+ compressed. A file named through the drm_client_lib.splash_bmp
+ command line parameter is preferred over any other image source;
+ otherwise 'drm_splash.bmp' is used when neither the device tree nor
+ the BGRT provides an image.
config DRM_CLIENT_SPLASH_BMP_SUPPORT
bool
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 4c30de7c5c16..46faae025ec2 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -40,18 +40,90 @@
* a static image.
*/
-static unsigned int splash_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
-module_param(splash_color, uint, 0400);
+/*
+ * What is given on the command line wins over the device tree and the
+ * firmware tables, and each parameter only for what it says. Neither has a
+ * value of its own until it is given: -1 and NULL mean "not given".
+ */
+static int splash_color = -1;
+module_param(splash_color, int, 0400);
MODULE_PARM_DESC(splash_color,
- "Background color (RGB888) around the BMP loaded as firmware, or of the whole screen with no image");
+ "Background color as 0xRRGGBB, preferred over any other");
+
+static int splash_pos[2];
+static unsigned int splash_pos_n;
+module_param_array(splash_pos, int, &splash_pos_n, 0400);
+MODULE_PARM_DESC(splash_pos,
+ "Image position as X,Y in screen pixels, -1 centres that axis");
+
+static int splash_offset[2];
+static unsigned int splash_offset_n;
+module_param_array(splash_offset, int, &splash_offset_n, 0400);
+MODULE_PARM_DESC(splash_offset,
+ "Displacement as DX,DY in screen pixels, added after placing");
+
+static int splash_rotation = -1;
+module_param(splash_rotation, int, 0400);
+MODULE_PARM_DESC(splash_rotation,
+ "Image rotation in degrees counter clockwise: 0, 90, 180 or 270");
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP)
#define DEFAULT_SPLASH_BMP "drm_splash.bmp"
-static char *splash_bmp = DEFAULT_SPLASH_BMP;
+static char *splash_bmp;
module_param(splash_bmp, charp, 0400);
-MODULE_PARM_DESC(splash_bmp, "Name of splash image (default: \"" DEFAULT_SPLASH_BMP "\")");
+MODULE_PARM_DESC(splash_bmp,
+ "BMP to load as firmware, preferred over any other image");
+
+/* Given on the command line, and not empty */
+static bool drm_splash_bmp_given(void)
+{
+ return splash_bmp && *splash_bmp;
+}
+
+/* "none" asks for no image at all, whatever the other sources have */
+static bool drm_splash_bmp_none(void)
+{
+ return drm_splash_bmp_given() && !strcmp(splash_bmp, "none");
+}
+
+static const char *drm_splash_bmp_name(void)
+{
+ return drm_splash_bmp_given() ? splash_bmp : DEFAULT_SPLASH_BMP;
+}
+#else
+static bool drm_splash_bmp_given(void)
+{
+ return false;
+}
+
+static bool drm_splash_bmp_none(void)
+{
+ return false;
+}
#endif // CONFIG_DRM_CLIENT_SPLASH_SRC_BMP
+/* Degrees counter clockwise, as for panels, and as DRM counts them */
+static bool drm_splash_rotation_from_degrees(u32 degrees,
+ unsigned int *rotation)
+{
+ switch (degrees) {
+ case 0:
+ *rotation = DRM_MODE_ROTATE_0;
+ return true;
+ case 90:
+ *rotation = DRM_MODE_ROTATE_90;
+ return true;
+ case 180:
+ *rotation = DRM_MODE_ROTATE_180;
+ return true;
+ case 270:
+ *rotation = DRM_MODE_ROTATE_270;
+ return true;
+ }
+
+ return false;
+}
+
#define BMP_FILE_MAGIC_ID 0x4d42
/* BMP header structures copied from drivers/video/fbdev/efifb.c */
@@ -134,6 +206,8 @@ struct drm_splash {
s32 off_x, off_y;
/* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
unsigned int rotation;
+ /* Placed from the command line, in screen pixels, whatever the source */
+ bool place_cmdline;
/* RGB888, from the same place as the image */
u32 bg_color;
};
@@ -218,10 +292,11 @@ static int drm_splash_fw_request_bmp(struct drm_splash *splash)
{
struct drm_client_dev *client = &splash->client;
- drm_info(client->dev, "splash: request %s as firmware", splash_bmp);
+ drm_info(client->dev, "splash: request %s as firmware",
+ drm_splash_bmp_name());
return request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
- splash_bmp, client->dev->dev, GFP_KERNEL,
+ drm_splash_bmp_name(), client->dev->dev, GFP_KERNEL,
splash, drm_splash_fw_callback);
}
#else
@@ -280,11 +355,9 @@ static void drm_splash_dt_props(struct drm_splash *splash,
{
u32 val[2];
- /* The colour travels with the image, or it is the kernel's default */
+ /* The colour travels with the image */
if (!of_property_read_u32(np, "background-color", &val[0]))
splash->bg_color = val[0] & 0xffffff;
- else
- splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
if (!of_property_read_u32_array(np, "logo-position", val, 2)) {
splash->pos_x = (s32)val[0];
@@ -296,27 +369,10 @@ static void drm_splash_dt_props(struct drm_splash *splash,
splash->off_y = (s32)val[1];
}
- /* Degrees counter clockwise, as for panels, and as DRM counts them */
- if (!of_property_read_u32(np, "rotation", &val[0])) {
- switch (val[0]) {
- case 0:
- splash->rotation = DRM_MODE_ROTATE_0;
- break;
- case 90:
- splash->rotation = DRM_MODE_ROTATE_90;
- break;
- case 180:
- splash->rotation = DRM_MODE_ROTATE_180;
- break;
- case 270:
- splash->rotation = DRM_MODE_ROTATE_270;
- break;
- default:
- drm_warn(splash->client.dev,
- "splash: %pOF: unsupported rotation %u",
- np, val[0]);
- }
- }
+ if (!of_property_read_u32(np, "rotation", &val[0]) &&
+ !drm_splash_rotation_from_degrees(val[0], &splash->rotation))
+ drm_warn(splash->client.dev,
+ "splash: %pOF: unsupported rotation %u", np, val[0]);
}
static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
@@ -702,7 +758,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
return -EINVAL;
}
- if (splash->src == DRM_SPLASH_SRC_BGRT) {
+ if (splash->src == DRM_SPLASH_SRC_BGRT && !splash->place_cmdline) {
/*
* The BGRT offsets place the upright image on the upright
* screen, which for a panel mounted turned is not the frame
@@ -827,10 +883,53 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
* is whatever the firmware shows, and the BMP loaded as firmware is the
* fallback. With none of them, only the background colour is drawn.
*/
+/*
+ * What the command line gives wins whatever the image and its source, each
+ * parameter for what it says. Positions come in pairs: half of one is
+ * ignored rather than guessed.
+ */
+static void drm_splash_cmdline_overrides(struct drm_splash *splash)
+{
+ struct drm_device *dev = splash->client.dev;
+
+ if (splash_color >= 0)
+ splash->bg_color = splash_color & 0xffffff;
+
+ if (splash_pos_n == 2) {
+ splash->pos_x = splash_pos[0];
+ splash->pos_y = splash_pos[1];
+ splash->place_cmdline = true;
+ } else if (splash_pos_n) {
+ drm_warn(dev, "splash: splash_pos needs X,Y, ignored");
+ }
+
+ if (splash_offset_n == 2) {
+ splash->off_x = splash_offset[0];
+ splash->off_y = splash_offset[1];
+ splash->place_cmdline = true;
+ } else if (splash_offset_n) {
+ drm_warn(dev, "splash: splash_offset needs DX,DY, ignored");
+ }
+
+ if (splash_rotation >= 0 &&
+ !drm_splash_rotation_from_degrees(splash_rotation, &splash->rotation))
+ drm_warn(dev, "splash: unsupported splash_rotation %d, ignored",
+ splash_rotation);
+}
+
static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
{
struct device_node *np;
+ /* Unless the source says otherwise, the kernel's default colour */
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+
+ /* A BMP named on the command line was asked for this very boot */
+ if (drm_splash_bmp_none())
+ return DRM_SPLASH_SRC_NONE;
+ if (drm_splash_bmp_given())
+ return DRM_SPLASH_SRC_BMP;
+
np = drm_splash_dt_node(splash);
if (np) {
drm_splash_dt_props(splash, np);
@@ -838,18 +937,11 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
return DRM_SPLASH_SRC_DT;
}
- /* The BGRT carries no colour: firmware splashes sit on black */
if (drm_splash_bgrt_available(splash)) {
- splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
splash->rotation = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
}
- /*
- * The BMP loaded as firmware is named on the command line, and so is
- * its colour, which also covers the screen when there is no image.
- */
- splash->bg_color = splash_color & 0xffffff;
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
return DRM_SPLASH_SRC_BMP;
@@ -1169,6 +1261,7 @@ static int drm_splash_client_hotplug(struct drm_client_dev *client)
splash->thread = thread;
splash->src = drm_splash_pick_source(splash);
+ drm_splash_cmdline_overrides(splash);
/* The BMP loaded as firmware is the only source that arrives later */
if (splash->src == DRM_SPLASH_SRC_BMP) {
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 13/13] drm/client: splash: document the image sources and parameters
2026-10-13 9:32 [PATCH v5 00/13] Add splash DRM client Màxim Pedraza Padilla
` (11 preceding siblings ...)
2026-10-13 9:32 ` [PATCH v5 12/13] drm/client: splash: prefer what the command line asks for Màxim Pedraza Padilla
@ 2026-10-13 9:32 ` Màxim Pedraza Padilla
12 siblings, 0 replies; 16+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-13 9:32 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Ard Biesheuvel, Jonathan Corbet
Cc: Francesco Valla, Jani Nikula, Mario Limonciello,
Javier Martinez Canillas, Jocelyn Falempe, Sam Ravnborg,
Ilias Apalodimas, Shuah Khan, Randy Dunlap, dri-devel, devicetree,
linux-efi, linux-doc, linux-embedded, linux-kernel
The overview only said that the client draws a colour or an image. By
now the image can come from four places in a set order, be placed and
turned, and every part of that can be overridden from the command line,
each parameter for its own part only. Write it down: the sources and
their order, the image format, placement and rotation, the background,
every parameter with its format, and what a parameter given alone
keeps from the image source.
Also note that the splash rules out fbdev emulation on the same device,
and that a bootloader that loads no image into a reserved region has to
disable or remove the node, since the region can keep the previous
image across a reset and even a short power cycle.
Pull the overview into Documentation/gpu/drm-client.rst, which covers
the in-kernel clients, so that it is published.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
Documentation/gpu/drm-client.rst | 6 +++
drivers/gpu/drm/clients/drm_splash.c | 79 +++++++++++++++++++++++++++-
2 files changed, 84 insertions(+), 1 deletion(-)
diff --git a/Documentation/gpu/drm-client.rst b/Documentation/gpu/drm-client.rst
index cbcfe30de777..a58d6440d9f2 100644
--- a/Documentation/gpu/drm-client.rst
+++ b/Documentation/gpu/drm-client.rst
@@ -16,3 +16,9 @@ Kernel clients
.. kernel-doc:: drivers/gpu/drm/drm_client_event.c
:export:
+
+Splash client
+=============
+
+.. kernel-doc:: drivers/gpu/drm/clients/drm_splash.c
+ :doc: overview
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 46faae025ec2..1092143df1f0 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -37,7 +37,84 @@
* DOC: overview
*
* This is a simple graphic bootsplash, able to display either a plain color or
- * a static image.
+ * a static image. It draws once the display driver has registered, and stays
+ * until userspace takes the display over.
+ *
+ * It is selected with ``drm_client_lib.active=splash`` or
+ * CONFIG_DRM_CLIENT_DEFAULT_SPLASH. A device has a single in-kernel client, so
+ * with the splash there is no fbdev emulation: no ``/dev/fb0`` and no
+ * framebuffer console.
+ *
+ * Image sources
+ * -------------
+ *
+ * The image is a BMP with a 40 byte BITMAPINFOHEADER, 24 bits per pixel and no
+ * compression. It comes from the first of these that provides one:
+ *
+ * 1. a BMP loaded as firmware and named on the command line with
+ * ``drm_client_lib.splash_bmp=``; ``splash_bmp=none`` asks for no image at
+ * all, and a named file that is missing gives no image rather than falling
+ * back to the sources below;
+ * 2. a "boot-logo" node under ``/chosen`` in the device tree, carrying the BMP
+ * itself or pointing at a reserved memory region the bootloader loaded it
+ * into (see Documentation/devicetree/bindings/display/boot-logo.yaml);
+ * 3. the EFI BGRT, the image the firmware showed;
+ * 4. ``drm_splash.bmp`` loaded as firmware, for instance built into the kernel
+ * with CONFIG_EXTRA_FIRMWARE.
+ *
+ * With none of them, only the background is drawn.
+ *
+ * A reserved memory region keeps its contents across a reset, and may even
+ * across a short power cycle, and the kernel cannot tell a stale image from a
+ * fresh one: a bootloader that loads no image there disables the node, or
+ * removes it.
+ *
+ * Placement
+ * ---------
+ *
+ * The image is placed at a position in screen pixels, where -1 centres it on
+ * that axis, and an offset is added afterwards; the result is clamped so that
+ * the whole image stays on screen. A rotation of 90, 180 or 270 degrees
+ * counter clockwise turns the image and not the screen: position and offset
+ * stay in screen pixels, and a quarter turn only swaps how much room the image
+ * takes up.
+ *
+ * A device tree image is placed and turned as its node says. A BGRT image is
+ * placed at the table offsets and turned as its orientation bits say, the
+ * offsets then being given on the upright screen. Any other image is centred
+ * and upright.
+ *
+ * Background
+ * ----------
+ *
+ * The rest of the screen is filled with the node's "background-color" for a
+ * device tree image, and with CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR
+ * otherwise.
+ *
+ * Command line
+ * ------------
+ *
+ * What the command line gives wins over every source, each parameter only for
+ * what it says, and only when it is given:
+ *
+ * ``drm_client_lib.splash_bmp=NAME``
+ * BMP to load as firmware, or ``none`` for no image.
+ * ``drm_client_lib.splash_color=0xRRGGBB``
+ * background color, whatever the image.
+ * ``drm_client_lib.splash_pos=X,Y``
+ * position; ``-1,-1`` centres, ``-1,183`` centres horizontally only.
+ * ``drm_client_lib.splash_offset=DX,DY``
+ * offset added to the position, for instance ``0,80``.
+ * ``drm_client_lib.splash_rotation=DEGREES``
+ * 0, 90, 180 or 270, counter clockwise.
+ *
+ * Positions take both values, or are ignored with a warning. Since each
+ * parameter only replaces its own part, the rest keeps coming from the image
+ * source: ``splash_offset=`` alone moves the image from where the source puts
+ * it, centred for a BMP loaded as firmware, and ``splash_pos=`` alone keeps a
+ * device tree node's "logo-offset". Give both to place the image exactly. A
+ * BGRT image placed from the command line is placed in screen pixels like any
+ * other: the table offsets are then not used.
*/
/*
--
2.39.5
^ permalink raw reply related [flat|nested] 16+ messages in thread