On 02/12/2025 15:34, Ard Biesheuvel wrote:
On Tue, 2 Dec 2025 at 15:10, Tvrtko Ursulin <[email protected]> wrote:
Valve Steam Deck has a 800x1280 portrait screen installed in a landscape
orientation. The firmware offers a software rotated 1280x800 mode which
GRUB can be made to switch to when displaying a boot menu. If this mode
was selected frame buffer drivers will see this fake mode and fbcon
rendering will be corrupted.
Lets therefore add a selective quirk inside the current "swap with and
height" handling, which will detect this exact mode and fix it up back to
the native one.
This will allow the DRM based frame buffer drivers to detect the correct
mode and, apply the existing panel orientation quirk, and render the
console in landscape mode with no corruption.
Signed-off-by: Tvrtko Ursulin <[email protected]>
Cc: Thomas Zimmermann <[email protected]>
Cc: Ard Biesheuvel <[email protected]>
Cc: Melissa Wen <[email protected]>
Cc: [email protected]
---
drivers/firmware/efi/sysfb_efi.c | 69 +++++++++++++++++++++++++++++---
1 file changed, 63 insertions(+), 6 deletions(-)
diff --git a/drivers/firmware/efi/sysfb_efi.c b/drivers/firmware/efi/sysfb_efi.c
index 2dea98395784..6458b3193093 100644
--- a/drivers/firmware/efi/sysfb_efi.c
+++ b/drivers/firmware/efi/sysfb_efi.c
@@ -231,6 +231,27 @@ static const struct dmi_system_id efifb_dmi_system_table[]
__initconst = {
{},
};
+struct efifb_mode_fixup {
+ unsigned int width;
+ unsigned int height;
+ unsigned int pitch;
+};
+
+static const struct efifb_mode_fixup efifb_steamdeck_mode_fixup = {
+ /*
+ * Valve Steam Deck has a 800x1280 portrait screen installed in a
+ * landscape orientation. The firmware offers a software rotated
+ * 1280x800 mode which GRUB can be made to switch to when displaying a
+ * boot menu. If this mode was selected we need to fix it up back to the
+ * native mode so frame buffer drivers can correctly probe, detect the
+ * panel orientation quirk based on it, and the console renders with no
+ * corruption in the software rotated mode.
+ */
I don't think we need this wall of text. The only material difference
between this quirk and the other ones is that the pitch is not simply
4x the width, but I suspect that in the other cases, it also only
works when using the Blit() interface, and I guess this is what
Windows uses.
Ok removed.
+ .width = 1280,
+ .height = 800,
+ .pitch = 3328,
Can we call this 'linelength' instead?
Done.
+};
+
/*
* Some devices have a portrait LCD but advertise a landscape resolution (and
* pitch). We simply swap width and height for these devices so that we can
@@ -281,6 +302,24 @@ static const struct dmi_system_id
efifb_dmi_swap_width_height[] __initconst = {
DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X91"),
},
},
+ {
+ /* Valve Steam Deck (Jupiter) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Jupiter"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"),
+ },
+ .driver_data = (void *)&efifb_steamdeck_mode_fixup,
+ },
+ {
+ /* Valve Steam Deck (Galileo) */
+ .matches = {
+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Valve"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Galileo"),
+ DMI_EXACT_MATCH(DMI_PRODUCT_VERSION, "1"),
+ },
+ .driver_data = (void *)&efifb_steamdeck_mode_fixup,
+ },
{},
};
@@ -351,16 +390,34 @@ static struct fwnode_handle efifb_fwnode;
__init void sysfb_apply_efi_quirks(void)
{
+ const struct dmi_system_id *match;
+
if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI ||
!(screen_info.capabilities & VIDEO_CAPABILITY_SKIP_QUIRKS))
dmi_check_system(efifb_dmi_system_table);
- if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI &&
- dmi_check_system(efifb_dmi_swap_width_height)) {
- swap(screen_info.lfb_width, screen_info.lfb_height);
- screen_info.lfb_linelength = (unsigned)screen_info.lfb_depth *
- screen_info.lfb_width /
- BITS_PER_BYTE;
+ if (screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
+ return;
+
+ for (match = dmi_first_match(efifb_dmi_swap_width_height);
+ match;
+ match = dmi_first_match(match + 1)) {
+ const struct efifb_mode_fixup *data = match->driver_data;
+
+ if (!data ||
+ (data->width == screen_info.lfb_width &&
+ data->height == screen_info.lfb_height)) {
+ swap(screen_info.lfb_width, screen_info.lfb_height);
+
+ if (data && data->pitch) {
+ screen_info.lfb_linelength = data->pitch;
+ screen_info.lfb_size = data->pitch *
data->width;
+ } else {
+ screen_info.lfb_linelength =
(unsigned)screen_info.lfb_depth *
Please don't use bare 'unsigned' as a type. Is it really needed in the
first place? Is it because it gets promoted to (signed int) otherwise?
Yep. I made it unsigned int. Was just trying to keep the line length
under control, otherwise I would have never used bare unsigned. :)
+
screen_info.lfb_width /
+ BITS_PER_BYTE;
+ }
+ }
I'd prefer to avoid all this boilerplate, and use a callback instead
(but perhaps combined with the previous fix):
static int __init efifb_swap_width_height(const struct dmi_system_id *id)
{
swap(screen_info.lfb_width, screen_info.lfb_height);
screen_info.lfb_linelength = screen_info.lfb_depth *
screen_info.lfb_width / BITS_PER_BYTE;
return 1;
}
and then add the handling of id->data in this patch.
You'll need to add .callback fields to the dmi_system_id array,
though, but this only takes up space that is already statically
allocated anyway.
Then, the code in sysfb_apply_efi_quirks() just becomes
if (screen_info.orig_video_isVGA == VIDEO_TYPE_EFI)
dmi_check_system(efifb_dmi_swap_width_height);
Okay done in v2 as well.
Regards,
Tvrtko