From de8db23aa7c337e606fca9faf48b3ba72968597a Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 1 Jul 2026 20:12:24 +0900 Subject: [PATCH 01/43] fbdev: uvesafb: unregister connector callback on init failure uvesafb_init() registers the v86d connector callback before registering the platform driver. If platform_driver_register() fails, the function returns the error directly and leaves the connector callback registered. The later platform-device failure path already unregisters the callback. Add the same cleanup before the final return when platform-driver registration fails. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 8bdb3a2d7df4 ("uvesafb: the driver core") Cc: stable@vger.kernel.org Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Helge Deller --- drivers/video/fbdev/uvesafb.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/video/fbdev/uvesafb.c b/drivers/video/fbdev/uvesafb.c index 9d82326c744f..ccc9dbc25813 100644 --- a/drivers/video/fbdev/uvesafb.c +++ b/drivers/video/fbdev/uvesafb.c @@ -1907,6 +1907,8 @@ static int uvesafb_init(void) err = 0; } } + if (err) + cn_del_callback(&uvesafb_cn_id); return err; } From 95a627143a696f70e17be5ed3b1e733bb6724b46 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Wed, 1 Jul 2026 20:21:47 +0900 Subject: [PATCH 02/43] fbdev: tdfxfb: fix PCI enable cleanup with pcim_enable_device() tdfxfb_probe() enables the PCI device with pci_enable_device(), but several failure paths after that point return without disabling it. The framebuffer_alloc() failure path returns -ENOMEM directly, and the later shared out_err path releases the framebuffer and returns -ENXIO without balancing the PCI enable state. The successful probe path has the same imbalance because tdfxfb_remove() releases the framebuffer, mappings and regions, but never calls pci_disable_device(). Use pcim_enable_device() so the PCI device is disabled automatically on probe failure and driver detach. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index cc6a074f3165..9a06cef75699 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -1385,7 +1385,7 @@ static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (err) return err; - err = pci_enable_device(pdev); + err = pcim_enable_device(pdev); if (err) { printk(KERN_ERR "tdfxfb: Can't enable pdev: %d\n", err); return err; From 31875c51c31edec57b3620f278680c23a823763b Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Thu, 2 Jul 2026 17:50:35 +0200 Subject: [PATCH 03/43] fbdev: omap2: dsi: do not copy isr table To te able to unregister stuff from isrs, the corresponding table was copied. Nobody seems to unregister stuff that way, so it does not help. But there are stack-allocated objects passed to these isrs giving chances of UAF of these objects if irqs are unregistered while they are handled, so better do not copy that table. Suggested-by: sashiko-bot@kernel.org Signed-off-by: Andreas Kemnade Signed-off-by: Helge Deller --- drivers/video/fbdev/omap2/omapfb/dss/dsi.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/dss/dsi.c b/drivers/video/fbdev/omap2/omapfb/dss/dsi.c index 330d9fb7d2b0..d98db01fdd39 100644 --- a/drivers/video/fbdev/omap2/omapfb/dss/dsi.c +++ b/drivers/video/fbdev/omap2/omapfb/dss/dsi.c @@ -326,8 +326,6 @@ struct dsi_data { spinlock_t irq_lock; struct dsi_isr_tables isr_tables; - /* space for a copy used by the interrupt handler */ - struct dsi_isr_tables isr_tables_copy; int update_channel; #ifdef DSI_PERF_MEASURE @@ -838,15 +836,10 @@ static irqreturn_t omap_dsi_irq_handler(int irq, void *arg) timer_delete(&dsi->te_timer); #endif - /* make a copy and unlock, so that isrs can unregister - * themselves */ - memcpy(&dsi->isr_tables_copy, &dsi->isr_tables, - sizeof(dsi->isr_tables)); + dsi_handle_isrs(&dsi->isr_tables, irqstatus, vcstatus, ciostatus); spin_unlock(&dsi->irq_lock); - dsi_handle_isrs(&dsi->isr_tables_copy, irqstatus, vcstatus, ciostatus); - dsi_handle_irq_errors(dsidev, irqstatus, vcstatus, ciostatus); dsi_collect_irq_stats(dsidev, irqstatus, vcstatus, ciostatus); From 74c09634a52da1fc3910cfc27a128c511f5e1d20 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Sun, 5 Jul 2026 08:14:27 +0800 Subject: [PATCH 04/43] fbdev: sstfb: add missing MODULE_DEVICE_TABLE() The driver has a match table for the pci bus wired into its driver structure, but the table is not exported with MODULE_DEVICE_TABLE(). Add the missing MODULE_DEVICE_TABLE() entry so module alias information is generated for automatic module loading. This is a source-level fix. It does not claim dynamic hardware reproduction; the evidence is the driver-owned match table, its use by the driver registration structure, and the missing module alias publication. Signed-off-by: Pengpeng Hou Signed-off-by: Helge Deller --- drivers/video/fbdev/sstfb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/fbdev/sstfb.c b/drivers/video/fbdev/sstfb.c index 2ea947f57efb..2745557822f7 100644 --- a/drivers/video/fbdev/sstfb.c +++ b/drivers/video/fbdev/sstfb.c @@ -1492,6 +1492,7 @@ static const struct pci_device_id sstfb_id_tbl[] = { .driver_data = ID_VOODOO2, }, { 0 }, }; +MODULE_DEVICE_TABLE(pci, sstfb_id_tbl); static struct pci_driver sstfb_driver = { .name = "sstfb", From 3cc2fad376ac7360b4518277aa923752624db59c Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:30:38 +0800 Subject: [PATCH 05/43] fbdev: udlfb: validate vendor descriptor items dlfb_parse_vendor_descriptor() walks key-length-value items inside the DisplayLink vendor descriptor. Require each item to contain its key, length and declared value bytes before reading item-specific fields such as max_area. Signed-off-by: Pengpeng Hou Signed-off-by: Helge Deller --- drivers/video/fbdev/udlfb.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/drivers/video/fbdev/udlfb.c b/drivers/video/fbdev/udlfb.c index fdbb8671a810..e78d6f95c9c5 100644 --- a/drivers/video/fbdev/udlfb.c +++ b/drivers/video/fbdev/udlfb.c @@ -1586,19 +1586,29 @@ static int dlfb_parse_vendor_descriptor(struct dlfb_data *dlfb, desc += 5; /* the fixed header we've already parsed */ while (desc < desc_end) { + char *value; u8 length; u16 key; - key = *desc++; - key |= (u16)*desc++ << 8; + if (desc_end - desc < sizeof(key) + sizeof(length)) + goto unrecognized; + + key = get_unaligned_le16(desc); + desc += sizeof(key); length = *desc++; + if (length > desc_end - desc) + goto unrecognized; + + value = desc; switch (key) { case 0x0200: { /* max_area */ - u32 max_area = *desc++; - max_area |= (u32)*desc++ << 8; - max_area |= (u32)*desc++ << 16; - max_area |= (u32)*desc++ << 24; + u32 max_area; + + if (length < sizeof(max_area)) + goto unrecognized; + + max_area = get_unaligned_le32(value); dev_warn(&intf->dev, "DL chip limited to %d pixel modes\n", max_area); From 5dc2e70dd74b1f03e2e13bfb6922111d9e0adf90 Mon Sep 17 00:00:00 2001 From: Florian Fuchs Date: Mon, 13 Jul 2026 12:16:38 +0200 Subject: [PATCH 06/43] fbdev: pvr2fb: correct user pointer annotation and sentinel initializer Add __user annotation to buf, as it is passed as a user pointer in pin_user_pages_fast(). Use an empty initializer for the sentinel board-table entry to avoid initializing a function pointer with an integer literal. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202607131247.fpQ6eTc7-lkp@intel.com/ Cc: stable@vger.kernel.org Signed-off-by: Florian Fuchs Signed-off-by: Helge Deller --- drivers/video/fbdev/pvr2fb.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/pvr2fb.c b/drivers/video/fbdev/pvr2fb.c index 9428716e2dc4..a6e7abca7a06 100644 --- a/drivers/video/fbdev/pvr2fb.c +++ b/drivers/video/fbdev/pvr2fb.c @@ -639,7 +639,7 @@ static irqreturn_t __maybe_unused pvr2fb_interrupt(int irq, void *dev_id) } #ifdef CONFIG_PVR2_DMA -static ssize_t pvr2fb_write(struct fb_info *info, const char *buf, +static ssize_t pvr2fb_write(struct fb_info *info, const char __user *buf, size_t count, loff_t *ppos) { unsigned long dst, start, end, len; @@ -1077,7 +1077,7 @@ static struct pvr2_board { #ifdef CONFIG_PCI { pvr2fb_pci_init, pvr2fb_pci_exit, "PCI PVR2" }, #endif - { 0, }, + { }, }; static int __init pvr2fb_init(void) From 048ad864d61feedc24129eeb976e973edcbc3aac Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sat, 18 Jul 2026 12:13:26 -0700 Subject: [PATCH 07/43] fonts: fixup font.h kernel-doc warnings Use the typedef keyword when describing a typedef. Add the missing function return value for font_glyph_size(). Warning: include/linux/font.h:84 cannot understand function prototype: 'typedef const unsigned char font_data_t;' Warning: include/linux/font.h:53 No description found for return value of 'font_glyph_size' Signed-off-by: Randy Dunlap Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Helge Deller --- include/linux/font.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/linux/font.h b/include/linux/font.h index 6845f02d739a..877efe165d2a 100644 --- a/include/linux/font.h +++ b/include/linux/font.h @@ -49,6 +49,8 @@ static inline unsigned int font_glyph_pitch(unsigned int width) * scanlines, which is usually the glyph's height in scanlines. Fonts * coming from user space can sometimes have a different vertical pitch * with empty scanlines between two adjacent glyphs. + * + * Returns: the number of bytes per glyph */ static inline unsigned int font_glyph_size(unsigned int width, unsigned int vpitch) { @@ -60,7 +62,7 @@ static inline unsigned int font_glyph_size(unsigned int width, unsigned int vpit */ /** - * font_data_t - Raw font data + * typedef font_data_t - Raw font data * * Values of type font_data_t store a pointer to raw font data. The format * is monochrome. Each bit sets a pixel of a stored glyph. Font data does From 87a1bc216d54d9165f2f21d5db2d89515a316007 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:41 +0800 Subject: [PATCH 08/43] fbdev: mmp: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/mmp/hw/mmp_ctrl.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/video/fbdev/mmp/hw/mmp_ctrl.c b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c index 75bbdc0b4aa6..2c457da67a9f 100644 --- a/drivers/video/fbdev/mmp/hw/mmp_ctrl.c +++ b/drivers/video/fbdev/mmp/hw/mmp_ctrl.c @@ -505,8 +505,6 @@ static int mmphw_probe(struct platform_device *pdev) ret = devm_request_irq(ctrl->dev, ctrl->irq, ctrl_handle_irq, IRQF_SHARED, "lcd_controller", ctrl); if (ret < 0) { - dev_err(ctrl->dev, "%s unable to request IRQ %d\n", - __func__, ctrl->irq); ret = -ENXIO; goto failed; } From 3c82279841c9fa967ff94fa45161eaab4ac9f65a Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:42 +0800 Subject: [PATCH 09/43] fbdev: omapfb/dsi-cm: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c index 5e7963b4aa93..7c3463ee02ef 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c @@ -1185,10 +1185,8 @@ static int dsicm_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING, "taal vsync", ddata); - if (r) { - dev_err(dev, "IRQ request failed\n"); + if (r) return r; - } INIT_DEFERRABLE_WORK(&ddata->te_timeout_work, dsicm_te_timeout_work_callback); From dde94092ec37a38cc3101adaa19cc24a7057cfae Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:43 +0800 Subject: [PATCH 10/43] fbdev: pxa168fb: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/pxa168fb.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/video/fbdev/pxa168fb.c b/drivers/video/fbdev/pxa168fb.c index 6784888d93c9..ce8a823d7128 100644 --- a/drivers/video/fbdev/pxa168fb.c +++ b/drivers/video/fbdev/pxa168fb.c @@ -725,7 +725,6 @@ static int pxa168fb_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, pxa168fb_handle_irq, IRQF_SHARED, info->fix.id, fbi); if (ret < 0) { - dev_err(&pdev->dev, "unable to request IRQ\n"); ret = -ENXIO; goto failed_free_cmap; } From 1f2b1870743460274d3fac91bcd7179e51f232db Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:44 +0800 Subject: [PATCH 11/43] fbdev: pxa3xx-gcu: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/pxa3xx-gcu.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/video/fbdev/pxa3xx-gcu.c b/drivers/video/fbdev/pxa3xx-gcu.c index a2320e2fb8f2..fc5bd7e7aae6 100644 --- a/drivers/video/fbdev/pxa3xx-gcu.c +++ b/drivers/video/fbdev/pxa3xx-gcu.c @@ -615,10 +615,8 @@ static int pxa3xx_gcu_probe(struct platform_device *pdev) ret = devm_request_irq(dev, irq, pxa3xx_gcu_handle_irq, 0, DRV_NAME, priv); - if (ret < 0) { - dev_err(dev, "request_irq failed\n"); + if (ret < 0) return ret; - } /* allocate dma memory */ priv->shared = dma_alloc_coherent(dev, SHARED_SIZE, From 24eadacc51eeb99d7258f233bf4755514a62bbc8 Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:45 +0800 Subject: [PATCH 12/43] fbdev: pxafb: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/pxafb.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/video/fbdev/pxafb.c b/drivers/video/fbdev/pxafb.c index e418eee825fb..1682345fb9b0 100644 --- a/drivers/video/fbdev/pxafb.c +++ b/drivers/video/fbdev/pxafb.c @@ -2312,7 +2312,6 @@ static int pxafb_probe(struct platform_device *dev) ret = devm_request_irq(&dev->dev, irq, pxafb_handle_irq, 0, "LCD", fbi); if (ret) { - dev_err(&dev->dev, "request_irq failed: %d\n", ret); ret = -EBUSY; goto failed_free_mem; } From 1867db62c1483f3d67820511e7beef27a506728a Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:46 +0800 Subject: [PATCH 13/43] fbdev: s3c-fb: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/s3c-fb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/video/fbdev/s3c-fb.c b/drivers/video/fbdev/s3c-fb.c index 2f4d707e2e09..82a3bc9de433 100644 --- a/drivers/video/fbdev/s3c-fb.c +++ b/drivers/video/fbdev/s3c-fb.c @@ -1421,10 +1421,8 @@ static int s3c_fb_probe(struct platform_device *pdev) ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq, 0, "s3c_fb", sfb); - if (ret) { - dev_err(dev, "irq request failed\n"); + if (ret) goto err_lcd_clk; - } dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs); From fa1005144f7e448017617187caa368a715fe6ddb Mon Sep 17 00:00:00 2001 From: Pan Chuang Date: Wed, 22 Jul 2026 15:57:47 +0800 Subject: [PATCH 14/43] fbdev: sa1100fb: Remove redundant dev_err() Since commit 55b48e23f5c4 ("genirq/devres: Add error handling in devm_request_*_irq()"), devm_request_irq() automatically logs detailed error messages on failure. Remove the now-redundant driver-specific dev_err() call. Signed-off-by: Pan Chuang Signed-off-by: Helge Deller --- drivers/video/fbdev/sa1100fb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/video/fbdev/sa1100fb.c b/drivers/video/fbdev/sa1100fb.c index 0d362d2bf0e3..fea0172c45b5 100644 --- a/drivers/video/fbdev/sa1100fb.c +++ b/drivers/video/fbdev/sa1100fb.c @@ -1167,10 +1167,8 @@ static int sa1100fb_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, sa1100fb_handle_irq, 0, "LCD", fbi); - if (ret) { - dev_err(&pdev->dev, "request_irq failed: %d\n", ret); + if (ret) return ret; - } fbi->shannon_lcden = gpiod_get_optional(&pdev->dev, "shannon-lcden", GPIOD_OUT_LOW); From d1917ccb7b6f830c0f0c3734bdc8af66a002a9af Mon Sep 17 00:00:00 2001 From: Julian Braha Date: Thu, 23 Jul 2026 13:28:18 +0100 Subject: [PATCH 15/43] fbdev: mb862xx: replace dead select with dependency 'select' does not work on config options in a 'choice', so currently it is possible to enable FB_MB862XX_LIME without FB_LITTLE_ENDIAN. We cannot replace the 'select FB_LITTLE_ENDIAN' without also changing FB_FOREIGN_ENDIAN from 'select' to 'depends on', otherwise we will get a recursive dependency. Since the default choice is FB_BOTH_ENDIAN, let's use: 'depends on FB_LITTLE_ENDIAN || FB_BOTH_ENDIAN' to avoid breaking defconfig. This dead select was found by kconfirm, a static analysis tool for Kconfig. Suggested-by: Arnd Bergmann Signed-off-by: Julian Braha Link: https://lore.kernel.org/all/20260722220023.196029-1-julianbraha@gmail.com/ Signed-off-by: Helge Deller --- drivers/video/fbdev/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig index 085d3a202148..e8cd8cb76874 100644 --- a/drivers/video/fbdev/Kconfig +++ b/drivers/video/fbdev/Kconfig @@ -1717,8 +1717,7 @@ config FB_MB862XX_PCI_GDC config FB_MB862XX_LIME bool "Lime GDC" depends on OF && PPC - select FB_FOREIGN_ENDIAN - select FB_LITTLE_ENDIAN + depends on FB_LITTLE_ENDIAN || FB_BOTH_ENDIAN help Framebuffer support for Fujitsu Lime GDC on host CPU bus. From 7b5c7bc55e13e7f5ac7b1eaf5c6d690389ea5ee3 Mon Sep 17 00:00:00 2001 From: Danila Chernetsov Date: Fri, 24 Jul 2026 00:42:45 +0000 Subject: [PATCH 16/43] fbdev: kyro: Validate overlay viewport coordinates The overlay viewport end coordinates are computed from the viewport origin and dimensions using 32-bit unsigned arithmetic. Large input values can cause these calculations to wrap around before the resulting coordinates are passed to SetOverlayViewPort(). SetOverlayViewPort() packs the viewport coordinates into 16-bit register fields. The X coordinates are additionally adjusted by +2 and +1 before being written. Validate the coordinate calculations for 32-bit wraparound and ensure that the adjusted coordinates fit within their 16-bit register fields before calling SetOverlayViewPort(). Found by Linux Verification Center (linuxtesting.org) with SVACE. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Danila Chernetsov Signed-off-by: Helge Deller --- drivers/video/fbdev/kyro/fbdev.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/video/fbdev/kyro/fbdev.c b/drivers/video/fbdev/kyro/fbdev.c index d756b3603fa6..c23738988822 100644 --- a/drivers/video/fbdev/kyro/fbdev.c +++ b/drivers/video/fbdev/kyro/fbdev.c @@ -369,6 +369,9 @@ static int kyro_dev_overlay_create(u32 ulWidth, static int kyro_dev_overlay_viewport_set(u32 x, u32 y, u32 ulWidth, u32 ulHeight) { + u32 right; + u32 bottom; + if (deviceInfo.ulOverlayOffset == 0) /* probably haven't called CreateOverlay yet */ return -EINVAL; @@ -378,11 +381,30 @@ static int kyro_dev_overlay_viewport_set(u32 x, u32 y, u32 ulWidth, u32 ulHeight (x < 2 && ulWidth + 2 == 0)) return -EINVAL; + /* + * SetOverlayViewPort() adjusts X coordinates by +2 (left) and +1 + * (right) before packing them into 16-bit register fields. + */ + if (x > U16_MAX - 2 || y > U16_MAX) + return -EINVAL; + + right = x + ulWidth; + bottom = y + ulHeight; + + if (right < x || bottom < y) + return -EINVAL; + + right--; + bottom--; + + if (right > U16_MAX - 1 || bottom > U16_MAX) + return -EINVAL; + /* Stop Ramdac Output */ DisableRamdacOutput(deviceInfo.pSTGReg); SetOverlayViewPort(deviceInfo.pSTGReg, - x, y, x + ulWidth - 1, y + ulHeight - 1); + x, y, right, bottom); EnableOverlayPlane(deviceInfo.pSTGReg); /* Start Ramdac Output */ From 27b8e3c27d858156c6a6b94919d5964fabd40f7a Mon Sep 17 00:00:00 2001 From: Mingyu Wang <25181214217@stu.xidian.edu.cn> Date: Tue, 21 Jul 2026 15:15:22 +0800 Subject: [PATCH 17/43] fbdev: core: Clamp total_size to smem_len in read/write functions Some legacy fbdev drivers may incorrectly set info->screen_size to a value larger than the actual mapped framebuffer size (info->fix.smem_len) during mode switches. This could allow out-of-bounds I/O and system memory accesses in fb_io_read(), fb_io_write(), fb_sys_read(), and fb_sys_write(). Prevent this by clamping total_size to smem_len when smem_len is non-zero. Virtual framebuffers (smem_len == 0) are unaffected. This is a hardening measure; no specific crash is fixed by this patch. Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn> Signed-off-by: Helge Deller --- drivers/video/fbdev/core/fb_io_fops.c | 16 ++++++++++++++++ drivers/video/fbdev/core/fb_sys_fops.c | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/drivers/video/fbdev/core/fb_io_fops.c b/drivers/video/fbdev/core/fb_io_fops.c index 0798e88799eb..a8a27d8f3009 100644 --- a/drivers/video/fbdev/core/fb_io_fops.c +++ b/drivers/video/fbdev/core/fb_io_fops.c @@ -24,6 +24,14 @@ ssize_t fb_io_read(struct fb_info *info, char __user *buf, size_t count, loff_t if (total_size == 0) total_size = info->fix.smem_len; + /* + * Security Hardening: Defend against buggy legacy drivers that may + * calculate a malformed screen_size. Clamp total_size to the actual + * hardware mapped memory limit (smem_len) to prevent OOB access. + */ + if (info->fix.smem_len && total_size > info->fix.smem_len) + total_size = info->fix.smem_len; + if (p >= total_size) return 0; @@ -96,6 +104,14 @@ ssize_t fb_io_write(struct fb_info *info, const char __user *buf, size_t count, if (total_size == 0) total_size = info->fix.smem_len; + /* + * Security Hardening: Defend against buggy legacy drivers that may + * calculate a malformed screen_size. Clamp total_size to the actual + * hardware mapped memory limit (smem_len) to prevent OOB access. + */ + if (info->fix.smem_len && total_size > info->fix.smem_len) + total_size = info->fix.smem_len; + if (p > total_size) return -EFBIG; diff --git a/drivers/video/fbdev/core/fb_sys_fops.c b/drivers/video/fbdev/core/fb_sys_fops.c index be96b3b3942e..e97cf02f7c70 100644 --- a/drivers/video/fbdev/core/fb_sys_fops.c +++ b/drivers/video/fbdev/core/fb_sys_fops.c @@ -35,6 +35,14 @@ ssize_t fb_sys_read(struct fb_info *info, char __user *buf, size_t count, if (total_size == 0) total_size = info->fix.smem_len; + /* + * Security Hardening: Defend against buggy legacy drivers that may + * calculate a malformed screen_size. Clamp total_size to the actual + * hardware mapped memory limit (smem_len) to prevent OOB access. + */ + if (info->fix.smem_len && total_size > info->fix.smem_len) + total_size = info->fix.smem_len; + if (p >= total_size) return 0; @@ -80,6 +88,14 @@ ssize_t fb_sys_write(struct fb_info *info, const char __user *buf, if (total_size == 0) total_size = info->fix.smem_len; + /* + * Security Hardening: Defend against buggy legacy drivers that may + * calculate a malformed screen_size. Clamp total_size to the actual + * hardware mapped memory limit (smem_len) to prevent OOB access. + */ + if (info->fix.smem_len && total_size > info->fix.smem_len) + total_size = info->fix.smem_len; + if (p > total_size) return -EFBIG; From ff21ab01014cd8625761a98d2ee09c55e9bbd0ea Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 31 Jul 2026 21:19:10 +0900 Subject: [PATCH 18/43] fbdev: tdfxfb: Add helper to read config table from BIOS In the case that the video BIOS didn't run because the card isn't the primary card, the BIOS doesn't support running old skool video BIOS (modern BIOS without CSM), or the machine isn't x86 it needs to be booted manually. To do this the config table in the BIOS is needed. Add a helper to get the config table in preparation for manually booting cards. Signed-off-by: Daniel Palmer Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index 9a06cef75699..fad365c00628 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -71,6 +71,7 @@ #include #include #include +#include #include #include #include @@ -336,6 +337,83 @@ static u32 do_calc_pll(int freq, int *freq_out) return (n << 8) | (m << 2) | k; } +/* + * Convert a pllctrl register value back to a frequency in kHz. + * Formula from 3dfx documentation. + */ +static u32 tdfx_pll_to_khz(u32 pll) +{ + return (14318 * (((pll >> 8) & 0xff) + 2) / + (((pll >> 2) & 0x3f) + 2)) >> (pll & 3); +} + +/* Layout of the "OEM config" table in voodoo 3 BIOS */ +struct tdfx_bios_cfg { + __le32 pciinit0; /* 0x00 */ + __le32 miscinit0; /* 0x04 */ + __le32 miscinit1; /* 0x08 */ + __le32 draminit0; /* 0x0c */ + __le32 draminit1; /* 0x10 */ + __le32 agpinit0; /* 0x14 */ + __le32 pllctrl1; /* 0x18 - memory PLL */ + __le32 pllctrl2; /* 0x1c - graphics PLL */ + __le32 sgrammode; /* 0x20 - SGRAM/SDRAM mode register data */ +} __packed; + +#define TDFX_ROM_CFG_PTR 0x50 + +static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev, + struct tdfx_bios_cfg *cfg) +{ + u16 romcfg, oemcfg; + void __iomem *rom; + size_t romsize; + u8 *image; + u32 khz; + + /* This only works for the Voodoo 3 for now */ + if (pdev->device != PCI_DEVICE_ID_3DFX_VOODOO3) + return false; + + rom = pci_map_rom(pdev, &romsize); + if (!rom || !romsize) + return false; + + image = vmalloc(romsize); + if (!image) { + pci_unmap_rom(pdev, rom); + return false; + } + memcpy_fromio(image, rom, romsize); + pci_unmap_rom(pdev, rom); + + /* ROM[0x50] -> ROM config table -> OEM config table */ + if (TDFX_ROM_CFG_PTR + 2 > romsize) + goto out; + romcfg = image[TDFX_ROM_CFG_PTR] | image[TDFX_ROM_CFG_PTR + 1] << 8; + if (romcfg == 0xffff || romcfg + 2 > romsize) + goto out; + oemcfg = image[romcfg] | image[romcfg + 1] << 8; + if (oemcfg == 0xffff || oemcfg + sizeof(*cfg) > romsize) + goto out; + memcpy(cfg, image + oemcfg, sizeof(*cfg)); + vfree(image); + + /* + * Make sure we didn't read garbage from the BIOS and will + * end up setting a frequency that explodes someone's expensive + * card. + */ + khz = tdfx_pll_to_khz(le32_to_cpu(cfg->pllctrl1)); + if (khz < 40000 || khz > 250000 || !le32_to_cpu(cfg->draminit0)) + return false; + return true; + +out: + vfree(image); + return false; +} + static void do_write_regs(struct fb_info *info, struct banshee_reg *reg) { struct tdfx_par *par = info->par; From 28c1d44af69705936f00826e0b60fb6aec95f008 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 31 Jul 2026 21:19:11 +0900 Subject: [PATCH 19/43] fbdev: tdfxfb: Attempt to detect if the card wasn't booted Until now a card had to have been booted by its video BIOS otherwise the driver would probe, create the fb etc but there would be no output on the display. There doesn't seem to be a documented way work out if the BIOS ran or not. Checking if the values in registers match what is in the config table in the BIOS seems to be the only option. On my 16MB Voodoo 3 3000 checking the contents of the draminit0 register versus what is in the config table seems to be enough. Signed-off-by: Daniel Palmer Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index fad365c00628..913624ebf805 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -414,6 +414,34 @@ static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev, return false; } +/* + * Try to work out if the card was booted or not, just checks if + * one of the dram config registers matches what is in the config + * table if there is one. + */ +static int tdfxfb_hw_init(struct fb_info *info, struct pci_dev *pdev) +{ + struct tdfx_par *par = info->par; + struct tdfx_bios_cfg cfg; + bool have_cfg = tdfxfb_get_bios_cfg(pdev, &cfg); + + /* + * Can't tell if the card is booted or not, + * also cannot boot it. Card might not function. + */ + if (!have_cfg) + return 0; + + /* Card is, probably, already configured. */ + if (tdfx_inl(par, DRAMINIT0) == le32_to_cpu(cfg.draminit0)) + return 0; + + dev_err(&pdev->dev, + "Card hasn't booted and is unusable\n"); + + return -ENODEV; +} + static void do_write_regs(struct fb_info *info, struct banshee_reg *reg) { struct tdfx_par *par = info->par; @@ -1509,6 +1537,9 @@ static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_err_regbase; } + if (tdfxfb_hw_init(info, pdev)) + goto out_err_regbase; + info->fix.smem_start = pci_resource_start(pdev, 1); info->fix.smem_len = do_lfb_size(default_par, pdev->device); if (!info->fix.smem_len) { From 08364e38dec37cb77ccc873e897c69d23b33c3e5 Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 31 Jul 2026 21:19:12 +0900 Subject: [PATCH 20/43] fbdev: tdfxfb: Manually boot unbooted cards If the card is detected as being unbooted it isn't too difficult to use the config table in its BIOS to fire it up so do it. Signed-off-by: Daniel Palmer Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index 913624ebf805..74a8dfef1d9d 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -67,6 +67,7 @@ #include #include #include +#include #include #include #include @@ -418,9 +419,13 @@ static bool tdfxfb_get_bios_cfg(struct pci_dev *pdev, * Try to work out if the card was booted or not, just checks if * one of the dram config registers matches what is in the config * table if there is one. + * + * If we have a BIOS config table attempt to manually boot the + * card if needed. */ static int tdfxfb_hw_init(struct fb_info *info, struct pci_dev *pdev) { + u32 mempll, gfxpll, draminit0, draminit1, miscinit1, dram_mode; struct tdfx_par *par = info->par; struct tdfx_bios_cfg cfg; bool have_cfg = tdfxfb_get_bios_cfg(pdev, &cfg); @@ -436,10 +441,39 @@ static int tdfxfb_hw_init(struct fb_info *info, struct pci_dev *pdev) if (tdfx_inl(par, DRAMINIT0) == le32_to_cpu(cfg.draminit0)) return 0; - dev_err(&pdev->dev, - "Card hasn't booted and is unusable\n"); + dev_info(&pdev->dev, + "Manually booting card using config table\n"); - return -ENODEV; + mempll = le32_to_cpu(cfg.pllctrl1); + gfxpll = le32_to_cpu(cfg.pllctrl2); + draminit0 = le32_to_cpu(cfg.draminit0); + draminit1 = le32_to_cpu(cfg.draminit1); + miscinit1 = le32_to_cpu(cfg.miscinit1); + dram_mode = le32_to_cpu(cfg.sgrammode); + tdfx_outl(par, PCIINIT0, le32_to_cpu(cfg.pciinit0)); + tdfx_outl(par, AGPINIT, le32_to_cpu(cfg.agpinit0)); + + /* memory clock, and the graphics clock if the card wants one */ + tdfx_outl(par, PLLCTRL1, mempll); + if (gfxpll) + tdfx_outl(par, PLLCTRL2, gfxpll); + /* flush posted writes */ + tdfx_inl(par, PLLCTRL1); + /* PLL lock */ + udelay(100); + + tdfx_outl(par, MISCINIT1, miscinit1); + tdfx_outl(par, DRAMINIT0, draminit0); + tdfx_outl(par, DRAMINIT1, draminit1); + + /* SDRAM/SGRAM wake up: load the mode register */ + tdfx_outl(par, DRAMDATA, dram_mode); + tdfx_outl(par, DRAMCOMMAND, 0x10d); + + tdfx_outl(par, LFBMEMORYCONFIG, 0x00001fff); + tdfx_outl(par, MISCINIT0, le32_to_cpu(cfg.miscinit0)); + + return 0; } static void do_write_regs(struct fb_info *info, struct banshee_reg *reg) From edfbc5d2adf151a33e8528e6e5da3cc69e49f1eb Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 31 Jul 2026 21:19:13 +0900 Subject: [PATCH 21/43] fbdev: tdfxfb: Wake the VGA core before programming the CRTC If the card was unbooted the VGA core needs to be woken up before poking at it. Signed-off-by: Daniel Palmer Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index 74a8dfef1d9d..8e5d2f3ef666 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -485,6 +485,10 @@ static void do_write_regs(struct fb_info *info, struct banshee_reg *reg) tdfx_outl(par, MISCINIT1, tdfx_inl(par, MISCINIT1) | 0x01); + /* Wake the VGA core if it hasn't already been woken up */ + tdfx_outl(par, VGAINIT0, reg->vgainit0); + vga_outb(par, 0x3c3, 0x01); + crt_outb(par, 0x11, crt_inb(par, 0x11) & 0x7f); /* CRT unprotect */ banshee_make_room(par, 3); From 0fc41be57d485ae27a696dd2e622b4d5c1a96cfa Mon Sep 17 00:00:00 2001 From: Daniel Palmer Date: Fri, 31 Jul 2026 21:19:14 +0900 Subject: [PATCH 22/43] fbdev: tdfxfb: Program the initial video mode If the card does not get bound to by fbcon set_par() never happens and the initial video mode is not setup and the display detects no signal. Program the video mode and also clear the framebuffer memory so random garbage isn't displayed. Signed-off-by: Daniel Palmer Signed-off-by: Helge Deller --- drivers/video/fbdev/tdfxfb.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/video/fbdev/tdfxfb.c b/drivers/video/fbdev/tdfxfb.c index 8e5d2f3ef666..2819875022f6 100644 --- a/drivers/video/fbdev/tdfxfb.c +++ b/drivers/video/fbdev/tdfxfb.c @@ -1686,6 +1686,14 @@ static int tdfxfb_probe(struct pci_dev *pdev, const struct pci_device_id *id) goto out_err_iobase; } + /* + * Program a video mode and clear the framebuffer now, this + * ensures the display comes up even if fbcon doesn't bind + * when the framebuffer is registered. + */ + tdfxfb_set_par(info); + memset_io(info->screen_base, 0, info->fix.smem_len); + if (register_framebuffer(info) < 0) { printk(KERN_ERR "tdfxfb: can't register framebuffer\n"); fb_dealloc_cmap(&info->cmap); From 49e4950e177858f3331028b030c40ed1a25e9eb2 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 1 Aug 2026 21:09:49 +0200 Subject: [PATCH 23/43] fbdev: au1100fb: drop unneeded semicolon When a function-like macro expands to an expression, that expression doesn't need a semicolon after it. All uses have been verified to have their own semicolons. This was found using the following Coccinelle semantic patch: @r@ identifier i : script:ocaml() { String.lowercase_ascii i = i }; expression e; @@ *#define i(...) e; Signed-off-by: Julia Lawall Signed-off-by: Helge Deller --- drivers/video/fbdev/au1100fb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/au1100fb.c b/drivers/video/fbdev/au1100fb.c index c54cfcd832bb..39f86ffb1ae3 100644 --- a/drivers/video/fbdev/au1100fb.c +++ b/drivers/video/fbdev/au1100fb.c @@ -393,7 +393,7 @@ static struct au1100fb_panel known_lcd_panels[] = #define DRIVER_DESC "LCD controller driver for AU1100 processors" #define to_au1100fb_device(_info) \ - (_info ? container_of(_info, struct au1100fb_device, info) : NULL); + (_info ? container_of(_info, struct au1100fb_device, info) : NULL) /* Bitfields format supported by the controller. Note that the order of formats * SHOULD be the same as in the LCD_CONTROL_SBPPF field, so we can retrieve the From 9ad709afdfa32509ed64938a6d9cd00db3cd54c2 Mon Sep 17 00:00:00 2001 From: Hui Su Date: Tue, 4 Aug 2026 02:39:57 +0800 Subject: [PATCH 24/43] fbdev: ssd1307fb: defer I2C transfers from damage callbacks The fbdev damage callbacks may run from fbcon while printk has disabled preemption. They currently update the display synchronously, which enters the sleeping I2C transfer path from atomic context. A complete report from an RK3566 system follows: [ 258.129004] watchdog: watchdog0: watchdog did not stop! [ 258.129067] BUG: scheduling while atomic: systemd/1/0x00000003 [ 258.129076] Modules linked in: algif_hash algif_skcipher af_alg bnep binfmt_misc lz4hc lz4 zram snd_soc_hdmi_codec brcmfmac_wcc hci_uart fb_ssd1306(C) fbtft(C) btqca btrtl btintel btsdio snd_soc_simple_card motorcomm pwm_fan snd_soc_simple_card_utils ssd130x_spi nls_iso8859_1 ssd130x btbcm drm_shmem_helper display_connector brcmfmac ssd1307fb brcmutil bluetooth cfg80211 rfkill snd_soc_rockchip_i2s_tdm snd_soc_rk817 hantro_vpu snd_soc_core snd_compress snd_pcm_dmaengine v4l2_vp9 snd_pcm v4l2_h264 rockchip_rga snd_timer rk_crypto2 spi_rockchip_sfc videobuf2_dma_contig snd sm3_generic v4l2_mem2mem videobuf2_dma_sg dwmac_rk sm3 soundcore videobuf2_memops videobuf2_v4l2 stmmac_platform dw_hdmi_cec videodev videobuf2_common dw_hdmi_i2s_audio stmmac rk817_charger pcs_xpcs mc cpufreq_dt sch_fq_codel ip_tables x_tables autofs4 [ 258.129215] Preemption disabled at: [ 258.129216] [] vprintk_emit+0x11c/0x340 [ 258.129234] CPU: 0 PID: 1 Comm: systemd Tainted: G C 6.6.0-rc5-rockchip-rk356x #4 [ 258.129239] Hardware name: Rockchip RK3566 OPi 3B (DT) [ 258.129243] Call trace: [ 258.129245] dump_backtrace+0xa0/0x128 [ 258.129252] show_stack+0x20/0x38 [ 258.129256] dump_stack_lvl+0x60/0xb0 [ 258.129265] dump_stack+0x18/0x28 [ 258.129269] __schedule_bug+0xa0/0xc8 [ 258.129274] __schedule+0x9ac/0xd30 [ 258.129279] schedule+0x60/0x100 [ 258.129282] schedule_timeout+0x194/0x338 [ 258.129289] rk3x_i2c_xfer_common.isra.0+0x384/0x498 [ 258.129296] rk3x_i2c_xfer+0x20/0x60 [ 258.129300] __i2c_transfer+0x194/0x648 [ 258.129308] i2c_transfer+0x9c/0x130 [ 258.129313] i2c_transfer_buffer_flags+0x64/0x98 [ 258.129318] ssd1307fb_update_rect+0x42c/0x560 [ssd1307fb] [ 258.129334] ssd1307fb_defio_imageblit+0x34/0x50 [ssd1307fb] [ 258.129343] soft_cursor+0x13c/0x210 [ 258.129350] bit_cursor+0x2dc/0x550 [ 258.129354] fbcon_cursor+0xec/0x108 [ 258.129359] hide_cursor+0x44/0xc8 [ 258.129365] vt_console_print+0x398/0x3b0 [ 258.129370] console_flush_all.isra.0+0x17c/0x410 [ 258.129377] console_unlock+0x4c/0x100 [ 258.129382] vprintk_emit+0x1c8/0x340 [ 258.129386] vprintk_default+0x40/0x58 [ 258.129389] vprintk+0xb8/0xd0 [ 258.129392] _printk+0x68/0x98 [ 258.129398] watchdog_release+0x170/0x230 [ 258.129404] __fput+0xbc/0x288 [ 258.129409] __fput_sync+0x58/0x70 [ 258.129413] __arm64_sys_close+0x40/0x90 [ 258.129419] invoke_syscall+0x4c/0x118 [ 258.129426] el0_svc_common.constprop.0+0x48/0xf0 [ 258.129432] do_el0_svc+0x24/0x38 [ 258.129437] el0_svc+0x48/0x100 [ 258.129443] el0t_64_sync_handler+0xc0/0xc8 [ 258.129448] el0t_64_sync+0x190/0x198 [ 258.573087] ------------[ cut here ]------------ [ 258.573098] DEBUG_LOCKS_WARN_ON(val > preempt_count()) [ 258.573111] WARNING: CPU: 0 PID: 1 at kernel/sched/core.c:5871 preempt_count_sub+0x9c/0x148 [ 258.573130] Modules linked in: algif_hash algif_skcipher af_alg bnep binfmt_misc lz4hc lz4 zram snd_soc_hdmi_codec brcmfmac_wcc hci_uart fb_ssd1306(C) fbtft(C) btqca btrtl btintel btsdio snd_soc_simple_card motorcomm pwm_fan snd_soc_simple_card_utils ssd130x_spi nls_iso8859_1 ssd130x btbcm drm_shmem_helper display_connector brcmfmac ssd1307fb brcmutil bluetooth cfg80211 rfkill snd_soc_rockchip_i2s_tdm snd_soc_rk817 hantro_vpu snd_soc_core snd_compress snd_pcm_dmaengine v4l2_vp9 snd_pcm v4l2_h264 rockchip_rga snd_timer rk_crypto2 spi_rockchip_sfc videobuf2_dma_contig snd sm3_generic v4l2_mem2mem videobuf2_dma_sg dwmac_rk sm3 soundcore videobuf2_memops videobuf2_v4l2 stmmac_platform dw_hdmi_cec videodev videobuf2_common dw_hdmi_i2s_audio stmmac rk817_charger pcs_xpcs mc cpufreq_dt sch_fq_codel ip_tables x_tables autofs4 [ 258.573268] CPU: 0 PID: 1 Comm: systemd Tainted: G WC 6.6.0-rc5-rockchip-rk356x #4 [ 258.573274] Hardware name: Rockchip RK3566 OPi 3B (DT) ** 37 printk messages dropped ** [ 258.574064] Preemption disabled at: ** 42 printk messages dropped ** [ 259.190237] Preemption disabled at: Track damage in the driver's private data under a spinlock and merge multiple updates into a bounding rectangle. Queue the existing deferred-I/O work immediately for damage reported by fbdev drawing and write helpers, so allocation and I2C transfers run from process context without adding the configured mmap refresh delay. Keep full-screen updates for dirty mmap pages, for which no precise rectangle is available. Tested on an RK3566 board with a 128x64 OLED by running five rounds of 250 KERN_EMERG messages in total while issuing framebuffer writes every 15 ms. No atomic-sleep, preemption, or lockdep warning occurred. Kprobe tracing also confirmed that cursor-only damage remained an 8x16 partial update. Fixes: a2ed00da5047 ("drivers/video: add support for the Solomon SSD1307 OLED Controller") Cc: stable@vger.kernel.org Signed-off-by: Hui Su Signed-off-by: Helge Deller --- drivers/video/fbdev/ssd1307fb.c | 72 ++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 5 deletions(-) diff --git a/drivers/video/fbdev/ssd1307fb.c b/drivers/video/fbdev/ssd1307fb.c index 644b8d97b381..c4fdecafd856 100644 --- a/drivers/video/fbdev/ssd1307fb.c +++ b/drivers/video/fbdev/ssd1307fb.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -72,6 +73,13 @@ struct ssd1307fb_par { struct i2c_client *client; u32 height; struct fb_info *info; + /* Pending damage, with exclusive x2/y2, protected by damage_lock. */ + spinlock_t damage_lock; + bool damage_pending; + u32 damage_x1; + u32 damage_x2; + u32 damage_y1; + u32 damage_y2; u8 lookup_table[4]; u32 page_offset; u32 col_offset; @@ -302,19 +310,49 @@ static int ssd1307fb_blank(int blank_mode, struct fb_info *info) return ssd1307fb_write_cmd(par->client, SSD1307FB_DISPLAY_ON); } +static void ssd1307fb_schedule_damage(struct fb_info *info, u32 x, u32 y, + u32 width, u32 height) +{ + struct ssd1307fb_par *par = info->par; + unsigned long flags; + u32 x2, y2; + + if (!width || !height || x >= par->width || y >= par->height) + return; + + x2 = x + min(width, par->width - x); + y2 = y + min(height, par->height - y); + + spin_lock_irqsave(&par->damage_lock, flags); + if (par->damage_pending) { + par->damage_x1 = min(par->damage_x1, x); + par->damage_y1 = min(par->damage_y1, y); + par->damage_x2 = max(par->damage_x2, x2); + par->damage_y2 = max(par->damage_y2, y2); + } else { + par->damage_x1 = x; + par->damage_y1 = y; + par->damage_x2 = x2; + par->damage_y2 = y2; + par->damage_pending = true; + } + spin_unlock_irqrestore(&par->damage_lock, flags); + + /* Advance an already-pending mmap update as well. */ + mod_delayed_work(system_wq, &info->deferred_work, 0); +} + static void ssd1307fb_defio_damage_range(struct fb_info *info, off_t off, size_t len) { struct ssd1307fb_par *par = info->par; - ssd1307fb_update_display(par); + ssd1307fb_schedule_damage(info, 0, 0, par->width, par->height); } static void ssd1307fb_defio_damage_area(struct fb_info *info, u32 x, u32 y, u32 width, u32 height) { - struct ssd1307fb_par *par = info->par; - - ssd1307fb_update_rect(par, x, y, width, height); + ssd1307fb_schedule_damage(info, x, y, width, height); } FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(ssd1307fb, @@ -329,7 +367,30 @@ static const struct fb_ops ssd1307fb_ops = { static void ssd1307fb_deferred_io(struct fb_info *info, struct list_head *pagereflist) { - ssd1307fb_update_display(info->par); + struct ssd1307fb_par *par = info->par; + unsigned long flags; + u32 x, y, width, height; + + spin_lock_irqsave(&par->damage_lock, flags); + if (!list_empty(pagereflist)) { + x = 0; + y = 0; + width = par->width; + height = par->height; + par->damage_pending = false; + } else if (par->damage_pending) { + x = par->damage_x1; + y = par->damage_y1; + width = par->damage_x2 - par->damage_x1; + height = par->damage_y2 - par->damage_y1; + par->damage_pending = false; + } else { + spin_unlock_irqrestore(&par->damage_lock, flags); + return; + } + spin_unlock_irqrestore(&par->damage_lock, flags); + + ssd1307fb_update_rect(par, x, y, width, height); } static int ssd1307fb_init(struct ssd1307fb_par *par) @@ -601,6 +662,7 @@ static int ssd1307fb_probe(struct i2c_client *client) par = info->par; par->info = info; par->client = client; + spin_lock_init(&par->damage_lock); par->device_info = device_get_match_data(dev); From de508ece1d37cdbbbfa52f074954310f9b066b13 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Thu, 6 Aug 2026 17:04:15 +0200 Subject: [PATCH 25/43] sticon/parisc: Detect default STI graphics card for console output If a machine has multiple graphic cards, detect the graphic card which is used to display firmware messages and use that one as the default graphic card for sticon and fbcon. On parisc machines the default graphic card used for BCH (boot console handler, aka BIOS menu) is stored in the stable storage (equivalent to CMOS storage on x86) or in the console path in page zero. Extract that path and store it as default STI path for later comparism. Take care that the graphic card can be a GSC or a PCI card which use different path strings. Increase max string size for default_sti_path to 32 chars as the print_pa_hwpath() function formats a hardware path using unbounded sprintf calls for up to 6 bus converter components and 1 module component (e.g., 255/255/...), which can produce a string up to 28 bytes long. Signed-off-by: Helge Deller Cc: stable@vger.kernel.org --- drivers/video/sticore.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/video/sticore.c b/drivers/video/sticore.c index 0d37e4b10447..1d4477f20450 100644 --- a/drivers/video/sticore.c +++ b/drivers/video/sticore.c @@ -325,7 +325,7 @@ static void sti_rom_copy(unsigned long base, unsigned long count, void *dest) -static char default_sti_path[21] __read_mostly; +static char default_sti_path[32] __read_mostly; #ifndef MODULE static int __init sti_setup(char *str) @@ -1148,6 +1148,26 @@ static void sti_init_roms(void) pr_info("STI GSC/PCI core graphics driver " STI_DRIVERVERSION "\n"); + /* + * Find default console by hardware path which is either stored in + * console entry in stable storage or alternatively from console path + * in PAGE0 used by BCH and PDC. + */ + if (!default_sti_path[0]) { + struct pdc_module_path conspath; + struct device *dev = NULL; + + if (pdc_stable_read(0x60, &conspath, sizeof(conspath)) == PDC_OK) + dev = hwpath_to_device(&conspath.path); + if (!dev) + dev = hwpath_to_device(&PAGE0->mem_cons.dp.path); + if (dev && dev_is_pci(dev)) + print_pci_hwpath(to_pci_dev(dev), default_sti_path); + else if (dev && !dev_is_pci(dev)) + print_pa_hwpath(to_parisc_device(dev), default_sti_path); + pr_debug("default graphic card: %s\n", default_sti_path); + } + /* Register drivers for native & PCI cards */ register_parisc_driver(&pa_sti_driver); WARN_ON(pci_register_driver(&pci_sti_driver)); From 4c1977df69dc1c22789fabdf0fd0db46a8aa16d3 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 8 Aug 2026 10:24:16 +0200 Subject: [PATCH 26/43] fbdev: mb862xxfb: Silence possibly unused functions When CONFIG_PCI=n, the kernel test robot reports that on powerpc some functions and variables may possibly be unused. Silence those warnings. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202608081537.o23Goj8d-lkp@intel.com/ Signed-off-by: Helge Deller --- drivers/video/fbdev/mb862xx/mb862xxfbdrv.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c b/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c index 676c6d3ccc12..35939175bd00 100644 --- a/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c +++ b/drivers/video/fbdev/mb862xx/mb862xxfbdrv.c @@ -408,7 +408,7 @@ static struct fb_ops mb862xxfb_ops = { }; /* initialize fb_info data */ -static int mb862xxfb_init_fbinfo(struct fb_info *fbi) +static int __maybe_unused mb862xxfb_init_fbinfo(struct fb_info *fbi) { struct mb862xxfb_par *par = fbi->par; struct mb862xx_gc_mode *mode = par->gc_mode; @@ -568,9 +568,9 @@ static ssize_t dispregs_show(struct device *dev, return ptr - buf; } -static DEVICE_ATTR_RO(dispregs); +static __maybe_unused DEVICE_ATTR_RO(dispregs); -static irqreturn_t mb862xx_intr(int irq, void *dev_id) +static irqreturn_t __maybe_unused mb862xx_intr(int irq, void *dev_id) { struct mb862xxfb_par *par = (struct mb862xxfb_par *) dev_id; unsigned long reg_ist, mask; From 39dd7598fbe90c5b4bb77fb34d8f3dad8385d11c Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sat, 8 Aug 2026 16:41:14 +0200 Subject: [PATCH 27/43] fbdev: clps711x-fb: Remove unreachable unregister_framebuffer() call The unregister_framebuffer() call in clps711x_fb_probe() is unreachable. register_framebuffer() failure jumps to the unwind label, while success returns immediately. Remove it. Found with Clang's -Wunreachable-code. Fixes: 36462ac193088 ("fbdev: clps711x-fb: Replace check_fb in favor of struct fb_info.lcd_dev") Assisted-by: Claude:claude-fable-5 Signed-off-by: Karl Mehltretter Acked-by: Thomas Zimmermann Signed-off-by: Helge Deller --- drivers/video/fbdev/clps711x-fb.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/video/fbdev/clps711x-fb.c b/drivers/video/fbdev/clps711x-fb.c index 7a7db7100499..6789773b22fb 100644 --- a/drivers/video/fbdev/clps711x-fb.c +++ b/drivers/video/fbdev/clps711x-fb.c @@ -329,8 +329,6 @@ static int clps711x_fb_probe(struct platform_device *pdev) return 0; - unregister_framebuffer(info); - out_fb_dealloc_cmap: regmap_update_bits(cfb->syscon, SYSCON_OFFSET, SYSCON1_LCDEN, 0); fb_dealloc_cmap(&info->cmap); From e36aa3669e0f2981bce9be85d38c50d83bf5e1ff Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 13 Aug 2026 20:50:11 +0800 Subject: [PATCH 28/43] fbdev: aty128fb: Convert to managed PCI and ioremap API Fix missing pci_disable_device() in probe and remove. Use pcim_enable_device(), pcim_request_region(), devm_ioremap() and devm_ioremap_wc() to replace manual resource management. Remove all release_mem_region() and iounmap() calls. Signed-off-by: Shixiong Ou Signed-off-by: Helge Deller --- drivers/video/fbdev/aty/aty128fb.c | 40 +++++++++--------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/drivers/video/fbdev/aty/aty128fb.c b/drivers/video/fbdev/aty/aty128fb.c index bcb10e66221c..c349a1711ff5 100644 --- a/drivers/video/fbdev/aty/aty128fb.c +++ b/drivers/video/fbdev/aty/aty128fb.c @@ -2009,31 +2009,30 @@ static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) return err; /* Enable device in PCI config */ - if ((err = pci_enable_device(pdev))) { + err = pcim_enable_device(pdev); + if (err) { printk(KERN_ERR "aty128fb: Cannot enable PCI device: %d\n", err); return -ENODEV; } fb_addr = pci_resource_start(pdev, 0); - if (!request_mem_region(fb_addr, pci_resource_len(pdev, 0), - "aty128fb FB")) { + if (!pcim_request_region(pdev, 0, "aty128fb FB")) { printk(KERN_ERR "aty128fb: cannot reserve frame " "buffer memory\n"); return -ENODEV; } reg_addr = pci_resource_start(pdev, 2); - if (!request_mem_region(reg_addr, pci_resource_len(pdev, 2), - "aty128fb MMIO")) { + if (!pcim_request_region(pdev, 2, "aty128fb MMIO")) { printk(KERN_ERR "aty128fb: cannot reserve MMIO region\n"); - goto err_free_fb; + return -ENODEV; } /* We have the resources. Now virtualize them */ info = framebuffer_alloc(sizeof(struct aty128fb_par), &pdev->dev); if (!info) - goto err_free_mmio; + goto err_free_info; par = info->par; @@ -2041,7 +2040,8 @@ static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* Virtualize mmio region */ info->fix.mmio_start = reg_addr; - par->regbase = pci_ioremap_bar(pdev, 2); + par->regbase = devm_ioremap(&pdev->dev, pci_resource_start(pdev, 2), + pci_resource_len(pdev, 2)); if (!par->regbase) goto err_free_info; @@ -2050,9 +2050,9 @@ static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) par->vram_size = aty_ld_le32(CNFG_MEMSIZE) & 0x03FFFFFF; /* Virtualize the framebuffer */ - info->screen_base = ioremap_wc(fb_addr, par->vram_size); + info->screen_base = devm_ioremap_wc(&pdev->dev, fb_addr, par->vram_size); if (!info->screen_base) - goto err_unmap_out; + goto err_free_info; /* Set up info->fix */ info->fix = aty128fb_fix; @@ -2063,7 +2063,7 @@ static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) /* If we can't test scratch registers, something is seriously wrong */ if (!register_test(par)) { printk(KERN_ERR "aty128fb: Can't write to video register!\n"); - goto err_out; + goto err_free_info; } #ifndef __sparc__ @@ -2085,25 +2085,15 @@ static int aty128_probe(struct pci_dev *pdev, const struct pci_device_id *ent) pci_set_drvdata(pdev, info); if (!aty128_init(pdev, ent)) - goto err_out; + goto err_free_info; if (mtrr) par->wc_cookie = arch_phys_wc_add(info->fix.smem_start, par->vram_size); return 0; -err_out: - iounmap(info->screen_base); -err_unmap_out: - iounmap(par->regbase); err_free_info: framebuffer_release(info); -err_free_mmio: - release_mem_region(pci_resource_start(pdev, 2), - pci_resource_len(pdev, 2)); -err_free_fb: - release_mem_region(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); return -ENODEV; } @@ -2124,13 +2114,7 @@ static void aty128_remove(struct pci_dev *pdev) unregister_framebuffer(info); arch_phys_wc_del(par->wc_cookie); - iounmap(par->regbase); - iounmap(info->screen_base); - release_mem_region(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - release_mem_region(pci_resource_start(pdev, 2), - pci_resource_len(pdev, 2)); framebuffer_release(info); } #endif /* CONFIG_PCI */ From a41961f364bc7aee1611796e7ab5bd687708d770 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 13 Aug 2026 20:50:12 +0800 Subject: [PATCH 29/43] fbdev: nvidia: Convert to managed PCI and ioremap API Fix missing pci_disable_device() in probe and remove. Use pcim_enable_device(), pcim_request_all_regions(), devm_ioremap() and devm_ioremap_wc() to replace manual resource management. Remove all pci_release_regions() and iounmap() calls. Signed-off-by: Shixiong Ou Signed-off-by: Helge Deller --- drivers/video/fbdev/nvidia/nvidia.c | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/video/fbdev/nvidia/nvidia.c b/drivers/video/fbdev/nvidia/nvidia.c index 7d20c4087aeb..4ef7a5ba43cc 100644 --- a/drivers/video/fbdev/nvidia/nvidia.c +++ b/drivers/video/fbdev/nvidia/nvidia.c @@ -1292,7 +1292,7 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) NVTRACE_ENTER(); assert(pd != NULL); - if (pci_enable_device(pd)) { + if (pcim_enable_device(pd)) { printk(KERN_ERR PFX "cannot enable PCI device\n"); return -ENODEV; } @@ -1305,7 +1305,7 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) nvidiafb_fix.mmio_start = pci_resource_start(pd, 0); nvidiafb_fix.mmio_len = pci_resource_len(pd, 0); - REGS = ioremap(nvidiafb_fix.mmio_start, nvidiafb_fix.mmio_len); + REGS = devm_ioremap(&pd->dev, nvidiafb_fix.mmio_start, nvidiafb_fix.mmio_len); if (!REGS) { printk(KERN_ERR PFX "cannot ioremap MMIO base\n"); return -ENODEV; @@ -1333,7 +1333,7 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) if (info->pixmap.addr == NULL) goto err_out_kfree; - if (pci_request_regions(pd, "nvidiafb")) { + if (pcim_request_all_regions(pd, "nvidiafb")) { printk(KERN_ERR PFX "cannot request PCI regions\n"); goto err_out_enable; } @@ -1358,7 +1358,7 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) sprintf(nvidiafb_fix.id, "NV%x", (pd->device & 0x0ff0) >> 4); if (NVCommonSetup(info)) - goto err_out_free_base0; + goto err_out_enable; par->FbAddress = nvidiafb_fix.smem_start; par->FbMapSize = par->RamAmountKBytes * 1024; @@ -1378,8 +1378,8 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) par->ScratchBufferStart = par->FbUsableSize - par->ScratchBufferSize; par->CursorStart = par->FbUsableSize + (32 * 1024); - info->screen_base = ioremap_wc(nvidiafb_fix.smem_start, - par->FbMapSize); + info->screen_base = devm_ioremap_wc(&pd->dev, nvidiafb_fix.smem_start, + par->FbMapSize); info->screen_size = par->FbUsableSize; nvidiafb_fix.smem_len = par->RamAmountKBytes * 1024; @@ -1423,19 +1423,15 @@ static int nvidiafb_probe(struct pci_dev *pd, const struct pci_device_id *ent) return 0; err_out_iounmap_fb: - iounmap(info->screen_base); fb_destroy_modelist(&info->modelist); err_out_free_base1: fb_destroy_modedb(info->monspecs.modedb); nvidia_delete_i2c_busses(par); -err_out_free_base0: - pci_release_regions(pd); err_out_enable: kfree(info->pixmap.addr); err_out_kfree: framebuffer_release(info); err_out: - iounmap(REGS); return -ENODEV; } @@ -1450,11 +1446,8 @@ static void nvidiafb_remove(struct pci_dev *pd) unregister_framebuffer(info); arch_phys_wc_del(par->wc_cookie); - iounmap(info->screen_base); fb_destroy_modedb(info->monspecs.modedb); nvidia_delete_i2c_busses(par); - iounmap(par->REGS); - pci_release_regions(pd); kfree(info->pixmap.addr); framebuffer_release(info); NVTRACE_LEAVE(); From d37b2326499593fe171a247e3c22489d0cbfef96 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 13 Aug 2026 20:50:13 +0800 Subject: [PATCH 30/43] fbdev: savage: Convert to managed PCI and ioremap API Fix missing pci_disable_device() in probe and remove. Use pcim_enable_device(), pcim_request_all_regions(), devm_ioremap() and devm_ioremap_wc() to replace manual resource management. Remove all pci_release_regions() and iounmap() calls. Merge failed_init label into failed_enable. Signed-off-by: Shixiong Ou Signed-off-by: Helge Deller --- drivers/video/fbdev/savage/savagefb_driver.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/drivers/video/fbdev/savage/savagefb_driver.c b/drivers/video/fbdev/savage/savagefb_driver.c index 7789196d2eb5..56ff3c50cd29 100644 --- a/drivers/video/fbdev/savage/savagefb_driver.c +++ b/drivers/video/fbdev/savage/savagefb_driver.c @@ -1728,7 +1728,7 @@ static int savage_map_mmio(struct fb_info *info) par->mmio.len = SAVAGE_NEWMMIO_REGSIZE; - par->mmio.vbase = ioremap(par->mmio.pbase, par->mmio.len); + par->mmio.vbase = devm_ioremap(&par->pcidev->dev, par->mmio.pbase, par->mmio.len); if (!par->mmio.vbase) { printk("savagefb: unable to map memory mapped IO\n"); return -ENOMEM; @@ -1755,7 +1755,6 @@ static void savage_unmap_mmio(struct fb_info *info) savage_disable_mmio(par); if (par->mmio.vbase) { - iounmap(par->mmio.vbase); par->mmio.vbase = NULL; } } @@ -1774,7 +1773,7 @@ static int savage_map_video(struct fb_info *info, int video_len) par->video.pbase = pci_resource_start(par->pcidev, resource); par->video.len = video_len; - par->video.vbase = ioremap_wc(par->video.pbase, par->video.len); + par->video.vbase = devm_ioremap_wc(&par->pcidev->dev, par->video.pbase, par->video.len); if (!par->video.vbase) { printk("savagefb: unable to map screen memory\n"); @@ -1802,7 +1801,6 @@ static void savage_unmap_video(struct fb_info *info) if (par->video.vbase) { arch_phys_wc_del(par->video.wc_cookie); - iounmap(par->video.vbase); par->video.vbase = NULL; info->screen_base = NULL; } @@ -2188,11 +2186,12 @@ static int savagefb_probe(struct pci_dev *dev, const struct pci_device_id *id) return -ENOMEM; par = info->par; mutex_init(&par->open_lock); - err = pci_enable_device(dev); + err = pcim_enable_device(dev); if (err) goto failed_enable; - if ((err = pci_request_regions(dev, "savagefb"))) { + err = pcim_request_all_regions(dev, "savagefb"); + if (err) { printk(KERN_ERR "cannot request PCI regions\n"); goto failed_enable; } @@ -2200,7 +2199,7 @@ static int savagefb_probe(struct pci_dev *dev, const struct pci_device_id *id) err = -ENOMEM; if ((err = savage_init_fb_info(info, dev, id))) - goto failed_init; + goto failed_enable; err = savage_map_mmio(info); if (err) @@ -2331,8 +2330,6 @@ static int savagefb_probe(struct pci_dev *dev, const struct pci_device_id *id) savage_unmap_mmio(info); failed_mmio: kfree(info->pixmap.addr); - failed_init: - pci_release_regions(dev); failed_enable: framebuffer_release(info); @@ -2355,7 +2352,6 @@ static void savagefb_remove(struct pci_dev *dev) savage_unmap_video(info); savage_unmap_mmio(info); kfree(info->pixmap.addr); - pci_release_regions(dev); framebuffer_release(info); } } From b2c3a91e2ba88aa3d671265c79b5eb321f96ed1e Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 13 Aug 2026 20:50:14 +0800 Subject: [PATCH 31/43] fbdev: matrox: Convert to managed PCI and ioremap API Fix missing pci_disable_device() in probe and remove. Use pcim_enable_device(), devm_request_mem_region(), devm_ioremap() and devm_ioremap_wc() to replace manual resource management. Remove all release_mem_region() and iounmap() calls. Use devm_request_mem_region() instead of pcim_request_region() because the requested sizes (16384 for MMIO, maxvram for FB) do not match the full PCI BAR sizes. Signed-off-by: Shixiong Ou Signed-off-by: Helge Deller --- drivers/video/fbdev/matrox/matroxfb_base.c | 30 ++++++++-------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/drivers/video/fbdev/matrox/matroxfb_base.c b/drivers/video/fbdev/matrox/matroxfb_base.c index ac04a19b6849..24d312c29fb6 100644 --- a/drivers/video/fbdev/matrox/matroxfb_base.c +++ b/drivers/video/fbdev/matrox/matroxfb_base.c @@ -374,10 +374,6 @@ static void matroxfb_remove(struct matrox_fb_info *minfo, int dummy) unregister_framebuffer(&minfo->fbcon); matroxfb_g450_shutdown(minfo); arch_phys_wc_del(minfo->wc_cookie); - iounmap(minfo->mmio.vbase.vaddr); - iounmap(minfo->video.vbase.vaddr); - release_mem_region(minfo->video.base, minfo->video.len_maximum); - release_mem_region(minfo->mmio.base, 16384); kfree(minfo); } @@ -1712,11 +1708,13 @@ static int initMatrox2(struct matrox_fb_info *minfo, struct board *b) goto fail; } memsize = b->base->maxvram; - if (!request_mem_region(ctrlptr_phys, 16384, "matroxfb MMIO")) { + if (!devm_request_mem_region(&minfo->pcidev->dev, ctrlptr_phys, 16384, + "matroxfb MMIO")) { goto fail; } - if (!request_mem_region(video_base_phys, memsize, "matroxfb FB")) { - goto failCtrlMR; + if (!devm_request_mem_region(&minfo->pcidev->dev, video_base_phys, + memsize, "matroxfb FB")) { + goto fail; } minfo->video.len_maximum = memsize; /* convert mem (autodetect k, M) */ @@ -1727,19 +1725,19 @@ static int initMatrox2(struct matrox_fb_info *minfo, struct board *b) memsize = mem; err = -ENOMEM; - minfo->mmio.vbase.vaddr = ioremap(ctrlptr_phys, 16384); + minfo->mmio.vbase.vaddr = devm_ioremap(&minfo->pcidev->dev, ctrlptr_phys, 16384); if (!minfo->mmio.vbase.vaddr) { printk(KERN_ERR "matroxfb: cannot ioremap(%lX, 16384), matroxfb disabled\n", ctrlptr_phys); - goto failVideoMR; + goto fail; } minfo->mmio.base = ctrlptr_phys; minfo->mmio.len = 16384; minfo->video.base = video_base_phys; - minfo->video.vbase.vaddr = ioremap_wc(video_base_phys, memsize); + minfo->video.vbase.vaddr = devm_ioremap_wc(&minfo->pcidev->dev, video_base_phys, memsize); if (!minfo->video.vbase.vaddr) { printk(KERN_ERR "matroxfb: cannot ioremap(%lX, %d), matroxfb disabled\n", video_base_phys, memsize); - goto failCtrlIO; + goto fail; } { u_int32_t cmd; @@ -1954,13 +1952,6 @@ static int initMatrox2(struct matrox_fb_info *minfo, struct board *b) return 0; failVideoIO:; matroxfb_g450_shutdown(minfo); - iounmap(minfo->video.vbase.vaddr); -failCtrlIO:; - iounmap(minfo->mmio.vbase.vaddr); -failVideoMR:; - release_mem_region(video_base_phys, minfo->video.len_maximum); -failCtrlMR:; - release_mem_region(ctrlptr_phys, 16384); fail:; return err; } @@ -2069,9 +2060,8 @@ static int matroxfb_probe(struct pci_dev* pdev, const struct pci_device_id* dumm return -ENODEV; } pci_read_config_dword(pdev, PCI_COMMAND, &cmd); - if (pci_enable_device(pdev)) { + if (pcim_enable_device(pdev)) return -1; - } minfo = kzalloc_obj(*minfo); if (!minfo) From 27c97be7f3c6b62cf872502374fb77d973950648 Mon Sep 17 00:00:00 2001 From: Shixiong Ou Date: Thu, 13 Aug 2026 20:50:15 +0800 Subject: [PATCH 32/43] fbdev: atyfb: Convert to managed PCI and ioremap API Fix missing pci_disable_device() in probe and remove. Use pcim_enable_device(), pcim_request_region(), devm_ioremap(), devm_ioremap_uc() and devm_ioremap_wc() for the PCI path. Convert aux_start to devm_request_mem_region(). Guard atyfb_remove() to only unmap/release for non-PCI (Atari) devices. Keep iounmap for sprite.addr outside the guard since it uses raw ioremap(). Signed-off-by: Shixiong Ou Signed-off-by: Helge Deller --- drivers/video/fbdev/aty/atyfb_base.c | 47 +++++++++++----------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/drivers/video/fbdev/aty/atyfb_base.c b/drivers/video/fbdev/aty/atyfb_base.c index 9fc5af09f86c..97cc8b6a9361 100644 --- a/drivers/video/fbdev/aty/atyfb_base.c +++ b/drivers/video/fbdev/aty/atyfb_base.c @@ -3435,7 +3435,7 @@ static int atyfb_setup_generic(struct pci_dev *pdev, struct fb_info *info, raddr = addr + 0x7ff000UL; rrp = &pdev->resource[2]; if ((rrp->flags & IORESOURCE_MEM) && - request_mem_region(rrp->start, resource_size(rrp), "atyfb")) { + devm_request_mem_region(&pdev->dev, rrp->start, resource_size(rrp), "atyfb")) { par->aux_start = rrp->start; par->aux_size = resource_size(rrp); raddr = rrp->start; @@ -3448,9 +3448,9 @@ static int atyfb_setup_generic(struct pci_dev *pdev, struct fb_info *info, * By using strong UC we force the MTRR to never have an * effect on the MMIO region on both non-PAT and PAT systems. */ - par->ati_regbase = ioremap_uc(info->fix.mmio_start, 0x1000); + par->ati_regbase = devm_ioremap_uc(&pdev->dev, info->fix.mmio_start, 0x1000); #else - par->ati_regbase = ioremap(info->fix.mmio_start, 0x1000); + par->ati_regbase = devm_ioremap(&pdev->dev, info->fix.mmio_start, 0x1000); #endif if (par->ati_regbase == NULL) return -ENOMEM; @@ -3490,8 +3490,8 @@ static int atyfb_setup_generic(struct pci_dev *pdev, struct fb_info *info, aty_fudge_framebuffer_len(info); - info->screen_base = ioremap_wc(info->fix.smem_start, - info->fix.smem_len); + info->screen_base = devm_ioremap_wc(&pdev->dev, info->fix.smem_start, + info->fix.smem_len); if (info->screen_base == NULL) { ret = -ENOMEM; goto atyfb_setup_generic_fail; @@ -3511,12 +3511,9 @@ static int atyfb_setup_generic(struct pci_dev *pdev, struct fb_info *info, return 0; atyfb_setup_generic_fail: - iounmap(par->ati_regbase); + /* devm handles cleanup automatically on probe failure */ par->ati_regbase = NULL; - if (info->screen_base) { - iounmap(info->screen_base); - info->screen_base = NULL; - } + info->screen_base = NULL; return ret; } @@ -3536,7 +3533,7 @@ static int atyfb_pci_probe(struct pci_dev *pdev, return rc; /* Enable device in PCI config */ - if (pci_enable_device(pdev)) { + if (pcim_enable_device(pdev)) { PRINTKE("Cannot enable PCI device\n"); return -ENXIO; } @@ -3552,7 +3549,7 @@ static int atyfb_pci_probe(struct pci_dev *pdev, /* Reserve space */ res_start = rp->start; res_size = resource_size(rp); - if (!request_mem_region(res_start, res_size, "atyfb")) + if (!pcim_request_region(pdev, rp - pdev->resource, "atyfb")) return -EBUSY; /* Allocate framebuffer */ @@ -3612,17 +3609,9 @@ static int atyfb_pci_probe(struct pci_dev *pdev, err_release_io: #ifdef __sparc__ kfree(par->mmap_map); -#else - if (par->ati_regbase) - iounmap(par->ati_regbase); - if (info->screen_base) - iounmap(info->screen_base); #endif + /* devm handles cleanup automatically for non-sparc PCI devices */ err_release_mem: - if (par->aux_start) - release_mem_region(par->aux_start, par->aux_size); - - release_mem_region(par->res_start, par->res_size); framebuffer_release(info); return rc; @@ -3733,10 +3722,13 @@ static void atyfb_remove(struct fb_info *info) arch_phys_wc_del(par->wc_cookie); #ifndef __sparc__ - if (par->ati_regbase) - iounmap(par->ati_regbase); - if (info->screen_base) - iounmap(info->screen_base); + /* For PCI devices, devm handles unmapping automatically */ + if (par->bus_type != PCI) { + if (par->ati_regbase) + iounmap(par->ati_regbase); + if (info->screen_base) + iounmap(info->screen_base); + } #ifdef __BIG_ENDIAN if (info->sprite.addr) iounmap(info->sprite.addr); @@ -3745,10 +3737,7 @@ static void atyfb_remove(struct fb_info *info) #ifdef __sparc__ kfree(par->mmap_map); #endif - if (par->aux_start) - release_mem_region(par->aux_start, par->aux_size); - - if (par->res_start) + if (par->res_start && par->bus_type != PCI) release_mem_region(par->res_start, par->res_size); framebuffer_release(info); From 311f2c770d6786a1b835d544196c345b0f9d7767 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 13 Aug 2026 16:29:32 -0700 Subject: [PATCH 33/43] fbdev: maxine: make functions static Make 3 functions static so that they don't need to be declared in a header file. ../drivers/video/fbdev/maxinefb.c:64:6: warning: no previous prototype for 'maxinefb_ims332_write_register' [-Wmissing-prototypes] 64 | void maxinefb_ims332_write_register(int regno, register unsigned int val) ../drivers/video/fbdev/maxinefb.c:74:14: warning: no previous prototype for 'maxinefb_ims332_read_register' [-Wmissing-prototypes] 74 | unsigned int maxinefb_ims332_read_register(int regno) ../drivers/video/fbdev/maxinefb.c:114:12: warning: no previous prototype for 'maxinefb_init' [-Wmissing-prototypes] 114 | int __init maxinefb_init(void) Signed-off-by: Randy Dunlap Acked-by: Maciej W. Rozycki Signed-off-by: Helge Deller --- drivers/video/fbdev/maxinefb.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/fbdev/maxinefb.c b/drivers/video/fbdev/maxinefb.c index 52528eb4dfb4..a9410a553113 100644 --- a/drivers/video/fbdev/maxinefb.c +++ b/drivers/video/fbdev/maxinefb.c @@ -61,7 +61,7 @@ static struct fb_fix_screeninfo maxinefb_fix __initdata = { /* Handle the funny Inmos RamDAC/video controller ... */ -void maxinefb_ims332_write_register(int regno, register unsigned int val) +static void maxinefb_ims332_write_register(int regno, register unsigned int val) { register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; unsigned char *wptr; @@ -71,7 +71,7 @@ void maxinefb_ims332_write_register(int regno, register unsigned int val) *((volatile unsigned short *) (wptr)) = val; } -unsigned int maxinefb_ims332_read_register(int regno) +static unsigned int maxinefb_ims332_read_register(int regno) { register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; unsigned char *rptr; @@ -111,7 +111,7 @@ static const struct fb_ops maxinefb_ops = { .fb_setcolreg = maxinefb_setcolreg, }; -int __init maxinefb_init(void) +static int __init maxinefb_init(void) { unsigned long fboff; unsigned long fb_start; From 47eb05e731fbef11dc36e9a9a9a05e61d43480ae Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 13 Aug 2026 16:29:33 -0700 Subject: [PATCH 34/43] fbdev: maxine: elide an unused function maxinefb_ims332_read_register() is not used, but since it describes a hardware interface, leave it in the source file as documentation and surround it inside an #if 0/#endif block. ../drivers/video/fbdev/maxinefb.c:74:21: warning: 'maxinefb_ims332_read_register' defined but not used [-Wunused-function] 74 | static unsigned int maxinefb_ims332_read_register(int regno) Signed-off-by: Randy Dunlap Acked-by: Maciej W. Rozycki Signed-off-by: Helge Deller --- drivers/video/fbdev/maxinefb.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/video/fbdev/maxinefb.c b/drivers/video/fbdev/maxinefb.c index a9410a553113..10249a1cfc72 100644 --- a/drivers/video/fbdev/maxinefb.c +++ b/drivers/video/fbdev/maxinefb.c @@ -71,6 +71,8 @@ static void maxinefb_ims332_write_register(int regno, register unsigned int val) *((volatile unsigned short *) (wptr)) = val; } +#if 0 +/* dead code: leave here for hardware interface documentation */ static unsigned int maxinefb_ims332_read_register(int regno) { register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; @@ -83,6 +85,7 @@ static unsigned int maxinefb_ims332_read_register(int regno) return (j & 0xffff) | ((k & 0xff00) << 8); } +#endif /* Set the palette */ static int maxinefb_setcolreg(unsigned regno, unsigned red, unsigned green, From 3adec5c640dc09dca5605043e138412fd5a0629d Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 13 Aug 2026 16:29:34 -0700 Subject: [PATCH 35/43] fbdev: maxine: fix 64-bit build error The KSEG1ADDR() macro is only defined for non-64BIT builds. Use the CKSEG1ADDR() macro instead. In file included from ../drivers/video/fbdev/maxinefb.c:34: ../drivers/video/fbdev/maxinefb.c: In function 'maxinefb_ims332_write_register': ../include/video/maxinefb.h:16:41: error: implicit declaration of function 'KSEG1ADDR'; did you mean 'CKSEG1ADDR'? [-Wimplicit-function-declaration] 16 | #define MAXINEFB_IMS332_ADDRESS KSEG1ADDR(0x1c140000) ../drivers/video/fbdev/maxinefb.c:66:49: note: in expansion of macro 'MAXINEFB_IMS332_ADDRESS' 66 | register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; ../drivers/video/fbdev/maxinefb.c:66:40: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast] 66 | register unsigned char *regs = (char *) MAXINEFB_IMS332_ADDRESS; Signed-off-by: Randy Dunlap Acked-by: Maciej W. Rozycki Signed-off-by: Helge Deller --- include/video/maxinefb.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/video/maxinefb.h b/include/video/maxinefb.h index 6aeb4acca2bd..2b66b32f355c 100644 --- a/include/video/maxinefb.h +++ b/include/video/maxinefb.h @@ -13,13 +13,13 @@ /* * IMS332 video controller register base address */ -#define MAXINEFB_IMS332_ADDRESS KSEG1ADDR(0x1c140000) +#define MAXINEFB_IMS332_ADDRESS CKSEG1ADDR(0x1c140000) /* * Begin of DECstation 5000/xx onboard framebuffer memory, default resolution * is 1024x768x8 */ -#define DS5000_xx_ONBOARD_FBMEM_START KSEG1ADDR(0x0a000000) +#define DS5000_xx_ONBOARD_FBMEM_START CKSEG1ADDR(0x0a000000) /* * The IMS 332 video controller used in the DECstation 5000/xx series From c761c0400fb7785672a5df5b21518f1dfd8fc81a Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 13 Aug 2026 19:56:40 -0700 Subject: [PATCH 36/43] fbdev: maxine: fix maxinefb_init() return value A driver should return a negative error code on failure of its module_init() function so that the system recognizes the failure. Change the "return 1" to "return -ENODEV". Suggested-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/patchset/20260809234810.982500-1-rdunlap@infradead.org?part=1 Signed-off-by: Randy Dunlap Acked-by: Maciej W. Rozycki Signed-off-by: Helge Deller --- drivers/video/fbdev/maxinefb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/fbdev/maxinefb.c b/drivers/video/fbdev/maxinefb.c index 10249a1cfc72..06de6cfbceb1 100644 --- a/drivers/video/fbdev/maxinefb.c +++ b/drivers/video/fbdev/maxinefb.c @@ -160,7 +160,7 @@ static int __init maxinefb_init(void) fb_alloc_cmap(&fb_info.cmap, 256, 0); if (register_framebuffer(&fb_info) < 0) - return 1; + return -ENODEV; return 0; } From 772bd1a84288e07d2621108473c96602c3d6b6bb Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 14 Aug 2026 11:42:34 -0700 Subject: [PATCH 37/43] fbdev: maxine: use MODULE_LICENSE() unconditionally This driver cannot be built as a loadable module so testing for "#ifdef MODULE" is not appropriate here. Also, MODULE_LICENSE() is always available. Signed-off-by: Randy Dunlap Acked-by: Maciej W. Rozycki Signed-off-by: Helge Deller --- drivers/video/fbdev/maxinefb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/video/fbdev/maxinefb.c b/drivers/video/fbdev/maxinefb.c index 06de6cfbceb1..67d4bd881041 100644 --- a/drivers/video/fbdev/maxinefb.c +++ b/drivers/video/fbdev/maxinefb.c @@ -169,9 +169,7 @@ static void __exit maxinefb_exit(void) unregister_framebuffer(&fb_info); } -#ifdef MODULE -MODULE_LICENSE("GPL"); -#endif module_init(maxinefb_init); module_exit(maxinefb_exit); +MODULE_LICENSE("GPL"); From ba13226fc966004713aed47931db8922b84d1960 Mon Sep 17 00:00:00 2001 From: BingKun Yue Date: Mon, 17 Aug 2026 14:39:42 +0800 Subject: [PATCH 38/43] fbdev: platinumfb: add error checking for ioremap calls The ioremap() and ioremap_wt() calls in platinumfb_probe() were not checked for failure. If any of these mappings fail, the driver would dereference NULL pointers, leading to a kernel panic. Add proper error checking and use goto-based cleanup to avoid code duplication across the error paths. Signed-off-by: BingKun Yue Signed-off-by: Helge Deller --- drivers/video/fbdev/platinumfb.c | 35 ++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/drivers/video/fbdev/platinumfb.c b/drivers/video/fbdev/platinumfb.c index a08d955d9b43..81fdaa3541db 100644 --- a/drivers/video/fbdev/platinumfb.c +++ b/drivers/video/fbdev/platinumfb.c @@ -567,15 +567,30 @@ static int platinumfb_probe(struct platform_device* odev) /* frame buffer - map only 4MB */ pinfo->frame_buffer_phys = pinfo->rsrc_fb.start; pinfo->frame_buffer = ioremap_wt(pinfo->rsrc_fb.start, 0x400000); + if (!pinfo->frame_buffer) { + dev_err(&odev->dev, "failed to ioremap frame buffer\n"); + rc = -ENOMEM; + goto err_release_fb; + } pinfo->base_frame_buffer = pinfo->frame_buffer; /* registers */ pinfo->platinum_regs_phys = pinfo->rsrc_reg.start; pinfo->platinum_regs = ioremap(pinfo->rsrc_reg.start, 0x1000); + if (!pinfo->platinum_regs) { + dev_err(&odev->dev, "failed to ioremap registers\n"); + rc = -ENOMEM; + goto err_unmap_fb; + } pinfo->cmap_regs_phys = 0xf301b000; /* XXX not in prom? */ request_mem_region(pinfo->cmap_regs_phys, 0x1000, "platinumfb cmap"); pinfo->cmap_regs = ioremap(pinfo->cmap_regs_phys, 0x1000); + if (!pinfo->cmap_regs) { + dev_err(&odev->dev, "failed to ioremap cmap registers\n"); + rc = -ENOMEM; + goto err_release_cmap; + } /* Grok total video ram */ out_be32(&pinfo->platinum_regs->reg[16].r, (unsigned)pinfo->frame_buffer_phys); @@ -623,13 +638,21 @@ static int platinumfb_probe(struct platform_device* odev) dev_set_drvdata(&odev->dev, info); rc = platinum_init_fb(info); - if (rc != 0) { - iounmap(pinfo->frame_buffer); - iounmap(pinfo->platinum_regs); - iounmap(pinfo->cmap_regs); - framebuffer_release(info); - } + if (rc != 0) + goto err_unmap_cmap; + return 0; + +err_unmap_cmap: + iounmap(pinfo->cmap_regs); +err_release_cmap: + release_mem_region(pinfo->cmap_regs_phys, 0x1000); + iounmap(pinfo->platinum_regs); +err_unmap_fb: + iounmap(pinfo->frame_buffer); +err_release_fb: + release_mem_region(pinfo->rsrc_fb.start, resource_size(&pinfo->rsrc_fb)); + framebuffer_release(info); return rc; } From f74cf70e5e78a43bd316e5c8e371caea0a49c207 Mon Sep 17 00:00:00 2001 From: Ajith P V Date: Tue, 18 Aug 2026 11:17:22 +0000 Subject: [PATCH 39/43] fbdev: viafb: refactor strcpy and viafb_name Eliminate the single-use file-scope variable `viafb_name` and pass the "Via" string literal directly to strcpy(). Since "Via" is a literal constant, the compiler safely executes compile-time bounds checking during fortify verification. This satisfies the security initiative requirements while minimizing code complexity. Signed-off-by: Ajith P V Signed-off-by: Helge Deller --- drivers/video/fbdev/via/viafbdev.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/video/fbdev/via/viafbdev.c b/drivers/video/fbdev/via/viafbdev.c index 80f95dac32c8..dd9374e8ad09 100644 --- a/drivers/video/fbdev/via/viafbdev.c +++ b/drivers/video/fbdev/via/viafbdev.c @@ -16,7 +16,6 @@ #define _MASTER_FILE #include "global.h" -static char *viafb_name = "Via"; static u32 pseudo_pal[17]; /* video mode */ @@ -144,7 +143,7 @@ static void viafb_setup_fixinfo(struct fb_fix_screeninfo *fix, struct viafb_par *viaparinfo) { memset(fix, 0, sizeof(struct fb_fix_screeninfo)); - strcpy(fix->id, viafb_name); + strcpy(fix->id, "Via"); fix->smem_start = viaparinfo->fbmem; fix->smem_len = viaparinfo->fbmem_free; From f8e43fe0f22b7137ce456e6fe3581d3098174f74 Mon Sep 17 00:00:00 2001 From: Runyu Xiao Date: Tue, 18 Aug 2026 21:53:18 +0800 Subject: [PATCH 40/43] fbdev: omapfb: panel-dsi-cm: initialize lock before registering display dsicm_probe() registers the display before initializing ddata->lock. Once omapdss_register_display() publishes the display, another consumer can reach a dsicm callback that takes this mutex while it is still uninitialized. Initialize the mutex before registering the display so the published callbacks always see a valid lock. Fixes: f76ee892a99e ("omapfb: copy omapdss & displays for omapfb") Cc: stable@vger.kernel.org Signed-off-by: Runyu Xiao Signed-off-by: Helge Deller --- drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c b/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c index 7c3463ee02ef..b2328671e067 100644 --- a/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c +++ b/drivers/video/fbdev/omap2/omapfb/displays/panel-dsi-cm.c @@ -1150,14 +1150,14 @@ static int dsicm_probe(struct platform_device *pdev) dssdev->caps = OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE | OMAP_DSS_DISPLAY_CAP_TEAR_ELIM; + mutex_init(&ddata->lock); + r = omapdss_register_display(dssdev); if (r) { dev_err(dev, "Failed to register panel\n"); goto err_reg; } - mutex_init(&ddata->lock); - atomic_set(&ddata->do_update, 0); ddata->reset_gpio = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_LOW); From d7dda89ad05173f9bbb795cd3cfc38262381f7f4 Mon Sep 17 00:00:00 2001 From: Miro Kropacek Date: Thu, 20 Aug 2026 10:28:35 +1200 Subject: [PATCH 41/43] fbdev: atafb: Give atafb proper parent The atafb fb device registers no parent, causing a missing symlink (/sys/class/graphics/fb0/device). Xorg fbdevhw driver looks for that symlink when scanning for devices, so add a parent node for atafb. The proper way to do that is use of framebuffer_alloc(), which requries refactoring atafb to move from static fb_info to allocation of fb_info by framebuffer_alloc(). About the only location where a fb_info pointer cannot be passed is monspec setup in atafb_setup(). Store static monspecs there and copy into place after framebuffer_alloc(). Pass fb_info everywhere it's needed (detect, decode_var and do_fb_set_var functions), and remove the static fb_info struct. Signed-off-by: Miro Kropacek Reviewed-by: Michael Schmitz Signed-off-by: Michael Schmitz Link: https://lists.debian.org/debian-68k/2026/08/msg00000.html Signed-off-by: Helge Deller --- drivers/video/fbdev/atafb.c | 174 +++++++++++++++++++----------------- 1 file changed, 92 insertions(+), 82 deletions(-) diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c index b8ed1c537293..0d415fdfb18d 100644 --- a/drivers/video/fbdev/atafb.c +++ b/drivers/video/fbdev/atafb.c @@ -158,14 +158,6 @@ static int DontCalcRes = 0; #define VMO_PREMASK 0x0c #endif -static struct fb_info fb_info = { - .fix = { - .id = "Atari ", - .visual = FB_VISUAL_PSEUDOCOLOR, - .accel = FB_ACCEL_NONE, - } -}; - static void *screen_base; /* base address of screen */ static unsigned long phys_screen_base; /* (only for Overscan) */ @@ -175,6 +167,12 @@ static int current_par_valid; static int mono_moni; +/* monspecs passed by user */ + +static __u32 mcap_hmin; /* hfreq lower limit (Hz) */ +static __u32 mcap_hmax; /* hfreq upper limit (Hz) */ +static __u16 mcap_vmin; /* vfreq lower limit (Hz) */ +static __u16 mcap_vmax; /* vfreq upper limit (Hz) */ #ifdef ATAFB_EXT @@ -299,7 +297,7 @@ static int *MV300_reg = MV300_reg_8bit; /* ++roman: This structure abstracts from the underlying hardware (ST(e), * TT, or Falcon. * - * int (*detect)(void) + * int (*detect)(struct fb_info *info) * This function should detect the current video mode settings and * store them in atafb_predefined[0] for later reference by the * user. Return the index+1 of an equivalent predefined mode or 0 @@ -311,7 +309,7 @@ static int *MV300_reg = MV300_reg_8bit; * values in the 'par' structure. * !!! Obsolete, perhaps !!! * - * int (*decode_var)(struct fb_var_screeninfo *var, + * int (*decode_var)(struct fb_info *info, struct fb_var_screeninfo *var, * struct atafb_par *par) * Get the video params out of 'var'. If a value doesn't fit, round * it up, if it's too big, return EINVAL. @@ -345,10 +343,10 @@ static int *MV300_reg = MV300_reg_8bit; */ static struct fb_hwswitch { - int (*detect)(void); + int (*detect)(struct fb_info *info); int (*encode_fix)(struct fb_fix_screeninfo *fix, struct atafb_par *par); - int (*decode_var)(struct fb_var_screeninfo *var, + int (*decode_var)(struct fb_info *info, struct fb_var_screeninfo *var, struct atafb_par *par); int (*encode_var)(struct fb_var_screeninfo *var, struct atafb_par *par); @@ -576,7 +574,7 @@ static int tt_encode_fix(struct fb_fix_screeninfo *fix, struct atafb_par *par) return 0; } -static int tt_decode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +static int tt_decode_var(struct fb_info *info, struct fb_var_screeninfo *var, struct atafb_par *par) { int xres = var->xres; int yres = var->yres; @@ -782,7 +780,7 @@ static int tt_setcolreg(unsigned int regno, unsigned int red, return 0; } -static int tt_detect(void) +static int tt_detect(struct fb_info *info) { struct atafb_par par; @@ -877,7 +875,7 @@ static int falcon_encode_fix(struct fb_fix_screeninfo *fix, return 0; } -static int falcon_decode_var(struct fb_var_screeninfo *var, +static int falcon_decode_var(struct fb_info *info, struct fb_var_screeninfo *var, struct atafb_par *par) { int bpp = var->bits_per_pixel; @@ -1069,13 +1067,13 @@ static int falcon_decode_var(struct fb_var_screeninfo *var, /* Choose master pixelclock depending on hor. timing */ plen = 1 * xstretch; if ((plen * xres + f25.right + f25.hsync + f25.left) * - fb_info.monspecs.hfmin < f25.f) + info->monspecs.hfmin < f25.f) pclock = &f25; else if ((plen * xres + f32.right + f32.hsync + - f32.left) * fb_info.monspecs.hfmin < f32.f) + f32.left) * info->monspecs.hfmin < f32.f) pclock = &f32; else if ((plen * xres + fext.right + fext.hsync + - fext.left) * fb_info.monspecs.hfmin < fext.f && + fext.left) * info->monspecs.hfmin < fext.f && fext.f) pclock = &fext; else @@ -1245,14 +1243,14 @@ static int falcon_decode_var(struct fb_var_screeninfo *var, /* check hor. frequency */ hfreq = pclock->f / ((par->HHT + 2) * prescale * 2); - if (hfreq > fb_info.monspecs.hfmax && mon_type != F_MON_VGA) { + if (hfreq > info->monspecs.hfmax && mon_type != F_MON_VGA) { /* ++guenther: ^^^^^^^^^^^^^^^^^^^ can't remember why I did this */ /* Too high -> enlarge margin */ left_margin += 1; right_margin += 1; goto again; } - if (hfreq > fb_info.monspecs.hfmax || hfreq < fb_info.monspecs.hfmin) + if (hfreq > info->monspecs.hfmax || hfreq < info->monspecs.hfmin) return -EINVAL; /* Vxx-registers */ @@ -1283,50 +1281,50 @@ static int falcon_decode_var(struct fb_var_screeninfo *var, /* V-frequency check, hope I didn't create any loop here. */ /* Interlace and doubleline are mutually exclusive. */ vfreq = (hfreq * 2) / (par->VFT + 1); - if (vfreq > fb_info.monspecs.vfmax && !doubleline && !interlace) { + if (vfreq > info->monspecs.vfmax && !doubleline && !interlace) { /* Too high -> try again with doubleline */ doubleline = 1; goto again; - } else if (vfreq < fb_info.monspecs.vfmin && !interlace && !doubleline) { + } else if (vfreq < info->monspecs.vfmin && !interlace && !doubleline) { /* Too low -> try again with interlace */ interlace = 1; goto again; - } else if (vfreq < fb_info.monspecs.vfmin && doubleline) { + } else if (vfreq < info->monspecs.vfmin && doubleline) { /* Doubleline too low -> clear doubleline and enlarge margins */ int lines; doubleline = 0; for (lines = 0; (hfreq * 2) / (par->VFT + 1 + 4 * lines - 2 * yres) > - fb_info.monspecs.vfmax; + info->monspecs.vfmax; lines++) ; upper_margin += lines; lower_margin += lines; goto again; - } else if (vfreq > fb_info.monspecs.vfmax && doubleline) { + } else if (vfreq > info->monspecs.vfmax && doubleline) { /* Doubleline too high -> enlarge margins */ int lines; for (lines = 0; (hfreq * 2) / (par->VFT + 1 + 4 * lines) > - fb_info.monspecs.vfmax; + info->monspecs.vfmax; lines += 2) ; upper_margin += lines; lower_margin += lines; goto again; - } else if (vfreq > fb_info.monspecs.vfmax && interlace) { + } else if (vfreq > info->monspecs.vfmax && interlace) { /* Interlace, too high -> enlarge margins */ int lines; for (lines = 0; (hfreq * 2) / (par->VFT + 1 + 4 * lines) > - fb_info.monspecs.vfmax; + info->monspecs.vfmax; lines++) ; upper_margin += lines; lower_margin += lines; goto again; - } else if (vfreq < fb_info.monspecs.vfmin || - vfreq > fb_info.monspecs.vfmax) + } else if (vfreq < info->monspecs.vfmin || + vfreq > info->monspecs.vfmax) return -EINVAL; set_screen_base: @@ -1720,7 +1718,7 @@ static int falcon_blank(int blank_mode) return 0; } -static int falcon_detect(void) +static int falcon_detect(struct fb_info *info) { struct atafb_par par; unsigned char fhw; @@ -1732,18 +1730,18 @@ static int falcon_detect(void) f030_bus_width = fhw << 6 & 0x80; switch (mon_type) { case F_MON_SM: - fb_info.monspecs.vfmin = 70; - fb_info.monspecs.vfmax = 72; - fb_info.monspecs.hfmin = 35713; - fb_info.monspecs.hfmax = 35715; + info->monspecs.vfmin = 70; + info->monspecs.vfmax = 72; + info->monspecs.hfmin = 35713; + info->monspecs.hfmax = 35715; break; case F_MON_SC: case F_MON_TV: /* PAL...NTSC */ - fb_info.monspecs.vfmin = 49; /* not 50, since TOS defaults to 49.9x Hz */ - fb_info.monspecs.vfmax = 60; - fb_info.monspecs.hfmin = 15620; - fb_info.monspecs.hfmax = 15755; + info->monspecs.vfmin = 49; /* not 50, since TOS defaults to 49.9x Hz */ + info->monspecs.vfmax = 60; + info->monspecs.hfmin = 15620; + info->monspecs.hfmax = 15755; break; } /* initialize hsync-len */ @@ -1795,7 +1793,7 @@ static int stste_encode_fix(struct fb_fix_screeninfo *fix, return 0; } -static int stste_decode_var(struct fb_var_screeninfo *var, +static int stste_decode_var(struct fb_info *info, struct fb_var_screeninfo *var, struct atafb_par *par) { int xres = var->xres; @@ -1971,7 +1969,7 @@ static int stste_setcolreg(unsigned int regno, unsigned int red, return 0; } -static int stste_detect(void) +static int stste_detect(struct fb_info *info) { struct atafb_par par; @@ -2112,7 +2110,8 @@ static int ext_encode_fix(struct fb_fix_screeninfo *fix, struct atafb_par *par) return 0; } -static int ext_decode_var(struct fb_var_screeninfo *var, struct atafb_par *par) +static int ext_decode_var(struct fb_info *info, struct fb_var_screeninfo *var, + struct atafb_par *par) { struct fb_var_screeninfo *myvar = &atafb_predefined[0]; @@ -2226,7 +2225,7 @@ static int ext_setcolreg(unsigned int regno, unsigned int red, } } -static int ext_detect(void) +static int ext_detect(struct fb_info *info) { struct fb_var_screeninfo *myvar = &atafb_predefined[0]; struct atafb_par dummy_par; @@ -2344,12 +2343,12 @@ static void ata_set_par(struct atafb_par *par) /* used for hardware scrolling */ -static int do_fb_set_var(struct fb_var_screeninfo *var, int isactive) +static int do_fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var, int isactive) { int err, activate; struct atafb_par par; - err = fbhw->decode_var(var, &par); + err = fbhw->decode_var(info, var, &par); if (err) return err; activate = var->activate; @@ -2368,7 +2367,7 @@ static int atafb_get_fix(struct fb_fix_screeninfo *fix, struct fb_info *info) struct atafb_par par; int err; // Get fix directly (case con == -1 before)?? - err = fbhw->decode_var(&info->var, &par); + err = fbhw->decode_var(info, &info->var, &par); if (err) return err; memset(fix, 0, sizeof(struct fb_fix_screeninfo)); @@ -2635,7 +2634,7 @@ static int atafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) /* Validate wanted screen parameters */ // if ((err = ata_decode_var(var, &par))) - err = fbhw->decode_var(var, &par); + err = fbhw->decode_var(info, var, &par); if (err) return err; @@ -2651,7 +2650,7 @@ static int atafb_set_par(struct fb_info *info) struct atafb_par *par = info->par; /* Decode wanted screen parameters */ - fbhw->decode_var(&info->var, par); + fbhw->decode_var(info, &info->var, par); mutex_lock(&info->mm_lock); fbhw->encode_fix(&info->fix, par); mutex_unlock(&info->mm_lock); @@ -2677,7 +2676,7 @@ static struct fb_ops atafb_ops = { __FB_DEFAULT_IOMEM_OPS_MMAP, }; -static void check_default_par(int detected_mode) +static void check_default_par(struct fb_info *info, int detected_mode) { char default_name[10]; int i; @@ -2688,14 +2687,14 @@ static void check_default_par(int detected_mode) if (default_par) { var = atafb_predefined[default_par - 1]; var.activate = FB_ACTIVATE_TEST; - if (do_fb_set_var(&var, 1)) + if (do_fb_set_var(info, &var, 1)) default_par = 0; /* failed */ } /* Next is the autodetected one */ if (!default_par) { var = atafb_predefined[detected_mode - 1]; /* autodetect */ var.activate = FB_ACTIVATE_TEST; - if (!do_fb_set_var(&var, 1)) + if (!do_fb_set_var(info, &var, 1)) default_par = detected_mode; } /* If that also failed, try some default modes... */ @@ -2708,7 +2707,7 @@ static void check_default_par(int detected_mode) panic("can't set default video mode"); var = atafb_predefined[default_par - 1]; var.activate = FB_ACTIVATE_TEST; - if (!do_fb_set_var(&var,1)) + if (!do_fb_set_var(info, &var, 1)) break; /* ok */ } } @@ -2901,10 +2900,10 @@ static void __init atafb_setup_mcap(char *spec) if (hmax <= 0 || hmax <= hmin) return; - fb_info.monspecs.vfmin = vmin; - fb_info.monspecs.vfmax = vmax; - fb_info.monspecs.hfmin = hmin; - fb_info.monspecs.hfmax = hmax; + mcap_vmin = vmin; + mcap_vmax = vmax; + mcap_hmin = hmin; + mcap_hmax = hmax; } #endif /* ATAFB_FALCON */ @@ -2991,12 +2990,19 @@ static int __init atafb_probe(struct platform_device *pdev) unsigned int defmode = 0; unsigned long mem_req; char *option = NULL; + struct fb_info *fb_info; if (fb_get_options("atafb", &option)) return -ENODEV; atafb_setup(option); dev_dbg(&pdev->dev, "%s: start\n", __func__); + fb_info = framebuffer_alloc(sizeof(struct atafb_par), &pdev->dev); + + strscpy(fb_info->fix.id, "Atari "); + fb_info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + fb_info->fix.accel = FB_ACCEL_NONE; + do { #ifdef ATAFB_EXT if (external_addr) { @@ -3052,15 +3058,20 @@ static int __init atafb_probe(struct platform_device *pdev) /* Multisync monitor capabilities */ /* Atari-TOS defaults if no boot option present */ - if (fb_info.monspecs.hfmin == 0) { - fb_info.monspecs.hfmin = 31000; - fb_info.monspecs.hfmax = 32000; - fb_info.monspecs.vfmin = 58; - fb_info.monspecs.vfmax = 62; + if (mcap_hmin == 0) { + fb_info->monspecs.hfmin = 31000; + fb_info->monspecs.hfmax = 32000; + fb_info->monspecs.vfmin = 58; + fb_info->monspecs.vfmax = 62; + } else { + fb_info->monspecs.vfmin = mcap_vmin; + fb_info->monspecs.vfmax = mcap_vmax; + fb_info->monspecs.hfmin = mcap_hmin; + fb_info->monspecs.hfmax = mcap_hmax; } - detected_mode = fbhw->detect(); - check_default_par(detected_mode); + detected_mode = fbhw->detect(fb_info); + check_default_par(fb_info, detected_mode); #ifdef ATAFB_EXT if (!external_addr) { #endif /* ATAFB_EXT */ @@ -3100,43 +3111,42 @@ static int __init atafb_probe(struct platform_device *pdev) } #endif /* ATAFB_EXT */ -// strcpy(fb_info.mode->name, "Atari Builtin "); - fb_info.fbops = &atafb_ops; +// strcpy(fb_info->mode->name, "Atari Builtin "); + fb_info->fbops = &atafb_ops; // try to set default (detected; requested) var - do_fb_set_var(&atafb_predefined[default_par - 1], 1); + do_fb_set_var(fb_info, &atafb_predefined[default_par - 1], 1); // reads hw state into current par, which may not be sane yet ata_get_par(¤t_par); - fb_info.par = ¤t_par; + fb_info->par = ¤t_par; // tries to read from HW which may not be initialized yet // so set sane var first, then call atafb_set_par - atafb_get_var(&fb_info.var, &fb_info); + atafb_get_var(&(fb_info->var), fb_info); #ifdef ATAFB_FALCON - fb_info.pseudo_palette = current_par.hw.falcon.pseudo_palette; + fb_info->pseudo_palette = current_par.hw.falcon.pseudo_palette; #endif - if (!fb_find_mode(&fb_info.var, &fb_info, mode_option, atafb_modedb, + if (!fb_find_mode(&(fb_info->var), fb_info, mode_option, atafb_modedb, NUM_TOTAL_MODES, &atafb_modedb[defmode], - fb_info.var.bits_per_pixel)) { + fb_info->var.bits_per_pixel)) { return -EINVAL; } fb_videomode_to_modelist(atafb_modedb, NUM_TOTAL_MODES, - &fb_info.modelist); + &(fb_info->modelist)); - atafb_set_disp(&fb_info); + atafb_set_disp(fb_info); - fb_alloc_cmap(&(fb_info.cmap), 1 << fb_info.var.bits_per_pixel, 0); + fb_alloc_cmap(&(fb_info->cmap), 1 << fb_info->var.bits_per_pixel, 0); - - dev_info(&pdev->dev, "Determined %dx%d, depth %d\n", fb_info.var.xres, - fb_info.var.yres, fb_info.var.bits_per_pixel); - if ((fb_info.var.xres != fb_info.var.xres_virtual) || - (fb_info.var.yres != fb_info.var.yres_virtual)) + dev_info(&pdev->dev, "Determined %dx%d, depth %d\n", fb_info->var.xres, + fb_info->var.yres, fb_info->var.bits_per_pixel); + if ((fb_info->var.xres != fb_info->var.xres_virtual) || + (fb_info->var.yres != fb_info->var.yres_virtual)) dev_info(&pdev->dev, " virtual %dx%d\n", - fb_info.var.xres_virtual, fb_info.var.yres_virtual); + fb_info->var.xres_virtual, fb_info->var.yres_virtual); - if (register_framebuffer(&fb_info) < 0) { + if (register_framebuffer(fb_info) < 0) { #ifdef ATAFB_EXT if (external_addr) { iounmap(external_screen_base); @@ -3150,7 +3160,7 @@ static int __init atafb_probe(struct platform_device *pdev) return -EINVAL; } - fb_info(&fb_info, "frame buffer device, using %dK of video memory\n", + fb_info(fb_info, "frame buffer device, using %dK of video memory\n", screen_len >> 10); /* TODO: This driver cannot be unloaded yet */ From 51df976c3268b96a926d8f389fd9e91c0b5392e1 Mon Sep 17 00:00:00 2001 From: Miro Kropacek Date: Thu, 20 Aug 2026 10:28:36 +1200 Subject: [PATCH 42/43] fbdev: atafb: Add support for further video bit depths on atafb:external Supervidel offers additional video bit depths: 8-bit chunky, 16-bit RGB565 (also on the original Videl) and ARGB888. Add code to support these bit depths. Signed-off-by: Miro Kropacek Reviewed-by: Michael Schmitz Signed-off-by: Michael Schmitz Link: https://lists.debian.org/debian-68k/2026/08/msg00000.html Signed-off-by: Helge Deller --- drivers/video/fbdev/atafb.c | 89 ++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c index 0d415fdfb18d..388752130c24 100644 --- a/drivers/video/fbdev/atafb.c +++ b/drivers/video/fbdev/atafb.c @@ -2079,8 +2079,12 @@ static int ext_encode_fix(struct fb_fix_screeninfo *fix, struct atafb_par *par) external_pmode == FB_TYPE_PACKED_PIXELS) ? FB_VISUAL_MONO10 : FB_VISUAL_MONO01; } else { - /* Use STATIC if we don't know how to access color registers */ - int visual = external_vgaiobase ? + /* Use STATIC if we don't know how to access color registers; + * SuperVidel 8bpp chunky (fb in SV RAM) uses the Falcon palette + */ + int visual = (external_vgaiobase || + (external_depth == 8 && + external_addr >= 0xa0000000)) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_STATIC_PSEUDOCOLOR; switch (external_pmode) { @@ -2158,6 +2162,35 @@ static int ext_encode_var(struct fb_var_screeninfo *var, struct atafb_par *par) var->transp.offset = 0; var->transp.length = 0; var->transp.msb_right = 0; + if (external_pmode == -1 && external_depth == 16) { + /* RGB565 truecolor (e.g. SuperVidel native mode) */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + } else if (external_pmode == -1 && external_depth == 32) { + /* ARGB8888 truecolor (e.g. SuperVidel native mode) */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + } else if (external_pmode == FB_TYPE_PACKED_PIXELS && + external_depth == 8 && external_addr >= 0xa0000000) { + /* SuperVidel 8bpp chunky: palette has 8 bits per channel. + * Without this, fb_get_color_depth() sees length 0 and + * fbcon falls back to its 2-color palette — the console + * text (color 7) stays black on black. + */ + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + } var->yres_virtual = var->yres; var->xoffset = 0; var->yoffset = 0; @@ -2192,6 +2225,38 @@ static int ext_setcolreg(unsigned int regno, unsigned int red, { unsigned char colmask = (1 << external_bitspercol) - 1; + if (external_pmode == -1 && external_depth == 16) { + /* truecolor: only the pseudo palette for fbcon is needed */ + if (regno > 15) + return 1; + ((u32 *)info->pseudo_palette)[regno] = (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + return 0; + } + if (external_pmode == -1 && external_depth == 32) { + /* ARGB8888, alpha forced opaque */ + if (regno > 15) + return 1; + ((u32 *)info->pseudo_palette)[regno] = 0xff000000 | + ((red & 0xff00) << 8) | + (green & 0xff00) | + ((blue & 0xff00) >> 8); + return 0; + } + if (external_pmode == FB_TYPE_PACKED_PIXELS && external_depth == 8 && + external_addr >= 0xa0000000) { + /* SuperVidel native 8bpp chunky scans out via the Falcon + * palette registers, honoring all 8 bits per channel + */ + if (regno > 255) + return 1; + f030_col[regno] = ((red & 0xff00) << 16) | + ((green & 0xff00) << 8) | + ((blue & 0xff00) >> 8); + return 0; + } + if (!external_vgaiobase) return 1; @@ -2421,7 +2486,9 @@ static void atafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) return; #ifdef ATAFB_FALCON - if (info->var.bits_per_pixel == 16) { + /* chunky modes (Falcon hicolor, external packed/truecolor) */ + if (info->fix.type == FB_TYPE_PACKED_PIXELS && + info->var.bits_per_pixel > 1) { cfb_fillrect(info, rect); return; } @@ -2462,7 +2529,9 @@ static void atafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) int rev_copy = 0; #ifdef ATAFB_FALCON - if (info->var.bits_per_pixel == 16) { + /* chunky modes (Falcon hicolor, external packed/truecolor) */ + if (info->fix.type == FB_TYPE_PACKED_PIXELS && + info->var.bits_per_pixel > 1) { cfb_copyarea(info, area); return; } @@ -2516,7 +2585,9 @@ static void atafb_imageblit(struct fb_info *info, const struct fb_image *image) u32 dx, dy, width, height, pitch; #ifdef ATAFB_FALCON - if (info->var.bits_per_pixel == 16) { + /* chunky modes (Falcon hicolor, external packed/truecolor) */ + if (info->fix.type == FB_TYPE_PACKED_PIXELS && + info->var.bits_per_pixel > 1) { cfb_imageblit(info, image); return; } @@ -2752,7 +2823,7 @@ static void __init atafb_setup_ext(char *spec) return; depth = simple_strtoul(p, NULL, 10); if (depth != 1 && depth != 2 && depth != 4 && depth != 8 && - depth != 16 && depth != 24) + depth != 16 && depth != 24 && depth != 32) return; p = strsep(&spec, ";"); @@ -3137,7 +3208,11 @@ static int __init atafb_probe(struct platform_device *pdev) atafb_set_disp(fb_info); - fb_alloc_cmap(&(fb_info->cmap), 1 << fb_info->var.bits_per_pixel, 0); + /* truecolor visuals only need the 16-entry console palette; this + * also avoids 1 << 32 overflowing at 32bpp + */ + fb_alloc_cmap(&(fb_info->cmap), fb_info->var.bits_per_pixel > 8 ? + 16 : 1 << fb_info->var.bits_per_pixel, 0); dev_info(&pdev->dev, "Determined %dx%d, depth %d\n", fb_info->var.xres, fb_info->var.yres, fb_info->var.bits_per_pixel); From d463633d63e6aa10384683bf39971a4b00780c56 Mon Sep 17 00:00:00 2001 From: Miro Kropacek Date: Thu, 20 Aug 2026 10:28:37 +1200 Subject: [PATCH 43/43] fbdev: atafb: Add support for SuperVidel's SuperBlitter The SuperVidel graphics FPGA includes a hardware blitter (bit block transfer engine) operating within the SuperVidel DDR2 video RAM. Two versions of this blitter exist. Later versions (>= 9) of the SuperVidel firmware support an asynchronous command FIFO, older versions must be polled for command completion. Add hardware-accelerated copyarea, fillrect and imageblit fb operations (falling back to the non-accelerated versions for anything that exceeds blitter capabilities). Signed-off-by: Miro Kropacek Reviewed-by: Michael Schmitz Signed-off-by: Michael Schmitz Link: https://lists.debian.org/debian-68k/2026/08/msg00000.html Signed-off-by: Helge Deller --- drivers/video/fbdev/atafb.c | 197 ++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/drivers/video/fbdev/atafb.c b/drivers/video/fbdev/atafb.c index 388752130c24..5bca34c45cef 100644 --- a/drivers/video/fbdev/atafb.c +++ b/drivers/video/fbdev/atafb.c @@ -2303,6 +2303,185 @@ static int ext_detect(struct fb_info *info) return 1; } +/* ------------------- SuperVidel SuperBlitter ---------------------- */ + +/* + * Hardware blitter in the SuperVidel FPGA, operating within SV DDR2 RAM. + * FW revision >= 9 provides a command FIFO (async operation); older + * revisions are programmed directly with busy-polling. + */ +#define SVBLIT_REGS_PHYS 0x80010000 +#define SVBLIT_SRC1 0x58 /* bits 26:0 */ +#define SVBLIT_SRC2 0x5c +#define SVBLIT_DST 0x60 +#define SVBLIT_COUNT 0x64 /* bytes per line - 1 */ +#define SVBLIT_SRC1_OFFSET 0x68 /* line start to next line start */ +#define SVBLIT_SRC2_OFFSET 0x6c +#define SVBLIT_DST_OFFSET 0x70 +#define SVBLIT_MASK_AND_LINES 0x74 /* bits 11:0: number of lines */ +#define SVBLIT_CONTROL 0x78 /* bit 0: busy/start, bits 4:1: mode */ +#define SVBLIT_VERSION 0x7c /* bits 9:0: FW revision */ +#define SVBLIT_FIFO 0x80 /* wr: data; rd: bit 0 empty, bit 1 full */ + +/* + * SuperBlitter bug: Instead of declared 2048 bytes, 2032 is the real maximum. + */ +#define SVBLIT_MAX_SPAN 2032 + +static void __iomem *svblit_regs; +static int svblit_fw; + +static inline u32 svblit_rd(unsigned int reg) +{ + return __raw_readl(svblit_regs + reg); +} + +static inline void svblit_wr(unsigned int reg, u32 val) +{ + __raw_writel(val, svblit_regs + reg); +} + +/* wait until all queued blits have finished */ +static void svblit_wait(void) +{ + if (svblit_fw >= 9) + /* FIFO empty flag = fewer than 9 longwords queued */ + while (!(svblit_rd(SVBLIT_FIFO) & 1)) + cpu_relax(); + while (svblit_rd(SVBLIT_CONTROL) & 1) + cpu_relax(); +} + +/* + * FW >= 9 queues commands through the 512-longword FIFO: a command is + * 9 longwords (registers 0x58..0x78 in order), executed whenever >= 9 + * words are queued and the blitter is idle. The full flag rises at + * >= 500 queued words, so below it there is always room for a whole + * command — one flag check per command prevents overflow (dropped + * words would desync the 9-word framing until an SV reinit, which is + * exactly what overflowing did before this guard existed). Older FW + * is programmed directly with busy-polling. + * + * The line byte count field is 11 bits but see SVBLIT_MAX_SPAN. + */ +static void svblit_copy(u32 src, u32 dst, u32 nbytes, u32 src_offset, + u32 dst_offset, u32 lines) +{ + while (nbytes) { + u32 chunk = min(nbytes, SVBLIT_MAX_SPAN); + + if (svblit_fw >= 9) { + while (svblit_rd(SVBLIT_FIFO) & 2) + cpu_relax(); + svblit_wr(SVBLIT_FIFO, src); + svblit_wr(SVBLIT_FIFO, 0); + svblit_wr(SVBLIT_FIFO, dst); + svblit_wr(SVBLIT_FIFO, chunk - 1); + svblit_wr(SVBLIT_FIFO, src_offset); + svblit_wr(SVBLIT_FIFO, 0); + svblit_wr(SVBLIT_FIFO, dst_offset); + svblit_wr(SVBLIT_FIFO, lines); + svblit_wr(SVBLIT_FIFO, 0x01); + } else { + while (svblit_rd(SVBLIT_CONTROL) & 1) + cpu_relax(); + svblit_wr(SVBLIT_SRC1, src); + svblit_wr(SVBLIT_SRC2, 0); + svblit_wr(SVBLIT_DST, dst); + svblit_wr(SVBLIT_COUNT, chunk - 1); + svblit_wr(SVBLIT_SRC1_OFFSET, src_offset); + svblit_wr(SVBLIT_SRC2_OFFSET, 0); + svblit_wr(SVBLIT_DST_OFFSET, dst_offset); + svblit_wr(SVBLIT_MASK_AND_LINES, lines); + svblit_wr(SVBLIT_CONTROL, 0x01); + } + + src += chunk; + dst += chunk; + nbytes -= chunk; + } +} + +static int svblit_sync(struct fb_info *info) +{ + svblit_wait(); + return 0; +} + +static void svblit_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + u32 bytespp = info->var.bits_per_pixel / 8; + u32 pitch = info->fix.line_length; + + /* + * The blitter walks lines in ascending order, so overlapping + * moves down/right would read already overwritten data. Those + * are rare for fbcon (scrolling backwards); leave them and + * oversized areas to the CPU. + */ + if (area->height > 4095 || + area->dy > area->sy || + (area->dy == area->sy && area->dx > area->sx)) { + svblit_wait(); + cfb_copyarea(info, area); + return; + } + + svblit_copy(external_addr + area->sy * pitch + area->sx * bytespp, + external_addr + area->dy * pitch + area->dx * bytespp, + area->width * bytespp, pitch, pitch, area->height); + /* async: every CPU access to the fb goes through svblit_wait() */ +} + +static void svblit_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + u32 bytespp = info->var.bits_per_pixel / 8; + u32 pitch = info->fix.line_length; + u8 *line; + u32 pix; + + svblit_wait(); /* the CPU is about to touch the fb */ + + if (rect->rop != ROP_COPY || rect->height <= 1 || + rect->height > 4096) { + cfb_fillrect(info, rect); + return; + } + + pix = (info->fix.visual == FB_VISUAL_TRUECOLOR) ? + ((u32 *)info->pseudo_palette)[rect->color] : rect->color; + + /* draw the first line with the CPU ... */ + line = (u8 *)info->screen_base + rect->dy * pitch + + rect->dx * bytespp; + switch (bytespp) { + case 1: + memset(line, pix, rect->width); + break; + case 2: + memset16((u16 *)line, pix, rect->width); + break; + default: + memset32((u32 *)line, pix, rect->width); + break; + } + + /* ... and let the blitter replicate it into the other lines */ + svblit_copy(external_addr + rect->dy * pitch + rect->dx * bytespp, + external_addr + (rect->dy + 1) * pitch + + rect->dx * bytespp, + rect->width * bytespp, 0, pitch, rect->height - 1); +} + +static void svblit_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + svblit_wait(); /* CPU rendering must not race queued blits */ + cfb_imageblit(info, image); +} + #endif /* ATAFB_EXT */ /* ------ This is the same for most hardware types -------- */ @@ -3179,6 +3358,24 @@ static int __init atafb_probe(struct platform_device *pdev) phys_screen_base = external_addr; screen_len = external_len & PAGE_MASK; memset (screen_base, 0, external_len); + + /* framebuffer in SV RAM: enable the SuperBlitter */ + if (external_addr >= 0xa0000000) { + svblit_regs = ioremap(SVBLIT_REGS_PHYS, 0x100); + if (svblit_regs) { + svblit_fw = svblit_rd(SVBLIT_VERSION) & 0x1ff; + atafb_ops.fb_fillrect = svblit_fillrect; + atafb_ops.fb_copyarea = svblit_copyarea; + atafb_ops.fb_imageblit = svblit_imageblit; + atafb_ops.fb_sync = svblit_sync; + fb_info->flags |= FBINFO_HWACCEL_COPYAREA | + FBINFO_HWACCEL_FILLRECT; + dev_info(&pdev->dev, + "SuperBlitter enabled, FW revision %d (%s)\n", + svblit_fw, svblit_fw >= 9 ? + "async FIFO" : "sync"); + } + } } #endif /* ATAFB_EXT */