[PATCH 5/8] firmware: sysfb: Implement screen_info relocation for primary display
From: Thomas Zimmermann <tzimmermann@suse.de>
Date: 2026-04-02 09:23:56
Also in:
dri-devel, linux-efi, linux-fbdev, linux-hyperv, linux-riscv, loongarch
Subsystem:
drm driver for firmware framebuffers, framebuffer layer, the rest · Maintainers:
Thomas Zimmermann, Javier Martinez Canillas, Helge Deller, Linus Torvalds
Move the relocation tracking for screen_info from the screen_info helpers to sysfb. The relocation code operates on sysfb_primary_display, which belongs to sysfb. The remaining screen_info helpers are now free from global state. Adapt some symbol names. Now prefer early returns in the helper sysfb_apply_screen_info_fixup() over nested branching. Also return an errno code from sysfb_apply_screen_info_fixup() if the relocation failed. In this case, do not create a device for the framebuffer. The original code advertised this behavior in a comment but never implemented it. Framebuffer aperture relocation can happen during boot if the PCI graphics device is located behind a PCI bridge. If the bridge's sub- bus gets relocated, the framebuffer aperture moves accordingly. The helper for tracking these relocations fixes up the values stored in sysfb_primary_display so that they refer to the correct address range again. Generic system-framebuffer drivers would not work otherwise. Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de> --- drivers/firmware/sysfb.h | 5 ++ drivers/firmware/sysfb_pci.c | 111 +++++++++++++++++++++++++++++++ drivers/firmware/sysfb_primary.c | 9 ++- drivers/video/screen_info_pci.c | 110 ------------------------------ include/linux/screen_info.h | 3 - 5 files changed, 123 insertions(+), 115 deletions(-)
diff --git a/drivers/firmware/sysfb.h b/drivers/firmware/sysfb.h
index 9f7fe2e03f68..1eaa3b0fa364 100644
--- a/drivers/firmware/sysfb.h
+++ b/drivers/firmware/sysfb.h@@ -8,8 +8,13 @@ struct pci_dev; #ifdef CONFIG_PCI +int sysfb_apply_screen_info_fixups(void); bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev); #else +static inline int sysfb_apply_screen_info_fixups(void) +{ + return 0; +} static inline bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev) { return false;
diff --git a/drivers/firmware/sysfb_pci.c b/drivers/firmware/sysfb_pci.c
index 8f3adeef4fb1..d972750c6bc6 100644
--- a/drivers/firmware/sysfb_pci.c
+++ b/drivers/firmware/sysfb_pci.c@@ -1,9 +1,120 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <linux/pci.h> +#include <linux/printk.h> +#include <linux/screen_info.h> +#include <linux/sysfb.h> #include "sysfb.h" +static struct pci_dev *sysfb_lfb_pdev; +static size_t sysfb_lfb_bar; +static resource_size_t sysfb_lfb_res_start; // original start of resource +static resource_size_t sysfb_lfb_offset; // framebuffer offset within resource + +static bool __sysfb_relocation_is_valid(const struct screen_info *si, struct resource *pr) +{ + u64 size = __screen_info_lfb_size(si, screen_info_video_type(si)); + + if (sysfb_lfb_offset > resource_size(pr)) + return false; + if (size > resource_size(pr)) + return false; + if (resource_size(pr) - size < sysfb_lfb_offset) + return false; + + return true; +} + +int sysfb_apply_screen_info_fixups(void) +{ + struct screen_info *si = &sysfb_primary_display.screen; + struct resource *pr; + + if (!sysfb_lfb_pdev) + return 0; /* primary display is not on a PCI device */ + + pr = &sysfb_lfb_pdev->resource[sysfb_lfb_bar]; + + if (pr->start == sysfb_lfb_res_start) + return 0; /* no relocation took place */ + + if (!__sysfb_relocation_is_valid(si, pr)) + return -ENXIO; + + /* + * Only update base if we have an actual relocation to a valid I/O range. + */ + __screen_info_set_lfb_base(si, pr->start + sysfb_lfb_offset); + pr_info("Relocating firmware framebuffer to offset %pa[d] within %pr\n", + &sysfb_lfb_offset, pr); + + return 0; +} + +static int __screen_info_lfb_pci_bus_region(const struct screen_info *si, unsigned int type, + struct pci_bus_region *r) +{ + u64 base, size; + + base = __screen_info_lfb_base(si); + if (!base) + return -EINVAL; + + size = __screen_info_lfb_size(si, type); + if (!size) + return -EINVAL; + + r->start = base; + r->end = base + size - 1; + + return 0; +} + +static void sysfb_fixup_lfb(struct pci_dev *pdev) +{ + unsigned int type; + struct pci_bus_region bus_region; + int ret; + struct resource r = { + .flags = IORESOURCE_MEM, + }; + const struct resource *pr; + const struct screen_info *si = &sysfb_primary_display.screen; + + if (sysfb_lfb_pdev) + return; // already found + + type = screen_info_video_type(si); + if (!__screen_info_has_lfb(type)) + return; // only applies to EFI; maybe VESA + + ret = __screen_info_lfb_pci_bus_region(si, type, &bus_region); + if (ret < 0) + return; + + /* + * Translate the PCI bus address to resource. Account for an offset if + * the framebuffer is behind a PCI host bridge. + */ + pcibios_bus_to_resource(pdev->bus, &r, &bus_region); + + pr = pci_find_resource(pdev, &r); + if (!pr) + return; + + /* + * We've found a PCI device with the framebuffer resource. Store away + * the parameters to track relocation of the framebuffer aperture. + */ + sysfb_lfb_pdev = pdev; + sysfb_lfb_bar = pr - pdev->resource; + sysfb_lfb_offset = r.start - pr->start; + sysfb_lfb_res_start = bus_region.start; +} +DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY, 16, + sysfb_fixup_lfb); + bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev) { /*
diff --git a/drivers/firmware/sysfb_primary.c b/drivers/firmware/sysfb_primary.c
index ab8d7fc468bb..298f87a43a7e 100644
--- a/drivers/firmware/sysfb_primary.c
+++ b/drivers/firmware/sysfb_primary.c@@ -32,6 +32,7 @@ #include <linux/pci.h> #include <linux/platform_data/simplefb.h> #include <linux/platform_device.h> +#include <linux/printk.h> #include <linux/screen_info.h> #include <linux/sysfb.h>
@@ -127,11 +128,15 @@ static __init int sysfb_init(void) struct simplefb_platform_data mode; const char *name; bool compatible; - int ret = 0; + int ret; - screen_info_apply_fixups(); + ret = sysfb_apply_screen_info_fixups(); mutex_lock(&disable_lock); + if (ret) { + pr_warn("Invalid relocation, disabling system framebuffer\n"); + disabled = true; /* screen_info relocation failed */ + } if (disabled) goto unlock_mutex;
diff --git a/drivers/video/screen_info_pci.c b/drivers/video/screen_info_pci.c
index 8f34d8a74f09..d8985a54ce71 100644
--- a/drivers/video/screen_info_pci.c
+++ b/drivers/video/screen_info_pci.c@@ -1,117 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/pci.h> -#include <linux/printk.h> #include <linux/screen_info.h> -#include <linux/string.h> -#include <linux/sysfb.h> - -static struct pci_dev *screen_info_lfb_pdev; -static size_t screen_info_lfb_bar; -static resource_size_t screen_info_lfb_res_start; // original start of resource -static resource_size_t screen_info_lfb_offset; // framebuffer offset within resource - -static bool __screen_info_relocation_is_valid(const struct screen_info *si, struct resource *pr) -{ - u64 size = __screen_info_lfb_size(si, screen_info_video_type(si)); - - if (screen_info_lfb_offset > resource_size(pr)) - return false; - if (size > resource_size(pr)) - return false; - if (resource_size(pr) - size < screen_info_lfb_offset) - return false; - - return true; -} - -void screen_info_apply_fixups(void) -{ - struct screen_info *si = &sysfb_primary_display.screen; - - if (screen_info_lfb_pdev) { - struct resource *pr = &screen_info_lfb_pdev->resource[screen_info_lfb_bar]; - - if (pr->start != screen_info_lfb_res_start) { - if (__screen_info_relocation_is_valid(si, pr)) { - /* - * Only update base if we have an actual - * relocation to a valid I/O range. - */ - __screen_info_set_lfb_base(si, pr->start + screen_info_lfb_offset); - pr_info("Relocating firmware framebuffer to offset %pa[d] within %pr\n", - &screen_info_lfb_offset, pr); - } else { - pr_warn("Invalid relocating, disabling firmware framebuffer\n"); - } - } - } -} - -static int __screen_info_lfb_pci_bus_region(const struct screen_info *si, unsigned int type, - struct pci_bus_region *r) -{ - u64 base, size; - - base = __screen_info_lfb_base(si); - if (!base) - return -EINVAL; - - size = __screen_info_lfb_size(si, type); - if (!size) - return -EINVAL; - - r->start = base; - r->end = base + size - 1; - - return 0; -} - -static void screen_info_fixup_lfb(struct pci_dev *pdev) -{ - unsigned int type; - struct pci_bus_region bus_region; - int ret; - struct resource r = { - .flags = IORESOURCE_MEM, - }; - const struct resource *pr; - const struct screen_info *si = &sysfb_primary_display.screen; - - if (screen_info_lfb_pdev) - return; // already found - - type = screen_info_video_type(si); - if (!__screen_info_has_lfb(type)) - return; // only applies to EFI; maybe VESA - - ret = __screen_info_lfb_pci_bus_region(si, type, &bus_region); - if (ret < 0) - return; - - /* - * Translate the PCI bus address to resource. Account - * for an offset if the framebuffer is behind a PCI host - * bridge. - */ - pcibios_bus_to_resource(pdev->bus, &r, &bus_region); - - pr = pci_find_resource(pdev, &r); - if (!pr) - return; - - /* - * We've found a PCI device with the framebuffer - * resource. Store away the parameters to track - * relocation of the framebuffer aperture. - */ - screen_info_lfb_pdev = pdev; - screen_info_lfb_bar = pr - pdev->resource; - screen_info_lfb_offset = r.start - pr->start; - screen_info_lfb_res_start = bus_region.start; -} -DECLARE_PCI_FIXUP_CLASS_HEADER(PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY, 16, - screen_info_fixup_lfb); static struct pci_dev *__screen_info_pci_dev(struct resource *res) {
diff --git a/include/linux/screen_info.h b/include/linux/screen_info.h
index c022403c599a..2adbe25b88d8 100644
--- a/include/linux/screen_info.h
+++ b/include/linux/screen_info.h@@ -140,11 +140,8 @@ u32 __screen_info_lfb_bits_per_pixel(const struct screen_info *si); int screen_info_pixel_format(const struct screen_info *si, struct pixel_format *f); #if defined(CONFIG_PCI) -void screen_info_apply_fixups(void); struct pci_dev *screen_info_pci_dev(const struct screen_info *si); #else -static inline void screen_info_apply_fixups(void) -{ } static inline struct pci_dev *screen_info_pci_dev(const struct screen_info *si) { return NULL;
--
2.53.0