* [PATCH v8 2/2] staging: fbtft: Make framebuffer registration message debug-only
From: Chintan Patel @ 2026-01-22 3:16 UTC (permalink / raw)
To: linux-fbdev, linux-staging, linux-omap
Cc: linux-kernel, dri-devel, tzimmermann, andy, deller, gregkh,
Chintan Patel, Andy Shevchenko
In-Reply-To: <20260122031635.11414-1-chintanlike@gmail.com>
The framebuffer registration message is informational only and not
useful during normal operation. Convert it to debug-level logging to
keep the driver quiet when working correctly.
Suggested-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: Chintan Patel <chintanlike@gmail.com>
---
Changes in v8:
- Add Reviewed-by tag from Andy Shevchenko
- Add Suggested-by tag from Greg Kroah-Hartman
drivers/staging/fbtft/fbtft-core.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/drivers/staging/fbtft/fbtft-core.c b/drivers/staging/fbtft/fbtft-core.c
index 1b3b62950205..f427c0914907 100644
--- a/drivers/staging/fbtft/fbtft-core.c
+++ b/drivers/staging/fbtft/fbtft-core.c
@@ -792,11 +792,11 @@ int fbtft_register_framebuffer(struct fb_info *fb_info)
if (spi)
sprintf(text2, ", spi%d.%d at %d MHz", spi->controller->bus_num,
spi_get_chipselect(spi, 0), spi->max_speed_hz / 1000000);
- fb_info(fb_info,
- "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n",
- fb_info->fix.id, fb_info->var.xres, fb_info->var.yres,
- fb_info->fix.smem_len >> 10, text1,
- HZ / fb_info->fbdefio->delay, text2);
+ fb_dbg(fb_info,
+ "%s frame buffer, %dx%d, %d KiB video memory%s, fps=%lu%s\n",
+ fb_info->fix.id, fb_info->var.xres, fb_info->var.yres,
+ fb_info->fix.smem_len >> 10, text1,
+ HZ / fb_info->fbdefio->delay, text2);
/* Turn on backlight if available */
if (fb_info->bl_dev) {
--
2.43.0
^ permalink raw reply related
* [PATCH 0/4] fbdev: defio: Protect against device/module removal
From: Thomas Zimmermann @ 2026-01-22 13:08 UTC (permalink / raw)
To: deller, simona, jayalk; +Cc: linux-fbdev, dri-devel, Thomas Zimmermann
There's a long-standing bug in defio where the framebuffer device or
module gets removed while mmap'ed areas of the framebuffer memory
persists in userspace. Page faults in the area then operate on defined
state.
Patches 1 and 2 fix these problems. Patches 3 and 4 build upon the fix
and put defio state into the correct places.
Thomas Zimmermann (4):
fbdev: defio: Disconnect deferred I/O from the lifetime of struct
fb_info
fbdev: defio: Keep module reference from VMAs
fbdev: defio: Move variable state into struct fb_deferred_io_state
fbdev: defio: Move pageref array to struct fb_deferred_io_state
drivers/video/fbdev/core/fb_defio.c | 266 ++++++++++++++++++++--------
include/linux/fb.h | 9 +-
2 files changed, 195 insertions(+), 80 deletions(-)
base-commit: a3ecd278f9a05323fab7471760a7ea10081251d6
--
2.52.0
^ permalink raw reply
* [PATCH 1/4] fbdev: defio: Disconnect deferred I/O from the lifetime of struct fb_info
From: Thomas Zimmermann @ 2026-01-22 13:08 UTC (permalink / raw)
To: deller, simona, jayalk; +Cc: linux-fbdev, dri-devel, Thomas Zimmermann, stable
In-Reply-To: <20260122131213.992810-1-tzimmermann@suse.de>
Hold state of deferred I/O in struct fb_deferred_io_state. Allocate an
instance as part of initializing deferred I/O and remove it only after
the final mapping has been closed. If the fb_info and the contained
deferred I/O meanwhile goes away, clear struct fb_deferred_io_state.info
to invalidate the mapping. Any access will then result in a SIGBUS
signal.
Fixes a long-standing problem, where a device hot-unplug happens while
user space still has an active mapping of the graphics memory. The hot-
unplug frees the instance of struct fb_info. Accessing the memory will
operate on undefined state.
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
Fixes: 60b59beafba8 ("fbdev: mm: Deferred IO support")
Cc: Helge Deller <deller@gmx.de>
Cc: linux-fbdev@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: <stable@vger.kernel.org> # v2.6.22+
---
drivers/video/fbdev/core/fb_defio.c | 178 ++++++++++++++++++++++------
include/linux/fb.h | 4 +-
2 files changed, 145 insertions(+), 37 deletions(-)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index 8df2e51e3390..0b099a89a823 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -24,6 +24,75 @@
#include <linux/rmap.h>
#include <linux/pagemap.h>
+/*
+ * struct fb_deferred_io_state
+ */
+
+struct fb_deferred_io_state {
+ struct kref ref;
+
+ struct mutex lock; /* mutex that protects the pageref list */
+ /* fields protected by lock */
+ struct fb_info *info;
+};
+
+static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
+{
+ struct fb_deferred_io_state *fbdefio_state;
+
+ fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
+ if (!fbdefio_state)
+ return NULL;
+
+ kref_init(&fbdefio_state->ref);
+ mutex_init(&fbdefio_state->lock);
+
+ return fbdefio_state;
+}
+
+static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
+{
+ mutex_destroy(&fbdefio_state->lock);
+
+ kfree(fbdefio_state);
+}
+
+static void fb_deferred_io_state_get(struct fb_deferred_io_state *fbdefio_state)
+{
+ kref_get(&fbdefio_state->ref);
+}
+
+static void __fb_deferred_io_state_release(struct kref *ref)
+{
+ struct fb_deferred_io_state *fbdefio_state =
+ container_of(ref, struct fb_deferred_io_state, ref);
+
+ fb_deferred_io_state_release(fbdefio_state);
+}
+
+static void fb_deferred_io_state_put(struct fb_deferred_io_state *fbdefio_state)
+{
+ kref_put(&fbdefio_state->ref, __fb_deferred_io_state_release);
+}
+
+/*
+ * struct vm_operations_struct
+ */
+
+static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
+{
+ struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
+
+ fb_deferred_io_state_get(fbdefio_state);
+}
+
+static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
+{
+ struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
+
+ fb_deferred_io_state_put(fbdefio_state);
+}
+
static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
@@ -121,25 +190,46 @@ static void fb_deferred_io_pageref_put(struct fb_deferred_io_pageref *pageref,
/* this is to find and return the vmalloc-ed fb pages */
static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
{
+ struct fb_info *info;
unsigned long offset;
struct page *page;
- struct fb_info *info = vmf->vma->vm_private_data;
+ vm_fault_t ret;
+ struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
+
+ mutex_lock(&fbdefio_state->lock);
+
+ info = fbdefio_state->info;
+ if (!info) {
+ ret = VM_FAULT_SIGBUS; /* our device is gone */
+ goto err_mutex_unlock;
+ }
offset = vmf->pgoff << PAGE_SHIFT;
- if (offset >= info->fix.smem_len)
- return VM_FAULT_SIGBUS;
+ if (offset >= info->fix.smem_len) {
+ ret = VM_FAULT_SIGBUS;
+ goto err_mutex_unlock;
+ }
page = fb_deferred_io_get_page(info, offset);
- if (!page)
- return VM_FAULT_SIGBUS;
+ if (!page) {
+ ret = VM_FAULT_SIGBUS;
+ goto err_mutex_unlock;
+ }
if (!vmf->vma->vm_file)
fb_err(info, "no mapping available\n");
BUG_ON(!info->fbdefio->mapping);
+ mutex_unlock(&fbdefio_state->lock);
+
vmf->page = page;
+
return 0;
+
+err_mutex_unlock:
+ mutex_unlock(&fbdefio_state->lock);
+ return ret;
}
int fb_deferred_io_fsync(struct file *file, loff_t start, loff_t end, int datasync)
@@ -166,15 +256,24 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_fsync);
* Adds a page to the dirty list. Call this from struct
* vm_operations_struct.page_mkwrite.
*/
-static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long offset,
- struct page *page)
+static vm_fault_t fb_deferred_io_track_page(struct fb_deferred_io_state *fbdefio_state,
+ unsigned long offset, struct page *page)
{
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_info *info;
+ struct fb_deferred_io *fbdefio;
struct fb_deferred_io_pageref *pageref;
vm_fault_t ret;
/* protect against the workqueue changing the page list */
- mutex_lock(&fbdefio->lock);
+ mutex_lock(&fbdefio_state->lock);
+
+ info = fbdefio_state->info;
+ if (!info) {
+ ret = VM_FAULT_SIGBUS; /* our device is gone */
+ goto err_mutex_unlock;
+ }
+
+ fbdefio = info->fbdefio;
pageref = fb_deferred_io_pageref_get(info, offset, page);
if (WARN_ON_ONCE(!pageref)) {
@@ -192,50 +291,38 @@ static vm_fault_t fb_deferred_io_track_page(struct fb_info *info, unsigned long
*/
lock_page(pageref->page);
- mutex_unlock(&fbdefio->lock);
+ mutex_unlock(&fbdefio_state->lock);
/* come back after delay to process the deferred IO */
schedule_delayed_work(&info->deferred_work, fbdefio->delay);
return VM_FAULT_LOCKED;
err_mutex_unlock:
- mutex_unlock(&fbdefio->lock);
+ mutex_unlock(&fbdefio_state->lock);
return ret;
}
-/*
- * fb_deferred_io_page_mkwrite - Mark a page as written for deferred I/O
- * @fb_info: The fbdev info structure
- * @vmf: The VM fault
- *
- * This is a callback we get when userspace first tries to
- * write to the page. We schedule a workqueue. That workqueue
- * will eventually mkclean the touched pages and execute the
- * deferred framebuffer IO. Then if userspace touches a page
- * again, we repeat the same scheme.
- *
- * Returns:
- * VM_FAULT_LOCKED on success, or a VM_FAULT error otherwise.
- */
-static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_info *info, struct vm_fault *vmf)
+static vm_fault_t fb_deferred_io_page_mkwrite(struct fb_deferred_io_state *fbdefio_state,
+ struct vm_fault *vmf)
{
unsigned long offset = vmf->pgoff << PAGE_SHIFT;
struct page *page = vmf->page;
file_update_time(vmf->vma->vm_file);
- return fb_deferred_io_track_page(info, offset, page);
+ return fb_deferred_io_track_page(fbdefio_state, offset, page);
}
-/* vm_ops->page_mkwrite handler */
static vm_fault_t fb_deferred_io_mkwrite(struct vm_fault *vmf)
{
- struct fb_info *info = vmf->vma->vm_private_data;
+ struct fb_deferred_io_state *fbdefio_state = vmf->vma->vm_private_data;
- return fb_deferred_io_page_mkwrite(info, vmf);
+ return fb_deferred_io_page_mkwrite(fbdefio_state, vmf);
}
static const struct vm_operations_struct fb_deferred_io_vm_ops = {
+ .open = fb_deferred_io_vm_open,
+ .close = fb_deferred_io_vm_close,
.fault = fb_deferred_io_fault,
.page_mkwrite = fb_deferred_io_mkwrite,
};
@@ -252,7 +339,10 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
if (!(info->flags & FBINFO_VIRTFB))
vm_flags_set(vma, VM_IO);
- vma->vm_private_data = info;
+ vma->vm_private_data = info->fbdefio_state;
+
+ fb_deferred_io_state_get(info->fbdefio_state); /* released in vma->vm_ops->close() */
+
return 0;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_mmap);
@@ -263,9 +353,10 @@ static void fb_deferred_io_work(struct work_struct *work)
struct fb_info *info = container_of(work, struct fb_info, deferred_work.work);
struct fb_deferred_io_pageref *pageref, *next;
struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
/* here we wrprotect the page's mappings, then do all deferred IO. */
- mutex_lock(&fbdefio->lock);
+ mutex_lock(&fbdefio_state->lock);
#ifdef CONFIG_MMU
list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
struct page *page = pageref->page;
@@ -283,12 +374,13 @@ static void fb_deferred_io_work(struct work_struct *work)
list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
fb_deferred_io_pageref_put(pageref, info);
- mutex_unlock(&fbdefio->lock);
+ mutex_unlock(&fbdefio_state->lock);
}
int fb_deferred_io_init(struct fb_info *info)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_state *fbdefio_state;
struct fb_deferred_io_pageref *pagerefs;
unsigned long npagerefs;
int ret;
@@ -298,7 +390,11 @@ int fb_deferred_io_init(struct fb_info *info)
if (WARN_ON(!info->fix.smem_len))
return -EINVAL;
- mutex_init(&fbdefio->lock);
+ fbdefio_state = fb_deferred_io_state_alloc();
+ if (!fbdefio_state)
+ return -ENOMEM;
+ fbdefio_state->info = info;
+
INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
INIT_LIST_HEAD(&fbdefio->pagereflist);
if (fbdefio->delay == 0) /* set a default of 1 s */
@@ -315,10 +411,12 @@ int fb_deferred_io_init(struct fb_info *info)
info->npagerefs = npagerefs;
info->pagerefs = pagerefs;
+ info->fbdefio_state = fbdefio_state;
+
return 0;
err:
- mutex_destroy(&fbdefio->lock);
+ fb_deferred_io_state_release(fbdefio_state);
return ret;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_init);
@@ -352,11 +450,19 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_release);
void fb_deferred_io_cleanup(struct fb_info *info)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
fb_deferred_io_lastclose(info);
+ info->fbdefio_state = NULL;
+
+ mutex_lock(&fbdefio_state->lock);
+ fbdefio_state->info = NULL;
+ mutex_unlock(&fbdefio_state->lock);
+
+ fb_deferred_io_state_put(fbdefio_state);
+
kvfree(info->pagerefs);
- mutex_destroy(&fbdefio->lock);
fbdefio->mapping = NULL;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 65fb70382675..0bf3f1a5cf1e 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -217,13 +217,14 @@ struct fb_deferred_io {
unsigned long delay;
bool sort_pagereflist; /* sort pagelist by offset */
int open_count; /* number of opened files; protected by fb_info lock */
- struct mutex lock; /* mutex that protects the pageref list */
struct list_head pagereflist; /* list of pagerefs for touched pages */
struct address_space *mapping; /* page cache object for fb device */
/* callback */
struct page *(*get_page)(struct fb_info *info, unsigned long offset);
void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
};
+
+struct fb_deferred_io_state;
#endif
/*
@@ -486,6 +487,7 @@ struct fb_info {
unsigned long npagerefs;
struct fb_deferred_io_pageref *pagerefs;
struct fb_deferred_io *fbdefio;
+ struct fb_deferred_io_state *fbdefio_state;
#endif
const struct fb_ops *fbops;
--
2.52.0
^ permalink raw reply related
* [PATCH 3/4] fbdev: defio: Move variable state into struct fb_deferred_io_state
From: Thomas Zimmermann @ 2026-01-22 13:08 UTC (permalink / raw)
To: deller, simona, jayalk; +Cc: linux-fbdev, dri-devel, Thomas Zimmermann
In-Reply-To: <20260122131213.992810-1-tzimmermann@suse.de>
Move variable fields from struct fb_deferred_io into struct
fb_deferred_io_state. These fields are internal to the defio code
and should not be exposed to drivers. At some later point, struct
fb_defered_io might become const in all defio code.
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
drivers/video/fbdev/core/fb_defio.c | 37 +++++++++++++++++------------
include/linux/fb.h | 3 ---
2 files changed, 22 insertions(+), 18 deletions(-)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index 331b1a9fe485..c6945b4422cc 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -25,6 +25,8 @@
#include <linux/rmap.h>
#include <linux/pagemap.h>
+struct address_space;
+
/*
* struct fb_deferred_io_state
*/
@@ -32,9 +34,13 @@
struct fb_deferred_io_state {
struct kref ref;
+ int open_count; /* number of opened files; protected by fb_info lock */
+ struct address_space *mapping; /* page cache object for fb device */
+
struct mutex lock; /* mutex that protects the pageref list */
/* fields protected by lock */
struct fb_info *info;
+ struct list_head pagereflist; /* list of pagerefs for touched pages */
};
static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
@@ -48,11 +54,14 @@ static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
kref_init(&fbdefio_state->ref);
mutex_init(&fbdefio_state->lock);
+ INIT_LIST_HEAD(&fbdefio_state->pagereflist);
+
return fbdefio_state;
}
static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
{
+ WARN_ON(!list_empty(&fbdefio_state->pagereflist));
mutex_destroy(&fbdefio_state->lock);
kfree(fbdefio_state);
@@ -147,7 +156,8 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info
struct page *page)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
- struct list_head *pos = &fbdefio->pagereflist;
+ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
+ struct list_head *pos = &fbdefio_state->pagereflist;
struct fb_deferred_io_pageref *pageref, *cur;
pageref = fb_deferred_io_pageref_lookup(info, offset, page);
@@ -171,7 +181,7 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info
* pages. If possible, drivers should try to work with
* unsorted page lists instead.
*/
- list_for_each_entry(cur, &fbdefio->pagereflist, list) {
+ list_for_each_entry(cur, &fbdefio_state->pagereflist, list) {
if (cur->offset > pageref->offset)
break;
}
@@ -222,7 +232,7 @@ static vm_fault_t fb_deferred_io_fault(struct vm_fault *vmf)
if (!vmf->vma->vm_file)
fb_err(info, "no mapping available\n");
- BUG_ON(!info->fbdefio->mapping);
+ fb_WARN_ON_ONCE(info, !fbdefio_state->mapping);
mutex_unlock(&fbdefio_state->lock);
@@ -364,20 +374,20 @@ static void fb_deferred_io_work(struct work_struct *work)
/* here we wrprotect the page's mappings, then do all deferred IO. */
mutex_lock(&fbdefio_state->lock);
#ifdef CONFIG_MMU
- list_for_each_entry(pageref, &fbdefio->pagereflist, list) {
+ list_for_each_entry(pageref, &fbdefio_state->pagereflist, list) {
struct page *page = pageref->page;
pgoff_t pgoff = pageref->offset >> PAGE_SHIFT;
- mapping_wrprotect_range(fbdefio->mapping, pgoff,
+ mapping_wrprotect_range(fbdefio_state->mapping, pgoff,
page_to_pfn(page), 1);
}
#endif
/* driver's callback with pagereflist */
- fbdefio->deferred_io(info, &fbdefio->pagereflist);
+ fbdefio->deferred_io(info, &fbdefio_state->pagereflist);
/* clear the list */
- list_for_each_entry_safe(pageref, next, &fbdefio->pagereflist, list)
+ list_for_each_entry_safe(pageref, next, &fbdefio_state->pagereflist, list)
fb_deferred_io_pageref_put(pageref, info);
mutex_unlock(&fbdefio_state->lock);
@@ -402,7 +412,6 @@ int fb_deferred_io_init(struct fb_info *info)
fbdefio_state->info = info;
INIT_DELAYED_WORK(&info->deferred_work, fb_deferred_io_work);
- INIT_LIST_HEAD(&fbdefio->pagereflist);
if (fbdefio->delay == 0) /* set a default of 1 s */
fbdefio->delay = HZ;
@@ -431,11 +440,11 @@ void fb_deferred_io_open(struct fb_info *info,
struct inode *inode,
struct file *file)
{
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
- fbdefio->mapping = file->f_mapping;
+ fbdefio_state->mapping = file->f_mapping;
file->f_mapping->a_ops = &fb_deferred_io_aops;
- fbdefio->open_count++;
+ fbdefio_state->open_count++;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_open);
@@ -446,16 +455,15 @@ static void fb_deferred_io_lastclose(struct fb_info *info)
void fb_deferred_io_release(struct fb_info *info)
{
- struct fb_deferred_io *fbdefio = info->fbdefio;
+ struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
- if (!--fbdefio->open_count)
+ if (!--fbdefio_state->open_count)
fb_deferred_io_lastclose(info);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_release);
void fb_deferred_io_cleanup(struct fb_info *info)
{
- struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_state *fbdefio_state = info->fbdefio_state;
fb_deferred_io_lastclose(info);
@@ -469,6 +477,5 @@ void fb_deferred_io_cleanup(struct fb_info *info)
fb_deferred_io_state_put(fbdefio_state);
kvfree(info->pagerefs);
- fbdefio->mapping = NULL;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 0bf3f1a5cf1e..2a9d05e51ff4 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -216,9 +216,6 @@ struct fb_deferred_io {
/* delay between mkwrite and deferred handler */
unsigned long delay;
bool sort_pagereflist; /* sort pagelist by offset */
- int open_count; /* number of opened files; protected by fb_info lock */
- struct list_head pagereflist; /* list of pagerefs for touched pages */
- struct address_space *mapping; /* page cache object for fb device */
/* callback */
struct page *(*get_page)(struct fb_info *info, unsigned long offset);
void (*deferred_io)(struct fb_info *info, struct list_head *pagelist);
--
2.52.0
^ permalink raw reply related
* [PATCH 2/4] fbdev: defio: Keep module reference from VMAs
From: Thomas Zimmermann @ 2026-01-22 13:08 UTC (permalink / raw)
To: deller, simona, jayalk; +Cc: linux-fbdev, dri-devel, Thomas Zimmermann
In-Reply-To: <20260122131213.992810-1-tzimmermann@suse.de>
Acquire a module reference on each mmap and VMA open; hold it until
the kernel closes the VMA. Protects against unloading the module
while user space still has a mapping of the graphics memory. The
VMA page-fault handling would then call into undefined code.
This situation can happen if the underlying device has been unplugged
and the driver has been unloaded. It would then be possible to trigger
the bug by unloading the fbdev core module.
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
drivers/video/fbdev/core/fb_defio.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index 0b099a89a823..331b1a9fe485 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -14,6 +14,7 @@
#include <linux/export.h>
#include <linux/string.h>
#include <linux/mm.h>
+#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
@@ -83,6 +84,7 @@ static void fb_deferred_io_vm_open(struct vm_area_struct *vma)
{
struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
+ WARN_ON_ONCE(!try_module_get(THIS_MODULE));
fb_deferred_io_state_get(fbdefio_state);
}
@@ -91,6 +93,7 @@ static void fb_deferred_io_vm_close(struct vm_area_struct *vma)
struct fb_deferred_io_state *fbdefio_state = vma->vm_private_data;
fb_deferred_io_state_put(fbdefio_state);
+ module_put(THIS_MODULE);
}
static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long offs)
@@ -335,6 +338,9 @@ int fb_deferred_io_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot);
+ if (!try_module_get(THIS_MODULE))
+ return -EINVAL;
+
vma->vm_ops = &fb_deferred_io_vm_ops;
vm_flags_set(vma, VM_DONTEXPAND | VM_DONTDUMP);
if (!(info->flags & FBINFO_VIRTFB))
--
2.52.0
^ permalink raw reply related
* [PATCH 4/4] fbdev: defio: Move pageref array to struct fb_deferred_io_state
From: Thomas Zimmermann @ 2026-01-22 13:08 UTC (permalink / raw)
To: deller, simona, jayalk; +Cc: linux-fbdev, dri-devel, Thomas Zimmermann
In-Reply-To: <20260122131213.992810-1-tzimmermann@suse.de>
The pageref array stores all pageref structures for a device's defio
helpers. Move it into struct fb_deferred_io_state to not expose it to
drivers.
Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
---
drivers/video/fbdev/core/fb_defio.c | 55 ++++++++++++++---------------
include/linux/fb.h | 2 --
2 files changed, 27 insertions(+), 30 deletions(-)
diff --git a/drivers/video/fbdev/core/fb_defio.c b/drivers/video/fbdev/core/fb_defio.c
index c6945b4422cc..c4be85f80d7d 100644
--- a/drivers/video/fbdev/core/fb_defio.c
+++ b/drivers/video/fbdev/core/fb_defio.c
@@ -41,28 +41,46 @@ struct fb_deferred_io_state {
/* fields protected by lock */
struct fb_info *info;
struct list_head pagereflist; /* list of pagerefs for touched pages */
+ unsigned long npagerefs;
+ struct fb_deferred_io_pageref *pagerefs;
};
-static struct fb_deferred_io_state *fb_deferred_io_state_alloc(void)
+static struct fb_deferred_io_state *fb_deferred_io_state_alloc(unsigned long len)
{
struct fb_deferred_io_state *fbdefio_state;
+ struct fb_deferred_io_pageref *pagerefs;
+ unsigned long npagerefs;
fbdefio_state = kzalloc(sizeof(*fbdefio_state), GFP_KERNEL);
if (!fbdefio_state)
return NULL;
+ npagerefs = DIV_ROUND_UP(len, PAGE_SIZE);
+
+ /* alloc a page ref for each page of the display memory */
+ pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL);
+ if (!pagerefs)
+ goto err_kfree;
+ fbdefio_state->npagerefs = npagerefs;
+ fbdefio_state->pagerefs = pagerefs;
+
kref_init(&fbdefio_state->ref);
mutex_init(&fbdefio_state->lock);
INIT_LIST_HEAD(&fbdefio_state->pagereflist);
return fbdefio_state;
+
+err_kfree:
+ kfree(fbdefio_state);
+ return NULL;
}
static void fb_deferred_io_state_release(struct fb_deferred_io_state *fbdefio_state)
{
WARN_ON(!list_empty(&fbdefio_state->pagereflist));
mutex_destroy(&fbdefio_state->lock);
+ kvfree(fbdefio_state->pagerefs);
kfree(fbdefio_state);
}
@@ -125,18 +143,19 @@ static struct page *fb_deferred_io_get_page(struct fb_info *info, unsigned long
return page;
}
-static struct fb_deferred_io_pageref *fb_deferred_io_pageref_lookup(struct fb_info *info,
- unsigned long offset,
- struct page *page)
+static struct fb_deferred_io_pageref *
+fb_deferred_io_pageref_lookup(struct fb_deferred_io_state *fbdefio_state, unsigned long offset,
+ struct page *page)
{
+ struct fb_info *info = fbdefio_state->info;
unsigned long pgoff = offset >> PAGE_SHIFT;
struct fb_deferred_io_pageref *pageref;
- if (fb_WARN_ON_ONCE(info, pgoff >= info->npagerefs))
+ if (fb_WARN_ON_ONCE(info, pgoff >= fbdefio_state->npagerefs))
return NULL; /* incorrect allocation size */
/* 1:1 mapping between pageref and page offset */
- pageref = &info->pagerefs[pgoff];
+ pageref = &fbdefio_state->pagerefs[pgoff];
if (pageref->page)
goto out;
@@ -160,7 +179,7 @@ static struct fb_deferred_io_pageref *fb_deferred_io_pageref_get(struct fb_info
struct list_head *pos = &fbdefio_state->pagereflist;
struct fb_deferred_io_pageref *pageref, *cur;
- pageref = fb_deferred_io_pageref_lookup(info, offset, page);
+ pageref = fb_deferred_io_pageref_lookup(fbdefio_state, offset, page);
if (!pageref)
return NULL;
@@ -397,16 +416,13 @@ int fb_deferred_io_init(struct fb_info *info)
{
struct fb_deferred_io *fbdefio = info->fbdefio;
struct fb_deferred_io_state *fbdefio_state;
- struct fb_deferred_io_pageref *pagerefs;
- unsigned long npagerefs;
- int ret;
BUG_ON(!fbdefio);
if (WARN_ON(!info->fix.smem_len))
return -EINVAL;
- fbdefio_state = fb_deferred_io_state_alloc();
+ fbdefio_state = fb_deferred_io_state_alloc(info->fix.smem_len);
if (!fbdefio_state)
return -ENOMEM;
fbdefio_state->info = info;
@@ -415,24 +431,9 @@ int fb_deferred_io_init(struct fb_info *info)
if (fbdefio->delay == 0) /* set a default of 1 s */
fbdefio->delay = HZ;
- npagerefs = DIV_ROUND_UP(info->fix.smem_len, PAGE_SIZE);
-
- /* alloc a page ref for each page of the display memory */
- pagerefs = kvcalloc(npagerefs, sizeof(*pagerefs), GFP_KERNEL);
- if (!pagerefs) {
- ret = -ENOMEM;
- goto err;
- }
- info->npagerefs = npagerefs;
- info->pagerefs = pagerefs;
-
info->fbdefio_state = fbdefio_state;
return 0;
-
-err:
- fb_deferred_io_state_release(fbdefio_state);
- return ret;
}
EXPORT_SYMBOL_GPL(fb_deferred_io_init);
@@ -475,7 +476,5 @@ void fb_deferred_io_cleanup(struct fb_info *info)
mutex_unlock(&fbdefio_state->lock);
fb_deferred_io_state_put(fbdefio_state);
-
- kvfree(info->pagerefs);
}
EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup);
diff --git a/include/linux/fb.h b/include/linux/fb.h
index 2a9d05e51ff4..71e2759f3cfd 100644
--- a/include/linux/fb.h
+++ b/include/linux/fb.h
@@ -481,8 +481,6 @@ struct fb_info {
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
- unsigned long npagerefs;
- struct fb_deferred_io_pageref *pagerefs;
struct fb_deferred_io *fbdefio;
struct fb_deferred_io_state *fbdefio_state;
#endif
--
2.52.0
^ permalink raw reply related
* Re: [PATCH RFC v6 05/26] nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Joel Fernandes @ 2026-01-22 23:16 UTC (permalink / raw)
To: Zhi Wang
Cc: linux-kernel, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Jonathan Corbet, Alex Deucher,
Christian Koenig, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
Tvrtko Ursulin, Huang Rui, Matthew Auld, Matthew Brost,
Lucas De Marchi, Thomas Hellstrom, Helge Deller, Danilo Krummrich,
Alice Ryhl, Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Bjorn Roy Baron, Benno Lossin, Andreas Hindborg, Trevor Gross,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Alexandre Courbot, Andrea Righi, Andy Ritger, Alexey Ivanov,
Balbir Singh, Philipp Stanner, Elle Rhumsaa, Daniel Almeida,
nouveau, dri-devel, rust-for-linux, linux-doc, amd-gfx, intel-gfx,
intel-xe, linux-fbdev
In-Reply-To: <e186973c-ce31-405a-8bfa-dc647737a666@nvidia.com>
On Wed, 21 Jan 2026 12:52:10 -0500, Joel Fernandes wrote:
> I think we can incrementally build on this series to add support for the same,
> it is not something this series directly addresses since I have spend majority
> of my time last several months making translation *work* which is itself no east
> task. This series is just preliminary based on work from last several months and
> to make BAR1 work. For instance, I kept PRAMIN simple based on feedback that we
> don't want to over complicate without fully understanding all the requirements.
> There is also additional requirements for locking design that have implications
> with DMA fencing etc, for instance.
>
> Anyway thinking out loud, I am thinking for handling concurrency at the page
> table entry level (if we ever need it), we could use per-PT spinlocks similar to
> the Linux kernel. But lets plan on how to do this properly and based on actual
> requirements.
Thanks for the discussion on concurrency, Zhi.
My plan is to make TLB and PRAMIN use immutable references in their function
calls and then implement internal locking. I've already done this for the GPU
buddy functions, so it should be doable, and we'll keep it consistent. As a
result, we will have finer-grain locking on the memory management objects
instead of requiring to globally lock a common GpuMm object. I'll plan on
doing this for v7.
Also, the PTE allocation race you mentioned is already handled by PRAMIN
serialization. Since threads must hold the PRAMIN lock to write page table
entries, concurrent writers are not possible:
Thread A: acquire PRAMIN lock
Thread A: read PDE (via PRAMIN) -> NULL
Thread A: alloc PT page, write PDE
Thread A: release PRAMIN lock
Thread B: acquire PRAMIN lock
Thread B: read PDE (via PRAMIN) -> sees A's pointer
Thread B: uses existing PT page, no allocation needed
No atomic compare-and-swap on VRAM is needed because the PRAMIN lock serializes
access. Please let me know if you had a different scenario in mind, but I think
this covers it.
Zhi, feel free to use v6 though for any testing you are doing while I
rework the locking.
--
Joel Fernandes
^ permalink raw reply
* Re: [PATCH RFC v6 05/26] nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Zhi Wang @ 2026-01-23 10:13 UTC (permalink / raw)
To: Joel Fernandes
Cc: linux-kernel, Maarten Lankhorst, Maxime Ripard, Simona Vetter,
Jonathan Corbet, Alex Deucher, Christian Koenig, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Huang Rui,
Matthew Auld, Matthew Brost, Lucas De Marchi, Thomas Hellstrom,
Helge Deller, Danilo Krummrich, Alice Ryhl, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Alistair Popple,
Alexandre Courbot, Andrea Righi, Alexey Ivanov, Philipp Stanner,
Elle Rhumsaa, Daniel Almeida, nouveau, dri-devel, rust-for-linux,
linux-doc, amd-gfx, intel-gfx, intel-xe, linux-fbdev
In-Reply-To: <DS0PR12MB6486717785F6DD14EE1F1C46A397A@DS0PR12MB6486.namprd12.prod.outlook.com>
On Thu, 22 Jan 2026 18:16:00 -0500
Joel Fernandes <joelagnelf@nvidia.com> wrote:
> On Wed, 21 Jan 2026 12:52:10 -0500, Joel Fernandes wrote:
> > I think we can incrementally build on this series to add support for
> > the same, it is not something this series directly addresses since I
> > have spend majority of my time last several months making translation
> > *work* which is itself no east task. This series is just preliminary
> > based on work from last several months and to make BAR1 work. For
> > instance, I kept PRAMIN simple based on feedback that we don't want to
> > over complicate without fully understanding all the requirements.
> > There is also additional requirements for locking design that have
> > implications with DMA fencing etc, for instance.
> >
> > Anyway thinking out loud, I am thinking for handling concurrency at
> > the page table entry level (if we ever need it), we could use per-PT
> > spinlocks similar to the Linux kernel. But lets plan on how to do this
> > properly and based on actual requirements.
>
> Thanks for the discussion on concurrency, Zhi.
>
> My plan is to make TLB and PRAMIN use immutable references in their
> function calls and then implement internal locking. I've already done
> this for the GPU buddy functions, so it should be doable, and we'll keep
> it consistent. As a result, we will have finer-grain locking on the
> memory management objects instead of requiring to globally lock a common
> GpuMm object. I'll plan on doing this for v7.
>
> Also, the PTE allocation race you mentioned is already handled by PRAMIN
> serialization. Since threads must hold the PRAMIN lock to write page
> table entries, concurrent writers are not possible:
>
> Thread A: acquire PRAMIN lock
> Thread A: read PDE (via PRAMIN) -> NULL
> Thread A: alloc PT page, write PDE
> Thread A: release PRAMIN lock
>
> Thread B: acquire PRAMIN lock
> Thread B: read PDE (via PRAMIN) -> sees A's pointer
> Thread B: uses existing PT page, no allocation needed
>
> No atomic compare-and-swap on VRAM is needed because the PRAMIN lock
> serializes access. Please let me know if you had a different scenario in
> mind, but I think this covers it.
>
> Zhi, feel free to use v6 though for any testing you are doing while I
> rework the locking.
>
Hi Joel:
Thanks so much for the work and the discussion. It is super important
efforts for me to move on for the vGPU work. :)
As we discussed, the concurrency matters most when booting multiple vGPUs.
At that time, the concurrency happens at:
1) Allocating GPU memory chunks
2) Reserving GPU channels
3) Mapping GPU memory to BAR1 page table
We basically need kinda protection there. E.g. Guard/Access on immutable
references, which is backed by the mutex. I believe there shouldn't be a
non-sleepible path reaching those. This should be fine.
I can see you are thinking of fine-granularity locking scheme, which I
think is the right direction to go. I agreed with the above two locks.
For 1), I can recall that you mentioned there is some lock protection
already there.
For 2), We can think of it when reaching there.
However for 3), We need to have one there as well beside the above two
locks. Have you already had one in the GPU VA allocator?
If yes, the above two locks should be good enough so far. IMO.
Z.
^ permalink raw reply
* Re: [PATCH v2 1/2] dt-bindings: backlight: gpio-backlight: allow multiple GPIOs
From: tessolveupstream @ 2026-01-23 11:11 UTC (permalink / raw)
To: Krzysztof Kozlowski, lee, danielt, jingoohan1
Cc: deller, pavel, robh, krzk+dt, conor+dt, dri-devel, linux-fbdev,
linux-leds, devicetree, linux-kernel
In-Reply-To: <3f3c47ea-1660-4bd4-ab89-3bdf58217995@kernel.org>
On 20-01-2026 20:01, Krzysztof Kozlowski wrote:
> On 20/01/2026 13:50, Sudarshan Shetty wrote:
>> Update the gpio-backlight binding to support configurations that require
>> more than one GPIO for enabling/disabling the backlight.
>
>
> Why? Which devices need it? How a backlight would have three enable
> GPIOs? I really do not believe, so you need to write proper hardware
> justification.
>
To clarify our hardware setup:
the panel requires one GPIO for the backlight enable signal, and it
also has a PWM input. Since the QCS615 does not provide a PWM controller
for this use case, the PWM input is connected to a GPIO that is driven
high to provide a constant 100% duty cycle, as explained in the link
below.
https://lore.kernel.org/all/20251028061636.724667-1-tessolveupstream@gmail.com/T/#m93ca4e5c7bf055715ed13316d91f0cd544244cf5
>>
>> Signed-off-by: Sudarshan Shetty <tessolveupstream@gmail.com>
>> ---
>> .../leds/backlight/gpio-backlight.yaml | 24 +++++++++++++++++--
>> 1 file changed, 22 insertions(+), 2 deletions(-)
>>
>> diff --git a/Documentation/devicetree/bindings/leds/backlight/gpio-backlight.yaml b/Documentation/devicetree/bindings/leds/backlight/gpio-backlight.yaml
>> index 584030b6b0b9..4e4a856cbcd7 100644
>> --- a/Documentation/devicetree/bindings/leds/backlight/gpio-backlight.yaml
>> +++ b/Documentation/devicetree/bindings/leds/backlight/gpio-backlight.yaml
>> @@ -16,8 +16,18 @@ properties:
>> const: gpio-backlight
>>
>> gpios:
>> - description: The gpio that is used for enabling/disabling the backlight.
>> - maxItems: 1
>> + description: |
>> + The gpio that is used for enabling/disabling the backlight.
>> + Multiple GPIOs can be specified for panels that require several
>> + enable signals. All GPIOs are controlled together.
>> + type: array
>
> There is no such syntax in the bindings, from where did you get it? Type
> is already defined.
>
> items:
> minItems: 1
> maxItems: 3
>
>
>> + minItems: 1
>> + items:
>> + type: array
>> + minItems: 3
>> + maxItems: 3
>> + items:
>> + type: integer
>
> All this is some odd stuff - just to be clear, don't send us LLM output.
> I don't want to waste my time to review microslop.
>
> Was it done with help of Microslop?
>
I understand now that the schema changes I proposed were not correct,
and I will address this in the next patch series. My intention was to
check whether the gpio-backlight binding could support more than one
enable-type GPIO.
Could you please advise what would be an appropriate maximum number of
GPIOs for gpio-backlight in such a scenario? For example, would allowing
2 GPIOs be acceptable, or should this case be handled in a different way?
> Best regards,
> Krzysztof
^ permalink raw reply
* [PATCH v7 2/4] backlight: add max25014atg backlight
From: Maud Spierings via B4 Relay @ 2026-01-23 11:31 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Liam Girdwood, Mark Brown
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev, imx,
linux-arm-kernel, Maud Spierings
In-Reply-To: <20260123-max25014-v7-0-15e504b9acc7@gocontroll.com>
From: Maud Spierings <maudspierings@gocontroll.com>
The Maxim MAX25014 is a 4-channel automotive grade backlight driver IC
with integrated boost controller.
Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
---
MAINTAINERS | 1 +
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/max25014.c | 377 +++++++++++++++++++++++++++++++++++++
4 files changed, 386 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index eb248f4634ac..346a8ede3f71 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15519,6 +15519,7 @@ MAX25014 BACKLIGHT DRIVER
M: Maud Spierings <maudspierings@gocontroll.com>
S: Maintained
F: Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
+F: drivers/video/backlight/max25014.c
MAX31335 RTC DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index a7a3fbaf7c29..6cd3a1673511 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -282,6 +282,13 @@ config BACKLIGHT_DA9052
help
Enable the Backlight Driver for DA9052-BC and DA9053-AA/Bx PMICs.
+config BACKLIGHT_MAX25014
+ tristate "Backlight driver for the Maxim MAX25014 chip"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ If you are using a MAX25014 chip as a backlight driver say Y to enable it.
+
config BACKLIGHT_MAX8925
tristate "Backlight driver for MAX8925"
depends on MFD_MAX8925
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index 794820a98ed4..21c8313cfb12 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o
obj-$(CONFIG_BACKLIGHT_LP855X) += lp855x_bl.o
obj-$(CONFIG_BACKLIGHT_LP8788) += lp8788_bl.o
obj-$(CONFIG_BACKLIGHT_LV5207LP) += lv5207lp.o
+obj-$(CONFIG_BACKLIGHT_MAX25014) += max25014.o
obj-$(CONFIG_BACKLIGHT_MAX8925) += max8925_bl.o
obj-$(CONFIG_BACKLIGHT_MP3309C) += mp3309c.o
obj-$(CONFIG_BACKLIGHT_MT6370) += mt6370-backlight.o
diff --git a/drivers/video/backlight/max25014.c b/drivers/video/backlight/max25014.c
new file mode 100644
index 000000000000..3ee45617261f
--- /dev/null
+++ b/drivers/video/backlight/max25014.c
@@ -0,0 +1,377 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Backlight driver for Maxim MAX25014
+ *
+ * Copyright (C) 2025 GOcontroll B.V.
+ * Author: Maud Spierings <maudspierings@gocontroll.com>
+ */
+
+#include <linux/backlight.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define MAX25014_ISET_DEFAULT_100 11
+#define MAX_BRIGHTNESS 100
+#define MIN_BRIGHTNESS 0
+#define TON_MAX 130720 /* @153Hz */
+#define TON_STEP 1307 /* @153Hz */
+#define TON_MIN 0
+
+#define MAX25014_DEV_ID 0x00
+#define MAX25014_REV_ID 0x01
+#define MAX25014_ISET 0x02
+#define MAX25014_IMODE 0x03
+#define MAX25014_TON1H 0x04
+#define MAX25014_TON1L 0x05
+#define MAX25014_TON2H 0x06
+#define MAX25014_TON2L 0x07
+#define MAX25014_TON3H 0x08
+#define MAX25014_TON3L 0x09
+#define MAX25014_TON4H 0x0A
+#define MAX25014_TON4L 0x0B
+#define MAX25014_TON_1_4_LSB 0x0C
+#define MAX25014_SETTING 0x12
+#define MAX25014_DISABLE 0x13
+#define MAX25014_BSTMON 0x14
+#define MAX25014_IOUT1 0x15
+#define MAX25014_IOUT2 0x16
+#define MAX25014_IOUT3 0x17
+#define MAX25014_IOUT4 0x18
+#define MAX25014_OPEN 0x1B
+#define MAX25014_SHORTGND 0x1C
+#define MAX25014_SHORTED_LED 0x1D
+#define MAX25014_MASK 0x1E
+#define MAX25014_DIAG 0x1F
+
+#define MAX25014_ISET_ENA BIT(5)
+#define MAX25014_ISET_PSEN BIT(4)
+#define MAX25014_IMODE_HDIM BIT(2)
+#define MAX25014_SETTING_FPWM GENMASK(6, 4)
+#define MAX25014_DISABLE_DIS_MASK GENMASK(3, 0)
+#define MAX25014_DIAG_OT BIT(0)
+#define MAX25014_DIAG_OTW BIT(1)
+#define MAX25014_DIAG_HW_RST BIT(2)
+#define MAX25014_DIAG_BSTOV BIT(3)
+#define MAX25014_DIAG_BSTUV BIT(4)
+#define MAX25014_DIAG_IREFOOR BIT(5)
+
+struct max25014 {
+ struct i2c_client *client;
+ struct backlight_device *bl;
+ struct regmap *regmap;
+ struct gpio_desc *enable;
+ uint32_t iset;
+ uint8_t strings_mask;
+};
+
+static const struct regmap_config max25014_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX25014_DIAG,
+};
+
+static int max25014_initial_power_state(struct max25014 *maxim)
+{
+ uint32_t val;
+ int ret;
+
+ ret = regmap_read(maxim->regmap, MAX25014_ISET, &val);
+ if (ret)
+ return ret;
+
+ return val & MAX25014_ISET_ENA ? BACKLIGHT_POWER_ON : BACKLIGHT_POWER_OFF;
+}
+
+static int max25014_check_errors(struct max25014 *maxim)
+{
+ uint32_t val;
+ uint8_t i;
+ int ret;
+
+ ret = regmap_read(maxim->regmap, MAX25014_OPEN, &val);
+ if (ret)
+ return ret;
+ if (val) {
+ dev_err(&maxim->client->dev, "Open led strings detected on:\n");
+ for (i = 0; i < 4; i++) {
+ if (val & 1 << i)
+ dev_err(&maxim->client->dev, "string %d\n", i + 1);
+ }
+ return -EIO;
+ }
+
+ ret = regmap_read(maxim->regmap, MAX25014_SHORTGND, &val);
+ if (ret)
+ return ret;
+ if (val) {
+ dev_err(&maxim->client->dev, "Short to ground detected on:\n");
+ for (i = 0; i < 4; i++) {
+ if (val & 1 << i)
+ dev_err(&maxim->client->dev, "string %d\n", i + 1);
+ }
+ return -EIO;
+ }
+
+ ret = regmap_read(maxim->regmap, MAX25014_SHORTED_LED, &val);
+ if (ret)
+ return ret;
+ if (val) {
+ dev_err(&maxim->client->dev, "Shorted led detected on:\n");
+ for (i = 0; i < 4; i++) {
+ if (val & 1 << i)
+ dev_err(&maxim->client->dev, "string %d\n", i + 1);
+ }
+ return -EIO;
+ }
+
+ ret = regmap_read(maxim->regmap, MAX25014_DIAG, &val);
+ if (ret)
+ return ret;
+ /*
+ * The HW_RST bit always starts at 1 after power up.
+ * It is reset on first read, does not indicate an error.
+ */
+ if (val && val != MAX25014_DIAG_HW_RST) {
+ if (val & MAX25014_DIAG_OT)
+ dev_err(&maxim->client->dev,
+ "Overtemperature shutdown\n");
+ if (val & MAX25014_DIAG_OTW)
+ dev_err(&maxim->client->dev,
+ "Chip is getting too hot (>125C)\n");
+ if (val & MAX25014_DIAG_BSTOV)
+ dev_err(&maxim->client->dev,
+ "Boost converter overvoltage\n");
+ if (val & MAX25014_DIAG_BSTUV)
+ dev_err(&maxim->client->dev,
+ "Boost converter undervoltage\n");
+ if (val & MAX25014_DIAG_IREFOOR)
+ dev_err(&maxim->client->dev, "IREF out of range\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+/*
+ * 1. disable unused strings
+ * 2. set dim mode
+ * 3. set setting register
+ * 4. enable the backlight
+ */
+static int max25014_configure(struct max25014 *maxim, int initial_state)
+{
+ uint32_t val;
+ int ret;
+
+ ret = regmap_read(maxim->regmap, MAX25014_DISABLE, &val);
+ if (ret)
+ return ret;
+
+ /*
+ * Strings can only be disabled when MAX25014_ISET_ENA == 0, check if
+ * it needs to be changed at all to prevent the backlight flashing when
+ * it is configured correctly by the bootloader
+ */
+ if (!((val & MAX25014_DISABLE_DIS_MASK) == maxim->strings_mask)) {
+ if (initial_state == BACKLIGHT_POWER_ON) {
+ ret = regmap_write(maxim->regmap, MAX25014_ISET, 0);
+ if (ret)
+ return ret;
+ }
+ ret = regmap_write(maxim->regmap, MAX25014_DISABLE, maxim->strings_mask);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(maxim->regmap, MAX25014_IMODE, MAX25014_IMODE_HDIM);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(maxim->regmap, MAX25014_SETTING, &val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(maxim->regmap, MAX25014_SETTING,
+ val & ~MAX25014_SETTING_FPWM);
+ if (ret)
+ return ret;
+
+ return regmap_write(maxim->regmap, MAX25014_ISET,
+ maxim->iset | MAX25014_ISET_ENA |
+ MAX25014_ISET_PSEN);
+}
+
+static int max25014_update_status(struct backlight_device *bl_dev)
+{
+ struct max25014 *maxim = bl_get_data(bl_dev);
+ uint32_t reg;
+ int ret;
+
+ reg = TON_STEP * backlight_get_brightness(bl_dev);
+
+ /*
+ * 18 bit number lowest, 2 bits in first register,
+ * next lowest 8 in the L register, next 8 in the H register
+ * Seemingly setting the strength of only one string controls all of
+ * them, individual settings don't affect the outcome.
+ */
+ ret = regmap_write(maxim->regmap, MAX25014_TON_1_4_LSB, reg & 0b00000011);
+ if (ret != 0)
+ return ret;
+ ret = regmap_write(maxim->regmap, MAX25014_TON1L, (reg >> 2) & 0b11111111);
+ if (ret != 0)
+ return ret;
+ return regmap_write(maxim->regmap, MAX25014_TON1H, (reg >> 10) & 0b11111111);
+}
+
+static const struct backlight_ops max25014_bl_ops = {
+ .options = BL_CORE_SUSPENDRESUME,
+ .update_status = max25014_update_status,
+};
+
+static int max25014_parse_dt(struct max25014 *maxim,
+ uint32_t *initial_brightness)
+{
+ struct device *dev = &maxim->client->dev;
+ struct device_node *node = dev->of_node;
+ uint32_t strings[4];
+ int res, i;
+
+ res = of_property_count_u32_elems(node, "maxim,strings");
+ if (res == 4) {
+ of_property_read_u32_array(node, "maxim,strings", strings, 4);
+ for (i = 0; i < 4; i++) {
+ if (strings[i] == 0)
+ maxim->strings_mask |= 1 << i;
+ }
+ } else {
+ maxim->strings_mask = 0;
+ }
+
+ *initial_brightness = 50U;
+ of_property_read_u32(node, "default-brightness", initial_brightness);
+
+ maxim->iset = MAX25014_ISET_DEFAULT_100;
+ of_property_read_u32(node, "maxim,iset", &maxim->iset);
+
+ if (maxim->iset > 15)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid iset, should be a value from 0-15, entered was %d\n",
+ maxim->iset);
+
+ if (*initial_brightness > 100)
+ return dev_err_probe(dev, -EINVAL,
+ "Invalid initial brightness, should be a value from 0-100, entered was %d\n",
+ *initial_brightness);
+
+ return 0;
+}
+
+static int max25014_probe(struct i2c_client *cl)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(cl);
+ struct backlight_properties props;
+ uint32_t initial_brightness = 50;
+ struct backlight_device *bl;
+ struct max25014 *maxim;
+ int ret;
+
+ maxim = devm_kzalloc(&cl->dev, sizeof(struct max25014), GFP_KERNEL);
+ if (!maxim)
+ return -ENOMEM;
+
+ maxim->client = cl;
+
+ ret = max25014_parse_dt(maxim, &initial_brightness);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_get_enable(&maxim->client->dev, "power");
+ if (ret)
+ return dev_err_probe(&maxim->client->dev, ret,
+ "failed to get power-supply");
+
+ maxim->enable = devm_gpiod_get_optional(&maxim->client->dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(maxim->enable))
+ return dev_err_probe(&maxim->client->dev, PTR_ERR(maxim->enable),
+ "failed to get enable gpio\n");
+
+ /* Datasheet Electrical Characteristics tSTARTUP 2ms */
+ fsleep(2000);
+
+ maxim->regmap = devm_regmap_init_i2c(cl, &max25014_regmap_config);
+ if (IS_ERR(maxim->regmap))
+ return dev_err_probe(&maxim->client->dev, PTR_ERR(maxim->regmap),
+ "failed to initialize the i2c regmap\n");
+
+ i2c_set_clientdata(cl, maxim);
+
+ ret = max25014_check_errors(maxim);
+ if (ret) /* error is already reported in the above function */
+ return ret;
+
+ ret = max25014_initial_power_state(maxim);
+ if (ret < 0)
+ return dev_err_probe(&maxim->client->dev, ret, "Could not get enabled state\n");
+
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_PLATFORM;
+ props.max_brightness = MAX_BRIGHTNESS;
+ props.brightness = initial_brightness;
+ props.scale = BACKLIGHT_SCALE_LINEAR;
+ props.power = ret;
+
+ ret = max25014_configure(maxim, ret);
+ if (ret)
+ return dev_err_probe(&maxim->client->dev, ret, "device config error");
+
+ bl = devm_backlight_device_register(&maxim->client->dev, id->name,
+ &maxim->client->dev, maxim,
+ &max25014_bl_ops, &props);
+ if (IS_ERR(bl))
+ return dev_err_probe(&maxim->client->dev, PTR_ERR(bl),
+ "failed to register backlight\n");
+
+ maxim->bl = bl;
+
+ backlight_update_status(maxim->bl);
+
+ return 0;
+}
+
+static void max25014_remove(struct i2c_client *cl)
+{
+ struct max25014 *maxim = i2c_get_clientdata(cl);
+
+ backlight_device_set_brightness(maxim->bl, 0);
+ gpiod_set_value_cansleep(maxim->enable, 0);
+}
+
+static const struct of_device_id max25014_dt_ids[] = {
+ { .compatible = "maxim,max25014", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max25014_dt_ids);
+
+static const struct i2c_device_id max25014_ids[] = {
+ { "max25014" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max25014_ids);
+
+static struct i2c_driver max25014_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = of_match_ptr(max25014_dt_ids),
+ },
+ .probe = max25014_probe,
+ .remove = max25014_remove,
+ .id_table = max25014_ids,
+};
+module_i2c_driver(max25014_driver);
+
+MODULE_DESCRIPTION("Maxim MAX25014 backlight driver");
+MODULE_AUTHOR("Maud Spierings <maudspierings@gocontroll.com>");
+MODULE_LICENSE("GPL");
--
2.52.0
^ permalink raw reply related
* [PATCH v7 0/4] backlight: add new max25014 backlight driver
From: Maud Spierings via B4 Relay @ 2026-01-23 11:31 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Liam Girdwood, Mark Brown
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev, imx,
linux-arm-kernel, Maud Spierings
The Maxim MAX25014 is an automotive grade backlight driver IC. Its
datasheet can be found at [1].
With its integrated boost controller, it can power 4 channels (led
strings) and has a number of different modes using pwm and or i2c.
Currently implemented is only i2c control.
link: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX25014.pdf [1]
Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
---
Changes in v7:
- remove the led subnodes
- always enable the regulator by using devm_regulator_get_enable()
- remove the no longer required gotos and simplify early returns
- fix the name of the SHORTED_LED error field
- fix the name of the SHORTGND error field
- use the proper backlight helper functions for setting/getting
brightness
- Link to v6: https://lore.kernel.org/r/20251201-max25014-v6-0-88e3ac8112ff@gocontroll.com
Changes in v6:
- fixup changes in v4 where default brightness handling was changed but
not noted
- remove leftover comment about initializing brightness
- use BIT definitions for fields in the DIAG register
- apply reverse christmas tree initialization of local variables
- remove !=0 from checks, just check if (ret)
- remove > 0 from checks, just check if (val)
- use dev_err_probe() more
- set enable gpio high in the get() instead of seperately calling set()
- change usleep_range() to fsleep()
- remove null checks when setting gpio value
- get regular regulator, not optional to avoid further NULL checks in
case none is provided
- introduce max25014_initial_power_state() to check if the bootloader
has already initialized the backlight and to correctly set props.power
- squash max25014_register_control() into max25014_update_status()
- in max25014_configure() perform extra checking on the DISABLE register
now that the state from the bootloader is taken into account
- Link to v5: https://lore.kernel.org/r/20251107-max25014-v5-0-9a6aa57306bf@gocontroll.com
Changes in v5:
- moved comment about current functions of the driver to the actual
comment section of the commit
- fixed the led@0 property, regex patternProperty is not needed as of
now
- added extra clarification about the ISET field/register
- moved #address-cells and #size-cells to the correct location
- remove leftover default-brightness in backlight nodes
- Link to v4: https://lore.kernel.org/r/20251009-max25014-v4-0-6adb2a0aa35f@gocontroll.com
Changes in v4:
- remove setting default brightness, let backlight core take care of it
- use a led node to describe the backlight
- use led-sources to enable specific channels
- also wait 2ms when there is a supply but no enable
- change dev_warn() to dev_err() in error path in max25014_check_errors()
- set backlight_properties.scale to BACKLIGHT_SCALE_LINEAR
- rebase latest next
- add address-cells and size-cells to i2c4 in av101hdt-a10.dtso
- Link to v3: https://lore.kernel.org/r/20250911-max25014-v3-0-d03f4eba375e@gocontroll.com
Changes in v3:
- fixed commit message type intgrated -> integrated
- added maximum and description to maxim,iset-property
- dropped unused labels and pinctrl in bindings example
- put the compatible first in the bindings example and dts
- removed brackets around defines
- removed the leftover pdata struct field
- removed the initial_brightness struct field
- Link to v2: https://lore.kernel.org/r/20250819-max25014-v2-0-5fd7aeb141ea@gocontroll.com
Changes in v2:
- Remove leftover unused property from the bindings example
- Complete the bindings example with all properties
- Remove some double info from the maxim,iset property
- Remove platform_data header, fold its data into the max25014 struct
- Don't force defines to be unsigned
- Remove stray struct max25014 declaration
- Remove chipname and device from the max25014 struct
- Inline the max25014_backlight_register() and strings_mask() functions
- Remove CONFIG_OF ifdef
- Link to v1: https://lore.kernel.org/r/20250725-max25014-v1-0-0e8cce92078e@gocontroll.com
---
Maud Spierings (4):
dt-bindings: backlight: Add max25014 support
backlight: add max25014atg backlight
arm64: dts: freescale: moduline-display-av101hdt-a10: add backlight
arm64: dts: freescale: moduline-display-av123z7m-n17: add backlight
.../bindings/leds/backlight/maxim,max25014.yaml | 91 +++++
MAINTAINERS | 6 +
...x8p-ml81-moduline-display-106-av101hdt-a10.dtso | 26 ++
...x8p-ml81-moduline-display-106-av123z7m-n17.dtso | 21 +-
drivers/video/backlight/Kconfig | 7 +
drivers/video/backlight/Makefile | 1 +
drivers/video/backlight/max25014.c | 377 +++++++++++++++++++++
7 files changed, 528 insertions(+), 1 deletion(-)
---
base-commit: a0c666c25aeefd16f4b088c6549a6fb6b65a8a1d
change-id: 20250626-max25014-4207591e1af5
Best regards,
--
Maud Spierings <maudspierings@gocontroll.com>
^ permalink raw reply
* [PATCH v7 1/4] dt-bindings: backlight: Add max25014 support
From: Maud Spierings via B4 Relay @ 2026-01-23 11:31 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Liam Girdwood, Mark Brown
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev, imx,
linux-arm-kernel, Maud Spierings
In-Reply-To: <20260123-max25014-v7-0-15e504b9acc7@gocontroll.com>
From: Maud Spierings <maudspierings@gocontroll.com>
The Maxim MAX25014 is a 4-channel automotive grade backlight driver IC
with integrated boost controller.
Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
---
In the current implementation the control registers for channel 1,
control all channels. So only one led subnode with led-sources is
supported right now. If at some point the driver functionality is
expanded the bindings can be easily extended with it.
---
.../bindings/leds/backlight/maxim,max25014.yaml | 91 ++++++++++++++++++++++
MAINTAINERS | 5 ++
2 files changed, 96 insertions(+)
diff --git a/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml b/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
new file mode 100644
index 000000000000..c499e6224a8f
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/leds/backlight/maxim,max25014.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Maxim max25014 backlight controller
+
+maintainers:
+ - Maud Spierings <maudspierings@gocontroll.com>
+
+properties:
+ compatible:
+ enum:
+ - maxim,max25014
+
+ reg:
+ maxItems: 1
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ default-brightness:
+ minimum: 0
+ maximum: 100
+ default: 50
+
+ enable-gpios:
+ maxItems: 1
+
+ interrupts:
+ maxItems: 1
+
+ power-supply:
+ description: Regulator which controls the boost converter input rail.
+
+ pwms:
+ maxItems: 1
+
+ maxim,iset:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ maximum: 15
+ default: 11
+ description:
+ Value of the ISET field in the ISET register. This controls the current
+ scale of the outputs, a higher number means more current.
+
+ maxim,strings:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description:
+ A 4-bit bitfield that describes which led strings to turn on.
+ minItems: 4
+ maxItems: 4
+ items:
+ maximum: 1
+ default:
+ [1 1 1 1]
+
+required:
+ - compatible
+ - reg
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ backlight@6f {
+ compatible = "maxim,max25014";
+ reg = <0x6f>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ default-brightness = <50>;
+ enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
+ interrupt-parent = <&gpio1>;
+ interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
+ power-supply = <®_backlight>;
+ pwms = <&pwm1>;
+ maxim,iset = <7>;
+ maxim,strings = <1 1 1 0>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 9b1b87d08fac..eb248f4634ac 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15515,6 +15515,11 @@ F: Documentation/userspace-api/media/drivers/max2175.rst
F: drivers/media/i2c/max2175*
F: include/uapi/linux/max2175.h
+MAX25014 BACKLIGHT DRIVER
+M: Maud Spierings <maudspierings@gocontroll.com>
+S: Maintained
+F: Documentation/devicetree/bindings/leds/backlight/maxim,max25014.yaml
+
MAX31335 RTC DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-rtc@vger.kernel.org
--
2.52.0
^ permalink raw reply related
* [PATCH v7 3/4] arm64: dts: freescale: moduline-display-av101hdt-a10: add backlight
From: Maud Spierings via B4 Relay @ 2026-01-23 11:31 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Liam Girdwood, Mark Brown
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev, imx,
linux-arm-kernel, Maud Spierings
In-Reply-To: <20260123-max25014-v7-0-15e504b9acc7@gocontroll.com>
From: Maud Spierings <maudspierings@gocontroll.com>
Add the missing backlight driver.
Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
---
...x8p-ml81-moduline-display-106-av101hdt-a10.dtso | 26 ++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av101hdt-a10.dtso b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av101hdt-a10.dtso
index e3965caca6be..e121c58b730b 100644
--- a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av101hdt-a10.dtso
+++ b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av101hdt-a10.dtso
@@ -17,6 +17,7 @@
panel {
compatible = "boe,av101hdt-a10";
+ backlight = <&backlight>;
enable-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
pinctrl-0 = <&pinctrl_panel>;
pinctrl-names = "default";
@@ -40,7 +41,32 @@ reg_vbus: regulator-vbus {
};
};
+&i2c4 {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ backlight: backlight@6f {
+ compatible = "maxim,max25014";
+ reg = <0x6f>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ default-brightness = <50>;
+ enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_backlight>;
+ maxim,iset = <7>;
+ maxim,strings = <1 1 1 0>;
+ };
+};
+
&iomuxc {
+ pinctrl_backlight: backlightgrp {
+ fsl,pins = <
+ MX8MP_IOMUXC_GPIO1_IO04__GPIO1_IO04
+ (MX8MP_PULL_UP | MX8MP_PULL_ENABLE)
+ >;
+ };
+
pinctrl_panel: panelgrp {
fsl,pins = <
MX8MP_IOMUXC_GPIO1_IO07__GPIO1_IO07
--
2.52.0
^ permalink raw reply related
* [PATCH v7 4/4] arm64: dts: freescale: moduline-display-av123z7m-n17: add backlight
From: Maud Spierings via B4 Relay @ 2026-01-23 11:31 UTC (permalink / raw)
To: Lee Jones, Daniel Thompson, Jingoo Han, Pavel Machek, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Helge Deller, Shawn Guo,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
Liam Girdwood, Mark Brown
Cc: dri-devel, linux-leds, devicetree, linux-kernel, linux-fbdev, imx,
linux-arm-kernel, Maud Spierings
In-Reply-To: <20260123-max25014-v7-0-15e504b9acc7@gocontroll.com>
From: Maud Spierings <maudspierings@gocontroll.com>
Add the missing backlight.
Signed-off-by: Maud Spierings <maudspierings@gocontroll.com>
---
...tx8p-ml81-moduline-display-106-av123z7m-n17.dtso | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av123z7m-n17.dtso b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av123z7m-n17.dtso
index 3eb665ce9d5d..66d98a18d898 100644
--- a/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av123z7m-n17.dtso
+++ b/arch/arm64/boot/dts/freescale/imx8mp-tx8p-ml81-moduline-display-106-av123z7m-n17.dtso
@@ -16,6 +16,7 @@
panel {
compatible = "boe,av123z7m-n17";
+ backlight = <&backlight>;
enable-gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
pinctrl-0 = <&pinctrl_panel>;
pinctrl-names = "default";
@@ -91,10 +92,28 @@ lvds1_out: endpoint {
};
};
- /* max25014 @ 0x6f */
+ backlight: backlight@6f {
+ compatible = "maxim,max25014";
+ reg = <0x6f>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ default-brightness = <50>;
+ enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&pinctrl_backlight>;
+ maxim,iset = <7>;
+ maxim,strings = <1 1 1 1>;
+ };
};
&iomuxc {
+ pinctrl_backlight: backlightgrp {
+ fsl,pins = <
+ MX8MP_IOMUXC_GPIO1_IO04__GPIO1_IO04
+ (MX8MP_PULL_UP | MX8MP_PULL_ENABLE)
+ >;
+ };
+
pinctrl_lvds_bridge: lvdsbridgegrp {
fsl,pins = <
MX8MP_IOMUXC_SAI1_TXD2__GPIO4_IO14
--
2.52.0
^ permalink raw reply related
* Re: [PATCH RFC v6 05/26] nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Joel Fernandes @ 2026-01-23 12:59 UTC (permalink / raw)
To: Zhi Wang
Cc: linux-kernel, Maarten Lankhorst, Maxime Ripard, Simona Vetter,
Jonathan Corbet, Alex Deucher, Christian Koenig, Jani Nikula,
Joonas Lahtinen, Rodrigo Vivi, Tvrtko Ursulin, Huang Rui,
Matthew Auld, Matthew Brost, Lucas De Marchi, Thomas Hellstrom,
Helge Deller, Danilo Krummrich, Alice Ryhl, Miguel Ojeda,
Alex Gaynor, Boqun Feng, Gary Guo, Bjorn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Alistair Popple,
Alexandre Courbot, Andrea Righi, Alexey Ivanov, Philipp Stanner,
Elle Rhumsaa, Daniel Almeida, nouveau, dri-devel, rust-for-linux,
linux-doc, amd-gfx, intel-gfx, intel-xe, linux-fbdev
In-Reply-To: <20260123121343.396bc4cd.zhiw@nvidia.com>
On Fri, 23 Jan 2026 12:13:43 +0200, Zhi Wang wrote:
> Thanks so much for the work and the discussion. It is super important
> efforts for me to move on for the vGPU work. :)
Great!
> As we discussed, the concurrency matters most when booting multiple vGPUs.
> At that time, the concurrency happens at:
>
> 1) Allocating GPU memory chunks
> 2) Reserving GPU channels
> 3) Mapping GPU memory to BAR1 page table
Yes all these are already covered from a concurrency PoV by the v6.
> I can see you are thinking of fine-granularity locking scheme, which I
> think is the right direction to go. I agreed with the above two locks.
Cool!
> However for 3), We need to have one there as well beside the above two
> locks. Have you already had one in the GPU VA allocator?
Currently for mapping Bar pages, you need a mutable reference to BarUser.
For the future, when we have multiple channels sharing the same va space,
it will still be protected because the va space allocator (virt_buddy)
already has internal locking. And for each map_page, it is protected as
well. So I believe that should also be covered. Thanks for checking.
> If yes, the above two locks should be good enough so far. IMO.
Ok, thanks for checking.
--
Joel Fernandes
^ permalink raw reply
* [PATCH v2] fbdev: avoid out-of-bounds read in fb_pad_unaligned_buffer()
From: Osama Abdelkader @ 2026-01-24 16:46 UTC (permalink / raw)
To: Simona Vetter, Helge Deller, Thomas Zimmermann, Lee Jones,
Daniel Thompson (RISCstar), Murad Masimov, Quanmin Yan,
Yongzhen Zhang, Osama Abdelkader, linux-fbdev, dri-devel,
linux-kernel
Cc: syzbot+55e03490a0175b8dd81d
fb_pad_unaligned_buffer() unconditionally reads and advances the source
pointer for the final byte of each row, even when no bits from that byte
are actually consumed.
When shift_high >= mod, the remaining bits do not cross a byte boundary,
but the code still accesses the next source byte. This can lead to
out-of-bounds reads under malformed geometry, as reported by syzbot.
Fix this by only accessing and consuming the final source byte when it
contributes bits (shift_high < mod).
This fixes the KASAN slab-out-of-bounds read reported by syzkaller:
https://syzkaller.appspot.com/bug?extid=55e03490a0175b8dd81d
Reported-by: syzbot+55e03490a0175b8dd81d@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=55e03490a0175b8dd81d
Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
---
v2: address the real issue (shift_high >= mod) condition.
---
drivers/video/fbdev/core/fbmem.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
index eff757ebbed1..d125c3db37a1 100644
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -100,7 +100,7 @@ EXPORT_SYMBOL(fb_pad_aligned_buffer);
void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height,
u32 shift_high, u32 shift_low, u32 mod)
{
- u8 mask = (u8) (0xfff << shift_high), tmp;
+ u8 mask = (u8) (0xff << shift_high), tmp;
int i, j;
for (i = height; i--; ) {
@@ -113,15 +113,18 @@ void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height,
dst[j+1] = tmp;
src++;
}
- tmp = dst[idx];
- tmp &= mask;
- tmp |= *src >> shift_low;
- dst[idx] = tmp;
+
+ /* Only consume another source byte if it contributes bits */
if (shift_high < mod) {
+ tmp = dst[idx];
+ tmp &= mask;
+ tmp |= *src >> shift_low;
+ dst[idx] = tmp;
tmp = *src << shift_high;
dst[idx+1] = tmp;
+ src++;
}
- src++;
+
dst += d_pitch;
}
}
--
2.43.0
^ permalink raw reply related
* Re: [PATCH] fbdev: Fix slab-out-of-bounds read in fb_pad_unaligned_buffer
From: Osama Abdelkader @ 2026-01-24 16:51 UTC (permalink / raw)
To: Thomas Zimmermann
Cc: Simona Vetter, Helge Deller, Lee Jones, Murad Masimov,
Quanmin Yan, Yongzhen Zhang, linux-fbdev, dri-devel, linux-kernel,
syzbot+55e03490a0175b8dd81d
In-Reply-To: <7d4b95ff-8a94-4d96-8b75-6153baad9fdf@suse.de>
On Mon, Jan 19, 2026 at 08:45:08AM +0100, Thomas Zimmermann wrote:
> Hi
>
> Am 18.01.26 um 14:47 schrieb Osama Abdelkader:
> > The function fb_pad_unaligned_buffer() was reading idx+1 bytes per row
> > from the source buffer, but when mod == 0 (font width is a multiple of
> > 8 bits), the source buffer only has idx bytes per row. This caused a
> > slab-out-of-bounds read when accessing src[idx] after the inner loop.
> >
> > Fix this by only reading the extra byte when mod != 0, ensuring we
> > never read beyond the source buffer boundaries.
> >
> > This fixes the KASAN slab-out-of-bounds read reported by syzkaller:
> > https://syzkaller.appspot.com/bug?extid=55e03490a0175b8dd81d
> >
> > Reported-by: syzbot+55e03490a0175b8dd81d@syzkaller.appspotmail.com
> > Closes: https://syzkaller.appspot.com/bug?extid=55e03490a0175b8dd81d
> > Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
> > ---
> > drivers/video/fbdev/core/fbmem.c | 18 ++++++++++--------
> > 1 file changed, 10 insertions(+), 8 deletions(-)
> >
> > diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c
> > index eff757ebbed1..a0c4932a6758 100644
> > --- a/drivers/video/fbdev/core/fbmem.c
> > +++ b/drivers/video/fbdev/core/fbmem.c
> > @@ -113,15 +113,17 @@ void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height,
> > dst[j+1] = tmp;
> > src++;
> > }
> > - tmp = dst[idx];
> > - tmp &= mask;
> > - tmp |= *src >> shift_low;
> > - dst[idx] = tmp;
> > - if (shift_high < mod) {
> > - tmp = *src << shift_high;
> > - dst[idx+1] = tmp;
> > + if (mod) {
>
> How do we end up here if mod equals 0? When I look at the callers of this
> function, cases with (mod == 0) take an entirely different branch. [1] [2]
>
> Best regards
> Thomas
>
> [1] https://elixir.bootlin.com/linux/v6.18.6/source/drivers/video/fbdev/core/bitblit.c#L208
> [2] https://elixir.bootlin.com/linux/v6.18.6/source/drivers/video/fbdev/core/fbcon_ud.c#L199
>
> > + tmp = dst[idx];
> > + tmp &= mask;
> > + tmp |= *src >> shift_low;
> > + dst[idx] = tmp;
> > + if (shift_high < mod) {
> > + tmp = *src << shift_high;
> > + dst[idx+1] = tmp;
> > + }
> > + src++;
> > }
> > - src++;
> > dst += d_pitch;
> > }
> > }
>
> --
> --
> Thomas Zimmermann
> Graphics Driver Developer
> SUSE Software Solutions Germany GmbH
> Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
> GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)
>
>
You’re right that callers should only reach this path when mod != 0.
The issue isn’t the mod == 0 case itself, but that the final source byte is read
and consumed even when shift_high >= mod, where no bits are actually used.
I resent a version that only accesses the extra byte when it contributes data.
Best regards,
osama
^ permalink raw reply
* Re: [PATCH] fbdev: sys_fillrect: Add bounds checking to prevent vmalloc-out-of-bounds
From: Osama Abdelkader @ 2026-01-24 16:53 UTC (permalink / raw)
To: Thomas Zimmermann
Cc: Zsolt Kajtar, Simona Vetter, Helge Deller, linux-fbdev, dri-devel,
linux-kernel, syzbot+7a63ce155648954e749b
In-Reply-To: <5bc62c51-308c-483f-a92d-29354f2deeac@suse.de>
On Mon, Jan 19, 2026 at 08:38:31AM +0100, Thomas Zimmermann wrote:
> Hi,
>
> thanks for the patch.
>
> Am 18.01.26 um 01:18 schrieb Osama Abdelkader:
> > The sys_fillrect function was missing bounds validation, which could lead
> > to vmalloc-out-of-bounds writes when the rectangle coordinates extend
> > beyond the framebuffer's virtual resolution. This was detected by KASAN
> > and reported by syzkaller.
> >
> > Add validation to:
> > 1. Check that width and height are non-zero
> > 2. Verify that dx and dy are within virtual resolution bounds
> > 3. Clip the rectangle dimensions to fit within virtual resolution if needed
>
> This is rather a problem with the caller of the fillrect helper and affects
> all drivers and all implementations of fb_fillrect. Clipping should happen
> in the fbcon functions before invoking ->fb_con.
>
> Best regards
> Thomas
>
> >
> > This follows the same pattern used in other framebuffer drivers like
> > pm2fb_fillrect.
> >
> > Reported-by: syzbot+7a63ce155648954e749b@syzkaller.appspotmail.com
> > Closes: https://syzkaller.appspot.com/bug?extid=7a63ce155648954e749b
> > Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
> > ---
> > drivers/video/fbdev/core/sysfillrect.c | 21 ++++++++++++++++++++-
> > 1 file changed, 20 insertions(+), 1 deletion(-)
> >
> > diff --git a/drivers/video/fbdev/core/sysfillrect.c b/drivers/video/fbdev/core/sysfillrect.c
> > index 12eea3e424bb..73fc322ff8fd 100644
> > --- a/drivers/video/fbdev/core/sysfillrect.c
> > +++ b/drivers/video/fbdev/core/sysfillrect.c
> > @@ -7,6 +7,7 @@
> > #include <linux/module.h>
> > #include <linux/fb.h>
> > #include <linux/bitrev.h>
> > +#include <linux/string.h>
> > #include <asm/types.h>
> > #ifdef CONFIG_FB_SYS_REV_PIXELS_IN_BYTE
> > @@ -18,10 +19,28 @@
> > void sys_fillrect(struct fb_info *p, const struct fb_fillrect *rect)
> > {
> > + struct fb_fillrect modded;
> > + int vxres, vyres;
> > +
> > if (!(p->flags & FBINFO_VIRTFB))
> > fb_warn_once(p, "%s: framebuffer is not in virtual address space.\n", __func__);
> > - fb_fillrect(p, rect);
> > + vxres = p->var.xres_virtual;
> > + vyres = p->var.yres_virtual;
> > +
> > + /* Validate and clip rectangle to virtual resolution */
> > + if (!rect->width || !rect->height ||
> > + rect->dx >= vxres || rect->dy >= vyres)
> > + return;
> > +
> > + memcpy(&modded, rect, sizeof(struct fb_fillrect));
> > +
> > + if (modded.dx + modded.width > vxres)
> > + modded.width = vxres - modded.dx;
> > + if (modded.dy + modded.height > vyres)
> > + modded.height = vyres - modded.dy;
> > +
> > + fb_fillrect(p, &modded);
> > }
> > EXPORT_SYMBOL(sys_fillrect);
>
> --
> --
> Thomas Zimmermann
> Graphics Driver Developer
> SUSE Software Solutions Germany GmbH
> Frankenstr. 146, 90461 Nürnberg, Germany, www.suse.com
> GF: Jochen Jaser, Andrew McDonald, Werner Knoblich, (HRB 36809, AG Nürnberg)
>
>
Thanks for the info.
Best regards,
Osama
^ permalink raw reply
* Re: [PATCH RFC v6 01/26] rust: clist: Add support to interface with C linked lists
From: Joel Fernandes @ 2026-01-25 1:51 UTC (permalink / raw)
To: Gary Guo
Cc: linux-kernel, Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann,
David Airlie, Simona Vetter, Jonathan Corbet, Alex Deucher,
Christian König, Jani Nikula, Joonas Lahtinen, Rodrigo Vivi,
Tvrtko Ursulin, Huang Rui, Matthew Auld, Matthew Brost,
Lucas De Marchi, Thomas Hellström, Helge Deller,
Danilo Krummrich, Alice Ryhl, Miguel Ojeda, Alex Gaynor,
Boqun Feng, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, John Hubbard, Alistair Popple, Timur Tabi,
Edwin Peer, Alexandre Courbot, Andrea Righi, Andy Ritger,
Zhi Wang, Alexey Ivanov, Balbir Singh, Philipp Stanner,
Elle Rhumsaa, Daniel Almeida, nouveau, dri-devel, rust-for-linux,
linux-doc, amd-gfx, intel-gfx, intel-xe, linux-fbdev
In-Reply-To: <DFUK089V1IEU.U83YQT72BO3@garyguo.net>
On Wed, Jan 21, 2026 at 08:36:05PM +0000, Gary Guo wrote:
>>> Why is this callback necessary? The user can just create the list head and
>>> then reference it later? I don't see what this specifically gains over just
>>> doing
>>>
>>> fn new() -> impl PinInit<Self>;
>>>
>>> and have user-side
>>>
>>> list <- CListHead::new(),
>>> _: {
>>> do_want_ever(&list)
>>> }
>>
>> The list initialization can fail, see the GPU buddy patch:
>>
>> // Create pin-initializer that initializes list and allocates blocks.
>> let init = try_pin_init!(AllocatedBlocks {
>> list <- CListHead::try_init(|list| {
>> // Lock while allocating to serialize with concurrent frees.
>> let guard = buddy_arc.lock();
>>
>> // SAFETY: guard provides exclusive access, list is initialized.
>> to_result(unsafe {
>> bindings::gpu_buddy_alloc_blocks(
>> guard.as_raw(),
>> params.start_range_address,
>> params.end_range_address,
>> params.size_bytes,
>> params.min_block_size_bytes,
>> list.as_raw(),
>> params.buddy_flags.as_raw(),
>> )
>> })
>> }),
>> buddy: Arc::clone(&buddy_arc),
>> flags: params.buddy_flags,
>> });
>
> The list initialization doesn't fail? It's the subsequent action you did that
> failed.
>
> You can put failing things in the `_: { ... }` block too.
This worked out well, thanks for the suggestion! I've updated the code
to use `CListHead::new()` with the failable allocation in a `_: { ... }` block:
let init = try_pin_init!(AllocatedBlocks {
buddy: Arc::clone(&buddy_arc),
list <- CListHead::new(),
flags: params.buddy_flags,
_: {
// Lock while allocating to serialize with concurrent frees.
let guard = buddy.lock();
// SAFETY: `guard` provides exclusive access to the buddy allocator.
to_result(unsafe {
bindings::gpu_buddy_alloc_blocks(
guard.as_raw(),
params.start_range_address,
params.end_range_address,
params.size_bytes,
params.min_block_size_bytes,
list.as_raw(),
params.buddy_flags.as_raw(),
)
})?
}
});
I'll remove the try_init() method from CListHead since new() is sufficient.
--
Joel Fernandes
^ permalink raw reply
* [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: pengfuyuan @ 2026-01-26 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Greg Kroah-Hartman,
Rafael J . Wysocki, David Airlie, Simona Vetter, Helge Deller,
Hans de Goede, Thomas Zimmermann, Lee Jones, Sam Ravnborg,
Zsolt Kajtar, Ville Syrjälä, rust-for-linux,
linux-kernel, dri-devel, linux-fbdev, pengfuyuan
This patch series adds Rust bindings and safe abstractions for the Linux
framebuffer subsystem, enabling framebuffer drivers to be implemented in Rust.
The series consists of 4 patches:
1. rust: io: mem: add ioremap_wc support
Adds write-combining memory mapping support to the Rust iomem abstraction,
which is essential for framebuffer memory regions that benefit from
write-combining semantics.
2. rust: device: add platdata accessors
Implements generic accessors for platform data, enabling drivers to access
platform-provided configuration. This is needed for framebuffer drivers
that use platform data for configuration.
3. rust: fb: add framebuffer driver support
Adds the core framebuffer framework abstraction, including:
- Device abstraction (`fb::Device`) with reference counting via `AlwaysRefCounted`
- Driver and Operations traits (`fb::Driver`, `fb::Operations`)
- Screen information wrappers (`fb::FixScreenInfo`, `fb::VarScreenInfo`)
- I/O operation helpers (`fb_io_read`, `fb_io_write`, `fb_io_mmap`)
- Blit operation helpers (`cfb_fillrect`, `cfb_copyarea`, `cfb_imageblit`)
4. rust: fb: add simplefb test driver
Adds a test driver that validates the framebuffer framework by porting
the C simplefb driver to Rust. This driver serves as both a validation
tool and a reference implementation for future Rust framebuffer drivers.
The implementation follows the same patterns established in the DRM subsystem
and maintains full compatibility with the existing C framebuffer subsystem.
All C callbacks are properly bridged to Rust trait methods via the `Operations`
trait, memory safety is ensured through proper use of `Opaque<fb_info>`, `ARef`,
and `AlwaysRefCounted` for reference counting, type invariants are documented
and enforced through the type system, and resource cleanup is handled via RAII
with proper cleanup order.
Testing:
--------
This series has been tested on:
- ARM64 platforms with various display configurations
- AMD RX550 graphics card
- Moore Threads S30 graphics card
- Multiple other graphics cards
All tested configurations show normal display functionality with proper
framebuffer initialization, rendering operations (including I/O, color register
management, and blitting), memory mapping, and resource cleanup. The simplefb
test driver successfully demonstrates the usage of all framebuffer framework
APIs and validates the abstraction's correctness.
The simplefb test driver serves as both a validation tool and a reference
implementation for future Rust framebuffer drivers.
pengfuyuan (4):
rust: io: mem: add ioremap_wc support
rust: device: add platdata accessors
rust: fb: add framebuffer driver support
rust: fb: add simplefb test driver
drivers/video/fbdev/Kconfig | 21 +
drivers/video/fbdev/Makefile | 1 +
drivers/video/fbdev/simplefb_rust.rs | 653 +++++++++++++++++++++++++++
rust/bindings/bindings_helper.h | 2 +
rust/helpers/device.c | 5 +
rust/helpers/io.c | 5 +
rust/kernel/device.rs | 31 ++
rust/kernel/fb/blit.rs | 106 +++++
rust/kernel/fb/device.rs | 463 +++++++++++++++++++
rust/kernel/fb/driver.rs | 169 +++++++
rust/kernel/fb/io.rs | 76 ++++
rust/kernel/fb/mod.rs | 23 +
rust/kernel/fb/screeninfo.rs | 318 +++++++++++++
rust/kernel/io/mem.rs | 71 +++
rust/kernel/lib.rs | 2 +
15 files changed, 1946 insertions(+)
create mode 100644 drivers/video/fbdev/simplefb_rust.rs
create mode 100644 rust/kernel/fb/blit.rs
create mode 100644 rust/kernel/fb/device.rs
create mode 100644 rust/kernel/fb/driver.rs
create mode 100644 rust/kernel/fb/io.rs
create mode 100644 rust/kernel/fb/mod.rs
create mode 100644 rust/kernel/fb/screeninfo.rs
--
2.25.1
^ permalink raw reply
* [PATCH v1 v1 2/4] rust: device: add platdata accessors
From: pengfuyuan @ 2026-01-26 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Greg Kroah-Hartman,
Rafael J . Wysocki, David Airlie, Simona Vetter, Helge Deller,
Hans de Goede, Thomas Zimmermann, Lee Jones, Sam Ravnborg,
Zsolt Kajtar, Ville Syrjälä, rust-for-linux,
linux-kernel, dri-devel, linux-fbdev, pengfuyuan
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
Implement generic accessors for the platform data of a device.
Platform data is typically set by platform code when creating the device (e.g.
via `platform_device_add_data()`). Drivers may use it to obtain per-device,
platform-provided configuration.
The accessor is `unsafe` because the caller must ensure that the chosen `T`
matches the actual object referenced by `platform_data`.
Platform data is generally a C type, so the method returns `&Opaque<T>` to
avoid creating a Rust reference to potentially uninitialised or otherwise
invalid C data. Drivers can then perform the FFI dereference behind an explicit
`unsafe` block.
The method is implemented for `Device<Ctx>` so it is available in all device
states. If no platform data is present, `-ENOENT` is returned.
Signed-off-by: pengfuyuan <pengfuyuan@kylinos.cn>
---
rust/helpers/device.c | 5 +++++
rust/kernel/device.rs | 31 +++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+)
diff --git a/rust/helpers/device.c b/rust/helpers/device.c
index 9a4316bafedf..4819eaf8c9f1 100644
--- a/rust/helpers/device.c
+++ b/rust/helpers/device.c
@@ -25,3 +25,8 @@ void rust_helper_dev_set_drvdata(struct device *dev, void *data)
{
dev_set_drvdata(dev, data);
}
+
+void *rust_helper_dev_get_platdata(const struct device *dev)
+{
+ return dev_get_platdata(dev);
+}
diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index 71b200df0f40..9221141b31ae 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -482,6 +482,37 @@ pub fn fwnode(&self) -> Option<&property::FwNode> {
// defined as a `#[repr(transparent)]` wrapper around `fwnode_handle`.
Some(unsafe { &*fwnode_handle.cast() })
}
+
+ /// Access the platform data for this device.
+ ///
+ /// Platform data is typically set by platform code when creating the device and is expected
+ /// to remain valid while the device is alive.
+ ///
+ /// Returns a reference to the opaque platform data, or [`ENOENT`] if no platform data
+ /// is set.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that:
+ /// - If platform data is set (i.e., `platform_data` is not null), the pointer points to valid,
+ /// properly aligned storage for `T` and remains valid for the lifetime of the returned
+ /// reference.
+ /// - The type `T` matches the type of the platform data structure set by platform code.
+ pub unsafe fn platdata<T>(&self) -> Result<&Opaque<T>> {
+ // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to a `struct device`.
+ let ptr = unsafe { bindings::dev_get_platdata(self.as_raw()) };
+
+ if ptr.is_null() {
+ return Err(ENOENT);
+ }
+
+ // SAFETY:
+ // - `ptr` is not null (checked above).
+ // - By the safety requirements of this function, `ptr` points to valid, properly aligned
+ // storage for `T` and remains valid for the lifetime of the returned reference.
+ // - `Opaque<T>` allows any bit pattern, so we can safely create a reference to it.
+ Ok(unsafe { &*ptr.cast::<Opaque<T>>() })
+ }
}
// SAFETY: `Device` is a transparent wrapper of a type that doesn't depend on `Device`'s generic
--
2.25.1
^ permalink raw reply related
* [PATCH v1 v1 1/4] rust: io: mem: add ioremap_wc support
From: pengfuyuan @ 2026-01-26 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Greg Kroah-Hartman,
Rafael J . Wysocki, David Airlie, Simona Vetter, Helge Deller,
Hans de Goede, Thomas Zimmermann, Lee Jones, Sam Ravnborg,
Zsolt Kajtar, Ville Syrjälä, rust-for-linux,
linux-kernel, dri-devel, linux-fbdev, pengfuyuan
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
Add write-combining memory mapping support to the Rust iomem abstraction.
This extends the existing IoMem and IoRequest abstractions to support
write-combining cache policy, which is essential for framebuffer memory
and other memory regions that benefit from write-combining semantics.
The implementation follows the same pattern as the existing ioremap and
ioremap_np support:
- Add rust_helper_ioremap_wc() in rust/helpers/io.c to wrap the C API
- Add IoMem::ioremap_wc() to perform the actual mapping with write-combining
- Add IoMem::new_wc() to create IoMem instances with write-combining policy
- Add IoRequest::iomap_wc_sized() and IoRequest::iomap_wc() methods
for compile-time and runtime-sized mappings respectively
This enables Rust drivers, such as framebuffer drivers, to properly map
memory regions with write-combining semantics.
The API design is consistent with the existing iomap() methods, providing
both sized and unsized variants to match the pattern established by the
generic iomem abstraction.
Signed-off-by: pengfuyuan <pengfuyuan@kylinos.cn>
---
rust/helpers/io.c | 5 +++
rust/kernel/io/mem.rs | 71 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 76 insertions(+)
diff --git a/rust/helpers/io.c b/rust/helpers/io.c
index c475913c69e6..6c9edf7f2233 100644
--- a/rust/helpers/io.c
+++ b/rust/helpers/io.c
@@ -13,6 +13,11 @@ void __iomem *rust_helper_ioremap_np(phys_addr_t offset, size_t size)
return ioremap_np(offset, size);
}
+void __iomem *rust_helper_ioremap_wc(phys_addr_t offset, size_t size)
+{
+ return ioremap_wc(offset, size);
+}
+
void rust_helper_iounmap(void __iomem *addr)
{
iounmap(addr);
diff --git a/rust/kernel/io/mem.rs b/rust/kernel/io/mem.rs
index b03b82cd531b..94403d899bbd 100644
--- a/rust/kernel/io/mem.rs
+++ b/rust/kernel/io/mem.rs
@@ -149,6 +149,41 @@ pub fn iomap(self) -> impl PinInit<Devres<IoMem<0>>, Error> + 'a {
pub fn iomap_exclusive(self) -> impl PinInit<Devres<ExclusiveIoMem<0>>, Error> + 'a {
Self::iomap_exclusive_sized::<0>(self)
}
+
+ /// Maps an [`IoRequest`] with write-combining cache policy where the size
+ /// is known at compile time.
+ ///
+ /// This uses the [`ioremap_wc()`] C API, which provides write-combining
+ /// semantics. This is useful for framebuffer memory and other memory
+ /// regions that benefit from write-combining, where multiple writes can
+ /// be combined and reordered for better performance.
+ ///
+ /// Unlike [`Self::iomap`], this method explicitly uses write-combining
+ /// mapping, which is typically needed for video framebuffers.
+ ///
+ /// [`ioremap_wc()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
+ pub fn iomap_wc_sized<const SIZE: usize>(
+ self,
+ ) -> impl PinInit<Devres<IoMem<SIZE>>, Error> + 'a {
+ IoMem::new_wc(self)
+ }
+
+ /// Maps an [`IoRequest`] with write-combining cache policy where the size
+ /// is not known at compile time.
+ ///
+ /// This uses the [`ioremap_wc()`] C API, which provides write-combining
+ /// semantics. This is useful for framebuffer memory and other memory
+ /// regions that benefit from write-combining.
+ ///
+ /// Unlike [`Self::iomap_wc_sized`], here the size of the memory region
+ /// is not known at compile time, so only the `try_read*` and `try_write*`
+ /// family of functions should be used, leading to runtime checks on every
+ /// access.
+ ///
+ /// [`ioremap_wc()`]: https://docs.kernel.org/driver-api/device-io.html#getting-access-to-the-device
+ pub fn iomap_wc(self) -> impl PinInit<Devres<IoMem<0>>, Error> + 'a {
+ Self::iomap_wc_sized::<0>(self)
+ }
}
/// An exclusive memory-mapped IO region.
@@ -261,6 +296,33 @@ fn ioremap(resource: &Resource) -> Result<Self> {
Ok(io)
}
+ fn ioremap_wc(resource: &Resource) -> Result<Self> {
+ // Note: Some ioremap() implementations use types that depend on the CPU
+ // word width rather than the bus address width.
+ //
+ // TODO: Properly address this in the C code to avoid this `try_into`.
+ let size = resource.size().try_into()?;
+ if size == 0 {
+ return Err(EINVAL);
+ }
+
+ let res_start = resource.start();
+
+ // SAFETY:
+ // - `res_start` and `size` are read from a presumably valid `struct resource`.
+ // - `size` is known not to be zero at this point.
+ let addr = unsafe { bindings::ioremap_wc(res_start, size) };
+
+ if addr.is_null() {
+ return Err(ENOMEM);
+ }
+
+ let io = IoRaw::new(addr as usize, size)?;
+ let io = IoMem { io };
+
+ Ok(io)
+ }
+
/// Creates a new `IoMem` instance from a previously acquired [`IoRequest`].
pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
let dev = io_request.device;
@@ -268,6 +330,15 @@ pub fn new<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> +
Devres::new(dev, Self::ioremap(res))
}
+
+ /// Creates a new `IoMem` instance with write-combining cache policy from
+ /// a previously acquired [`IoRequest`].
+ pub fn new_wc<'a>(io_request: IoRequest<'a>) -> impl PinInit<Devres<Self>, Error> + 'a {
+ let dev = io_request.device;
+ let res = io_request.resource;
+
+ Devres::new(dev, Self::ioremap_wc(res))
+ }
}
impl<const SIZE: usize> Drop for IoMem<SIZE> {
--
2.25.1
^ permalink raw reply related
* [PATCH v1 v1 3/4] rust: fb: add framebuffer driver support
From: pengfuyuan @ 2026-01-26 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Greg Kroah-Hartman,
Rafael J . Wysocki, David Airlie, Simona Vetter, Helge Deller,
Hans de Goede, Thomas Zimmermann, Lee Jones, Sam Ravnborg,
Zsolt Kajtar, Ville Syrjälä, rust-for-linux,
linux-kernel, dri-devel, linux-fbdev, pengfuyuan
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
Add Rust bindings and safe abstractions for the Linux framebuffer subsystem.
This implementation provides Rust abstractions for the framebuffer subsystem,
following the same patterns established in the DRM subsystem. The abstraction
wraps the existing C framebuffer infrastructure (`struct fb_info`, `struct
fb_ops`) with type-safe Rust interfaces, allowing drivers to be implemented
in Rust while maintaining full compatibility with the existing C framebuffer
subsystem.
The implementation includes:
- Device abstraction (`device.rs`): Provides a typed `fb::Device` wrapper
around `struct fb_info` with reference counting via `AlwaysRefCounted`. The
device abstraction provides safe access to screen information (`var()`,
`fix()`), memory mappings (`screen_base()`, `screen_size()`), and
driver-specific data (via `data()` or direct dereference). Device lifecycle
is managed through `ARef` and the `destroy_callback` which properly cleans
up driver resources.
- Driver abstractions (`driver.rs`): Defines the `Driver` trait (which
specifies driver metadata via `DriverInfo` and associates an `Operations`
implementation) and the `Operations` trait (corresponding to `struct fb_ops`).
The `Operations` trait provides methods for reading, writing, color register
management (`setcolreg`), blitting operations (`fillrect`, `copyarea`,
`imageblit`), memory mapping (`mmap`), and resource cleanup (`destroy`).
The `Registration` type manages device registration and unregistration with
the framebuffer subsystem.
- Screen information (`screeninfo.rs`): Safe wrappers for `fb_var_screeninfo`
and `fb_fix_screeninfo` structures. Provides accessors for screen parameters
(resolution, color depth, memory layout) and color component bitfields
(`red()`, `green()`, `blue()`, `transp()`). Also includes related constants
for framebuffer types, visual modes, acceleration, activation, and video
modes.
- I/O operations (`io.rs`): Safe wrappers for the generic framebuffer I/O
helpers (`fb_io_read`, `fb_io_write`, `fb_io_mmap`). These functions provide
generic implementations for reading from, writing to, and memory-mapping
framebuffer devices, which can be used by drivers that don't implement
custom I/O operations.
- Blit operations (`blit.rs`): Safe wrappers for framebuffer blit operation
structures (`FillRect` for `fb_fillrect`, `CopyArea` for `fb_copyarea`,
`Image` for `fb_image`) and software implementation functions
(`cfb_fillrect`, `cfb_copyarea`, `cfb_imageblit`). These software functions
provide generic implementations for rectangle filling, area copying, and
image blitting, which can be used by drivers that don't implement hardware
acceleration.
The implementation follows Rust for Linux safety guidelines:
- C callbacks (`read_callback`, `write_callback`, `setcolreg_callback`, etc.)
are properly bridged to Rust trait methods via the `Operations` trait
- Memory safety is ensured through proper use of `Opaque<fb_info>`, `ARef`,
and `AlwaysRefCounted` for reference counting
- Type invariants are documented (e.g., `Device` invariants) and enforced
through the type system
- Resource cleanup is handled via RAII: `Registration` uses `Drop` for
unregistration, and `destroy_callback` ensures proper cleanup order
(driver resources via `T::Ops::destroy`, driver data via `drop_in_place`,
then `fb_info` structure via `framebuffer_release`)
Signed-off-by: pengfuyuan <pengfuyuan@kylinos.cn>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/fb/blit.rs | 106 ++++++++
rust/kernel/fb/device.rs | 463 ++++++++++++++++++++++++++++++++
rust/kernel/fb/driver.rs | 169 ++++++++++++
rust/kernel/fb/io.rs | 76 ++++++
rust/kernel/fb/mod.rs | 23 ++
rust/kernel/fb/screeninfo.rs | 318 ++++++++++++++++++++++
rust/kernel/lib.rs | 2 +
8 files changed, 1158 insertions(+)
create mode 100644 rust/kernel/fb/blit.rs
create mode 100644 rust/kernel/fb/device.rs
create mode 100644 rust/kernel/fb/driver.rs
create mode 100644 rust/kernel/fb/io.rs
create mode 100644 rust/kernel/fb/mod.rs
create mode 100644 rust/kernel/fb/screeninfo.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b42..bc47806eb365 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -53,6 +53,7 @@
#include <linux/dma-mapping.h>
#include <linux/errname.h>
#include <linux/ethtool.h>
+#include <linux/fb.h>
#include <linux/fdtable.h>
#include <linux/file.h>
#include <linux/firmware.h>
diff --git a/rust/kernel/fb/blit.rs b/rust/kernel/fb/blit.rs
new file mode 100644
index 000000000000..b5378694d88d
--- /dev/null
+++ b/rust/kernel/fb/blit.rs
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer blit operations.
+//!
+//! This module provides safe wrappers for framebuffer blit operation structures and functions.
+//!
+//! C header: [`include/linux/fb.h`](srctree/include/linux/fb.h)
+
+use crate::{bindings, fb};
+
+/// Wrapper for `fb_fillrect` with safe accessors.
+///
+/// Describes a filled rectangle operation for framebuffer devices.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct FillRect(bindings::fb_fillrect);
+
+impl FillRect {
+ /// Create a new `FillRect` from the raw C structure.
+ ///
+ /// `fb_fillrect` is a POD type, so any bit pattern is valid.
+ pub const fn from_raw(raw: bindings::fb_fillrect) -> Self {
+ Self(raw)
+ }
+
+ /// Returns a reference to the underlying C `fb_fillrect` structure.
+ #[inline]
+ fn as_raw(&self) -> &bindings::fb_fillrect {
+ &self.0
+ }
+}
+
+/// Wrapper for `fb_copyarea` with safe accessors.
+///
+/// Describes a copy area operation for framebuffer devices.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct CopyArea(bindings::fb_copyarea);
+
+impl CopyArea {
+ /// Create a new `CopyArea` from the raw C structure.
+ ///
+ /// `fb_copyarea` is a POD type, so any bit pattern is valid.
+ pub const fn from_raw(raw: bindings::fb_copyarea) -> Self {
+ Self(raw)
+ }
+
+ /// Returns a reference to the underlying C `fb_copyarea` structure.
+ #[inline]
+ fn as_raw(&self) -> &bindings::fb_copyarea {
+ &self.0
+ }
+}
+
+/// Wrapper for `fb_image` with safe accessors.
+///
+/// Describes an image blit operation for framebuffer devices.
+#[repr(transparent)]
+pub struct Image(bindings::fb_image);
+
+impl Image {
+ /// Create a new `Image` from the raw C structure.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `raw` is properly initialized.
+ pub const unsafe fn from_raw(raw: bindings::fb_image) -> Self {
+ Self(raw)
+ }
+
+ /// Returns a reference to the underlying C `fb_image` structure.
+ #[inline]
+ fn as_raw(&self) -> &bindings::fb_image {
+ &self.0
+ }
+}
+
+/// Software rectangle fill operation.
+///
+/// Invokes the generic `cfb_fillrect` helper.
+pub fn cfb_fillrect<T: fb::Driver>(device: &fb::Device<T>, rect: &FillRect) {
+ // SAFETY: Both `device.as_raw()` and `rect.as_raw()` return valid pointers by type invariants.
+ unsafe {
+ bindings::cfb_fillrect(device.as_raw(), rect.as_raw());
+ }
+}
+
+/// Software area copy operation.
+///
+/// Invokes the generic `cfb_copyarea` helper.
+pub fn cfb_copyarea<T: fb::Driver>(device: &fb::Device<T>, area: &CopyArea) {
+ // SAFETY: Both `device.as_raw()` and `area.as_raw()` return valid pointers by type invariants.
+ unsafe {
+ bindings::cfb_copyarea(device.as_raw(), area.as_raw());
+ }
+}
+
+/// Software image blit operation.
+///
+/// Invokes the generic `cfb_imageblit` helper.
+pub fn cfb_imageblit<T: fb::Driver>(device: &fb::Device<T>, image: &Image) {
+ // SAFETY: Both `device.as_raw()` and `image.as_raw()` return valid pointers by type invariants.
+ unsafe {
+ bindings::cfb_imageblit(device.as_raw(), image.as_raw());
+ }
+}
diff --git a/rust/kernel/fb/device.rs b/rust/kernel/fb/device.rs
new file mode 100644
index 000000000000..7e7f36e5e4b8
--- /dev/null
+++ b/rust/kernel/fb/device.rs
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer device.
+//!
+//! This module provides the core abstractions for framebuffer device management.
+//!
+//! C header: [`include/linux/fb.h`]
+
+use crate::{
+ bindings, device,
+ error::from_err_ptr,
+ fb,
+ fb::driver::Operations,
+ fs::file,
+ mm,
+ prelude::*,
+ sync::{
+ aref::{ARef, AlwaysRefCounted},
+ Refcount,
+ },
+ types::Opaque,
+};
+use core::{
+ ffi::{c_int, c_uint},
+ marker::PhantomData,
+ mem,
+ ops::Deref,
+ ptr,
+ ptr::NonNull,
+};
+
+/// A typed framebuffer device with a specific `fb::Driver` implementation.
+///
+/// # Invariants
+///
+/// A [`Device`] instance represents a valid `struct fb_info` created by the C portion of the kernel.
+///
+/// - `self.0` is a valid pointer to a `struct fb_info`.
+/// - The `fb_info.par` field points to a valid `T::Data` instance.
+/// - The `fb_info.fbops` field always points to `Self::FBOPS` and is never null.
+///
+/// Instances of this type are always reference-counted.
+#[repr(transparent)]
+pub struct Device<T: fb::Driver>(Opaque<bindings::fb_info>, PhantomData<T>);
+
+impl<T: fb::Driver> Device<T> {
+ /// Returns a reference to the underlying C `struct fb_info`.
+ #[inline]
+ fn as_ref(&self) -> &bindings::fb_info {
+ // SAFETY: By the type invariant, the pointer stored in `self` is valid.
+ unsafe { &*self.as_raw() }
+ }
+
+ /// Returns the variable screen info.
+ pub fn var(&self) -> &fb::VarScreenInfo {
+ // SAFETY: `var` is a valid field of `fb_info` and remains valid for the lifetime of `self`.
+ unsafe { fb::VarScreenInfo::from_raw(&self.as_ref().var) }
+ }
+
+ /// Returns the fixed screen info.
+ pub fn fix(&self) -> &fb::FixScreenInfo {
+ // SAFETY: `fix` is a valid field of `fb_info` and remains valid for the lifetime of `self`.
+ unsafe { fb::FixScreenInfo::from_raw(&self.as_ref().fix) }
+ }
+
+ /// Returns the screen base address.
+ pub fn screen_base(&self) -> *mut u8 {
+ // SAFETY: `screen_base` is a union field accessed via the generated union.
+ unsafe { self.as_ref().__bindgen_anon_1.screen_base as *mut u8 }
+ }
+
+ /// Returns the screen size.
+ pub fn screen_size(&self) -> usize {
+ self.as_ref().screen_size as usize
+ }
+
+ /// Returns the pseudo palette pointer.
+ pub fn pseudo_palette(&self) -> *mut core::ffi::c_void {
+ self.as_ref().pseudo_palette
+ }
+
+ /// Returns the framebuffer device node number.
+ ///
+ /// This is the device node identifier assigned by the framebuffer subsystem
+ /// when the device is registered (e.g., 0 for /dev/fb0, 1 for /dev/fb1, etc.).
+ pub fn node(&self) -> i32 {
+ self.as_ref().node
+ }
+
+ /// Configures the fixed screen information.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that:
+ /// - The device is not yet registered with the framebuffer subsystem
+ /// - The provided configuration is valid for this device
+ pub unsafe fn configure_fix<F>(&self, f: F)
+ where
+ F: FnOnce(&mut bindings::fb_fix_screeninfo),
+ {
+ // SAFETY: By the safety requirements, we have exclusive access to the device during
+ // initialization, before registration.
+ let info_ptr = self.as_raw();
+ unsafe {
+ f(&mut (*info_ptr).fix);
+ }
+ }
+
+ /// Configures the variable screen information.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that:
+ /// - The device is not yet registered with the framebuffer subsystem
+ /// - The provided configuration is valid for this device
+ pub unsafe fn configure_var<F>(&self, f: F)
+ where
+ F: FnOnce(&mut bindings::fb_var_screeninfo),
+ {
+ // SAFETY: By the safety requirements, we have exclusive access to the device during
+ // initialization, before registration.
+ let info_ptr = self.as_raw();
+ unsafe {
+ f(&mut (*info_ptr).var);
+ }
+ }
+
+ /// Sets the screen base address.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that:
+ /// - The device is not yet registered with the framebuffer subsystem
+ /// - The address points to valid, mapped framebuffer memory
+ /// - The memory remains valid for the lifetime of the device
+ pub unsafe fn set_screen_base(&self, addr: *mut u8) {
+ // SAFETY: By the safety requirements, we have exclusive access during initialization.
+ let info_ptr = self.as_raw();
+ unsafe {
+ (*info_ptr).__bindgen_anon_1.screen_base = addr;
+ }
+ }
+
+ /// Sets the pseudo palette pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that:
+ /// - The device is not yet registered with the framebuffer subsystem
+ /// - The pointer points to valid memory with sufficient size
+ /// - The memory remains valid for the lifetime of the device
+ pub unsafe fn set_pseudo_palette(&self, palette: *mut core::ffi::c_void) {
+ // SAFETY: By the safety requirements, we have exclusive access during initialization.
+ let info_ptr = self.as_raw();
+ unsafe {
+ (*info_ptr).pseudo_palette = palette;
+ }
+ }
+
+ /// Callback for reading from framebuffers with non-linear layouts.
+ extern "C" fn read_callback(
+ info: *mut bindings::fb_info,
+ buf: *mut core::ffi::c_char,
+ count: usize,
+ ppos: *mut bindings::loff_t,
+ ) -> isize {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: C code ensures `buf` and `ppos` are valid. `buf` is a valid buffer pointer with
+ // `count` bytes, and `ppos` is a valid `file::Offset` pointer with exclusive access.
+ let pos: &mut file::Offset = unsafe { &mut *ppos };
+ let result = T::Ops::read(
+ &device,
+ unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) },
+ pos,
+ );
+ match result {
+ Ok(n) => n as isize,
+ Err(e) => -(e.to_errno() as isize),
+ }
+ }
+
+ /// Callback for writing to framebuffers with non-linear layouts.
+ extern "C" fn write_callback(
+ info: *mut bindings::fb_info,
+ buf: *const core::ffi::c_char,
+ count: usize,
+ ppos: *mut bindings::loff_t,
+ ) -> isize {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: C code ensures `buf` and `ppos` are valid. `buf` is a valid buffer pointer with
+ // `count` bytes, and `ppos` is a valid `file::Offset` pointer with exclusive access.
+ let pos: &mut file::Offset = unsafe { &mut *ppos };
+ let result = T::Ops::write(
+ &device,
+ unsafe { core::slice::from_raw_parts(buf as *const u8, count) },
+ pos,
+ );
+ match result {
+ Ok(n) => n as isize,
+ Err(e) => -(e.to_errno() as isize),
+ }
+ }
+
+ /// Callback for setting color registers.
+ extern "C" fn setcolreg_callback(
+ regno: c_uint,
+ red: c_uint,
+ green: c_uint,
+ blue: c_uint,
+ transp: c_uint,
+ info: *mut bindings::fb_info,
+ ) -> c_int {
+ let device = unsafe { Self::from_raw(info) };
+ let result = T::Ops::setcolreg(&device, regno, red, green, blue, transp);
+ match result {
+ Ok(()) => 0,
+ Err(e) => e.to_errno() as c_int,
+ }
+ }
+
+ /// Callback for filling a rectangle.
+ extern "C" fn fillrect_callback(
+ info: *mut bindings::fb_info,
+ rect: *const bindings::fb_fillrect,
+ ) {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: C code ensures `rect` is valid and points to a properly initialized `fb_fillrect`.
+ let rect = unsafe { fb::FillRect::from_raw(*rect) };
+ T::Ops::fillrect(&device, &rect);
+ }
+
+ /// Callback for copying an area.
+ extern "C" fn copyarea_callback(
+ info: *mut bindings::fb_info,
+ area: *const bindings::fb_copyarea,
+ ) {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: C code ensures `area` is valid and points to a properly initialized `fb_copyarea`.
+ let area = unsafe { fb::CopyArea::from_raw(*area) };
+ T::Ops::copyarea(&device, &area);
+ }
+
+ /// Callback for blitting an image.
+ extern "C" fn imageblit_callback(
+ info: *mut bindings::fb_info,
+ image: *const bindings::fb_image,
+ ) {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: C code ensures `image` is valid and points to a properly initialized `fb_image`.
+ let image = unsafe { fb::Image::from_raw(*image) };
+ T::Ops::imageblit(&device, &image);
+ }
+
+ /// Callback for memory mapping the framebuffer.
+ extern "C" fn mmap_callback(
+ info: *mut bindings::fb_info,
+ vma: *mut bindings::vm_area_struct,
+ ) -> c_int {
+ let device = unsafe { Self::from_raw(info) };
+ // SAFETY: The caller provides a `vma` that is undergoing initial VMA setup.
+ let area = unsafe { mm::virt::VmaNew::from_raw(vma) };
+ let result = T::Ops::mmap(&device, area);
+ match result {
+ Ok(()) => 0,
+ Err(e) => e.to_errno() as c_int,
+ }
+ }
+
+ /// Callback for destroying the framebuffer device.
+ ///
+ /// Performs cleanup in the correct order: driver resources, driver data, and finally
+ /// the fb_info structure itself.
+ extern "C" fn destroy_callback(info: *mut bindings::fb_info) {
+ let device = unsafe { Self::from_raw(info) };
+
+ // First, let the driver clean up its own resources (iounmap, release_mem_region, etc.)
+ T::Ops::destroy(&device);
+
+ // Get the pointer to the driver data (stored in `info->par`).
+ // SAFETY: `info` is valid and was allocated by `framebuffer_alloc` in `Device::new()`.
+ let par_ptr = unsafe { (*info).par };
+ if !par_ptr.is_null() {
+ // Manually call `Drop` for the driver data before `framebuffer_release`, since
+ // `framebuffer_release` will `kfree` the entire `fb_info` structure (including `par`),
+ // and `kfree` doesn't call Rust's `Drop`.
+ // SAFETY: `par_ptr` points to a valid `T::Data` instance that was initialized in
+ // `Device::new()`. This is the last access to the data before it's freed.
+ unsafe {
+ core::ptr::drop_in_place(par_ptr.cast::<T::Data>());
+ }
+ }
+
+ // Release the `fb_info` structure that was allocated by `framebuffer_alloc`.
+ // SAFETY: `info` is valid and was allocated by `framebuffer_alloc` in `Device::new()`.
+ unsafe {
+ bindings::framebuffer_release(info);
+ }
+ }
+
+ /// Static `fb_ops` table for this driver type.
+ ///
+ /// This table is shared by all instances of this driver type.
+ const FBOPS: bindings::fb_ops = bindings::fb_ops {
+ owner: core::ptr::null_mut(),
+ fb_open: None,
+ fb_release: None,
+ fb_read: Some(Self::read_callback),
+ fb_write: Some(Self::write_callback),
+ fb_check_var: None,
+ fb_set_par: None,
+ fb_setcolreg: Some(Self::setcolreg_callback),
+ fb_setcmap: None,
+ fb_blank: None,
+ fb_pan_display: None,
+ fb_fillrect: Some(Self::fillrect_callback),
+ fb_copyarea: Some(Self::copyarea_callback),
+ fb_imageblit: Some(Self::imageblit_callback),
+ fb_cursor: None,
+ fb_sync: None,
+ fb_ioctl: None,
+ fb_compat_ioctl: None,
+ fb_mmap: Some(Self::mmap_callback),
+ fb_get_caps: None,
+ fb_destroy: Some(Self::destroy_callback),
+ fb_debug_enter: None,
+ fb_debug_leave: None,
+ };
+
+ /// Creates a new `fb::Device` for a `fb::Driver`.
+ ///
+ /// The C `framebuffer_alloc` function allocates memory as:
+ /// `[fb_info][padding][driver_data]`
+ ///
+ /// `Device<T>` is `#[repr(transparent)]` around `Opaque<fb_info>`, so a `Device<T>` pointer
+ /// is actually just an `fb_info` pointer. The driver data `T::Data` is stored in `info->par`.
+ pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<ARef<Self>> {
+ let data_size = mem::size_of::<T::Data>();
+
+ // SAFETY: `dev.as_raw()` is valid by its type invariants.
+ let raw_info = unsafe { bindings::framebuffer_alloc(data_size, dev.as_raw()) };
+
+ let raw_info = NonNull::new(from_err_ptr(raw_info)?).ok_or(ENOMEM)?;
+
+ // SAFETY: `raw_info` is valid and non-null.
+ let par_ptr = unsafe { (*raw_info.as_ptr()).par };
+ if par_ptr.is_null() && data_size > 0 {
+ // SAFETY: We just allocated this, so it's safe to free.
+ unsafe { bindings::framebuffer_release(raw_info.as_ptr()) };
+ return Err(ENOMEM);
+ }
+
+ // Cast `par` to our data type pointer.
+ // SAFETY: `framebuffer_alloc` allocated enough space for `T::Data`.
+ let data_ptr = par_ptr.cast::<T::Data>();
+
+ // Initialize the data.
+ // SAFETY: `data_ptr` is a valid pointer to uninitialized memory of the correct size, and
+ // will not move until it is dropped.
+ if let Err(e) = unsafe { data.__pinned_init(data_ptr) } {
+ // SAFETY: We just allocated this, so it's safe to free.
+ unsafe { bindings::framebuffer_release(raw_info.as_ptr()) };
+ return Err(e);
+ }
+
+ // Set up `fb_ops`.
+ // SAFETY: `raw_info` is valid.
+ unsafe {
+ (*raw_info.as_ptr()).fbops = &Self::FBOPS;
+ }
+
+ // Initialize refcount to 1 for the `ARef` we're about to return.
+ // SAFETY: `raw_info` is valid and points to a properly allocated `fb_info`.
+ let device_ref = unsafe { &*raw_info.cast::<Self>().as_ptr() };
+ // SAFETY: `device_ref` is valid and points to a properly allocated `fb_info`.
+ unsafe {
+ device_ref.refcount().set(1);
+ }
+
+ // SAFETY: We've initialized the refcount to 1, and we're taking ownership of it.
+ // `Device<T>` is `#[repr(transparent)]` around `Opaque<fb_info>`, so this cast is valid.
+ Ok(unsafe { ARef::from_raw(raw_info.cast::<Self>()) })
+ }
+
+ pub(crate) fn as_raw(&self) -> *mut bindings::fb_info {
+ self.0.get()
+ }
+
+ /// Returns a reference to the refcount field of the `fb_info`.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `self.as_raw()` is valid.
+ unsafe fn refcount(&self) -> &Refcount {
+ // SAFETY: `Refcount` is a transparent wrapper around `refcount_t`, and `count` is the
+ // first field of `fb_info`. By the safety requirements, `self.as_raw()` is valid.
+ unsafe {
+ let count_ptr = ptr::addr_of_mut!((*self.as_raw()).count);
+ &*count_ptr.cast::<Refcount>()
+ }
+ }
+
+ /// Creates a reference from a raw `fb_info` pointer.
+ ///
+ /// # Safety
+ ///
+ /// Callers must ensure that `ptr` is valid, non-null, and points to an `fb_info`
+ /// that was created by `Device::<T>::new()`.
+ pub(crate) unsafe fn from_raw<'a>(ptr: *const bindings::fb_info) -> &'a Self {
+ // SAFETY: `Device<T>` is a transparent wrapper around `Opaque<fb_info>`.
+ unsafe { &*ptr.cast::<Self>() }
+ }
+
+ /// Returns a reference to the driver data stored in `fb_info.par`.
+ pub fn data(&self) -> &T::Data {
+ // SAFETY: By the type invariant, `info->par` points to a valid `T::Data` instance.
+ unsafe {
+ let par = (*self.as_raw()).par;
+ &*par.cast::<T::Data>()
+ }
+ }
+}
+
+impl<T: fb::Driver> Deref for Device<T> {
+ type Target = T::Data;
+
+ fn deref(&self) -> &Self::Target {
+ self.data()
+ }
+}
+
+// SAFETY: Framebuffer device objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: fb::Driver> AlwaysRefCounted for Device<T> {
+ fn inc_ref(&self) {
+ // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
+ unsafe { self.refcount().inc() };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: By the safety requirements, `obj` is valid.
+ let device = unsafe { &*obj.as_ptr() };
+
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ if unsafe { device.refcount().dec_and_test() } {
+ Self::destroy_callback(device.as_raw());
+ }
+ }
+}
+
+impl<T: fb::Driver> AsRef<device::Device> for Device<T> {
+ fn as_ref(&self) -> &device::Device {
+ // SAFETY: `fb_info::device` is valid as long as the `fb_info` itself is valid, which is
+ // guaranteed by the type invariant.
+ unsafe { device::Device::from_raw((*self.as_raw()).device) }
+ }
+}
+
+// SAFETY: `Device<T>` can be sent to any thread.
+unsafe impl<T: fb::Driver> Send for Device<T> {}
+
+// SAFETY: `Device<T>` can be shared among threads because all immutable methods are protected by
+// the synchronization in `struct fb_info`.
+unsafe impl<T: fb::Driver> Sync for Device<T> {}
diff --git a/rust/kernel/fb/driver.rs b/rust/kernel/fb/driver.rs
new file mode 100644
index 000000000000..fa06a613c909
--- /dev/null
+++ b/rust/kernel/fb/driver.rs
@@ -0,0 +1,169 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer driver core.
+//!
+//! This module provides the core abstractions for implementing framebuffer drivers.
+//!
+//! C header: [`include/linux/fb.h`]
+
+use crate::{
+ bindings, device, devres, error::to_result, fb, fs::file, mm, prelude::*, sync::aref::ARef,
+};
+use macros::vtable;
+
+/// Framebuffer driver information.
+pub struct DriverInfo {
+ /// Driver name.
+ pub name: &'static CStr,
+ /// Driver description.
+ pub desc: &'static CStr,
+}
+
+/// Framebuffer operations trait.
+///
+/// This trait defines the operations that a framebuffer driver must or can implement.
+/// It corresponds to `struct fb_ops` in C.
+///
+/// All methods receive a `device` parameter that provides access to both driver-specific data
+/// (via `device.data()` or direct dereference) and generic framebuffer info (via `device.var()`,
+/// `device.fix()`, etc.).
+#[vtable]
+pub trait Operations {
+ /// Driver-specific data type for operations context.
+ type Data: Sync + Send;
+
+ /// Read from framebuffer device.
+ ///
+ /// For framebuffers with strange non-linear layouts or that do not work with normal memory
+ /// mapped access.
+ fn read(
+ _device: &fb::Device<impl Driver<Data = Self::Data>>,
+ _buf: &mut [u8],
+ _ppos: &mut file::Offset,
+ ) -> Result<usize> {
+ Err(EINVAL)
+ }
+
+ /// Write to framebuffer device.
+ ///
+ /// For framebuffers with strange non-linear layouts or that do not work with normal memory
+ /// mapped access.
+ fn write(
+ _device: &fb::Device<impl Driver<Data = Self::Data>>,
+ _buf: &[u8],
+ _ppos: &mut file::Offset,
+ ) -> Result<usize> {
+ Err(EINVAL)
+ }
+
+ /// Set a color register.
+ fn setcolreg(
+ _device: &fb::Device<impl Driver<Data = Self::Data>>,
+ _regno: u32,
+ _red: u32,
+ _green: u32,
+ _blue: u32,
+ _transp: u32,
+ ) -> Result {
+ Ok(())
+ }
+
+ /// Draws a rectangle.
+ fn fillrect(_device: &fb::Device<impl Driver<Data = Self::Data>>, _rect: &fb::FillRect) {
+ // Default: no-op (driver may rely on software fallback or macro)
+ }
+
+ /// Copy data from area to another.
+ fn copyarea(_device: &fb::Device<impl Driver<Data = Self::Data>>, _area: &fb::CopyArea) {
+ // Default: no-op (driver may rely on software fallback or macro)
+ }
+
+ /// Draws an image to the display.
+ fn imageblit(_device: &fb::Device<impl Driver<Data = Self::Data>>, _image: &fb::Image) {
+ // Default: no-op (driver may rely on software fallback or macro)
+ }
+
+ /// Perform framebuffer-specific mmap.
+ fn mmap(
+ _device: &fb::Device<impl Driver<Data = Self::Data>>,
+ _vma: &mm::virt::VmaNew,
+ ) -> Result {
+ Err(EINVAL)
+ }
+
+ /// Teardown any resources to do with this framebuffer.
+ ///
+ /// Called when the last reference to the framebuffer is dropped. Use this to clean up
+ /// driver-specific resources.
+ ///
+ /// Note: The framework automatically calls `framebuffer_release()` after this method
+ /// returns, so drivers should *not* call `framebuffer_release()` themselves. This follows
+ /// RAII principles: since `Device::new()` calls `framebuffer_alloc()`, the framework is
+ /// responsible for calling `framebuffer_release()`.
+ fn destroy(_device: &fb::Device<impl Driver<Data = Self::Data>>) {}
+}
+
+/// Trait for framebuffer drivers.
+#[vtable]
+pub trait Driver {
+ /// Driver-specific context data.
+ type Data: Sync + Send;
+
+ /// Operations implementation for this driver.
+ type Ops: Operations<Data = Self::Data>;
+
+ /// Driver metadata.
+ const INFO: DriverInfo;
+}
+
+/// Registration for a framebuffer device.
+///
+/// The device is unregistered when this structure is dropped.
+pub struct Registration<T: Driver>(ARef<fb::Device<T>>);
+
+impl<T: Driver> Registration<T> {
+ /// Creates a new [`Registration`] and registers the framebuffer device.
+ fn new(fb: &fb::Device<T>) -> Result<Self> {
+ // SAFETY: `fb.as_raw()` is valid by the invariants of `fb::Device`.
+ to_result(unsafe { bindings::register_framebuffer(fb::Device::as_raw(fb)) })?;
+
+ Ok(Self(ARef::from(fb)))
+ }
+
+ /// Creates a new [`Registration`] and transfers ownership to devres.
+ pub fn new_foreign_owned(fb: &fb::Device<T>, dev: &device::Device<device::Bound>) -> Result
+ where
+ T: 'static,
+ {
+ // Verify that the device in fb_info matches the provided device
+ let fb_device = <fb::Device<T> as AsRef<device::Device>>::as_ref(fb);
+ if fb_device.as_raw() != dev.as_raw() {
+ return Err(EINVAL);
+ }
+
+ let reg = Registration::<T>::new(fb)?;
+
+ devres::register(dev, reg, GFP_KERNEL)
+ }
+
+ /// Returns a reference to the registered framebuffer device.
+ pub fn device(&self) -> &fb::Device<T> {
+ &self.0
+ }
+}
+
+// SAFETY: All `&self` methods on this type are thread-safe. `ARef<fb::Device<T>>` and
+// `fb::Device<T>` are `Sync`, so it is safe to share `Registration` between threads.
+unsafe impl<T: Driver> Sync for Registration<T> {}
+
+// SAFETY: Registration and unregistration from the framebuffer subsystem can happen from any
+// thread.
+unsafe impl<T: Driver> Send for Registration<T> {}
+
+impl<T: Driver> Drop for Registration<T> {
+ fn drop(&mut self) {
+ // SAFETY: `self.0` is guaranteed to be valid for the lifetime of `Registration`. The
+ // existence of this `Registration` guarantees that the device is registered.
+ unsafe { bindings::unregister_framebuffer(fb::Device::as_raw(&self.0)) };
+ }
+}
diff --git a/rust/kernel/fb/io.rs b/rust/kernel/fb/io.rs
new file mode 100644
index 000000000000..fde7d9eab7b7
--- /dev/null
+++ b/rust/kernel/fb/io.rs
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer I/O helpers.
+//!
+//! This module provides safe wrappers for the C `fb_io_*` helpers.
+//!
+//! C header: [`include/linux/fb.h`](srctree/include/linux/fb.h)
+
+use crate::{
+ bindings,
+ error::{to_result, Result},
+ fb,
+ fs::file,
+ mm,
+ prelude::*,
+};
+
+/// Generic framebuffer read helper.
+///
+/// Calls the C `fb_io_read` helper.
+pub fn fb_io_read<T: fb::Driver>(
+ device: &fb::Device<T>,
+ buf: &mut [u8],
+ ppos: &mut file::Offset,
+) -> Result<usize> {
+ // SAFETY: Both `device.as_raw()` and `ppos` are valid by type invariants, and `buf` is a valid
+ // mutable slice. The C helper treats the buffer pointer as `__user` and will return `-EFAULT`
+ // if it is not a valid user pointer.
+ let result = unsafe {
+ bindings::fb_io_read(
+ device.as_raw(),
+ buf.as_mut_ptr() as *mut core::ffi::c_char,
+ buf.len(),
+ ppos as *mut file::Offset as *mut bindings::loff_t,
+ )
+ };
+ if result < 0 {
+ Err(Error::from_errno(result as i32))
+ } else {
+ Ok(result as usize)
+ }
+}
+
+/// Generic framebuffer write helper.
+///
+/// Calls the C `fb_io_write` helper.
+pub fn fb_io_write<T: fb::Driver>(
+ device: &fb::Device<T>,
+ buf: &[u8],
+ ppos: &mut file::Offset,
+) -> Result<usize> {
+ // SAFETY: Both `device.as_raw()` and `ppos` are valid by type invariants, and `buf` is a valid
+ // slice. The C helper treats the buffer pointer as `__user` and will return `-EFAULT` if it is
+ // not a valid user pointer.
+ let result = unsafe {
+ bindings::fb_io_write(
+ device.as_raw(),
+ buf.as_ptr() as *const core::ffi::c_char,
+ buf.len(),
+ ppos as *mut file::Offset as *mut bindings::loff_t,
+ )
+ };
+ if result < 0 {
+ Err(Error::from_errno(result as i32))
+ } else {
+ Ok(result as usize)
+ }
+}
+
+/// Generic framebuffer mmap helper.
+///
+/// Calls the C `fb_io_mmap` helper.
+pub fn fb_io_mmap<T: fb::Driver>(device: &fb::Device<T>, vma: &mm::virt::VmaNew) -> Result {
+ // SAFETY: Both `device.as_raw()` and `vma.as_ptr()` are valid by type invariants.
+ unsafe { to_result(bindings::fb_io_mmap(device.as_raw(), vma.as_ptr())) }
+}
diff --git a/rust/kernel/fb/mod.rs b/rust/kernel/fb/mod.rs
new file mode 100644
index 000000000000..d9a40488a4bd
--- /dev/null
+++ b/rust/kernel/fb/mod.rs
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer subsystem.
+//!
+//! This module provides abstractions for the Linux framebuffer subsystem,
+//! allowing drivers to be written in Rust.
+//!
+//! C headers:
+//! - [`include/linux/fb.h`](srctree/include/linux/fb.h)
+
+pub mod blit;
+pub mod device;
+pub mod driver;
+pub mod io;
+pub mod screeninfo;
+
+pub use blit::{cfb_copyarea, cfb_fillrect, cfb_imageblit, CopyArea, FillRect, Image};
+pub use device::Device;
+pub use driver::{Driver, DriverInfo, Operations, Registration};
+pub use io::{fb_io_mmap, fb_io_read, fb_io_write};
+pub use screeninfo::{
+ accel, activate, types, visual, vmode, Bitfield, FixScreenInfo, VarScreenInfo,
+};
diff --git a/rust/kernel/fb/screeninfo.rs b/rust/kernel/fb/screeninfo.rs
new file mode 100644
index 000000000000..41315131b53f
--- /dev/null
+++ b/rust/kernel/fb/screeninfo.rs
@@ -0,0 +1,318 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Framebuffer screen information types.
+//!
+//! This module provides safe wrappers for framebuffer screen information structures and
+//! related constants.
+//!
+//! C header: [`include/linux/fb.h`](srctree/include/linux/fb.h)
+
+use crate::{bindings, ffi, prelude::*};
+
+/// Framebuffer type constants.
+pub mod types {
+ /// Packed pixels.
+ pub const FB_TYPE_PACKED_PIXELS: u32 = crate::bindings::FB_TYPE_PACKED_PIXELS;
+}
+
+/// Framebuffer visual constants.
+pub mod visual {
+ /// True color.
+ pub const FB_VISUAL_TRUECOLOR: u32 = crate::bindings::FB_VISUAL_TRUECOLOR;
+}
+
+/// Framebuffer acceleration constants.
+pub mod accel {
+ /// No hardware accelerator.
+ pub const FB_ACCEL_NONE: u32 = crate::bindings::FB_ACCEL_NONE;
+}
+
+/// Framebuffer activation constants.
+pub mod activate {
+ /// Set values immediately (or at vblank).
+ pub const FB_ACTIVATE_NOW: u32 = crate::bindings::FB_ACTIVATE_NOW;
+}
+
+/// Framebuffer video mode constants.
+pub mod vmode {
+ /// Non-interlaced.
+ pub const FB_VMODE_NONINTERLACED: u32 = crate::bindings::FB_VMODE_NONINTERLACED;
+}
+
+/// Wrapper for `fb_bitfield`.
+///
+/// Describes a bitfield within a pixel, typically used for color components.
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct Bitfield(bindings::fb_bitfield);
+
+impl Bitfield {
+ /// Create a new `Bitfield`.
+ pub const fn new(offset: u32, length: u32, msb_right: u32) -> Self {
+ Self(bindings::fb_bitfield {
+ offset,
+ length,
+ msb_right,
+ })
+ }
+
+ /// Create a new `Bitfield` from the raw C structure.
+ ///
+ /// `fb_bitfield` is a POD type, so any bit pattern is valid.
+ pub(crate) const fn from_raw(raw: bindings::fb_bitfield) -> Self {
+ Self(raw)
+ }
+
+ /// Returns the wrapped C structure.
+ pub(crate) const fn into_raw(self) -> bindings::fb_bitfield {
+ self.0
+ }
+
+ /// Bit offset within the pixel.
+ pub const fn offset(&self) -> u32 {
+ self.0.offset
+ }
+
+ /// Bitfield length in bits.
+ pub const fn length(&self) -> u32 {
+ self.0.length
+ }
+}
+
+/// Wrapper for `fb_var_screeninfo`.
+///
+/// Describes variable screen parameters that can be changed by the user or driver
+/// (e.g., resolution, color depth).
+#[repr(transparent)]
+pub struct VarScreenInfo(bindings::fb_var_screeninfo);
+
+impl VarScreenInfo {
+ /// Create a zeroed `VarScreenInfo`.
+ ///
+ /// Most fields will need to be set by the driver.
+ ///
+ /// `fb_var_screeninfo` is a POD type, so the all-zero bit pattern is valid.
+ pub const fn new_zeroed() -> Self {
+ Self(unsafe { core::mem::zeroed() })
+ }
+
+ /// Create a reference from a raw C structure pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `ptr` is valid for reading and remains valid for the lifetime
+ /// of the returned reference.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *const bindings::fb_var_screeninfo) -> &'a Self {
+ // SAFETY: `VarScreenInfo` is a transparent wrapper around `bindings::fb_var_screeninfo`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the wrapped C structure.
+ pub fn into_raw(self) -> bindings::fb_var_screeninfo {
+ self.0
+ }
+
+ /// Visible resolution (horizontal).
+ #[inline]
+ pub fn xres(&self) -> u32 {
+ self.0.xres
+ }
+
+ /// Visible resolution (vertical).
+ #[inline]
+ pub fn yres(&self) -> u32 {
+ self.0.yres
+ }
+
+ /// Bits per pixel.
+ pub fn bits_per_pixel(&self) -> u32 {
+ self.0.bits_per_pixel
+ }
+
+ /// Red color bitfield.
+ pub fn red(&self) -> Bitfield {
+ Bitfield::from_raw(self.0.red)
+ }
+
+ /// Green color bitfield.
+ pub fn green(&self) -> Bitfield {
+ Bitfield::from_raw(self.0.green)
+ }
+
+ /// Blue color bitfield.
+ pub fn blue(&self) -> Bitfield {
+ Bitfield::from_raw(self.0.blue)
+ }
+
+ /// Transparency/alpha color bitfield.
+ pub fn transp(&self) -> Bitfield {
+ Bitfield::from_raw(self.0.transp)
+ }
+
+ /// Set the visible resolution (horizontal).
+ pub fn set_xres(&mut self, xres: u32) {
+ self.0.xres = xres;
+ }
+
+ /// Set the visible resolution (vertical).
+ pub fn set_yres(&mut self, yres: u32) {
+ self.0.yres = yres;
+ }
+
+ /// Set the virtual resolution (horizontal).
+ pub fn set_xres_virtual(&mut self, xres_virtual: u32) {
+ self.0.xres_virtual = xres_virtual;
+ }
+
+ /// Set the virtual resolution (vertical).
+ pub fn set_yres_virtual(&mut self, yres_virtual: u32) {
+ self.0.yres_virtual = yres_virtual;
+ }
+
+ /// Set bits per pixel.
+ pub fn set_bits_per_pixel(&mut self, bits_per_pixel: u32) {
+ self.0.bits_per_pixel = bits_per_pixel;
+ }
+
+ /// Set the red color bitfield.
+ pub fn set_red(&mut self, red: Bitfield) {
+ self.0.red = red.into_raw();
+ }
+
+ /// Set the green color bitfield.
+ pub fn set_green(&mut self, green: Bitfield) {
+ self.0.green = green.into_raw();
+ }
+
+ /// Set the blue color bitfield.
+ pub fn set_blue(&mut self, blue: Bitfield) {
+ self.0.blue = blue.into_raw();
+ }
+
+ /// Set the transparency (alpha) color bitfield.
+ pub fn set_transp(&mut self, transp: Bitfield) {
+ self.0.transp = transp.into_raw();
+ }
+
+ /// Set the activation flags.
+ pub fn set_activate(&mut self, activate: u32) {
+ self.0.activate = activate;
+ }
+
+ /// Set the video mode flags.
+ pub fn set_vmode(&mut self, vmode: u32) {
+ self.0.vmode = vmode;
+ }
+
+ /// Set the width (for compatibility, typically same as xres).
+ pub fn set_width(&mut self, width: u32) {
+ self.0.width = width;
+ }
+
+ /// Set the height (for compatibility, typically same as yres).
+ pub fn set_height(&mut self, height: u32) {
+ self.0.height = height;
+ }
+}
+
+/// Wrapper for `fb_fix_screeninfo`.
+///
+/// Describes fixed screen parameters that cannot be changed by the user
+/// (e.g., framebuffer memory address, type).
+#[repr(transparent)]
+pub struct FixScreenInfo(bindings::fb_fix_screeninfo);
+
+impl FixScreenInfo {
+ /// Create a zeroed `FixScreenInfo`.
+ ///
+ /// Most fields will need to be set by the driver.
+ ///
+ /// `fb_fix_screeninfo` is a POD type, so the all-zero bit pattern is valid.
+ pub const fn new_zeroed() -> Self {
+ Self(unsafe { core::mem::zeroed() })
+ }
+
+ /// Create a reference from a raw C structure pointer.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `ptr` is valid for reading and remains valid for the lifetime
+ /// of the returned reference.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *const bindings::fb_fix_screeninfo) -> &'a Self {
+ // SAFETY: `FixScreenInfo` is a transparent wrapper around `bindings::fb_fix_screeninfo`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns the wrapped C structure.
+ pub fn into_raw(self) -> bindings::fb_fix_screeninfo {
+ self.0
+ }
+
+ /// Framebuffer memory start (physical address).
+ #[inline]
+ pub fn smem_start(&self) -> usize {
+ self.0.smem_start as usize
+ }
+
+ /// Length of framebuffer memory in bytes.
+ #[inline]
+ pub fn smem_len(&self) -> u32 {
+ self.0.smem_len
+ }
+
+ /// Length of a line in bytes.
+ #[inline]
+ pub fn line_length(&self) -> u32 {
+ self.0.line_length
+ }
+
+ /// Set the framebuffer identification string.
+ ///
+ /// The string (including NUL terminator) is truncated to 16 bytes if it exceeds that length.
+ pub fn set_id(&mut self, id: &'static CStr) {
+ const FB_ID_LEN: usize = 16;
+ let src = id.to_bytes_with_nul();
+
+ // Copy the string into the id array
+ let len = core::cmp::min(src.len(), FB_ID_LEN);
+ for (i, &byte) in src.iter().take(len).enumerate() {
+ self.0.id[i] = byte as ffi::c_char;
+ }
+ // Zero out the rest of the array if the string is shorter
+ for i in len..FB_ID_LEN {
+ self.0.id[i] = 0;
+ }
+ }
+
+ /// Set the framebuffer type.
+ pub fn set_type(&mut self, type_: u32) {
+ self.0.type_ = type_;
+ }
+
+ /// Set the visual type.
+ pub fn set_visual(&mut self, visual: u32) {
+ self.0.visual = visual;
+ }
+
+ /// Set the acceleration type.
+ pub fn set_accel(&mut self, accel: u32) {
+ self.0.accel = accel;
+ }
+
+ /// Set the physical address of framebuffer memory start.
+ pub fn set_smem_start(&mut self, start: usize) {
+ self.0.smem_start = start;
+ }
+
+ /// Set the length of framebuffer memory in bytes.
+ pub fn set_smem_len(&mut self, len: u32) {
+ self.0.smem_len = len;
+ }
+
+ /// Set the length of a line in bytes.
+ pub fn set_line_length(&mut self, length: u32) {
+ self.0.line_length = length;
+ }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index f812cf120042..feebfe3aa032 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -93,6 +93,8 @@
pub mod drm;
pub mod error;
pub mod faux;
+#[cfg(CONFIG_FB)]
+pub mod fb;
#[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
pub mod firmware;
pub mod fmt;
--
2.25.1
^ permalink raw reply related
* [PATCH v1 v1 4/4] rust: fb: add simplefb test driver
From: pengfuyuan @ 2026-01-26 8:17 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda
Cc: Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, Greg Kroah-Hartman,
Rafael J . Wysocki, David Airlie, Simona Vetter, Helge Deller,
Hans de Goede, Thomas Zimmermann, Lee Jones, Sam Ravnborg,
Zsolt Kajtar, Ville Syrjälä, rust-for-linux,
linux-kernel, dri-devel, linux-fbdev, pengfuyuan
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
Add a test driver for the Rust framebuffer framework abstraction. This
driver is a Rust port of the C simplefb driver (`drivers/video/fbdev/simplefb.c`)
and serves as a validation and testing tool for the Rust framebuffer API
implementation.
The driver implements a minimal framebuffer driver that assumes the display
hardware has been initialized before the kernel boots, and the kernel simply
renders to the pre-allocated framebuffer surface. Configuration regarding
surface address, size, and format must be provided through device tree or
platform data.
Key features:
- Supports 11 pixel formats (RGB565, RGBA5551, XRGB1555, ARGB1555, RGB888,
XRGB8888, ARGB8888, XBGR8888, ABGR8888, XRGB2101010, ARGB2101010)
- Implements all required framebuffer operations via the `fb::Operations` trait:
- I/O operations (`read`, `write`) using generic helpers
- Color register management (`setcolreg`) with pseudo-palette support
- Blitting operations (`fillrect`, `copyarea`, `imageblit`) using software
implementations
- Memory mapping (`mmap`) using generic helpers
- Supports both device tree and platform data configuration
- Uses RAII for resource management (memory regions, I/O mappings)
- Integrates with devres for automatic resource cleanup
**WARNING**: This driver is for testing purposes only and should not be used
in production systems. It has incomplete functionality compared to the C
simplefb driver (no clock management, power management, regulator management,
kernel parameter parsing, or aperture acquisition support). For production
use, please use FB_SIMPLE instead.
The driver demonstrates the usage of the Rust framebuffer framework:
- `fb::Device` for framebuffer device lifecycle management
- `fb::Driver` and `fb::Operations` traits for driver implementation
- `fb::Registration` for device registration with devres integration
- `fb::FixScreenInfo` and `fb::VarScreenInfo` for screen configuration
- `fb::Bitfield` for color component bitfield manipulation
- Generic I/O helpers (`fb_io_read`, `fb_io_write`, `fb_io_mmap`)
- Software blitting functions (`cfb_fillrect`, `cfb_copyarea`, `cfb_imageblit`)
Signed-off-by: pengfuyuan <pengfuyuan@kylinos.cn>
---
drivers/video/fbdev/Kconfig | 21 +
drivers/video/fbdev/Makefile | 1 +
drivers/video/fbdev/simplefb_rust.rs | 653 +++++++++++++++++++++++++++
rust/bindings/bindings_helper.h | 1 +
4 files changed, 676 insertions(+)
create mode 100644 drivers/video/fbdev/simplefb_rust.rs
diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig
index a733f90eca55..0b615c85ef6c 100644
--- a/drivers/video/fbdev/Kconfig
+++ b/drivers/video/fbdev/Kconfig
@@ -1797,6 +1797,27 @@ config FB_SIMPLE
Configuration re: surface address, size, and format must be provided
through device tree, or plain old platform data.
+config FB_SIMPLE_RUST
+ tristate "Simple framebuffer support (Rust) [TEST ONLY]"
+ depends on FB && RUST
+ depends on !DRM_SIMPLEDRM
+ depends on !FB_SIMPLE
+ select APERTURE_HELPERS
+ select FB_IOMEM_HELPERS
+ help
+ WARNING: This driver is for testing purposes only and should not be
+ used in production systems.
+
+ This is a test driver for the Rust framebuffer framework abstraction.
+ It is used to validate and test the Rust framebuffer API implementation.
+ The driver has incomplete functionality and is not suitable for real-world
+ use. For production use, please use FB_SIMPLE instead.
+
+ This driver assumes that the display hardware has been initialized before
+ the kernel boots, and the kernel will simply render to the pre-allocated
+ frame buffer surface. Configuration re: surface address, size, and format
+ must be provided through device tree, or plain old platform data.
+
config FB_SSD1307
tristate "Solomon SSD1307 framebuffer support"
depends on FB && I2C
diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile
index b3d12f977c06..58dff87966ed 100644
--- a/drivers/video/fbdev/Makefile
+++ b/drivers/video/fbdev/Makefile
@@ -123,6 +123,7 @@ obj-$(CONFIG_FB_VGA16) += vga16fb.o
obj-$(CONFIG_FB_OF) += offb.o
obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o
obj-$(CONFIG_FB_SIMPLE) += simplefb.o
+obj-$(CONFIG_FB_SIMPLE_RUST) += simplefb_rust.o
# the test framebuffer is last
obj-$(CONFIG_FB_VIRTUAL) += vfb.o
diff --git a/drivers/video/fbdev/simplefb_rust.rs b/drivers/video/fbdev/simplefb_rust.rs
new file mode 100644
index 000000000000..9806cc2daa3e
--- /dev/null
+++ b/drivers/video/fbdev/simplefb_rust.rs
@@ -0,0 +1,653 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+//! Simple framebuffer driver in Rust (TEST ONLY)
+//!
+//! **WARNING**: This driver is for testing purposes only and should not be used in production systems.
+//!
+//! This is a test driver for the Rust framebuffer framework abstraction. It is used to validate
+//! and test the Rust framebuffer API implementation. The driver has incomplete functionality and
+//! is not suitable for real-world use. For production use, please use FB_SIMPLE instead.
+//!
+//! **Limitations**:
+//! - No clock management support
+//! - No power management support
+//! - No regulator management support
+//! - No kernel parameter parsing support
+//! - No `devm_aperture_acquire_for_platform_device` support
+//!
+//! This driver assumes that the display hardware has been initialized before the kernel boots,
+//! and the kernel will simply render to the pre-allocated frame buffer surface. Configuration
+//! regarding surface address, size, and format must be provided through device tree or platform data.
+
+use kernel::{
+ bindings, c_str,
+ device::Core,
+ devres::Devres,
+ fb,
+ io::{
+ mem::IoMem,
+ resource::{Flags, Region, Resource},
+ PhysAddr, ResourceSize,
+ },
+ macros::vtable,
+ of, platform,
+ prelude::*,
+ str::CStr,
+ sync::aref::ARef,
+};
+
+/// Pseudo palette size for framebuffer
+const PSEUDO_PALETTE_SIZE: usize = 16;
+
+/// Number of supported framebuffer formats
+const SIMPLEFB_FORMAT_COUNT: usize = 11;
+
+/// Initialize fixed screen information template
+fn init_simplefb_fix() -> fb::FixScreenInfo {
+ let mut fix = fb::FixScreenInfo::new_zeroed();
+
+ // Set the initial values
+ fix.set_id(c_str!("simplefb-rust"));
+ fix.set_type(fb::types::FB_TYPE_PACKED_PIXELS);
+ fix.set_visual(fb::visual::FB_VISUAL_TRUECOLOR);
+ fix.set_accel(fb::accel::FB_ACCEL_NONE);
+
+ fix
+}
+
+/// Initialize variable screen information template
+fn init_simplefb_var() -> fb::VarScreenInfo {
+ let mut var = fb::VarScreenInfo::new_zeroed();
+
+ // Set the initial values
+ var.set_height(u32::MAX);
+ var.set_width(u32::MAX);
+ var.set_activate(fb::activate::FB_ACTIVATE_NOW);
+ var.set_vmode(fb::vmode::FB_VMODE_NONINTERLACED);
+
+ var
+}
+
+/// Framebuffer pixel format descriptor.
+struct SimplefbFormat {
+ name: &'static CStr,
+ bits_per_pixel: u32,
+ red: fb::Bitfield,
+ green: fb::Bitfield,
+ blue: fb::Bitfield,
+ transp: fb::Bitfield,
+ fourcc: u32,
+}
+
+impl SimplefbFormat {
+ const fn new(
+ name: &'static CStr,
+ bits_per_pixel: u32,
+ red: fb::Bitfield,
+ green: fb::Bitfield,
+ blue: fb::Bitfield,
+ transp: fb::Bitfield,
+ fourcc: u32,
+ ) -> Self {
+ Self {
+ name,
+ bits_per_pixel,
+ red,
+ green,
+ blue,
+ transp,
+ fourcc,
+ }
+ }
+
+ const fn name(&self) -> &'static CStr {
+ self.name
+ }
+
+ const fn bits_per_pixel(&self) -> u32 {
+ self.bits_per_pixel
+ }
+
+ const fn red(&self) -> fb::Bitfield {
+ self.red
+ }
+
+ const fn green(&self) -> fb::Bitfield {
+ self.green
+ }
+
+ const fn blue(&self) -> fb::Bitfield {
+ self.blue
+ }
+
+ const fn transp(&self) -> fb::Bitfield {
+ self.transp
+ }
+
+ #[allow(dead_code)]
+ const fn fourcc(&self) -> u32 {
+ self.fourcc
+ }
+}
+
+/// Supported framebuffer formats.
+///
+/// This matches the format array from `include/linux/platform_data/simplefb.h`.
+const SIMPLEFB_FORMATS: [SimplefbFormat; SIMPLEFB_FORMAT_COUNT] = [
+ SimplefbFormat::new(
+ c_str!("r5g6b5"),
+ 16,
+ fb::Bitfield::new(11, 5, 0),
+ fb::Bitfield::new(5, 6, 0),
+ fb::Bitfield::new(0, 5, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x36314752, // DRM_FORMAT_RGB565 = fourcc_code('R', 'G', '1', '6')
+ ),
+ SimplefbFormat::new(
+ c_str!("r5g5b5a1"),
+ 16,
+ fb::Bitfield::new(11, 5, 0),
+ fb::Bitfield::new(6, 5, 0),
+ fb::Bitfield::new(1, 5, 0),
+ fb::Bitfield::new(0, 1, 0),
+ 0x31354152, // DRM_FORMAT_RGBA5551 = fourcc_code('R', 'A', '1', '5')
+ ),
+ SimplefbFormat::new(
+ c_str!("x1r5g5b5"),
+ 16,
+ fb::Bitfield::new(10, 5, 0),
+ fb::Bitfield::new(5, 5, 0),
+ fb::Bitfield::new(0, 5, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x35315258, // DRM_FORMAT_XRGB1555 = fourcc_code('X', 'R', '1', '5')
+ ),
+ SimplefbFormat::new(
+ c_str!("a1r5g5b5"),
+ 16,
+ fb::Bitfield::new(10, 5, 0),
+ fb::Bitfield::new(5, 5, 0),
+ fb::Bitfield::new(0, 5, 0),
+ fb::Bitfield::new(15, 1, 0),
+ 0x35315241, // DRM_FORMAT_ARGB1555 = fourcc_code('A', 'R', '1', '5')
+ ),
+ SimplefbFormat::new(
+ c_str!("r8g8b8"),
+ 24,
+ fb::Bitfield::new(16, 8, 0),
+ fb::Bitfield::new(8, 8, 0),
+ fb::Bitfield::new(0, 8, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x34324752, // DRM_FORMAT_RGB888 = fourcc_code('R', 'G', '2', '4')
+ ),
+ SimplefbFormat::new(
+ c_str!("x8r8g8b8"),
+ 32,
+ fb::Bitfield::new(16, 8, 0),
+ fb::Bitfield::new(8, 8, 0),
+ fb::Bitfield::new(0, 8, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x34325258, // DRM_FORMAT_XRGB8888 = fourcc_code('X', 'R', '2', '4')
+ ),
+ SimplefbFormat::new(
+ c_str!("a8r8g8b8"),
+ 32,
+ fb::Bitfield::new(16, 8, 0),
+ fb::Bitfield::new(8, 8, 0),
+ fb::Bitfield::new(0, 8, 0),
+ fb::Bitfield::new(24, 8, 0),
+ 0x34325241, // DRM_FORMAT_ARGB8888 = fourcc_code('A', 'R', '2', '4')
+ ),
+ SimplefbFormat::new(
+ c_str!("x8b8g8r8"),
+ 32,
+ fb::Bitfield::new(0, 8, 0),
+ fb::Bitfield::new(8, 8, 0),
+ fb::Bitfield::new(16, 8, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x34324258, // DRM_FORMAT_XBGR8888 = fourcc_code('X', 'B', '2', '4')
+ ),
+ SimplefbFormat::new(
+ c_str!("a8b8g8r8"),
+ 32,
+ fb::Bitfield::new(0, 8, 0),
+ fb::Bitfield::new(8, 8, 0),
+ fb::Bitfield::new(16, 8, 0),
+ fb::Bitfield::new(24, 8, 0),
+ 0x34324241, // DRM_FORMAT_ABGR8888 = fourcc_code('A', 'B', '2', '4')
+ ),
+ SimplefbFormat::new(
+ c_str!("x2r10g10b10"),
+ 32,
+ fb::Bitfield::new(20, 10, 0),
+ fb::Bitfield::new(10, 10, 0),
+ fb::Bitfield::new(0, 10, 0),
+ fb::Bitfield::new(0, 0, 0),
+ 0x30335258, // DRM_FORMAT_XRGB2101010 = fourcc_code('X', 'R', '3', '0')
+ ),
+ SimplefbFormat::new(
+ c_str!("a2r10g10b10"),
+ 32,
+ fb::Bitfield::new(20, 10, 0),
+ fb::Bitfield::new(10, 10, 0),
+ fb::Bitfield::new(0, 10, 0),
+ fb::Bitfield::new(30, 2, 0),
+ 0x30335241, // DRM_FORMAT_ARGB2101010 = fourcc_code('A', 'R', '3', '0')
+ ),
+];
+
+/// Find a format by name.
+fn find_format(name: &CStr) -> Option<&'static SimplefbFormat> {
+ SIMPLEFB_FORMATS
+ .iter()
+ .find(|format| format.name().to_bytes() == name.to_bytes())
+}
+
+/// Platform data for simple framebuffer devices.
+struct SimplefbPlatformData {
+ width: u32,
+ height: u32,
+ stride: u32,
+ format: &'static CStr,
+}
+
+impl SimplefbPlatformData {
+ /// Extract platform data from a device.
+ ///
+ /// # Safety
+ ///
+ /// * The platform data type must be `simplefb_platform_data`.
+ /// * The platform data structure must be properly initialized.
+ unsafe fn from_device(pdev: &platform::Device<Core>) -> Result<Self> {
+ let dev = pdev.as_ref();
+ // SAFETY: The caller guarantees the platform data type matches.
+ let pd_opaque = unsafe { dev.platdata::<bindings::simplefb_platform_data>()? };
+ // SAFETY: The caller guarantees the platform data structure is properly initialized.
+ let pd_raw = unsafe { &*pd_opaque.get() };
+
+ let format_cstr = if pd_raw.format.is_null() {
+ return Err(ENODEV);
+ } else {
+ // SAFETY: `format` is not null (checked above) and points to a valid C string.
+ unsafe { CStr::from_char_ptr(pd_raw.format) }
+ };
+
+ Ok(Self {
+ width: pd_raw.width,
+ height: pd_raw.height,
+ stride: pd_raw.stride,
+ format: format_cstr,
+ })
+ }
+
+ const fn width(&self) -> u32 {
+ self.width
+ }
+
+ const fn height(&self) -> u32 {
+ self.height
+ }
+
+ const fn stride(&self) -> u32 {
+ self.stride
+ }
+
+ const fn format(&self) -> &'static CStr {
+ self.format
+ }
+}
+
+/// Parsed framebuffer parameters.
+struct SimplefbParams {
+ width: u32,
+ height: u32,
+ stride: u32,
+ format: &'static SimplefbFormat,
+}
+
+/// Driver-specific data for the simple framebuffer.
+#[pin_data]
+struct SimplefbData {
+ palette: [u32; PSEUDO_PALETTE_SIZE],
+ #[allow(dead_code)] // Reserved for future devm_aperture_acquire_for_platform_device support
+ base: PhysAddr,
+ #[allow(dead_code)] // Reserved for future devm_aperture_acquire_for_platform_device support
+ size: ResourceSize,
+ #[allow(dead_code)] // Used via Drop trait for automatic resource cleanup
+ mem: Option<Region>,
+ #[pin]
+ #[allow(dead_code)]
+ // I/O memory mapping; ensures iounmap happens before release_mem_region via drop order
+ _iomem: Pin<KBox<Devres<IoMem<0>>>>,
+}
+
+/// Simple framebuffer operations implementation
+struct SimplefbOps;
+
+#[vtable]
+impl fb::Operations for SimplefbOps {
+ type Data = SimplefbData;
+
+ fn read(
+ device: &fb::Device<impl fb::Driver<Data = Self::Data>>,
+ buf: &mut [u8],
+ ppos: &mut kernel::fs::file::Offset,
+ ) -> Result<usize> {
+ fb::fb_io_read(device, buf, ppos)
+ }
+
+ fn write(
+ device: &fb::Device<impl fb::Driver<Data = Self::Data>>,
+ buf: &[u8],
+ ppos: &mut kernel::fs::file::Offset,
+ ) -> Result<usize> {
+ fb::fb_io_write(device, buf, ppos)
+ }
+
+ fn setcolreg(
+ device: &fb::Device<impl fb::Driver<Data = Self::Data>>,
+ regno: u32,
+ red: u32,
+ green: u32,
+ blue: u32,
+ _transp: u32,
+ ) -> Result {
+ if regno >= PSEUDO_PALETTE_SIZE as u32 {
+ return Err(EINVAL);
+ }
+
+ let var = device.var();
+ let red_len = var.red().length();
+ let green_len = var.green().length();
+ let blue_len = var.blue().length();
+ let red_offset = var.red().offset();
+ let green_offset = var.green().offset();
+ let blue_offset = var.blue().offset();
+
+ let cr = red >> (16 - red_len);
+ let cg = green >> (16 - green_len);
+ let cb = blue >> (16 - blue_len);
+
+ let mut value = (cr << red_offset) | (cg << green_offset) | (cb << blue_offset);
+
+ let transp_len = var.transp().length();
+ if transp_len > 0 {
+ let transp_offset = var.transp().offset();
+ let mask = ((1u32 << transp_len) - 1) << transp_offset;
+ value |= mask;
+ }
+
+ // Access the palette through the driver data
+ // SAFETY: device.pseudo_palette() returns a valid pointer to the palette array
+ unsafe {
+ let palette = device.pseudo_palette() as *mut u32;
+ *palette.add(regno as usize) = value;
+ }
+
+ Ok(())
+ }
+
+ fn fillrect(device: &fb::Device<impl fb::Driver<Data = Self::Data>>, rect: &fb::FillRect) {
+ fb::cfb_fillrect(device, rect);
+ }
+
+ fn copyarea(device: &fb::Device<impl fb::Driver<Data = Self::Data>>, area: &fb::CopyArea) {
+ fb::cfb_copyarea(device, area);
+ }
+
+ fn imageblit(device: &fb::Device<impl fb::Driver<Data = Self::Data>>, image: &fb::Image) {
+ fb::cfb_imageblit(device, image);
+ }
+
+ fn mmap(
+ device: &fb::Device<impl fb::Driver<Data = Self::Data>>,
+ vma: &kernel::mm::virt::VmaNew,
+ ) -> Result {
+ fb::fb_io_mmap(device, vma)
+ }
+}
+
+/// Framebuffer driver type.
+struct SimplefbDriverImpl;
+
+#[vtable]
+impl fb::Driver for SimplefbDriverImpl {
+ type Data = SimplefbData;
+ type Ops = SimplefbOps;
+
+ const INFO: fb::DriverInfo = fb::DriverInfo {
+ name: c_str!("simplefb-rust"),
+ desc: c_str!("Simple framebuffer driver"),
+ };
+}
+
+/// Platform driver data.
+struct SimplefbDriver {
+ _pdev: ARef<platform::Device>,
+}
+
+impl SimplefbDriver {
+ /// Map framebuffer memory with write-combining cache policy.
+ ///
+ /// Returns a tuple of the IoMem handle and the virtual address (screen_base).
+ fn map_framebuffer_memory(
+ pdev: &platform::Device<Core>,
+ ) -> Result<(Pin<KBox<Devres<IoMem<0>>>>, *mut u8)> {
+ let dev = pdev.as_ref();
+
+ let io_request = pdev.io_request_by_index(0).ok_or_else(|| {
+ dev_err!(dev, "[rust] No memory resource for framebuffer\n");
+ ENODEV
+ })?;
+
+ // Map with write-combining. The Devres must remain at a fixed address for the devres
+ // callback to work correctly, so we keep it in KBox.
+ let iomem_init = io_request.iomap_wc();
+ let iomem_kbox = KBox::pin_init(iomem_init, GFP_KERNEL)?;
+
+ let screen_base = {
+ let io = iomem_kbox.access(dev)?;
+ io.addr() as *mut u8
+ };
+
+ Ok((iomem_kbox, screen_base))
+ }
+
+ /// Parse platform data.
+ fn parse_pd(pdev: &platform::Device<Core>) -> Result<SimplefbParams> {
+ let dev = pdev.as_ref();
+
+ // SAFETY: The platform data type matches simplefb_platform_data for this device.
+ let pd = unsafe { SimplefbPlatformData::from_device(pdev)? };
+
+ let width = pd.width();
+ let height = pd.height();
+ let stride = pd.stride();
+ let format_cstr = pd.format();
+
+ let format = find_format(format_cstr).ok_or_else(|| {
+ dev_err!(dev, "[rust] Invalid format value\n");
+ EINVAL
+ })?;
+
+ Ok(SimplefbParams {
+ width,
+ height,
+ stride,
+ format,
+ })
+ }
+
+ /// Parse device tree properties.
+ fn parse_dt(pdev: &platform::Device<Core>) -> Result<SimplefbParams> {
+ let dev = pdev.as_ref();
+ let fwnode = dev.fwnode().ok_or(ENODEV)?;
+
+ let width: u32 = fwnode.property_read(c_str!("width")).required_by(dev)?;
+ let height: u32 = fwnode.property_read(c_str!("height")).required_by(dev)?;
+ let stride: u32 = fwnode.property_read(c_str!("stride")).required_by(dev)?;
+
+ let format_name = fwnode
+ .property_read::<kernel::str::CString>(c_str!("format"))
+ .required_by(dev)?;
+
+ let format = find_format(&format_name).ok_or_else(|| {
+ dev_err!(dev, "[rust] Invalid format value\n");
+ EINVAL
+ })?;
+
+ Ok(SimplefbParams {
+ width,
+ height,
+ stride,
+ format,
+ })
+ }
+
+ /// Probe the framebuffer device.
+ fn probe_internal(pdev: &platform::Device<Core>) -> Result {
+ let dev = pdev.as_ref();
+
+ let params = Self::parse_pd(pdev).or_else(|_| {
+ if dev.fwnode().is_some() {
+ Self::parse_dt(pdev)
+ } else {
+ Err(ENODEV)
+ }
+ })?;
+
+ let resource = pdev.resource_by_index(0).ok_or_else(|| {
+ dev_err!(dev, "[rust] No memory resource\n");
+ EINVAL
+ })?;
+
+ let mem_region = resource.request_region(
+ resource.start(),
+ resource.size(),
+ c_str!("simplefb").to_cstring()?,
+ Flags::IORESOURCE_MEM,
+ );
+
+ let mem: &Resource = match &mem_region {
+ Some(region) => region as &Resource,
+ None => {
+ dev_warn!(
+ dev,
+ "[rust] simplefb: cannot reserve video memory at 0x{:x}-0x{:x}\n",
+ resource.start(),
+ resource.start() + resource.size() - 1
+ );
+ resource
+ }
+ };
+
+ let memory_start = mem.start();
+ let memory_size = mem.size();
+
+ let (iomem_kbox, screen_base) = Self::map_framebuffer_memory(pdev)?;
+
+ let fb_device = fb::Device::<SimplefbDriverImpl>::new(
+ dev,
+ try_pin_init!(SimplefbData {
+ palette: [0u32; PSEUDO_PALETTE_SIZE],
+ base: memory_start,
+ size: memory_size,
+ mem: mem_region,
+ _iomem: iomem_kbox,
+ }),
+ )?;
+
+ let mut fix_template = init_simplefb_fix();
+ let fmt = params.format;
+ let mut var_template = init_simplefb_var();
+
+ fix_template.set_smem_start(memory_start as usize);
+ fix_template.set_smem_len(memory_size as u32);
+ fix_template.set_line_length(params.stride);
+
+ var_template.set_xres(params.width);
+ var_template.set_yres(params.height);
+ var_template.set_xres_virtual(params.width);
+ var_template.set_yres_virtual(params.height);
+ var_template.set_bits_per_pixel(fmt.bits_per_pixel());
+ var_template.set_red(fmt.red());
+ var_template.set_green(fmt.green());
+ var_template.set_blue(fmt.blue());
+ var_template.set_transp(fmt.transp());
+
+ // SAFETY: We have exclusive access to the fb_device during initialization,
+ // before it is registered with the framebuffer subsystem.
+ unsafe {
+ fb_device.configure_fix(|fix| {
+ *fix = fix_template.into_raw();
+ });
+
+ fb_device.configure_var(|var| {
+ *var = var_template.into_raw();
+ });
+
+ fb_device.set_screen_base(screen_base);
+ fb_device.set_pseudo_palette(fb_device.data().palette.as_ptr() as *mut _);
+ }
+
+ dev_info!(
+ dev,
+ "[rust] framebuffer at 0x{:x}, 0x{:x} bytes\n",
+ fb_device.fix().smem_start(),
+ fb_device.fix().smem_len()
+ );
+
+ dev_info!(
+ dev,
+ "[rust] format={}, mode={}x{}x{}, linelength={}\n",
+ params.format.name(),
+ fb_device.var().xres(),
+ fb_device.var().yres(),
+ fb_device.var().bits_per_pixel(),
+ fb_device.fix().line_length()
+ );
+
+ // TODO: Implement devm_aperture_acquire_for_platform_device to manage framebuffer
+ // memory ownership and prevent conflicts with dedicated drivers (e.g., DRM).
+
+ if let Err(err) = fb::Registration::new_foreign_owned(&fb_device, pdev.as_ref()) {
+ dev_err!(
+ dev,
+ "[rust] Unable to register simplefb: {}\n",
+ err.to_errno()
+ );
+ return Err(err);
+ }
+
+ dev_info!(dev, "[rust] fb{}: simplefb registered!\n", fb_device.node());
+
+ Ok(())
+ }
+}
+
+kernel::of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <SimplefbDriver as platform::Driver>::IdInfo,
+ [(of::DeviceId::new(c_str!("simple-framebuffer")), ())]
+);
+
+impl platform::Driver for SimplefbDriver {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ pdev: &platform::Device<Core>,
+ _id_info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> {
+ Self::probe_internal(pdev)?;
+ Ok(Self { _pdev: pdev.into() })
+ }
+}
+
+kernel::module_platform_driver! {
+ type: SimplefbDriver,
+ name: "simple-framebuffer",
+ authors: ["pengfuyuan <pengfuyuan@kylinos.cn>", "Rust port"],
+ description: "Simple framebuffer driver (Rust)",
+ license: "GPL v2",
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index bc47806eb365..01eda020e7e6 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -70,6 +70,7 @@
#include <linux/pci.h>
#include <linux/phy.h>
#include <linux/pid_namespace.h>
+#include <linux/platform_data/simplefb.h>
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/poll.h>
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v2 01/12] firmware: google: framebuffer: Do not unregister platform device
From: Tzung-Bi Shih @ 2026-01-26 8:28 UTC (permalink / raw)
To: Thomas Zimmermann
Cc: briannorris, jwerner, javierm, samuel, maarten.lankhorst, mripard,
airlied, simona, chrome-platform, dri-devel, Hans de Goede,
linux-fbdev, stable
In-Reply-To: <20260115082128.12460-2-tzimmermann@suse.de>
On Thu, Jan 15, 2026 at 08:57:11AM +0100, Thomas Zimmermann wrote:
> The native driver takes over the framebuffer aperture by removing the
> system- framebuffer platform device. Afterwards the pointer in drvdata
> is dangling. Remove the entire logic around drvdata and let the kernel's
> aperture helpers handle this. The platform device depends on the native
> hardware device instead of the coreboot device anyway.
>
> When commit 851b4c14532d ("firmware: coreboot: Add coreboot framebuffer
> driver") added the coreboot framebuffer code, the kernel did not support
> device-based aperture management. Instead native driviers only removed
> the conflicting fbdev device. At that point, unregistering the framebuffer
> device most likely worked correctly. It was definitely broken after
> commit d9702b2a2171 ("fbdev/simplefb: Do not use struct
> fb_info.apertures"). So take this commit for the Fixes tag. Earlier
> releases might work depending on the native hardware driver.
>
> Signed-off-by: Thomas Zimmermann <tzimmermann@suse.de>
> Fixes: d9702b2a2171 ("fbdev/simplefb: Do not use struct fb_info.apertures")
Acked-by: Tzung-Bi Shih <tzungbi@kernel.org>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox