* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: pengfuyuan @ 2026-01-27 8:04 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Thomas Zimmermann, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, 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, Lee Jones, Sam Ravnborg, Zsolt Kajtar,
Ville Syrjälä, rust-for-linux, linux-kernel, dri-devel,
linux-fbdev
In-Reply-To: <DFYG7MT5JINY.1T8ZZ4ASIWXU@nvidia.com>
On Mon, Jan 26, 2026 at 07:28:21PM +0900, Alexandre Courbot wrote:
> On Mon Jan 26, 2026 at 7:01 PM JST, Thomas Zimmermann wrote:
> > Hi
> >
> > Am 26.01.26 um 09:17 schrieb pengfuyuan:
> >> This patch series adds Rust bindings and safe abstractions for the Linux
> >> framebuffer subsystem, enabling framebuffer drivers to be implemented in Rust.
> >
> > The framebuffer subsystem is obsolete and has been deprecated for a
> > decade. No new drivers accepted. Anything that really wants fbdev
> > already has a driver. Can we please let it die?
>
> This, and the patchset is also obviously AI-generated.
Hi,
Thank you for the feedback.
I’d like to be clear about how I used AI in this work:
1.Cover letter – Yes, I used AI to help summarize and phrase the cover letter.
2.Comments in the code – Some comments were written or refined with AI assistance.
3.Learning the codebase – When reading and understanding existing Rust-for-Linux code (including DRM and other abstractions), I used AI as a helper to analyze and explain structure and patterns.
4.Writing the code – The implementation was not fully generated by AI. I wrote the code myself and used AI mainly to look up existing abstractions, traits, and APIs (e.g. “how does X work? ”, “what’s the right trait for Y?”) while I was coding.
So: AI was used for summaries, comments, learning, and looking things up; the logic and structure of the code are mine, and I take responsibility for them.
If you have concerns about specific parts (e.g. wording, style, or design), I’m happy to rework those patches or to adjust how I describe tool use in future submissions.
Thanks,
pengfuyuan
^ permalink raw reply
* Re: [PATCH v1 v1 3/4] rust: fb: add framebuffer driver support
From: kernel test robot @ 2026-01-26 22:29 UTC (permalink / raw)
To: pengfuyuan, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda
Cc: llvm, oe-kbuild-all, 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-4-pengfuyuan@kylinos.cn>
Hi pengfuyuan,
kernel test robot noticed the following build errors:
[auto build test ERROR on rust/rust-next]
[also build test ERROR on driver-core/driver-core-linus linus/master v6.19-rc7]
[cannot apply to driver-core/driver-core-testing driver-core/driver-core-next next-20260123]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/pengfuyuan/rust-io-mem-add-ioremap_wc-support/20260126-162117
base: https://github.com/Rust-for-Linux/linux rust-next
patch link: https://lore.kernel.org/r/20260126081744.781392-4-pengfuyuan%40kylinos.cn
patch subject: [PATCH v1 v1 3/4] rust: fb: add framebuffer driver support
config: x86_64-rhel-9.4-rust (https://download.01.org/0day-ci/archive/20260127/202601270630.FHzCSbVv-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
rustc: rustc 1.88.0 (6b00bc388 2025-06-23)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260127/202601270630.FHzCSbVv-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202601270630.FHzCSbVv-lkp@intel.com/
All errors (new ones prefixed by >>):
>> error[E0308]: mismatched types
--> rust/kernel/fb/device.rs:307:23
|
307 | fb_read: Some(Self::read_callback),
| ---- ^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item
| |
| arguments to this enum variant are incorrect
|
= note: expected fn pointer `unsafe extern "C" fn(_, *mut u8, _, _) -> _`
found fn item `extern "C" fn(_, *mut i8, _, _) -> _ {fb::device::Device::<T>::read_callback}`
help: the type constructed contains `extern "C" fn(*mut fb_info, *mut i8, usize, *mut i64) -> isize {fb::device::Device::<T>::read_callback}` due to the type of the argument passed
--> rust/kernel/fb/device.rs:307:18
|
307 | fb_read: Some(Self::read_callback),
| ^^^^^-------------------^
| |
| this argument influences the type of `Some`
note: tuple variant defined here
--> /opt/cross/rustc-1.88.0-bindgen-0.72.1/rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:597:5
|
597 | Some(#[stable(feature = "rust1", since = "1.0.0")] T),
| ^^^^
--
>> error[E0308]: mismatched types
--> rust/kernel/fb/device.rs:308:24
|
308 | fb_write: Some(Self::write_callback),
| ---- ^^^^^^^^^^^^^^^^^^^^ expected fn pointer, found fn item
| |
| arguments to this enum variant are incorrect
|
= note: expected fn pointer `unsafe extern "C" fn(_, *const u8, _, _) -> _`
found fn item `extern "C" fn(_, *const i8, _, _) -> _ {fb::device::Device::<T>::write_callback}`
help: the type constructed contains `extern "C" fn(*mut fb_info, *const i8, usize, *mut i64) -> isize {fb::device::Device::<T>::write_callback}` due to the type of the argument passed
--> rust/kernel/fb/device.rs:308:19
|
308 | fb_write: Some(Self::write_callback),
| ^^^^^--------------------^
| |
| this argument influences the type of `Some`
note: tuple variant defined here
--> /opt/cross/rustc-1.88.0-bindgen-0.72.1/rustup/toolchains/1.88.0-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:597:5
|
597 | Some(#[stable(feature = "rust1", since = "1.0.0")] T),
| ^^^^
--
>> error[E0308]: mismatched types
--> rust/kernel/fb/io.rs:32:13
|
30 | bindings::fb_io_read(
| -------------------- arguments to this function are incorrect
31 | device.as_raw(),
32 | buf.as_mut_ptr() as *mut core::ffi::c_char,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `*mut u8`, found `*mut i8`
|
= note: expected raw pointer `*mut u8`
found raw pointer `*mut i8`
note: function defined here
--> rust/bindings/bindings_generated.rs:123562:12
|
123562 | pub fn fb_io_read(
| ^^^^^^^^^^
--
>> error[E0308]: mismatched types
--> rust/kernel/fb/io.rs:58:13
|
56 | bindings::fb_io_write(
| --------------------- arguments to this function are incorrect
57 | device.as_raw(),
58 | buf.as_ptr() as *const core::ffi::c_char,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `*const u8`, found `*const i8`
|
= note: expected raw pointer `*const u8`
found raw pointer `*const i8`
note: function defined here
--> rust/bindings/bindings_generated.rs:123570:12
|
123570 | pub fn fb_io_write(
| ^^^^^^^^^^^
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply
* [PATCH] printk, vt, fbcon: Remove console_conditional_schedule()
From: Sebastian Andrzej Siewior @ 2026-01-26 18:08 UTC (permalink / raw)
To: linux-kernel, linux-serial, linux-fbdev, dri-devel,
linux-rt-devel
Cc: Petr Mladek, Steven Rostedt, John Ogness, Sergey Senozhatsky,
Greg Kroah-Hartman, Jiri Slaby, Simona Vetter, Helge Deller
do_con_write(), fbcon_redraw.*() invoke console_conditional_schedule()
which is a conditional scheduling point based on printk's internal
variables console_may_schedule. It may only be used if the console lock
is acquired for instance via console_lock() or console_trylock().
Prinkt sets the internal variable to 1 (and allows to schedule)
if the console lock has been acquired via console_lock(). The trylock
does not allow it.
The console_conditional_schedule() invocation in do_con_write() is
invoked shortly before console_unlock().
The console_conditional_schedule() invocation in fbcon_redraw.*()
original from fbcon_scroll() / vt's con_scroll() which originate from a
line feed.
In console_unlock() the variable is set to 0 (forbids to schedule) and
it tries to schedule while making progress printing. This is brand new
compared to when console_conditional_schedule() was added in v2.4.9.11.
In v2.6.38-rc3, console_unlock() (started its existence) iterated over
all consoles and flushed them with disabled interrupts. A scheduling
attempt here was not possible, it relied that a long print scheduled
before console_unlock().
Since commit 8d91f8b15361d ("printk: do cond_resched() between lines
while outputting to consoles"), which appeared in v4.5-rc1,
console_unlock() attempts to schedule if it was allowed to schedule
while during console_lock(). Each record is idealy one line so after
every line feed.
This console_conditional_schedule() is also only relevant on
PREEMPT_NONE and PREEMPT_VOLUNTARY builds. In other configurations
cond_resched() becomes a nop and has no impact.
I'm bringing this all up just proof that it is not required anymore. It
becomes a problem on a PREEMPT_RT build with debug code enabled because
that might_sleep() in cond_resched() remains and triggers a warnings.
This is due to
legacy_kthread_func-> console_flush_one_record -> vt_console_print-> lf
-> con_scroll -> fbcon_scroll
and vt_console_print() acquires a spinlock_t which does not allow a
voluntary schedule. There is no need to fb_scroll() to schedule since
console_flush_one_record() attempts to schedule after each line.
!PREEMPT_RT is not affected because the legacy printing thread is only
enabled on PREEMPT_RT builds.
Therefore I suggest to remove console_conditional_schedule().
Cc: Simona Vetter <simona@ffwll.ch>
Cc: Helge Deller <deller@gmx.de>
Cc: linux-fbdev@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Fixes: 5f53ca3ff83b4 ("printk: Implement legacy printer kthread for PREEMPT_RT")
Signed-off-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
---
A follow-up to
https://lore.kernel.org/all/20260114145955.d924Z-zu@linutronix.de/
drivers/tty/vt/vt.c | 1 -
drivers/video/fbdev/core/fbcon.c | 6 ------
include/linux/console.h | 1 -
kernel/printk/printk.c | 16 ----------------
4 files changed, 24 deletions(-)
diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c
index 59b4b5e126ba1..53daf7614b1af 100644
--- a/drivers/tty/vt/vt.c
+++ b/drivers/tty/vt/vt.c
@@ -3236,7 +3236,6 @@ static int do_con_write(struct tty_struct *tty, const u8 *buf, int count)
goto rescan_last_byte;
}
con_flush(vc, &draw);
- console_conditional_schedule();
notify_update(vc);
return n;
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e865325d9..36dd9d4a46ae0 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -1607,12 +1607,10 @@ static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
start = s;
}
}
- console_conditional_schedule();
s++;
} while (s < le);
if (s > start)
fbcon_putcs(vc, start, s - start, dy, x);
- console_conditional_schedule();
dy++;
}
}
@@ -1648,14 +1646,12 @@ static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
}
scr_writew(c, d);
- console_conditional_schedule();
s++;
d++;
} while (s < le);
if (s > start)
par->bitops->bmove(vc, info, line + ycount, x, line, x, 1,
s - start);
- console_conditional_schedule();
if (ycount > 0)
line++;
else {
@@ -1703,13 +1699,11 @@ static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
}
}
scr_writew(c, d);
- console_conditional_schedule();
s++;
d++;
} while (s < le);
if (s > start)
fbcon_putcs(vc, start, s - start, line, x);
- console_conditional_schedule();
if (offset > 0)
line++;
else {
diff --git a/include/linux/console.h b/include/linux/console.h
index fc9f5c5c1b04c..ec506d3501965 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -697,7 +697,6 @@ extern int unregister_console(struct console *);
extern void console_lock(void);
extern int console_trylock(void);
extern void console_unlock(void);
-extern void console_conditional_schedule(void);
extern void console_unblank(void);
extern void console_flush_on_panic(enum con_flush_mode mode);
extern struct tty_driver *console_device(int *);
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 1d765ad242b82..9296bf41aa49d 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -3362,22 +3362,6 @@ void console_unlock(void)
}
EXPORT_SYMBOL(console_unlock);
-/**
- * console_conditional_schedule - yield the CPU if required
- *
- * If the console code is currently allowed to sleep, and
- * if this CPU should yield the CPU to another task, do
- * so here.
- *
- * Must be called within console_lock();.
- */
-void __sched console_conditional_schedule(void)
-{
- if (console_may_schedule)
- cond_resched();
-}
-EXPORT_SYMBOL(console_conditional_schedule);
-
void console_unblank(void)
{
bool found_unblank = false;
--
2.51.0
^ permalink raw reply related
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Helge Deller @ 2026-01-26 12:27 UTC (permalink / raw)
To: Thomas Zimmermann, pengfuyuan, 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, Hans de Goede,
Lee Jones, Sam Ravnborg, Zsolt Kajtar, Ville Syrjälä,
rust-for-linux, linux-kernel, dri-devel, linux-fbdev
In-Reply-To: <ed48e82a-cb94-477f-83c4-b2d87ae3cde6@suse.de>
On 1/26/26 11:01, Thomas Zimmermann wrote:
> Am 26.01.26 um 09:17 schrieb pengfuyuan:
>> This patch series adds Rust bindings and safe abstractions for the Linux
>> framebuffer subsystem, enabling framebuffer drivers to be implemented in Rust.
>
> The framebuffer subsystem is obsolete and has been deprecated for a
> decade. No new drivers accepted. Anything that really wants fbdev
> already has a driver. Can we please let it die?
Agreed. Adding rust bindings for fbdev doesn't make sense. Existing fbdev
drivers are often used on architectures, where rust isn't even available
(alpha, hppa, m68k, sh4, ..), so even the idea to "port" existing drivers
to rust is useless.
Helge
^ permalink raw reply
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Miguel Ojeda @ 2026-01-26 12:15 UTC (permalink / raw)
To: pengfuyuan
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
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
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
On Mon, Jan 26, 2026 at 9:18 AM pengfuyuan <pengfuyuan@kylinos.cn> wrote:
>
> rust: io: mem: add ioremap_wc support
> rust: device: add platdata accessors
For future reference / others, these are essentially the same as:
https://lore.kernel.org/rust-for-linux/tencent_63DD850B43CC086844717B73C574B8358F05@qq.com/
https://lore.kernel.org/rust-for-linux/20260109080528.478731-2-pengfuyuan@kylinos.cn/
In general, please try to mention if a patch was already somewhere
else and add a changelog to understand if feedback was addressed.
Otherwise, others will have to do a manual comparison.
For instance, I may guess you sent this as a sample user due to what
Greg said back then. But that is just a guess, since you didn't reply
to the feedback nor mention it here.
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Miguel Ojeda @ 2026-01-26 11:46 UTC (permalink / raw)
To: Alexandre Courbot, pengfuyuan
Cc: Thomas Zimmermann, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, 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, Lee Jones, Sam Ravnborg, Zsolt Kajtar,
Ville Syrjälä, rust-for-linux, linux-kernel, dri-devel,
linux-fbdev
In-Reply-To: <DFYG7MT5JINY.1T8ZZ4ASIWXU@nvidia.com>
On Mon, Jan 26, 2026 at 11:28 AM Alexandre Courbot <acourbot@nvidia.com> wrote:
>
> This, and the patchset is also obviously AI-generated.
pengfuyuan: the generated content guidelines I mentioned earlier this
month in another of your patches have been merged now, please read:
https://docs.kernel.org/next/process/generated-content.html
Thanks!
Cheers,
Miguel
^ permalink raw reply
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Greg Kroah-Hartman @ 2026-01-26 10:32 UTC (permalink / raw)
To: pengfuyuan
Cc: Danilo Krummrich, Alice Ryhl, Daniel Almeida, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross, 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
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
On Mon, Jan 26, 2026 at 04:17:40PM +0800, pengfuyuan wrote:
> 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
How? If there is no rust framebuffer drivers for those hardware
platforms, how exactly were these new codepaths tested?
> 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.
Really? Where are the drivers for this? Or did simplefb really work
for all of them?
confused,
greg k-h
^ permalink raw reply
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Alexandre Courbot @ 2026-01-26 10:28 UTC (permalink / raw)
To: Thomas Zimmermann
Cc: pengfuyuan, Danilo Krummrich, Alice Ryhl, Daniel Almeida,
Miguel Ojeda, 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, Lee Jones, Sam Ravnborg, Zsolt Kajtar,
Ville Syrjälä, rust-for-linux, linux-kernel, dri-devel,
linux-fbdev
In-Reply-To: <ed48e82a-cb94-477f-83c4-b2d87ae3cde6@suse.de>
On Mon Jan 26, 2026 at 7:01 PM JST, Thomas Zimmermann wrote:
> Hi
>
> Am 26.01.26 um 09:17 schrieb pengfuyuan:
>> This patch series adds Rust bindings and safe abstractions for the Linux
>> framebuffer subsystem, enabling framebuffer drivers to be implemented in Rust.
>
> The framebuffer subsystem is obsolete and has been deprecated for a
> decade. No new drivers accepted. Anything that really wants fbdev
> already has a driver. Can we please let it die?
This, and the patchset is also obviously AI-generated.
^ permalink raw reply
* Re: [PATCH v1 v1 0/4] [RUST] Framebuffer driver support
From: Thomas Zimmermann @ 2026-01-26 10:01 UTC (permalink / raw)
To: pengfuyuan, 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, Lee Jones, Sam Ravnborg, Zsolt Kajtar,
Ville Syrjälä, rust-for-linux, linux-kernel, dri-devel,
linux-fbdev
In-Reply-To: <20260126081744.781392-1-pengfuyuan@kylinos.cn>
Hi
Am 26.01.26 um 09:17 schrieb pengfuyuan:
> This patch series adds Rust bindings and safe abstractions for the Linux
> framebuffer subsystem, enabling framebuffer drivers to be implemented in Rust.
The framebuffer subsystem is obsolete and has been deprecated for a
decade. No new drivers accepted. Anything that really wants fbdev
already has a driver. Can we please let it die?
Best regards
Thomas
>
> 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
>
--
--
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)
^ permalink raw reply
* 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
* [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
* [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 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 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 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
* 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
* 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] 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
* [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 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 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
* [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 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 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 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
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