[PATCH 5/8] firmware: sysfb: Implement screen_info relocation for primary display

Thomas Zimmermann
2026-04-02T09:23:49Z

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