[PATCH v1] fbdev: atmel_lcdfb: Prevent FIFO work after removal
From: Yibo Tan
Date: Tue Sep 15 2026 - 11:46:57 EST
The LCD controller IRQ schedules sinfo->task to recover from a FIFO
underflow. Probe currently requests the IRQ before initializing the
work, so an early interrupt can schedule an uninitialized work item.
The probe error path and remove path cancel the work before freeing the
IRQ. A FIFO-underflow interrupt can schedule the work again after that
cancellation. framebuffer_release() may then free sinfo while the work
is still pending.
KASAN reported a use-after-free in atmel_lcdfb_task() when an underflow
interrupt occurred during removal. With the IRQ freed before the work
was cancelled, the same test completed without a kernel diagnostic.
Initialize the work before requesting the IRQ. In both cleanup paths,
free the IRQ before cancelling the work so that it cannot be scheduled
again after cancellation.
Fixes: d22579b83735 ("atmel_lcdfb: FIFO underflow management")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:GPT-5
Signed-off-by: Yibo Tan <lhfff@xxxxxxxxxx>
---
drivers/video/fbdev/atmel_lcdfb.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/drivers/video/fbdev/atmel_lcdfb.c b/drivers/video/fbdev/atmel_lcdfb.c
index 53f0156992e6..9a0cc09004b7 100644
--- a/drivers/video/fbdev/atmel_lcdfb.c
+++ b/drivers/video/fbdev/atmel_lcdfb.c
@@ -1146,6 +1146,9 @@ static int atmel_lcdfb_probe(struct platform_device *pdev)
/* Initialize PWM for contrast or backlight ("off") */
init_contrast(sinfo);
+ /* Some LCDC recovery operations require process context. */
+ INIT_WORK(&sinfo->task, atmel_lcdfb_task);
+
/* interrupt */
ret = request_irq(sinfo->irq_base, atmel_lcdfb_interrupt, 0, pdev->name, info);
if (ret) {
@@ -1153,10 +1156,6 @@ static int atmel_lcdfb_probe(struct platform_device *pdev)
goto unmap_mmio;
}
- /* Some operations on the LCDC might sleep and
- * require a preemptible task context */
- INIT_WORK(&sinfo->task, atmel_lcdfb_task);
-
ret = atmel_lcdfb_init_fbinfo(sinfo);
if (ret < 0) {
dev_err(dev, "init fbinfo failed: %d\n", ret);
@@ -1192,8 +1191,8 @@ static int atmel_lcdfb_probe(struct platform_device *pdev)
dev_set_drvdata(dev, NULL);
fb_dealloc_cmap(&info->cmap);
unregister_irqs:
- cancel_work_sync(&sinfo->task);
free_irq(sinfo->irq_base, info);
+ cancel_work_sync(&sinfo->task);
unmap_mmio:
exit_backlight(sinfo);
iounmap(sinfo->mmio);
@@ -1230,6 +1229,7 @@ static void atmel_lcdfb_remove(struct platform_device *pdev)
return;
sinfo = info->par;
+ free_irq(sinfo->irq_base, info);
cancel_work_sync(&sinfo->task);
exit_backlight(sinfo);
atmel_lcdfb_power_control(sinfo, 0);
@@ -1238,7 +1238,6 @@ static void atmel_lcdfb_remove(struct platform_device *pdev)
clk_put(sinfo->lcdc_clk);
clk_put(sinfo->bus_clk);
fb_dealloc_cmap(&info->cmap);
- free_irq(sinfo->irq_base, info);
iounmap(sinfo->mmio);
release_mem_region(info->fix.mmio_start, info->fix.mmio_len);
if (platform_get_resource(pdev, IORESOURCE_MEM, 1)) {
--
2.39.5