* [PATCH v6 00/15] Add splash DRM client
@ 2026-10-27 13:53 Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 01/15] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
` (14 more replies)
0 siblings, 15 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, linux-doc, linux-embedded,
linux-kernel
This is the sixth version of the splash client, which I took over from
Francesco with v4 [1]. It works in Thomas's review of v5 [13] and Rob's
of the binding [18], fixes what sashiko found in v5 [11][12], and adds
three patches: the BMP headers move to <video/bmp.h> to be shared with
efifb (patch 3), the choice of the image source gets a patch of its own
(patch 8), and the splash follows the connector's orientation
(patch 14). The command line parameters are gone, as Thomas asked [20].
Why this is in the kernel
-------------------------
Maxime asked in v1 why this should not be left to userspace or to the
bootloader. The cases it is for are the ones neither covers:
- Nothing initialises the display before the kernel does. U-Boot's
Falcon mode, meant to cut boot time, starts the kernel from the SPL
and never runs U-Boot proper, where display support lives: there is
no bootloader splash to hand over, and the device tree is the only
thing that reaches the kernel. Userspace comes seconds later.
- One kernel image for several products. An image built into the
kernel, with CONFIG_EXTRA_FIRMWARE, means one kernel per logo, or
every logo in every product's kernel, all of them staying in memory
for good. Francesco described exactly this case in v2: one board
support package for a family of devices, each with its own logo and
background, picked by the bootloader.
- A splash handed over from the bootloader does not always survive the
native driver taking over the display.
Where the image comes from
--------------------------
In this order: the device tree, the EFI BGRT, and a BMP loaded as
firmware, named at build time ("drm_splash.bmp" by default); with none
of them, only the background is drawn, in a colour set at build time
or, for a device tree image, by its node.
The device tree node lives under /chosen (patch 7) and carries the BMP
either in its "logo-image" property, which dtc fills in with /incbin/,
or in a reserved memory region the bootloader loaded it into. The
region keeps the image out of the device tree, so that it can be
changed from userspace by updating a partition, as Francesco suggested.
A node is only there if someone put it there for that board, so it goes
before the BGRT.
Placement uses a position with -1 centring an axis, plus an offset, in
screen pixels. A rotation turns the image and not the screen, counter
clockwise as for panels and as DRM_MODE_ROTATE_* count. A device tree
image is placed as its node says, a BGRT image at the table offsets, and
anything else is centred.
BGRT images with the ACPI 6.2 orientation bits set are no longer skipped
but turned (patch 13). I took the meaning of the bits and of the offsets
from Plymouth, the main user of the BGRT, so that the kernel and
Plymouth put the image in the same place and the handover does not
jump. Plymouth takes orientation 1 (90 degrees) for a panel mounted
left side up and 3 (270) for right side up, which DRM counts as
DRM_MODE_ROTATE_90 and DRM_MODE_ROTATE_270; with the bits set it draws
the image upright at offsets given on the turned screen, and without
them it takes the image as already turned for a turned panel, at
offsets on the native screen. Patches 13 and 14 do the same. I have
not found the specification text to check against; corrections are
welcome.
Changes since v5
----------------
From Thomas's review [13][14][15]:
- Patch 4 draws from a work item on system_power_efficient_wq instead
of a render thread. One mutex covers the scanouts, the image and
the firmware handed over by its callback, and the image is kept
until unregister, so that the work can run again.
- Every hotplug sets the outputs up again, so an output plugged in
later gets the splash, and one unplugged is no longer driven. The
buffers on screen stay there until the new ones are committed, by
the work or by a restore, so the outputs do not go dark in between,
nor when a commit or the hotplug fails; and a hotplug that fails no
longer stops the later ones.
- Restore commits whatever the modesets hold: an output that has no
buffer has no mode either, and is switched off rather than left
showing something else.
- New patch 3: the BMP headers move from efifb to include/video/bmp.h,
as __le16/__le32 fields; efifb reads them with get_unaligned_le*(),
with no change on the little endian systems EFI runs on.
- The image is drawn in two steps: the BMP is read, flipped and turned
into an XRGB8888 buffer, which is then converted into the
framebuffer's format; filling and blitting go by the format's
drm_format_info rather than by bpp. Rotation (patch 11) only
touches the first step.
- No command line parameters [13][16][17][20]: patch 4 drops
splash_color and splash_bmp, the colour and the BMP's file name are
set at build time (CONFIG_DRM_CLIENT_SPLASH_BMP_NAME is new), and
the v5 patch that added splash_bmp=none, splash_pos, splash_offset
and splash_rotation is gone.
- New patch 8: the image source is an enum, picked in one place, so
that the device tree patch only adds its entry; the switch on it
has no default, so that -Wswitch catches a source left out.
- Messages are drm_dbg(), selects in alphabetical order, the mutex is
destroyed at unregister, and the commit message of patch 4 carries
the changes of each version.
From Rob's review [18]:
- Patch 7: "image" is now "logo-image", and "rotation" is the one of
panel-common.yaml, keeping its own description, as it turns the
image rather than say how a panel is mounted.
From sashiko's review of v5 [11][12], and further review:
- The 24 bpp source pixels are read with get_unaligned_le24() rather
than a u32 load, which read a byte past the last pixel of the image
and was unaligned [11], and the BGRT offsets are checked without the
addition that could overflow [11]. Later patches already did; the
fix now comes with the code it fixes.
- The device tree source holds the node for as long as it draws the
image in place, and drops it with the image [12].
- Patch 4: a tiled framebuffer is sized from the tile offsets, in
whatever order the CRTCs come, and a tile whose plane cannot take
its group's format is left out; a preferred format that cannot be
drawn to, such as C8, is not picked; a modeset left without a buffer
is switched off instead of failing the commit for every output, and
never keeps the framebuffer it had; nothing is drawn or committed
while the device is suspended, a restore included, and the work is
queued again on resume; only formats the plane takes
with the linear modifier are picked; a background drawn again gets
the image drawn again on top; the V4 and V5 BMP headers, which GIMP
and ImageMagick write, are accepted; the build time background
colour is limited to RGB888; the Kconfig help says that the BMP is
requested only once.
- Patch 2: efi_bgrt_has_image() says no when the image address, a u64
in the table, does not fit in phys_addr_t, so that it cannot
disagree with efi_bgrt_image_address() on 32-bit kernels without
PAE. Thomas, I have kept your Reviewed-by, as said in [19].
- Patch 7: the node is a "logo" node under /chosen with compatible
"boot-logo"; logo-position and logo-offset give their defaults, and
"logo-image" says that some architectures limit the size of the
device tree (arm64 to 2 MB), so a large image belongs in a reserved
memory region.
- Patch 9: the image is found when the source is picked, and a node
whose image cannot be had (a region that does not resolve or cannot
be mapped, or "memory-region" on a kernel without
CONFIG_OF_RESERVED_MEM) is warned about once and skipped, so the
other sources are tried, rather than giving no image.
- Patch 10: logo-position and logo-offset must hold two cells each,
within the binding's range; otherwise the kernel warns and keeps the
default.
- Patch 12: a background-color that is not one cell of RGB888 is
warned about and left out.
- New patch 14: the splash follows the connector's orientation, the
panel's or video=...,rotate=, as fbdev does: the plane turns the
buffer when it can, and the splash turns it in software otherwise.
A BGRT image stays as the firmware drew it, and tiles that need
different turns get none in software, as with fbdev; so does a
reflection, which is not drawn, and the legacy commit, which leaves
the plane upright, gets the whole turn in software. It can be
dropped; see the open questions.
- Patch 15: the documentation follows all of the above.
Changes in v5
-------------
- efi: bgrt: patch 2 adds accessors for the boot graphics instead of
exporting bgrt_tab and bgrt_image_size, with stubs in efi-bgrt.h for
CONFIG_ACPI_BGRT=n (Jani [2]); both variables are __ro_after_init
(Ard [3]). Ard, this is the shape we discussed [4]: it is no longer
the patch you acked, so I have not carried your Acked-by.
- The client reads the BGRT through those accessors only, and drops
its own wrappers, its stubs and the #ifdef around them; no "static
inline" in drm_splash.c any more (Jani [5]).
- Fixes from sashiko's review [6]:
- when the render thread could not be created, the framebuffers
were freed but the modesets still pointed at them, and the next
restore committed them: a use-after-free that closing the last
DRM file is enough to trigger;
- when no buffer could be created, the scanouts were not freed, and
a later restore committed a mode without a framebuffer;
- a BMP loaded as firmware that arrived after the render thread had
left (another master took over) was kept until unbind; it is now
released when it arrives.
- The binding and the documentation now ask a bootloader that loads no
image into the reserved region to disable or remove the node, rather
than to clear the region, which left the kernel an invalid image to
complain about on every boot (sashiko [7]).
- Dropped the RFC tag.
Changes in v4
-------------
Fixes to Francesco's patches, folded in and listed in the commit notes;
most of them were also in sashiko's review of v3 [9]:
- the render thread stayed in TASK_UNINTERRUPTIBLE for good when the
BMP firmware was missing (hung task warning), could miss a wake up,
and kthread_stop() was called on a thread that had already exited
(use-after-free), never existed or failed to start;
- a use-after-free on unbind: the image was released after
drm_client_release(), which frees the client;
- a use-after-free in the firmware callback when the device goes away
first, as the request cannot be cancelled; unregister now waits;
- the BMP bounds check could overflow and let a crafted bitmap offset
read 4 GiB away;
- the 24 bit blitters read a byte past the image;
- buffers went to the wrong modesets when one was skipped, tiled
groups dereferenced a buffer before creating it and swapped width
and height, a failed dumb buffer left an ERR_PTR behind, and the
modeset list was walked without modeset_mutex;
- the BGRT symbols are exported (Mario's report [10]), GPL only;
- the DRM_CLIENT_DEFAULT entry is indented with tabs (patch 1).
Additions: the device tree source and its binding, placement, rotation,
the background colour following the image, BGRT orientation, the
command line parameters, and the documentation.
Testing
-------
- QEMU versatilepb (pl111, 16 bpp): 45 cases for the device tree
source (inline and region, invalid nodes and values, placement,
clamping, rotations, rows with and without padding, backgrounds, a
V5 BMP header), plus 3 for the order of the sources, among them a
region that cannot be read falling back to the BMP loaded as
firmware, each compared pixel by pixel.
- QEMU virt arm64 (virtio-gpu, 32 bpp) with KASAN and kmemleak, with
the BMP loaded as firmware from an initramfs and the driver bound
and unbound repeatedly: no reports, also with a crafted bitmap
offset and with the firmware arriving only after the unbind (sysfs
fallback).
- Hotplug on vkms: when the connection moves from one connector to
another after the splash is up, a restore drives the new one, and
the old buffers go only once the new ones are committed; with the
first hotplug forced to fail, the next one brings the splash up.
- Several outputs: with the first output off and the second on, v3 put
the buffer on the dead output and left the live one blank; now the
splash is on the live one.
- A tiled display, simulated by forcing on two virtio-gpu heads EDIDs
with a DisplayID tiled topology block (2x1 tiles of 1024x768): one
2048x768 buffer, the image centred across both tiles; v3 oopsed on
such a display.
- The error paths, with a debug patch forcing them: a buffer or a
format that fails on one output of two leaves that one off and the
splash on the other; work queued while suspended runs on resume; a
background that fails to draw is drawn on the next try; no WARN or
KASAN report in any of them.
- x86-64 with OVMF for the BGRT through the new accessors, including
forced orientation bits, a connector turned with video=...,rotate=,
and drm_client_lib built as a module.
- efifb before and after patch 3, with the BGRT replaced from the
initrd to move the image: efifb draws it at the new place, the same
pixel for pixel with and without the patch.
- i386 without PAE on 32-bit OVMF, with the BGRT replaced from the
initrd so that it points above 4 GiB: without the patch 2 check the
splash draws the image at the truncated address; with it, the
background alone.
- An AM335x board (tilcdc, 800x480 panel of which only the bottom 297
lines are visible, 16 bpp), booting over TFTP and NFS: 6 cases,
plus 6 with the BMP loaded as firmware from an initramfs, and 5
unbind and bind cycles of tilcdc, checked by reading the scanout
buffer back through /dev/mem.
- The handover on the same board, with the product's Qt5 application
drawing through EGLFS and the SGX GPU: it takes the display straight
from the splash, and when it exits the splash is restored, pixel for
pixel.
- Falcon mode, on an AM335x board booting from NAND: the SPL starts
the kernel directly, with the device tree appended to it and the
image inline in the node, and U-Boot proper never runs. The splash
comes up as the node asks, checked pixel by pixel.
- W=1 and sparse clean; every patch builds on its own on arm and
x86-64; checkpatch --strict clean apart from a quoted modpost line,
the MAINTAINERS reminder on patch 4, answered by patch 5, and the
one on patch 3, whose new header FRAMEBUFFER LAYER already covers;
dt_binding_check clean for the binding.
Not tested: a BGRT with orientation bits from real firmware, nor one
on a plane that turns in hardware.
Open questions
--------------
- The second drawing step, from XRGB8888 into the framebuffer's
format, converts each pixel itself for now. It is kept apart so that
it can move onto a shared drawing interface for clients once there
is one [15].
- Rob: how the image should be described, a BMP in a property or raw
pixels described like simple-framebuffer [8]. The binding here uses
the BMP; the client would need a second drawing path for the other.
- Once the binding settles I will send the dt-schema change that lets
the node live under /chosen; until then dtbs_check flags it.
- The splash is a DRM client, so a device using it has no fbdev
emulation and no /dev/fb0. That rules it out where userspace still
draws through fbdev.
- Unbinding waits for a pending firmware request, as the request cannot
be cancelled and its callback uses the splash, and it waits under
the device's clientlist_mutex. Normally the request is long over; with
the sysfs fallback enabled it can take up to the loading timeout
(60 s by default). Not waiting would mean splitting what the callback
uses into its own refcounted object; I left it as is, but can do it
if preferred.
- Patch 14 makes the splash follow the connector's orientation the way
fbcon does: what the plane does not turn in hardware is turned in
software, so the logo looks the same on every driver, and the logo's
own rotation is relative to what is seen. Without it the logo
follows the plane, which matches userspace that ignores the panel
orientation, but the result depends on the driver (and can change
when a native driver takes over from simpledrm). If following the
plane is preferred, patch 14 can simply be dropped.
- The BGRT offsets are for the mode the firmware drew in. When KMS
picks another one (a non-atomic driver, a secondary output, or
video=), the x86 sanity check taken from efifb leaves the screen
without a logo, and elsewhere the logo lands off centre. The offsets
could be used only when the scanout matches the firmware's
framebuffer, as screen_info describes it, and the image centred
otherwise; I have left it as efifb has it for now.
Based on drm-misc-next-2026-10-23 (cf5d8e390e2a).
[1] https://lore.kernel.org/all/aryqYhHqFk3cXh9d@bywater/
[2] https://lore.kernel.org/all/5c92c6f27b0778124d2365603374f70bd9b69b8d@intel.com/
[3] https://lore.kernel.org/all/001eb42a-ec22-474a-83c6-371f1f4b2b8c@app.fastmail.com/
[4] https://lore.kernel.org/all/9cf259a5-6083-433c-8544-ca3f6286334e@app.fastmail.com/
[5] https://lore.kernel.org/all/1800459b75ea057c42f8c601a09646cca938b3db@intel.com/
[6] https://lore.kernel.org/all/20261002091339.B64EB1F00893@smtp.kernel.org/
[7] https://lore.kernel.org/all/20261002091338.E12D61F000FF@smtp.kernel.org/
[8] https://lore.kernel.org/all/CAEUXW=Gh6wMk1i=x_KT0eSdkHeHs8Hkxkj4hfsuTRyY6=aphpw@mail.gmail.com/
[9] https://sashiko.dev/#/patchset/20260510-drm_client_splash-v3-0-a9aee9f0b2fc%40valla.it
[10] https://lore.kernel.org/dri-devel/5d7067de-97b7-4232-9cf6-e4b978696482@amd.com/
[11] https://lore.kernel.org/all/sashiko-outbox-169372@kernel.org/
[12] https://lore.kernel.org/all/sashiko-outbox-169390@kernel.org/
[13] https://lore.kernel.org/all/fafa7327-e624-49f2-98b2-83fd602a0ad7@suse.de/
[14] https://lore.kernel.org/all/9834b569-935c-484b-befb-b913acead9c1@suse.de/
[15] https://lore.kernel.org/all/350adc03-dd7f-4120-ac9c-b6d04ff44faf@suse.de/
[16] https://lore.kernel.org/all/ad6a04b7-cf57-4b22-9c22-85a61af29f1f@suse.de/
[17] https://lore.kernel.org/all/45b55f94-0cd2-425b-b985-ba3958502b06@suse.de/
[18] https://lore.kernel.org/all/20261020164651.GA4017884-robh@kernel.org/
[19] https://lore.kernel.org/all/20261021005006.2341200-1-maximpedraza@gmail.com/
[20] https://lore.kernel.org/all/645c1c2b-9aa8-44b4-8a67-dcd7bfbe8fbd@suse.de/
v5: https://lore.kernel.org/r/20261013093243.505776-1-maximpedraza@gmail.com
v4: https://lore.kernel.org/r/20261001195847.141192-1-maximpedraza@gmail.com
v3: https://lore.kernel.org/r/20260510-drm_client_splash-v3-0-a9aee9f0b2fc@valla.it
v2: https://lore.kernel.org/r/20260106-drm_client_splash-v2-0-6e86a7434b59@valla.it
v1: https://lore.kernel.org/r/20251027-drm_client_splash-v1-0-00698933b34a@valla.it
Màxim
Francesco Valla (3):
drm: client: add splash client
MAINTAINERS: add entry for DRM splash client
drm: docs: remove bootsplash from TODO
Màxim Pedraza Padilla (12):
drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs
efi: bgrt: add accessors for the boot graphics
fbdev: efifb: move the BMP headers to <video/bmp.h>
dt-bindings: display: add a boot logo node under /chosen
drm/client: splash: pick the image source in one place
drm/client: splash: add a device tree image source
drm/client: splash: place the device tree image where it asks
drm/client: splash: turn the device tree image as it asks
drm/client: splash: take the background colour from the image source
drm/client: splash: turn the BGRT image on panels mounted turned
drm/client: splash: follow the connector's orientation
drm/client: splash: document the image sources
.../bindings/display/boot-logo.yaml | 156 ++
Documentation/gpu/drm-client.rst | 6 +
Documentation/gpu/todo.rst | 17 -
MAINTAINERS | 9 +
drivers/firmware/efi/efi-bgrt.c | 75 +-
drivers/gpu/drm/clients/Kconfig | 96 +-
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 | 1482 +++++++++++++++++
drivers/video/fbdev/efifb.c | 49 +-
include/linux/efi-bgrt.h | 39 +-
include/video/bmp.h | 40 +
13 files changed, 1930 insertions(+), 57 deletions(-)
create mode 100644 Documentation/devicetree/bindings/display/boot-logo.yaml
create mode 100644 drivers/gpu/drm/clients/drm_splash.c
create mode 100644 include/video/bmp.h
base-commit: cf5d8e390e2ac2cfa24a50645efdbb9f0eebe65f
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 01/15] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 02/15] efi: bgrt: add accessors for the boot graphics Màxim Pedraza Padilla
` (13 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
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 [flat|nested] 17+ messages in thread
* [PATCH v6 02/15] efi: bgrt: add accessors for the boot graphics
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 01/15] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 03/15] fbdev: efifb: move the BMP headers to <video/bmp.h> Màxim Pedraza Padilla
` (12 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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.
The table gives the image address as a u64. efi_bgrt_has_image() only
says yes when phys_addr_t can hold it, so that it never disagrees with
efi_bgrt_image_address() on kernels with 32-bit physical addresses.
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
Reviewed-by: Thomas Zimmermann <tzimmermann@suse.de>
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/firmware/efi/efi-bgrt.c | 75 ++++++++++++++++++++++++++++++++-
include/linux/efi-bgrt.h | 39 ++++++++++++++++-
2 files changed, 111 insertions(+), 3 deletions(-)
diff --git a/drivers/firmware/efi/efi-bgrt.c b/drivers/firmware/efi/efi-bgrt.c
index 1da451582812..1affe5528139 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,74 @@ 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, at an address that phys_addr_t can hold.
+ */
+bool efi_bgrt_has_image(void)
+{
+ return bgrt_tab.image_address &&
+ bgrt_tab.image_address == (phys_addr_t)bgrt_tab.image_address;
+}
+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 [flat|nested] 17+ messages in thread
* [PATCH v6 03/15] fbdev: efifb: move the BMP headers to <video/bmp.h>
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 01/15] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 02/15] efi: bgrt: add accessors for the boot graphics Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 04/15] drm: client: add splash client Màxim Pedraza Padilla
` (11 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, linux-doc, linux-embedded,
linux-kernel
The DRM splash client parses the same BMP headers as efifb. Rather than
keep a second copy of the structures, move them to include/video/bmp.h
with the magic number.
BMP is little endian whatever the CPU, so the header gives the fields
as __le16 and __le32, and efifb now reads them with
get_unaligned_le16() and get_unaligned_le32(). Width and height are
read signed, as the format has them. No change on the little endian
systems EFI runs on.
Suggested-by: Thomas Zimmermann <tzimmermann@suse.de>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
Notes:
TODO for the efifb maintainers, not changed here so that this patch
only moves the headers: the bound check
if ((bitmap_offset + bmp_pitch * bmp_height) > bgrt_image_size)
is done in u32 and can wrap with a crafted BGRT image, for instance
with a bitmap_offset close to 4 GiB, and so can round_up(3 * bmp_width,
4) and the product with a huge width; image_offset_x + bmp_width against
lfb_width is a u32 sum too. efifb_copy_bmp() would then read outside the
mapped image. The DRM splash client checks the same headers with
check_add_overflow() and check_mul_overflow(). I can send a separate fix
if wanted.
drivers/video/fbdev/efifb.c | 49 ++++++++++++++-----------------------
include/video/bmp.h | 40 ++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+), 31 deletions(-)
create mode 100644 include/video/bmp.h
diff --git a/drivers/video/fbdev/efifb.c b/drivers/video/fbdev/efifb.c
index 3411be06d3e6..fd348a72f780 100644
--- a/drivers/video/fbdev/efifb.c
+++ b/drivers/video/fbdev/efifb.c
@@ -16,32 +16,13 @@
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/sysfb.h>
+#include <linux/unaligned.h>
+#include <video/bmp.h>
#include <video/vga.h>
#include <asm/efi.h>
#include <drm/drm_connector.h> /* For DRM_MODE_PANEL_ORIENTATION_* */
#include <drm/drm_panel_quirks.h> /* For drm_get_panel_orientation_quirk */
-struct bmp_file_header {
- u16 id;
- u32 file_size;
- u32 reserved;
- u32 bitmap_offset;
-} __packed;
-
-struct bmp_dib_header {
- u32 dib_header_size;
- s32 width;
- s32 height;
- u16 planes;
- u16 bpp;
- u32 compression;
- u32 bitmap_size;
- u32 horz_resolution;
- u32 vert_resolution;
- u32 colors_used;
- u32 colors_important;
-} __packed;
-
static bool use_bgrt = true;
static bool request_mem_succeeded = false;
static u64 mem_flags = EFI_MEMORY_WC | EFI_MEMORY_UC;
@@ -145,9 +126,10 @@ static bool efifb_bgrt_sanity_check(const struct screen_info *si, u32 bmp_width)
static void efifb_show_boot_graphics(struct fb_info *info, const struct screen_info *si)
{
- u32 bmp_width, bmp_height, bmp_pitch, dst_x, y, src_y;
+ u32 bmp_width, bmp_height, bmp_pitch, bitmap_offset, dst_x, y, src_y;
struct bmp_file_header *file_header;
struct bmp_dib_header *dib_header;
+ s32 width, height;
void *bgrt_image = NULL;
u8 *dst = info->screen_base;
@@ -186,20 +168,25 @@ static void efifb_show_boot_graphics(struct fb_info *info, const struct screen_i
goto error;
file_header = bgrt_image;
- if (file_header->id != 0x4d42 || file_header->reserved != 0)
+ if (get_unaligned_le16(&file_header->id) != BMP_FILE_MAGIC_ID ||
+ get_unaligned_le32(&file_header->reserved) != 0)
goto error;
+ bitmap_offset = get_unaligned_le32(&file_header->bitmap_offset);
dib_header = bgrt_image + sizeof(*file_header);
- if (dib_header->dib_header_size != 40 || dib_header->width < 0 ||
- dib_header->planes != 1 || dib_header->bpp != 24 ||
- dib_header->compression != 0)
+ width = get_unaligned_le32(&dib_header->width);
+ height = get_unaligned_le32(&dib_header->height);
+ if (get_unaligned_le32(&dib_header->dib_header_size) != 40 ||
+ width < 0 || get_unaligned_le16(&dib_header->planes) != 1 ||
+ get_unaligned_le16(&dib_header->bpp) != 24 ||
+ get_unaligned_le32(&dib_header->compression) != 0)
goto error;
- bmp_width = dib_header->width;
- bmp_height = abs(dib_header->height);
+ bmp_width = width;
+ bmp_height = abs(height);
bmp_pitch = round_up(3 * bmp_width, 4);
- if ((file_header->bitmap_offset + bmp_pitch * bmp_height) >
+ if ((bitmap_offset + bmp_pitch * bmp_height) >
bgrt_image_size)
goto error;
@@ -222,12 +209,12 @@ static void efifb_show_boot_graphics(struct fb_info *info, const struct screen_i
src_y = y - bgrt_tab.image_offset_y;
/* Positive header height means upside down row order */
- if (dib_header->height > 0)
+ if (height > 0)
src_y = (bmp_height - 1) - src_y;
memset(dst, 0, bgrt_tab.image_offset_x * 4);
dst_x = bgrt_tab.image_offset_x;
- efifb_copy_bmp(bgrt_image + file_header->bitmap_offset +
+ efifb_copy_bmp(bgrt_image + bitmap_offset +
src_y * bmp_pitch,
(u32 *)dst + dst_x, bmp_width, si);
dst_x += bmp_width;
diff --git a/include/video/bmp.h b/include/video/bmp.h
new file mode 100644
index 000000000000..8aa249f2302f
--- /dev/null
+++ b/include/video/bmp.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _VIDEO_BMP_H
+#define _VIDEO_BMP_H
+
+#include <linux/types.h>
+
+/* "BM", the first two bytes of a BMP file */
+#define BMP_FILE_MAGIC_ID 0x4d42
+
+/*
+ * The headers at the start of a BMP file. Every field is little endian,
+ * whatever the CPU, and the headers need not be aligned in memory: read them
+ * with get_unaligned_le16() and get_unaligned_le32().
+ */
+struct bmp_file_header {
+ __le16 id;
+ __le32 file_size;
+ __le32 reserved;
+ __le32 bitmap_offset;
+} __packed;
+
+/*
+ * BITMAPINFOHEADER. Width and height are signed: a negative width means
+ * nothing, and a negative height that the rows go top to bottom.
+ */
+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;
+
+#endif /* _VIDEO_BMP_H */
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 04/15] drm: client: add splash client
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (2 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 03/15] fbdev: efifb: move the BMP headers to <video/bmp.h> Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 05/15] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
` (10 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 is set at build time from a dedicated config
option. 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 is set at build time, 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.
v6:
- draw from a work item on system_power_efficient_wq instead of a
render thread
- set the scanouts up again on every hotplug, and keep the image for
the outputs plugged in later
- keep the scanouts on screen until the new ones are committed, so that
a hotplug does not blank every output
- also when the commit or the hotplug fails; restore retires them too
- redraw the image after a background that is drawn again
- accept the V4 and V5 BMP headers
- limit the background color to RGB888
- point no modeset at the framebuffer it had, which a probe without
connectors leaves in place
- leave restore to the work while the device is suspended
- say in DRM_CLIENT_SPLASH_BMP_NAME that the BMP is requested only once
- don't fail a hotplug, which would stop the later ones for good
- restore commits whatever the modesets hold: an output without a
buffer has no mode either, and is switched off
- take the BMP headers from <video/bmp.h>
- draw the image in two steps: the BMP upright in XRGB8888, then into
the buffer's format, taken from its drm_format_info
- drop the splash_color and splash_bmp parameters: the color is set at
build time only, and so is the BMP's name, with
DRM_CLIENT_SPLASH_BMP_NAME
- read the 24 bpp source pixels with get_unaligned_le24() instead of a
u32 load that runs past the pixel
- check the BGRT offsets without overflowing
- size a tiled framebuffer from the tile offsets, whatever order the
CRTCs come in, and leave out a tile whose plane cannot take the
format of its group
- don't pick a preferred format that cannot be drawn to, such as C8;
without one from the driver, pick the one fbdev would
- only pick formats the plane takes with the linear modifier, the
layout of a dumb buffer
- switch off a modeset left without a buffer instead of failing the
commit for every output
- mark the background drawn only once it is
- hold the work back while the device is suspended, drawing included,
and queue it on resume
- drop the fallback to a BMP that was never requested when the BGRT
fails to load, and the unused struct device
- log with drm_dbg() rather than drm_info(), and end the messages with
a newline
- destroy the mutex at unregister, and when registering fails
- sort the selects of DRM_CLIENT_SELECTION, use unsigned loop counters,
and reword the comment on the x86 BGRT sanity check
v5:
- clear the modesets' framebuffers when the scanouts are freed
- free the scanouts when setting up fails
- release a firmware that lands after the drawing is done
- read the BGRT through the efi_bgrt_*() accessors
- drop "static inline" from the .c file
v4:
- 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
Signed-off-by: Francesco Valla <francesco@valla.it>
Co-developed-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 70 +-
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 | 976 ++++++++++++++++++
5 files changed, 1063 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..51ae6c6bd89b 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -13,6 +13,7 @@ config DRM_CLIENT_SELECTION
tristate
depends on DRM
select DRM_CLIENT_LIB if DRM_CLIENT_LOG
+ select DRM_CLIENT_LIB if DRM_CLIENT_SPLASH
select DRM_CLIENT_LIB if DRM_FBDEV_EMULATION
help
Drivers that support in-kernel DRM clients have to select this
@@ -85,10 +86,70 @@ 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 is displayed on every screen, including those plugged in
+ later.
+
+config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
+ hex "Splash background color"
+ depends on DRM_CLIENT_SPLASH
+ default 0x000000
+ range 0x000000 0xffffff
+ help
+ The splash background color, in RGB888 format.
+
+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, drawn where the
+ firmware drew it. 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.
+
+config DRM_CLIENT_SPLASH_BMP_NAME
+ string "Name of the BMP image"
+ depends on DRM_CLIENT_SPLASH_SRC_BMP
+ default "drm_splash.bmp"
+ help
+ The file the BMP image is loaded from, looked up like any other
+ firmware: built into the kernel through EXTRA_FIRMWARE, in the
+ initramfs, or under /lib/firmware. It is requested only once, when
+ the splash first sets up: for a driver that probes before the root
+ file system is mounted, it has to be built in or in the initramfs.
+
+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 +172,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 +185,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..337c7160c4a1
--- /dev/null
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -0,0 +1,976 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+/*
+ * Copyright (c) 2025-2026 Francesco Valla <francesco@valla.it>
+ */
+
+#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/module.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+
+#include <acpi/actbl1.h>
+
+#include <video/bmp.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.
+ */
+
+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;
+
+ /* Draws what is missing and shows it, queued when there is news */
+ struct work_struct work;
+
+ /*
+ * Serialises the work against hotplug events, which set the scanouts
+ * up again, suspend and resume, and the firmware callback
+ */
+ struct mutex lock;
+ /* Suspended, and whether the work was held back meanwhile */
+ bool suspended, work_skipped;
+ /* The source is picked at the first hotplug that finds an output */
+ bool source_picked;
+
+ u32 n_scanout;
+ struct drm_splash_scanout *scanout;
+ /*
+ * The scanouts a hotplug replaced, kept on screen until the work has
+ * committed the new ones, so that the outputs do not go dark meanwhile
+ */
+ u32 n_old_scanout;
+ struct drm_splash_scanout *old_scanout;
+
+ /* A request was made, and fw_done tells when its callback is over */
+ bool fw_requested;
+ struct completion fw_done;
+ /*
+ * The image is kept for as long as the splash is there, so that an
+ * output plugged in later gets it too
+ */
+ const struct firmware *fw;
+ 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_queue(struct drm_splash *splash)
+{
+ queue_work(system_power_efficient_wq, &splash->work);
+}
+
+#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 = splash->fw;
+
+ /* Under the lock, which the firmware callback takes to hand it over */
+ 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) {
+ /* The firmware loader has said why already */
+ drm_dbg(client->dev, "splash: no firmware\n");
+ release_firmware(fw);
+ fw = NULL;
+ }
+
+ scoped_guard(mutex, &splash->lock)
+ splash->fw = fw;
+
+ /* Even without it: a background that failed to draw gets another go */
+ drm_splash_queue(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_dbg(client->dev, "splash: request %s as firmware\n",
+ CONFIG_DRM_CLIENT_SPLASH_BMP_NAME);
+
+ return request_firmware_nowait(THIS_MODULE, FW_ACTION_UEVENT,
+ CONFIG_DRM_CLIENT_SPLASH_BMP_NAME,
+ 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_dbg(client->dev, "splash: no BGRT found\n");
+ return false;
+ }
+
+ if (efi_bgrt_status() & ACPI_BGRT_ORIENTATION_OFFSET) {
+ drm_dbg(client->dev, "splash: BGRT rotation bits set, skipping\n");
+ 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;
+
+ /* Mapped once, and kept for the outputs plugged in later */
+ if (!splash->map_data) {
+ drm_dbg(client->dev, "splash: BGRT image is at %pa, size=%zX\n",
+ &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\n");
+ return -ENOMEM;
+ }
+ }
+
+ *data = splash->map_data;
+ *size = len;
+
+ return 0;
+}
+
+static u32 drm_splash_find_usable_format(struct drm_plane *plane,
+ u32 preferred_format)
+{
+ unsigned int i;
+
+ /* Dumb buffers are linear: the plane must take the format so */
+ if (drm_draw_can_convert_from_xrgb8888(preferred_format) &&
+ drm_plane_has_format(plane, preferred_format, DRM_FORMAT_MOD_LINEAR))
+ return preferred_format;
+
+ /* Otherwise, find the first format that can be converted from XRGB8888 */
+ for (i = 0; i < plane->format_count; i++) {
+ u32 format = plane->format_types[i];
+
+ if (drm_draw_can_convert_from_xrgb8888(format) &&
+ drm_plane_has_format(plane, format, DRM_FORMAT_MOD_LINEAR))
+ return format;
+ }
+
+ return DRM_FORMAT_INVALID;
+}
+
+static void drm_splash_fill(struct iosys_map *map, unsigned int dst_pitch,
+ unsigned int height, unsigned int width,
+ const struct drm_format_info *format, u32 color)
+{
+ switch (drm_format_info_bpp(format, 0)) {
+ case 16:
+ drm_draw_fill16(map, dst_pitch, height, width, color);
+ break;
+ case 24:
+ drm_draw_fill24(map, dst_pitch, height, width, color);
+ break;
+ case 32:
+ drm_draw_fill32(map, dst_pitch, height, width, color);
+ break;
+ default:
+ WARN_ONCE(1, "Can't fill %p4cc\n", &format->format);
+ }
+}
+
+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);
+ 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\n", ret);
+ return ret;
+ }
+
+ drm_splash_fill(&map, fb->pitches[0], drm_rect_height(&r),
+ drm_rect_width(&r), fb->format, color);
+
+ drm_client_buffer_vunmap_local(buffer);
+
+ return drm_client_buffer_flush(buffer, &r);
+}
+
+#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+/* The rows of a 24 bpp BMP, bottom up or top down, upright in XRGB8888 */
+static void drm_splash_bmp_decode(u32 *dst, const u8 *src, unsigned int spitch,
+ unsigned int width, unsigned int height,
+ bool invert_y)
+{
+ unsigned int x, y;
+
+ for (y = 0; y < height; y++) {
+ const u8 *p = src + (invert_y ? height - 1 - y : y) * spitch;
+
+ for (x = 0; x < width; x++, p += 3)
+ *dst++ = get_unaligned_le24(p);
+ }
+}
+
+/* An XRGB8888 image into a buffer of another format, at x0, y0 */
+static void drm_splash_blit(struct iosys_map *map, unsigned int dpitch,
+ const struct drm_format_info *format,
+ unsigned int x0, unsigned int y0, const u32 *src,
+ unsigned int width, unsigned int height)
+{
+ unsigned int bpp = drm_format_info_bpp(format, 0);
+ unsigned int x, y, off;
+ u32 c;
+
+ for (y = 0; y < height; y++) {
+ off = (y0 + y) * dpitch + x0 * bpp / 8;
+
+ switch (bpp) {
+ case 16:
+ for (x = 0; x < width; x++, off += 2) {
+ c = drm_draw_color_from_xrgb8888(*src++, format->format);
+ iosys_map_wr(map, off, u16, c);
+ }
+ break;
+ case 24:
+ for (x = 0; x < width; x++, off += 3) {
+ c = drm_draw_color_from_xrgb8888(*src++, format->format);
+ iosys_map_wr(map, off, u8, c & 0xff);
+ iosys_map_wr(map, off + 1, u8, (c >> 8) & 0xff);
+ iosys_map_wr(map, off + 2, u8, (c >> 16) & 0xff);
+ }
+ break;
+ case 32:
+ for (x = 0; x < width; x++, off += 4) {
+ c = drm_draw_color_from_xrgb8888(*src++, format->format);
+ iosys_map_wr(map, off, u32, c);
+ }
+ break;
+ }
+ }
+}
+
+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;
+ 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;
+ u32 *img;
+ int ret;
+
+ if (data_len < (sizeof(*file_header) + sizeof(*dib_header))) {
+ drm_err(client->dev, "splash: BMP file too short\n");
+ 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\n", 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. The V4 and
+ * V5 headers only add fields after these, and bitmap_offset finds the
+ * pixels.
+ */
+ 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\n");
+ 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\n");
+ 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\n");
+ return -EINVAL;
+ }
+
+ if (splash->use_bgrt) {
+ x_pad = efi_bgrt_image_offset_x();
+ y_pad = efi_bgrt_image_offset_y();
+
+ if (x_pad > scanout->width - bmp_width ||
+ y_pad > scanout->height - bmp_height) {
+ drm_err(client->dev, "splash: BGRT image would overflow\n");
+ return -EINVAL;
+ }
+
+#ifdef CONFIG_X86
+ /*
+ * BGRT sanity check, taken from efifb.c:
+ *
+ * Some firmware on x86 uses a non-native resolution for the
+ * display if it has shown some text messages, while the BGRT
+ * still describes the image for the native resolution. If
+ * that image still fits, it ends up 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\n");
+ 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;
+
+ /* First the image upright in XRGB8888, then into the buffer's format */
+ img = kvmalloc_array(bmp_width, bmp_height * sizeof(*img), GFP_KERNEL);
+ if (!img)
+ return -ENOMEM;
+ drm_splash_bmp_decode(img, image_data, bmp_pitch, bmp_width, bmp_height,
+ bmp_invert_y);
+
+ ret = drm_client_buffer_vmap_local(buffer, &map);
+ if (ret) {
+ drm_err(client->dev, "splash: cannot vmap buffer: %d\n", ret);
+ kvfree(img);
+ return ret;
+ }
+
+ r = DRM_RECT_INIT(x_pad, y_pad, bmp_width, bmp_height);
+ drm_splash_blit(&map, fb->pitches[0], fb->format, x_pad, y_pad, img,
+ bmp_width, bmp_height);
+
+ kvfree(img);
+ 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)
+{
+ if (splash->use_bgrt)
+ return drm_splash_bgrt_load(splash, img_data, img_data_len);
+
+ return drm_splash_fw_load(splash, img_data, img_data_len);
+}
+
+/* At unregister, once nothing else can use the image */
+static void drm_splash_image_cleanup(struct drm_splash *splash)
+{
+ memunmap(splash->map_data);
+ splash->map_data = NULL;
+ release_firmware(splash->fw);
+ splash->fw = NULL;
+}
+
+/* Delete the buffers of an array of scanouts, and the array */
+static void drm_splash_delete_scanouts(struct drm_splash_scanout **scanout,
+ u32 *n_scanout)
+{
+ unsigned int i;
+
+ for (i = 0; i < *n_scanout; i++)
+ drm_client_buffer_delete((*scanout)[i].buffer);
+
+ kfree(*scanout);
+ *scanout = NULL;
+ *n_scanout = 0;
+}
+
+static void drm_splash_work(struct work_struct *work)
+{
+ struct drm_splash *splash = container_of(work, struct drm_splash, work);
+ struct drm_client_dev *client = &splash->client;
+ const u8 *img_data = NULL;
+ size_t img_data_len = 0;
+ bool img_loaded;
+ unsigned int i;
+ int ret;
+
+ guard(mutex)(&splash->lock);
+
+ /*
+ * Not while the device sleeps, not even drawing, which flushes the
+ * buffers to the hardware: resume queues the work again
+ */
+ if (splash->suspended) {
+ splash->work_skipped = true;
+ return;
+ }
+
+ if (!splash->n_scanout)
+ return;
+
+ /* A BMP loaded as firmware may not be there yet: its callback requeues */
+ img_loaded = !drm_splash_image_load(splash, &img_data, &img_data_len);
+
+ for (i = 0; i < splash->n_scanout; i++) {
+ struct drm_splash_scanout *scanout = &splash->scanout[i];
+
+ /* Its outputs are off: init_client could not create it */
+ if (!scanout->buffer)
+ continue;
+
+ if (!scanout->bg_drawn) {
+ u32 rgb = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+ u32 color = drm_draw_color_from_xrgb8888(rgb, scanout->format);
+
+ /* Only success marks it done: a later pass retries */
+ ret = drm_splash_fill_solid_color(scanout->buffer, color);
+ scanout->bg_drawn = !ret;
+ /* The fill covers the image too */
+ scanout->img_drawn = false;
+ }
+
+ if (img_loaded && !scanout->img_drawn) {
+ /* A failure leaves the background alone */
+ ret = drm_splash_bmp_to_scanout(splash, scanout, img_data,
+ img_data_len);
+ scanout->img_drawn = !ret;
+ }
+ }
+
+ /*
+ * With another master, the display is not the splash's to show; the
+ * buffers stay drawn for when restore hands it back.
+ */
+ ret = drm_client_modeset_commit(client);
+ if (ret) {
+ /* What a hotplug replaced is still on screen: it stays */
+ drm_dbg(client->dev, "splash: commit: %d\n", ret);
+ return;
+ }
+
+ /* Whatever a hotplug replaced is no longer on screen */
+ drm_splash_delete_scanouts(&splash->old_scanout, &splash->n_old_scanout);
+}
+
+/* No mode and no connectors: a commit switches the output off */
+static void drm_splash_modeset_off(struct drm_device *dev,
+ struct drm_mode_set *modeset)
+{
+ unsigned int k;
+
+ for (k = 0; k < modeset->num_connectors; k++) {
+ drm_connector_put(modeset->connectors[k]);
+ modeset->connectors[k] = NULL;
+ }
+ modeset->num_connectors = 0;
+ drm_mode_destroy(dev, modeset->mode);
+ modeset->mode = NULL;
+ modeset->fb = NULL;
+}
+
+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;
+ int err = -ENODEV;
+
+ 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,
+ * which reaches the far edge of the last tile, whatever
+ * order the tiles come in.
+ */
+ tiled = get_scanout_from_tile_group(splash, new_id);
+ if (tiled) {
+ /* Left off unless its plane takes the group's format */
+ if (!drm_plane_has_format(modeset->crtc->primary,
+ tiled->format,
+ DRM_FORMAT_MOD_LINEAR)) {
+ drm_warn(client->dev,
+ "splash: tile plane can't take %p4cc\n",
+ &tiled->format);
+ continue;
+ }
+ tiled->width = max(tiled->width, modeset->x +
+ modeset->mode->hdisplay);
+ tiled->height = max(tiled->height, modeset->y +
+ modeset->mode->vdisplay);
+ 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\n");
+ 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\n");
+ 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->x + modeset->mode->hdisplay;
+ scanout->height = modeset->y + 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)) {
+ err = PTR_ERR(buffer);
+ drm_warn(client->dev,
+ "splash: can't create dumb buffer %d %d %p4cc: %pe\n",
+ scanout->width, scanout->height, &scanout->format,
+ buffer);
+ continue;
+ }
+
+ drm_dbg(client->dev, "splash: created dumb buffer %d %d %p4cc\n",
+ scanout->width, scanout->height, &scanout->format);
+
+ scanout->buffer = buffer;
+ fb_count++;
+ }
+
+ /*
+ * And point every modeset at the buffer of its scanout. One left
+ * without, whatever the reason, is switched off: a mode without a
+ * framebuffer fails the commit, and with it every other output.
+ * Never the old one: a probe without connectors leaves it there.
+ */
+ i = 0;
+ drm_client_for_each_modeset(modeset, client) {
+ unsigned int idx = i++;
+
+ modeset->fb = NULL;
+ for (k = 0; idx < 32 && 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;
+ }
+ }
+
+ if (!modeset->fb)
+ drm_splash_modeset_off(client->dev, modeset);
+ }
+
+ mutex_unlock(&client->modeset_mutex);
+
+ /* No scanout at all is not an error; scanouts without buffers are */
+ return fb_count ? 0 : err;
+}
+
+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;
+
+ /*
+ * The modesets must not point at the framebuffers freed below, nor
+ * keep a mode without one, which would fail every later commit
+ */
+ mutex_lock(&client->modeset_mutex);
+ drm_client_for_each_modeset(modeset, client)
+ drm_splash_modeset_off(client->dev, modeset);
+ mutex_unlock(&client->modeset_mutex);
+
+ drm_splash_delete_scanouts(&splash->scanout, &splash->n_scanout);
+}
+
+static int drm_splash_client_hotplug(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ int ret;
+
+ guard(mutex)(&splash->lock);
+
+ /*
+ * Every hotplug starts over, with the outputs there are now. The
+ * scanouts on screen stay there until the work or restore has
+ * committed the new ones; with some still waiting for that, the
+ * current ones were never committed and can go.
+ */
+ if (splash->old_scanout) {
+ drm_splash_free_scanout(client);
+ } else {
+ splash->old_scanout = splash->scanout;
+ splash->n_old_scanout = splash->n_scanout;
+ splash->scanout = NULL;
+ splash->n_scanout = 0;
+ }
+
+ ret = drm_splash_init_client(splash);
+ if (ret) {
+ /*
+ * It may have set up scanouts before failing to get buffers.
+ * Those on screen stay there, for the next hotplug to replace.
+ */
+ drm_splash_free_scanout(client);
+ if (ret == -ENODEV)
+ drm_dbg(client->dev, "splash: no usable modeset\n");
+ else
+ drm_err(client->dev,
+ "splash: failed to init client: %d\n", ret);
+
+ /* An error would stop the hotplug events: let the next one retry */
+ return 0;
+ }
+
+ if (!splash->source_picked) {
+ splash->source_picked = true;
+ 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) {
+ ret = drm_splash_fw_request_bmp(splash);
+ splash->fw_requested = !ret;
+ if (ret)
+ drm_err(client->dev,
+ "splash: failed to kick image load: %d\n", ret);
+ }
+ }
+
+ drm_splash_queue(splash);
+
+ return 0;
+}
+
+/* An output without a buffer has no mode either: the commit switches it off */
+static int drm_splash_client_restore(struct drm_client_dev *client, bool force)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+ int ret;
+
+ guard(mutex)(&splash->lock);
+
+ /* Not while the device sleeps: resume runs the work, which commits */
+ if (splash->suspended) {
+ splash->work_skipped = true;
+ return 0;
+ }
+
+ if (force)
+ ret = drm_client_modeset_commit_locked(client);
+ else
+ ret = drm_client_modeset_commit(client);
+
+ /* Whatever a hotplug replaced is no longer on screen */
+ if (!ret)
+ drm_splash_delete_scanouts(&splash->old_scanout,
+ &splash->n_old_scanout);
+
+ 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 queues the work: 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);
+
+ /* Hotplug events no longer come, so nothing queues the work again */
+ cancel_work_sync(&splash->work);
+ drm_splash_free_scanout(client);
+ drm_splash_delete_scanouts(&splash->old_scanout, &splash->n_old_scanout);
+ drm_splash_image_cleanup(splash);
+ mutex_destroy(&splash->lock);
+
+ /* 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;
+
+ kfree(splash);
+
+ drm_dbg(dev, "Freed drm splash\n");
+}
+
+static int drm_splash_client_suspend(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+
+ guard(mutex)(&splash->lock);
+ splash->suspended = true;
+
+ return 0;
+}
+
+static int drm_splash_client_resume(struct drm_client_dev *client)
+{
+ struct drm_splash *splash = client_to_drm_splash(client);
+
+ guard(mutex)(&splash->lock);
+ splash->suspended = false;
+ if (splash->work_skipped) {
+ splash->work_skipped = false;
+ drm_splash_queue(splash);
+ }
+
+ return 0;
+}
+
+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,
+ .suspend = drm_splash_client_suspend,
+ .resume = drm_splash_client_resume,
+};
+
+/**
+ * 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;
+
+ INIT_WORK(&splash->work, drm_splash_work);
+ mutex_init(&splash->lock);
+ init_completion(&splash->fw_done);
+
+ if (format && format->num_planes == 1) {
+ splash->preferred_format = format->format;
+ } else {
+ unsigned int mode = dev->mode_config.preferred_depth;
+
+ /* As fbdev: 32 bits per pixel, unless the driver wants fewer */
+ if (!mode || mode == 24)
+ mode = 32;
+ splash->preferred_format =
+ drm_driver_color_mode_format(dev, mode);
+ }
+
+ 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\n");
+
+ return;
+
+err_free:
+ mutex_destroy(&splash->lock);
+ kfree(splash);
+err_warn:
+ drm_warn(dev, "Failed to register with drm splash\n");
+}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 05/15] MAINTAINERS: add entry for DRM splash client
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (3 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 04/15] drm: client: add splash client Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 06/15] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
` (9 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 [flat|nested] 17+ messages in thread
* [PATCH v6 06/15] drm: docs: remove bootsplash from TODO
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (4 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 05/15] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
` (8 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 e6d36f8da086..5e987e18bfcd 100644
--- a/Documentation/gpu/todo.rst
+++ b/Documentation/gpu/todo.rst
@@ -756,23 +756,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 [flat|nested] 17+ messages in thread
* [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (5 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 06/15] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 18:59 ` Rob Herring (Arm)
2026-10-27 13:53 ` [PATCH v6 08/15] drm/client: splash: pick the image source in one place Màxim Pedraza Padilla
` (7 subsequent siblings)
14 siblings, 1 reply; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 "logo" node, compatible "boot-logo", under /chosen. It 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
a "logo-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" is the one of
panel-common.yaml, degrees counter clockwise, but 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 | 156 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 157 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..9cd4b2565112
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/boot-logo.yaml
@@ -0,0 +1,156 @@
+# 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.
+
+allOf:
+ - $ref: panel/panel-common.yaml#
+
+properties:
+ $nodename:
+ const: logo
+
+ compatible:
+ const: boot-logo
+
+ 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, and some architectures limit its size (arm64 to 2 MB), so
+ this is meant for small images; at three bytes per pixel, 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.
+ default: [-1, -1]
+ 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. Defaults to no displacement.
+ default: [0, 0]
+ items:
+ - description: X displacement
+ minimum: -65535
+ maximum: 65535
+ - description: Y displacement
+ minimum: -65535
+ maximum: 65535
+
+ rotation:
+ description:
+ Rotation applied to the image as the screen is seen, in degrees counter
+ clockwise, as for the rotation property of panels. It turns the image
+ and not the screen; a panel mounted turned is described by its own
+ rotation property, which is made up for first, so this means the same
+ whichever way the panel is mounted. A quarter turn swaps how much room
+ the image takes up, but logo-position and logo-offset stay in pixels of
+ the screen as seen either way. Defaults to 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:
+ - logo-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: logo-image = /incbin/("logo.bmp"); */
+ logo-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 [flat|nested] 17+ messages in thread
* [PATCH v6 08/15] drm/client: splash: pick the image source in one place
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (6 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 09/15] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
` (6 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, linux-doc, linux-embedded,
linux-kernel
The client chooses between two sources with a boolean: the BGRT if the
firmware left one, otherwise the BMP loaded as firmware. A third one is
coming, and with it an order of preference that a boolean cannot hold.
Name the source in use with an enum, picked once by
drm_splash_pick_source(), and have drm_splash_image_load() switch on
it. No functional change.
Suggested-by: Thomas Zimmermann <tzimmermann@suse.de>
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 45 +++++++++++++++++++++++-----
1 file changed, 38 insertions(+), 7 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 337c7160c4a1..911149b92a52 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -39,6 +39,16 @@
* a static image.
*/
+/*
+ * Where the image comes from. drm_splash_pick_source() sets the order of
+ * preference; NONE is zero, so that a new splash starts without a source.
+ */
+enum drm_splash_source {
+ DRM_SPLASH_SRC_NONE,
+ DRM_SPLASH_SRC_BGRT,
+ DRM_SPLASH_SRC_BMP,
+};
+
struct drm_splash_scanout {
int id;
/* Modesets scanning this buffer out, by position in the list */
@@ -87,7 +97,7 @@ struct drm_splash {
const struct firmware *fw;
void *map_data;
- bool use_bgrt;
+ enum drm_splash_source src;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -429,7 +439,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();
@@ -498,10 +508,31 @@ 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)
{
- if (splash->use_bgrt)
+ switch (splash->src) {
+ 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);
+ case DRM_SPLASH_SRC_NONE:
+ break;
+ }
+
+ return -ENOENT;
+}
+
+/*
+ * Pick the one source the image will come from: the BGRT is whatever the
+ * firmware shows, and the BMP loaded as firmware is the fallback. With
+ * neither, only the background colour is drawn.
+ */
+static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
+{
+ 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_fw_load(splash, img_data, img_data_len);
+ return DRM_SPLASH_SRC_NONE;
}
/* At unregister, once nothing else can use the image */
@@ -818,10 +849,10 @@ static int drm_splash_client_hotplug(struct drm_client_dev *client)
if (!splash->source_picked) {
splash->source_picked = true;
- 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) {
ret = drm_splash_fw_request_bmp(splash);
splash->fw_requested = !ret;
if (ret)
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 09/15] drm/client: splash: add a device tree image source
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (7 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 08/15] drm/client: splash: pick the image source in one place Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 10/15] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
` (5 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 a "logo-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, with nothing to copy; the
client holds the node, or keeps the region mapped, for as long as it is
there, so that an output plugged in later gets the image too. The region
is mapped like the BGRT image, and the BMP header is checked against its
size. The image is found when the source is picked: a node whose image
cannot be had, because its region does not resolve or cannot be mapped,
or because reading a region takes CONFIG_OF_RESERVED_MEM and it is not
there, is skipped with a warning, so that the other sources still get
their turn.
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 source is built with CONFIG_DRM_CLIENT_SPLASH_SRC_DT, which needs
CONFIG_OF.
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 | 142 ++++++++++++++++++++++++++-
2 files changed, 154 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 51ae6c6bd89b..172781bbc2d7 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -106,6 +106,21 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
help
The splash background color, in RGB888 format.
+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 node compatible with "boot-logo"
+ 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 911149b92a52..618c060f33d5 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -8,9 +8,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/module.h>
#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_reserved_mem.h>
#include <linux/overflow.h>
#include <linux/slab.h>
#include <linux/types.h>
@@ -45,6 +48,7 @@
*/
enum drm_splash_source {
DRM_SPLASH_SRC_NONE,
+ DRM_SPLASH_SRC_DT,
DRM_SPLASH_SRC_BGRT,
DRM_SPLASH_SRC_BMP,
};
@@ -96,6 +100,10 @@ struct drm_splash {
*/
const struct firmware *fw;
void *map_data;
+ /* The device tree node, and where in it or in its region the image is */
+ struct device_node *dt_np;
+ const u8 *dt_data;
+ size_t dt_len;
enum drm_splash_source src;
};
@@ -185,6 +193,127 @@ 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, "logo-image");
+ region = of_property_present(np, "memory-region");
+ if (image == region) {
+ drm_warn(client->dev,
+ "splash: %pOF needs exactly one of logo-image or memory-region\n",
+ np);
+ goto out_put;
+ }
+
+ /* Without it the region can never be read: let the other sources go */
+ if (region && !IS_ENABLED(CONFIG_OF_RESERVED_MEM)) {
+ drm_warn(client->dev,
+ "splash: %pOF needs CONFIG_OF_RESERVED_MEM for memory-region\n",
+ np);
+ goto out_put;
+ }
+
+ return np;
+
+out_put:
+ of_node_put(np);
+ return NULL;
+}
+
+/*
+ * Find the image when the source is picked, and keep it for the outputs
+ * plugged in later. A node whose image cannot be had leaves the other
+ * sources their turn.
+ */
+static bool drm_splash_dt_open(struct drm_splash *splash)
+{
+ struct drm_client_dev *client = &splash->client;
+ struct device_node *np;
+ struct resource res;
+ const void *prop;
+ int len, ret;
+
+ np = drm_splash_dt_node(splash);
+ if (!np)
+ return false;
+
+ /*
+ * An image in the node is used in place, with nothing to copy: the
+ * node is held until the image is cleaned up.
+ */
+ prop = of_get_property(np, "logo-image", &len);
+ if (prop) {
+ splash->dt_data = prop;
+ splash->dt_len = len;
+ goto out_keep;
+ }
+
+ /* 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\n",
+ 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\n", &res);
+ goto out_put;
+ }
+
+ /* The BMP header, checked against this size, says how much is image */
+ splash->dt_data = splash->map_data;
+ splash->dt_len = resource_size(&res);
+
+out_keep:
+ splash->dt_np = np;
+ return true;
+
+out_put:
+ of_node_put(np);
+ return false;
+}
+
+static int drm_splash_dt_load(struct drm_splash *splash, const u8 **data,
+ size_t *size)
+{
+ *data = splash->dt_data;
+ *size = splash->dt_len;
+ return 0;
+}
+#else
+static bool drm_splash_dt_open(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;
@@ -509,6 +638,8 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
size_t *img_data_len)
{
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:
@@ -521,12 +652,15 @@ static int drm_splash_image_load(struct drm_splash *splash, const u8 **img_data,
}
/*
- * Pick the one source the image will come from: the BGRT is whatever the
- * firmware shows, and the BMP loaded as firmware is the fallback. With
- * neither, only the background colour is drawn.
+ * 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_open(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))
@@ -540,6 +674,8 @@ static void drm_splash_image_cleanup(struct drm_splash *splash)
{
memunmap(splash->map_data);
splash->map_data = NULL;
+ of_node_put(splash->dt_np);
+ splash->dt_np = NULL;
release_firmware(splash->fw);
splash->fw = NULL;
}
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 10/15] drm/client: splash: place the device tree image where it asks
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (8 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 09/15] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 11/15] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
` (4 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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. A property that is not two
cells, or that is out of the binding's range, is reported and keeps its
default;
otherwise 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 | 82 ++++++++++++++++++++++++++--
1 file changed, 78 insertions(+), 4 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 618c060f33d5..81070547390f 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -106,6 +106,14 @@ struct drm_splash {
size_t dt_len;
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)
@@ -238,6 +246,39 @@ static struct device_node *drm_splash_dt_node(struct drm_splash *splash)
return NULL;
}
+/* An X and Y pair; anything but two cells from @min to @max is reported */
+static void drm_splash_dt_pair(struct drm_splash *splash,
+ struct device_node *np, const char *name,
+ s32 min, s32 max, s32 *x, s32 *y)
+{
+ u32 val[2];
+
+ if (!of_property_present(np, name))
+ return;
+
+ if (of_property_count_u32_elems(np, name) != 2 ||
+ of_property_read_u32_array(np, name, val, 2) ||
+ (s32)val[0] < min || (s32)val[0] > max ||
+ (s32)val[1] < min || (s32)val[1] > max) {
+ drm_warn(splash->client.dev, "splash: %pOF: invalid %s\n",
+ np, name);
+ return;
+ }
+
+ *x = val[0];
+ *y = val[1];
+}
+
+/* The optional placement properties of the node, read once */
+static void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
+{
+ drm_splash_dt_pair(splash, np, "logo-position", -1, 65535,
+ &splash->pos_x, &splash->pos_y);
+ drm_splash_dt_pair(splash, np, "logo-offset", -65535, 65535,
+ &splash->off_x, &splash->off_y);
+}
+
/*
* Find the image when the source is picked, and keep it for the outputs
* plugged in later. A node whose image cannot be had leaves the other
@@ -307,6 +348,11 @@ static bool drm_splash_dt_open(struct drm_splash *splash)
return false;
}
+static void drm_splash_dt_placement(struct drm_splash *splash,
+ struct device_node *np)
+{
+}
+
static int drm_splash_dt_load(struct drm_splash *splash,
const u8 **data, size_t *size)
{
@@ -428,6 +474,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);
+}
+
/* The rows of a 24 bpp BMP, bottom up or top down, upright in XRGB8888 */
static void drm_splash_bmp_decode(u32 *dst, const u8 *src, unsigned int spitch,
unsigned int width, unsigned int height,
@@ -595,9 +661,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;
@@ -659,8 +726,11 @@ 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_open(splash))
+ if (drm_splash_dt_open(splash)) {
+ drm_splash_dt_placement(splash, splash->dt_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))
@@ -1114,6 +1184,10 @@ void drm_splash_register(struct drm_device *dev,
mutex_init(&splash->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 [flat|nested] 17+ messages in thread
* [PATCH v6 11/15] drm/client: splash: turn the device tree image as it asks
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (9 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 10/15] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 12/15] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
` (3 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, linux-doc, linux-embedded,
linux-kernel
Let the device tree node ask for the image to be turned, for a logo
drawn in one orientation and meant to be seen in another, 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.
It describes the image only: a panel mounted turned is described on the
panel, and a later patch in this series makes the splash follow it.
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 turn happens in the first of the two drawing steps, which reads
the BMP into an XRGB8888 buffer: it walks the source through the
rotation, so the buffer holds the image as it is to be seen. The second
step, into the framebuffer's format, does not change.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/drm_splash.c | 109 +++++++++++++++++++++++----
1 file changed, 93 insertions(+), 16 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 81070547390f..a50036155763 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -28,6 +28,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>
@@ -114,6 +115,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)
@@ -273,10 +276,34 @@ static void drm_splash_dt_pair(struct drm_splash *splash,
static void drm_splash_dt_placement(struct drm_splash *splash,
struct device_node *np)
{
+ u32 val;
+
drm_splash_dt_pair(splash, np, "logo-position", -1, 65535,
&splash->pos_x, &splash->pos_y);
drm_splash_dt_pair(splash, np, "logo-offset", -65535, 65535,
&splash->off_x, &splash->off_y);
+
+ /* Degrees counter clockwise, as for panels, and as DRM counts them */
+ if (!of_property_read_u32(np, "rotation", &val)) {
+ switch (val) {
+ 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\n",
+ np, val);
+ }
+ }
}
/*
@@ -494,19 +521,59 @@ static unsigned int drm_splash_place_axis(s32 pos, s32 off, unsigned int span,
return clamp_t(s64, coord + off, 0, last);
}
-/* The rows of a 24 bpp BMP, bottom up or top down, upright in XRGB8888 */
-static void drm_splash_bmp_decode(u32 *dst, const u8 *src, unsigned int spitch,
+/*
+ * The pixels of a 24 bpp BMP, whose rows go bottom up or top down, into
+ * XRGB8888, turned by @rotation, one of DRM_MODE_ROTATE_*, which count
+ * counter clockwise. A quarter turn swaps width and height in @dst.
+ *
+ * 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_bmp_decode(u32 *dst, const u8 *sbuf8, unsigned int spitch,
unsigned int width, unsigned int height,
- bool invert_y)
+ bool invert_y, unsigned int rotation)
{
+ bool quarter = rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ unsigned int dw = quarter ? height : width;
+ unsigned int dh = quarter ? width : height;
+ /* A positive BMP height stores the rows bottom up */
+ ptrdiff_t down = invert_y ? -(ptrdiff_t)spitch : spitch;
+ const u8 *first = sbuf8 + (invert_y ? (height - 1) * spitch : 0);
+ const u8 *last = first + (height - 1) * down;
+ ptrdiff_t step_x, step_y;
+ const u8 *src, *p;
unsigned int x, y;
- for (y = 0; y < height; y++) {
- const u8 *p = src + (invert_y ? height - 1 - y : y) * spitch;
+ /*
+ * Where the source pixel for the top left of the turned image is, and
+ * how far one step right and one step down in it move in the source
+ */
+ switch (rotation) {
+ case DRM_MODE_ROTATE_90:
+ src = first + 3 * (width - 1);
+ step_x = down;
+ step_y = -3;
+ break;
+ case DRM_MODE_ROTATE_180:
+ src = last + 3 * (width - 1);
+ step_x = -3;
+ step_y = -down;
+ break;
+ case DRM_MODE_ROTATE_270:
+ src = last;
+ step_x = -down;
+ step_y = 3;
+ break;
+ default:
+ src = first;
+ step_x = 3;
+ step_y = down;
+ break;
+ }
- for (x = 0; x < width; x++, p += 3)
+ for (y = 0; y < dh; y++, src += step_y)
+ for (x = 0, p = src; x < dw; x++, p += step_x)
*dst++ = get_unaligned_le24(p);
- }
}
/* An XRGB8888 image into a buffer of another format, at x0, y0 */
@@ -564,7 +631,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;
@@ -629,7 +696,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\n");
return -EINVAL;
}
@@ -662,19 +738,19 @@ 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;
- /* First the image upright in XRGB8888, then into the buffer's format */
+ /* First the image turned in XRGB8888, then into the buffer's format */
img = kvmalloc_array(bmp_width, bmp_height * sizeof(*img), GFP_KERNEL);
if (!img)
return -ENOMEM;
drm_splash_bmp_decode(img, image_data, bmp_pitch, bmp_width, bmp_height,
- bmp_invert_y);
+ bmp_invert_y, splash->rotation);
ret = drm_client_buffer_vmap_local(buffer, &map);
if (ret) {
@@ -683,9 +759,9 @@ 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);
drm_splash_blit(&map, fb->pitches[0], fb->format, x_pad, y_pad, img,
- bmp_width, bmp_height);
+ img_w, img_h);
kvfree(img);
drm_client_buffer_vunmap_local(buffer);
@@ -1184,9 +1260,10 @@ void drm_splash_register(struct drm_device *dev,
mutex_init(&splash->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 [flat|nested] 17+ messages in thread
* [PATCH v6 12/15] drm/client: splash: take the background colour from the image source
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (10 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 11/15] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 13/15] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
` (2 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 a device tree image takes it from the optional "background-color" of
its node. A value that is not one cell of RGB888 is reported and left
out.
CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR remains the colour wherever
the source says nothing, as with the BGRT, which carries no colour, and
the BMP loaded as firmware.
Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Màxim Pedraza Padilla <maximpedraza@gmail.com>
---
drivers/gpu/drm/clients/Kconfig | 3 ++-
drivers/gpu/drm/clients/drm_splash.c | 33 +++++++++++++++++++++-------
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/drivers/gpu/drm/clients/Kconfig b/drivers/gpu/drm/clients/Kconfig
index 172781bbc2d7..bb5fe5019756 100644
--- a/drivers/gpu/drm/clients/Kconfig
+++ b/drivers/gpu/drm/clients/Kconfig
@@ -104,7 +104,8 @@ config DRM_CLIENT_SPLASH_BACKGROUND_COLOR
default 0x000000
range 0x000000 0xffffff
help
- The splash background color, in RGB888 format.
+ The splash background color, in RGB888 format, unless the image
+ source has its own: a device tree image can carry its color.
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 a50036155763..3f54bec87413 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -117,6 +117,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 image source or the kernel's default */
+ u32 bg_color;
};
static struct drm_splash *client_to_drm_splash(struct drm_client_dev *client)
@@ -272,12 +274,23 @@ static void drm_splash_dt_pair(struct drm_splash *splash,
*y = val[1];
}
-/* 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;
+ /* The colour travels with the image; a bad one is reported */
+ if (of_property_present(np, "background-color")) {
+ if (of_property_count_u32_elems(np, "background-color") != 1 ||
+ of_property_read_u32(np, "background-color", &val) ||
+ val > 0xffffff)
+ drm_warn(splash->client.dev, "splash: %pOF: invalid %s\n",
+ np, "background-color");
+ else
+ splash->bg_color = val;
+ }
+
drm_splash_dt_pair(splash, np, "logo-position", -1, 65535,
&splash->pos_x, &splash->pos_y);
drm_splash_dt_pair(splash, np, "logo-offset", -65535, 65535,
@@ -375,8 +388,8 @@ static bool drm_splash_dt_open(struct drm_splash *splash)
return false;
}
-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)
{
}
@@ -802,13 +815,17 @@ 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)
{
+ /* Unless the source says otherwise, the kernel's default colour */
+ splash->bg_color = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
+
if (drm_splash_dt_open(splash)) {
- drm_splash_dt_placement(splash, splash->dt_np);
+ drm_splash_dt_props(splash, splash->dt_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))
return DRM_SPLASH_SRC_BMP;
@@ -875,8 +892,8 @@ static void drm_splash_work(struct work_struct *work)
continue;
if (!scanout->bg_drawn) {
- u32 rgb = CONFIG_DRM_CLIENT_SPLASH_BACKGROUND_COLOR;
- u32 color = drm_draw_color_from_xrgb8888(rgb, scanout->format);
+ u32 color = drm_draw_color_from_xrgb8888(splash->bg_color,
+ scanout->format);
/* Only success marks it done: a later pass retries */
ret = drm_splash_fill_solid_color(scanout->buffer, color);
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 13/15] drm/client: splash: turn the BGRT image on panels mounted turned
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (11 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 12/15] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 14/15] drm/client: splash: follow the connector's orientation Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 15/15] drm/client: splash: document the image sources Màxim Pedraza Padilla
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 | 70 +++++++++++++++++++++++-----
1 file changed, 58 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 3f54bec87413..0bf9d969dd8f 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -412,11 +412,6 @@ static bool drm_splash_bgrt_available(struct drm_splash *splash)
return false;
}
- if (efi_bgrt_status() & ACPI_BGRT_ORIENTATION_OFFSET) {
- drm_dbg(client->dev, "splash: BGRT rotation bits set, skipping\n");
- return false;
- }
-
return true;
}
@@ -449,6 +444,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)
{
@@ -724,11 +739,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 > scanout->width - bmp_width ||
- y_pad > scanout->height - bmp_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\n");
return -EINVAL;
}
@@ -744,11 +769,30 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
* edge of 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\n");
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);
@@ -823,8 +867,10 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
return DRM_SPLASH_SRC_DT;
}
- if (drm_splash_bgrt_available(splash))
+ if (drm_splash_bgrt_available(splash)) {
+ splash->rotation = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
+ }
if (IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_SRC_BMP))
return DRM_SPLASH_SRC_BMP;
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 14/15] drm/client: splash: follow the connector's orientation
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (12 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 13/15] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 15/15] drm/client: splash: document the image sources Màxim Pedraza Padilla
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, linux-doc, linux-embedded,
linux-kernel
A connector can need its picture turned to be seen upright: a panel
mounted turned, as its orientation property says, or a rotation asked
for with video=...,rotate=. On an atomic driver,
drm_client_modeset_commit() turns the plane in hardware when it can,
which is 0 or 180 degrees on a plane with a rotation property, and
nothing turns it otherwise. fbdev makes up for
the rest by turning the console in software; the splash did not. On a
panel mounted upside down, the logo was then upright on a plane that
turns and upside down on one that does not, and a device tree asking
for a turn of 180 degrees to make up for it turned it twice on the
former.
Do what fbdev does: ask drm_client_rotation() what each connector needs
and whether the plane does it, and turn the buffer in software by what
the plane does not do. As for fbdev, the legacy commit leaves every
plane upright, and a reflection, which is not drawn here, means no turn
in software at all. Tiles that share a buffer but need different turns
get none in software, which is what fbdev falls back to; a plane among
them that turns in hardware still does, and a BGRT image is then not
made up for on that tile. The
image is placed and turned as the screen is seen, then moved into the
buffer, so "rotation", "logo-position" and "logo-offset" mean the same
whatever the panel and the plane, a BGRT image apart: the panel's own
orientation belongs to the panel.
A BGRT image is left on the screen the firmware drew it on, unturned
but for the table's own orientation bits: its offsets are given there,
and a logo for a panel mounted turned comes already turned unless those
bits say otherwise. Only the turn the plane does is undone 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 | 162 +++++++++++++++++++--------
1 file changed, 114 insertions(+), 48 deletions(-)
diff --git a/drivers/gpu/drm/clients/drm_splash.c b/drivers/gpu/drm/clients/drm_splash.c
index 0bf9d969dd8f..d3ed53cde0f5 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -61,6 +61,12 @@ struct drm_splash_scanout {
u32 format;
unsigned int width;
unsigned int height;
+ /*
+ * What the connector needs turned to be seen upright (panel orientation
+ * and video=...,rotate=), and the part of it the plane does not do in
+ * hardware, which is then done here.
+ */
+ unsigned int conn_rot, sw_rot;
struct drm_client_buffer *buffer;
bool bg_drawn;
bool img_drawn;
@@ -109,14 +115,20 @@ struct drm_splash {
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.
+ * Where the image goes, in pixels as the screen is seen: 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; a BGRT image
+ * goes at the table offsets, on the firmware's screen.
*/
s32 pos_x, pos_y;
s32 off_x, off_y;
- /* One of DRM_MODE_ROTATE_*: turns the image, not the screen */
+ /*
+ * One of DRM_MODE_ROTATE_*: turns the image as it is seen, whichever
+ * way the panel is mounted.
+ */
unsigned int rotation;
+ /* What the BGRT bits ask for: the turn of the firmware's own picture */
+ unsigned int bgrt_rot;
/* RGB888, from the image source or the kernel's default */
u32 bg_color;
};
@@ -529,6 +541,12 @@ static int drm_splash_fill_solid_color(struct drm_client_buffer *buffer,
}
#if IS_ENABLED(CONFIG_DRM_CLIENT_SPLASH_BMP_SUPPORT)
+/* Two DRM_MODE_ROTATE_* turns, one after the other */
+static unsigned int drm_splash_rotate_add(unsigned int a, unsigned int b)
+{
+ return BIT((ilog2(a) + ilog2(b)) & 3);
+}
+
/*
* 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
@@ -659,8 +677,11 @@ 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;
+ bool bgrt = splash->src == DRM_SPLASH_SRC_BGRT;
unsigned int x_pad, y_pad, img_w, img_h;
+ unsigned int frame, rot, vw, vh, x, y, w, h;
const u8 *image_data;
+ bool quarter;
struct iosys_map map;
struct drm_rect r;
u32 *img;
@@ -724,36 +745,41 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
}
bmp_pitch = row_len & ~(size_t)3;
- /* The room the image takes up on screen, once turned */
+ /*
+ * Work as the screen is seen, then move the result into the buffer,
+ * which is turned from what is seen by whatever the connector needs
+ * and the plane does not do. A BGRT image is seen as the firmware drew
+ * it: on the unturned screen, but for the table's own orientation
+ * bits, where its offsets are given and where a logo for a panel
+ * mounted turned comes already turned unless the bits say otherwise.
+ */
+ frame = bgrt ? BIT((ilog2(splash->bgrt_rot) + ilog2(scanout->sw_rot) -
+ ilog2(scanout->conn_rot)) & 3) : scanout->sw_rot;
+ rot = drm_splash_rotate_add(splash->rotation, frame);
+ quarter = frame & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270);
+ vw = quarter ? scanout->height : scanout->width;
+ vh = quarter ? scanout->width : scanout->height;
+
+ /* The room the image takes up as seen, once turned */
if (splash->rotation & (DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270)) {
- img_w = bmp_height;
- img_h = bmp_width;
+ w = bmp_height;
+ h = bmp_width;
} else {
- img_w = bmp_width;
- img_h = bmp_height;
+ w = bmp_width;
+ h = bmp_height;
}
- if (img_w > scanout->width || img_h > scanout->height) {
+ if (w > vw || h > vh) {
drm_err(client->dev, "splash: BMP image is too big for the screen\n");
return -EINVAL;
}
- if (splash->src == DRM_SPLASH_SRC_BGRT) {
- /*
- * 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;
+ /* A BGRT image goes where the table says, on the firmware's screen */
+ if (bgrt) {
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) {
+ if (xo > vw - w || yo > vh - h) {
drm_err(client->dev, "splash: BGRT image would overflow\n");
return -EINVAL;
}
@@ -769,36 +795,40 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
* edge of the display, looking quite bad.
*/
- if (xo != (lw - bmp_width) / 2) {
+ if (xo != (vw - w) / 2) {
drm_err(client->dev, "splash: BGRT sanity check failed\n");
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;
- }
+ x = xo;
+ y = yo;
} else {
- x_pad = drm_splash_place_axis(splash->pos_x, splash->off_x,
- scanout->width, img_w);
- y_pad = drm_splash_place_axis(splash->pos_y, splash->off_y,
- scanout->height, img_h);
+ x = drm_splash_place_axis(splash->pos_x, splash->off_x, vw, w);
+ y = drm_splash_place_axis(splash->pos_y, splash->off_y, vh, h);
+ }
+
+ /* Into the buffer: turn the rectangle as the buffer is turned */
+ switch (frame) {
+ case DRM_MODE_ROTATE_90:
+ x_pad = y;
+ y_pad = vw - x - w;
+ break;
+ case DRM_MODE_ROTATE_180:
+ x_pad = vw - x - w;
+ y_pad = vh - y - h;
+ break;
+ case DRM_MODE_ROTATE_270:
+ x_pad = vh - y - h;
+ y_pad = x;
+ break;
+ default:
+ x_pad = x;
+ y_pad = y;
+ break;
}
+ img_w = quarter ? h : w;
+ img_h = quarter ? w : h;
image_data = data + bitmap_offset;
@@ -807,7 +837,7 @@ static int drm_splash_bmp_to_scanout(struct drm_splash *splash,
if (!img)
return -ENOMEM;
drm_splash_bmp_decode(img, image_data, bmp_pitch, bmp_width, bmp_height,
- bmp_invert_y, splash->rotation);
+ bmp_invert_y, rot);
ret = drm_client_buffer_vmap_local(buffer, &map);
if (ret) {
@@ -868,7 +898,7 @@ static enum drm_splash_source drm_splash_pick_source(struct drm_splash *splash)
}
if (drm_splash_bgrt_available(splash)) {
- splash->rotation = drm_splash_bgrt_rotation();
+ splash->bgrt_rot = drm_splash_bgrt_rotation();
return DRM_SPLASH_SRC_BGRT;
}
@@ -987,10 +1017,31 @@ static void drm_splash_modeset_off(struct drm_device *dev,
modeset->fb = NULL;
}
+/*
+ * What the connector needs turned to be seen upright, and the part of it
+ * left for the splash to do. As fbdev: only an atomic commit turns the
+ * plane, and a reflection, which is not drawn here, means no turn at all.
+ */
+static void drm_splash_rotation(struct drm_client_dev *client,
+ struct drm_mode_set *modeset,
+ unsigned int *conn_rot, unsigned int *sw_rot)
+{
+ unsigned int rot = DRM_MODE_ROTATE_0;
+ bool hw = drm_client_rotation(modeset, &rot) &&
+ drm_drv_uses_atomic_modeset(client->dev);
+
+ if (!hw && (rot & ~DRM_MODE_ROTATE_MASK))
+ rot = DRM_MODE_ROTATE_0;
+
+ *conn_rot = rot & DRM_MODE_ROTATE_MASK;
+ *sw_rot = hw ? DRM_MODE_ROTATE_0 : *conn_rot;
+}
+
static int drm_splash_init_client(struct drm_splash *splash)
{
struct drm_client_dev *client = &splash->client;
struct drm_mode_set *modeset;
+ unsigned int conn_rot, sw_rot;
unsigned int fb_count = 0;
unsigned int i, k;
int err = -ENODEV;
@@ -1046,6 +1097,16 @@ static int drm_splash_init_client(struct drm_splash *splash)
tiled->height = max(tiled->height, modeset->y +
modeset->mode->vdisplay);
tiled->modesets |= BIT(idx);
+
+ /*
+ * One buffer cannot suit tiles that need different
+ * turns: as fbdev, turn none of them in software
+ */
+ drm_splash_rotation(client, modeset, &conn_rot, &sw_rot);
+ if (conn_rot != tiled->conn_rot || sw_rot != tiled->sw_rot) {
+ tiled->conn_rot = DRM_MODE_ROTATE_0;
+ tiled->sw_rot = DRM_MODE_ROTATE_0;
+ }
continue;
}
@@ -1080,6 +1141,11 @@ static int drm_splash_init_client(struct drm_splash *splash)
scanout->format = format;
scanout->width = modeset->x + modeset->mode->hdisplay;
scanout->height = modeset->y + modeset->mode->vdisplay;
+ drm_splash_rotation(client, modeset, &scanout->conn_rot,
+ &scanout->sw_rot);
+ drm_dbg(client->dev, "splash: scanout %u: connector turned %u, here %u\n",
+ splash->n_scanout - 1, 90 * ilog2(scanout->conn_rot),
+ 90 * ilog2(scanout->sw_rot));
}
/* Now that the sizes are known, allocate one buffer per scanout */
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH v6 15/15] drm/client: splash: document the image sources
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
` (13 preceding siblings ...)
2026-10-27 13:53 ` [PATCH v6 14/15] drm/client: splash: follow the connector's orientation Màxim Pedraza Padilla
@ 2026-10-27 13:53 ` Màxim Pedraza Padilla
14 siblings, 0 replies; 17+ messages in thread
From: Màxim Pedraza Padilla @ 2026-10-27 13:53 UTC (permalink / raw)
To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Ard Biesheuvel, Jonathan Corbet, Helge Deller,
Peter Jones
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-fbdev, 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 three places in a set order, and be placed
and turned. Write it down: the sources and their order, the image
format, placement and rotation, and the background.
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 | 61 +++++++++++++++++++++++++++-
2 files changed, 66 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 d3ed53cde0f5..0fc72a3fe989 100644
--- a/drivers/gpu/drm/clients/drm_splash.c
+++ b/drivers/gpu/drm/clients/drm_splash.c
@@ -40,7 +40,66 @@
* 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 again
+ * on every hotplug, on the outputs there are then, until userspace takes the
+ * display over; when userspace lets it go, the splash is shown again.
+ *
+ * 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 BITMAPINFOHEADER, or its V4 or V5 extension,
+ * 24 bits per pixel and no compression. It comes from the first of these that
+ * is present, each built in with its CONFIG_DRM_CLIENT_SPLASH_SRC_* option; if
+ * that one then fails to load, only the background is drawn:
+ *
+ * 1. a node compatible with "boot-logo" under ``/chosen`` in the device tree,
+ * carrying the BMP itself or pointing at a reserved memory region the
+ * bootloader loaded it into, if that region can be mapped (see
+ * Documentation/devicetree/bindings/display/boot-logo.yaml);
+ * 2. the EFI BGRT, the image the firmware showed;
+ * 3. the BMP named by CONFIG_DRM_CLIENT_SPLASH_BMP_NAME, ``drm_splash.bmp`` by
+ * default, 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
+ * ---------
+ *
+ * Everything is given as the screen is seen: once turned as its connector
+ * needs, for a panel mounted turned (the panel's orientation) or as asked
+ * with video=...,rotate=. The plane turns it in hardware when it can, and the
+ * splash turns its buffer in software otherwise, as fbdev does with the
+ * console. A panel mounted turned is described on the panel, not here.
+ *
+ * The image is placed at a position in 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
+ * the same 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, and any other
+ * image is centred and upright. A BGRT image is the exception to the above: it
+ * is drawn at the table offsets on the screen the firmware drew it on,
+ * unturned but for the table's orientation bits.
+ *
+ * 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.
*/
/*
--
2.39.5
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen
2026-10-27 13:53 ` [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
@ 2026-10-27 18:59 ` Rob Herring (Arm)
0 siblings, 0 replies; 17+ messages in thread
From: Rob Herring (Arm) @ 2026-10-27 18:59 UTC (permalink / raw)
To: Màxim Pedraza Padilla
Cc: linux-doc, Mario Limonciello, Thomas Zimmermann,
Maarten Lankhorst, Conor Dooley, linux-kernel, Shuah Khan,
Randy Dunlap, Ilias Apalodimas, Helge Deller, David Airlie,
Javier Martinez Canillas, Ard Biesheuvel, linux-embedded,
Francesco Valla, Jani Nikula, Krzysztof Kozlowski, Peter Jones,
linux-fbdev, Jonathan Corbet, Maxime Ripard, Simona Vetter,
linux-efi, dri-devel, Sam Ravnborg, Jocelyn Falempe, devicetree
On Tue, 27 Oct 2026 14:53:34 +0100, Màxim Pedraza Padilla wrote:
> 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 "logo" node, compatible "boot-logo", under /chosen. It 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
> a "logo-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" is the one of
> panel-common.yaml, degrees counter clockwise, but 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 | 156 ++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 157 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/boot-logo.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/display/boot-logo.example.dtb: chosen: 'logo' does not match any of the regexes: '^framebuffer', '^pinctrl-[0-9]+$'
from schema $id: http://devicetree.org/schemas/chosen.yaml
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20261027135342.2003632-8-maximpedraza@gmail.com
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-27 18:59 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 13:53 [PATCH v6 00/15] Add splash DRM client Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 01/15] drm/clients: Kconfig: indent DRM_CLIENT_DEFAULT with tabs Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 02/15] efi: bgrt: add accessors for the boot graphics Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 03/15] fbdev: efifb: move the BMP headers to <video/bmp.h> Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 04/15] drm: client: add splash client Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 05/15] MAINTAINERS: add entry for DRM " Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 06/15] drm: docs: remove bootsplash from TODO Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 07/15] dt-bindings: display: add a boot logo node under /chosen Màxim Pedraza Padilla
2026-10-27 18:59 ` Rob Herring (Arm)
2026-10-27 13:53 ` [PATCH v6 08/15] drm/client: splash: pick the image source in one place Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 09/15] drm/client: splash: add a device tree image source Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 10/15] drm/client: splash: place the device tree image where it asks Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 11/15] drm/client: splash: turn the device tree image as " Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 12/15] drm/client: splash: take the background colour from the image source Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 13/15] drm/client: splash: turn the BGRT image on panels mounted turned Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 14/15] drm/client: splash: follow the connector's orientation Màxim Pedraza Padilla
2026-10-27 13:53 ` [PATCH v6 15/15] drm/client: splash: document the image sources Màxim Pedraza Padilla
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®