Skip to contentSkip to conversation

rust: i2c: Add basic I2C driver abstractions

PATCH Superseded Igor Korotin 3 participants 22 messages Oct 5, 2025 – Nov 2, 2025

Subsystems (3)

v6 of 8 Superseded Latest is v8 →

lore
Share & tools
lore
#0 lore raw

v6 cover rust: i2c: Add basic I2C driver abstractions

This patch series lays the groundwork for writing Linux I2C drivers in Rust by:

1. Core abstractions
Introduce i2c::I2cClient, i2c::I2cAdapter, i2c::Driver and built on the existing struct i2c_client, struct i2c_adapter and struct i2c_driver, with safe Rust wrappers around probe, transfer, and teardown logic.

2. Manual device creation
Provide an API to register an I2C device at runtime from Rust using I2cBoardInfo and I2cAdapter, including automatic cleanup when the driver unloads.

3. Sample driver (legacy table, OF & ACPI)
Add rust_driver_i2c, a sample that:
- creates an I2C client device using i2c::Registration::new()
- binds to an I2C client via:
- legacy I2C-ID table,
- Open Firmware (device-tree) compatible strings, or
- ACPI IDs.
- destroyes the I2C client device on exit.

Together, these three patches:

- Establish the essential Rust traits and types for I2C drivers.
- Enable driver binding via legacy ID table, device-tree (OF), or ACPI
- Enable manual device creation at runtime.
- Ship a samples showing typical usage

Igor Korotin (3):
rust: i2c: add basic I2C device and driver abstractions
rust: i2c: add manual I2C device creation abstractions
samples: rust: add Rust I2C sample driver

Changelog
---------
v6:
- Add implementation of unbind for i2c::Driver trait;
- Add argument Pin<&Self> to i2c::Driver::shutdown method;
- Adjust usage of i2c::Driver::shutdown in
i2c::Adapter::shutdown_callback in i2c::Driver trait code
example and in rust_driver_i2c code;
- Remove dummy AsRef implementation for I2cAdapter. Adjust code
in rust_driver_i2c;
- Add i2c::I2cAdapter::get_nr method that returns I2cAdapter index;
- Optimize unsafe sections in inc_ref/dec_ref in AlwaysRefCounted
for I2cAdapter implementation;
- Remove unnecessary Drop implementation for I2cAdapter, because
I2cAdapter is always a reference;
- Remove unnecessary type definition Ops<T> in rust_driver_i2c
- Simplify call of i2c::I2cAdapter::get in try_pin_init! macro
for rust_driver_i2c
- Link to v5: https://lore.kernel.org/rust-for-linux/[email protected]/
v5:
- Rename missed pdev variables to idev (thanks to Daniel).
- Import crate::device_id::RawDeviceIdIndex and
crate::types::AlwaysRefCounted in i2c.rs.
- Switch dev_dbg to dev_info in the sample I2C driver messages.
- Make I2cAdapter::get() return ARef<I2cAdapter> instead of
&I2cAdapter.
- Remove TryFrom<device::Device<Ctx>> for I2cAdapter<Ctx> (unused;
to be reintroduced in a later I2C series).
- Remove AsRef<device::Device<Ctx>> for I2cAdapter<Ctx> (unused;
to be reintroduced in a later I2C series).
- Add AsRef<I2cAdapter> for I2cAdapter<Ctx>.
- Use i2c_get/put_adapter instead of get/put_device for
AlwaysRefCounted<I2cAdapter>.
- Update safety comment for unsafe impl Sync for Registration {}.
- Tweak comment for I2cBoardInfo::new.
- Adjust build-time assertion message in Adapter::register.
- Link to v4: https://lore.kernel.org/rust-for-linux/[email protected]/
v4:
- Renamed i2c::I2cAdapterRef to i2c::I2cAdapter.
- Renamed i2c::Device to i2c::I2cClient for consistency with
i2c::I2cAdapter and to avoid confusion with i2c::Adapter
- Reworked i2c::I2cAdapter to be an Opaque around i2c_adapter struct
- Implemented AlwaysRefCounted trait for i2c::I2cAdapter.
- Fixed numerous comment mistakes and typos all over the code, thanks
to Danilo and Daniel
- Got rid of all unwrap() use-cases in i2c.rs and rust_driver_i2c.rs.
This covers 0-day kernel panic <[email protected]>
- Removed unnecessary casts.
- Replaced all addr_of_mut! macros to &raw mut.
- In i2c::Adapter::register method build assert if all ID tables are
None.
- Renamed all pdrv and pdev instances to idrv and idev respectivly
- Implemented an ealry return in i2c::Adapter::i2c_id_info
- Added all missing Safety comments.
- Removed unsafe impl<Ctx: device::DeviceContext> crate::types::AlwaysRefCounted for Device<Ctx> implementation which came to v3 from v2 by mistake.
- Added more details regarding i2c-stub driver usage in rust_driver_i2c
comment.
- Changed i2c::I2cAdapter::get return type from Option<Self> to
Result<&'static Self>.
- Added Daniel Almeida as a reviewer to the "I2C Subsystem [RUST]" entry
in MAINTAINERS, per his offer.
- Link to v3: https://lore.kernel.org/rust-for-linux/[email protected]/
v3:
- removed unnecessary i2c_get_clientdata and i2c_set_clientdata rust
helpers. Using generic accessors implemented in [1] instead.
- Reimplemented i2c::DeviceId based on changes in [2].
- Using from_result in i2c::Adapter::probe_callback
- Using explicit drop() for i2c client private data in
i2c::Adapter::remove_callback
- replaced device::Device::as_ref() with device::Device::from_raw in i2c::Device::as_ref(). It is renamed in device::Device.
- Build Rust I2C only if I2C is built-in
- Reimplement overcomplicated trait i2c::DeviceOwned the same way it is
implemented in auxiliary [3].
- Merge rust_device_i2c and rust_driver_i2c samples. Resulting
rust_driver_i2c creates pined i2c_client using i2c::Registration::new
and probes newly created i2c_client.
- Created a new entry in MAINTAINERS file containing i2c.rs and
rust_driver_i2c.rs in it.
- Link to v2: [4]

[1] https://lore.kernel.org/lkml/[email protected]/
[2] https://lore.kernel.org/rust-for-linux/[email protected]/
[3] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/rust/kernel/auxiliary.rs?h=v6.16-rc4#n299
[4] https://lore.kernel.org/rust-for-linux/[email protected]/

v2:
- Merged separated ACPI support patches since ACPI-table support is
merged into driver-core-next.
- Added I2cAdapterRef and I2cBoardInfo abstractions
- Added DeviceState generic parameter which is used for i2c::Device
as a sign if the device is created manually
- Added DeviceOwned abstraction which is a safe reference to a
manually created i2c::Device<Ctx, state::Owned>.
- Added Rust manual I2C device creation sample
- Link to v1: https://lore.kernel.org/rust-for-linux/[email protected]/

 MAINTAINERS                     |   9 +
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/i2c.rs              | 565 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 samples/rust/Kconfig            |  11 +
 samples/rust/Makefile           |   1 +
 samples/rust/rust_driver_i2c.rs | 126 +++++++
 7 files changed, 715 insertions(+)
 create mode 100644 rust/kernel/i2c.rs
 create mode 100644 samples/rust/rust_driver_i2c.rs

base-commit: 8f5ae30d69d7543eee0d70083daf4de8fe15d585

Signature · 1 line
2.43.0
#1 lore raw

PATCH v6 1/3 rust: i2c: add basic I2C device and driver abstractions

Implement the core abstractions needed for I2C drivers, including:

* i2c::Driver — the trait drivers must implement, including probe

* i2c::I2cClient — a safe wrapper around struct i2c_client

* i2c::Adapter — implements driver::RegistrationOps to hook into the generic driver::Registration machinery

* i2c::DeviceId — a RawDeviceIdIndex implementation for I2C device IDs

---
 MAINTAINERS                     |   8 +
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/i2c.rs              | 423 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 4 files changed, 434 insertions(+)
 create mode 100644 rust/kernel/i2c.rs
MAINTAINERS +8 -0
index fe168477caa4..c44c7ac317b1 100644
@@ -11516,6 +11516,14 @@ F:	include/linux/i2c.h
 F:	include/uapi/linux/i2c-*.h
 F:	include/uapi/linux/i2c.h
 
+I2C SUBSYSTEM [RUST]
+M:	Igor Korotin <[email protected]>
+R:	Danilo Krummrich <[email protected]>
+R:	Daniel Almeida <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	rust/kernel/i2c.rs
+
 I2C SUBSYSTEM HOST DRIVERS
 M:	Andi Shyti <[email protected]>
 L:	[email protected]
rust/bindings/bindings_helper.h +1 -0
index 84d60635e8a9..81796d5e16e8 100644
@@ -53,6 +53,7 @@
 #include <linux/file.h>
 #include <linux/firmware.h>
 #include <linux/fs.h>
+#include <linux/i2c.h>
 #include <linux/ioport.h>
 #include <linux/jiffies.h>
 #include <linux/jump_label.h>
rust/kernel/i2c.rs new file +423 -0

423 lines not on this page · raw message

rust/kernel/lib.rs +2 -0
index ed53169e795c..a5e97a9b1546 100644
@@ -89,6 +89,8 @@
 pub mod firmware;
 pub mod fmt;
 pub mod fs;
+#[cfg(CONFIG_I2C = "y")]
+pub mod i2c;
 pub mod init;
 pub mod io;
 pub mod ioctl;
Signature · 1 line
2.43.0
reviewer Acked-by on v6 1/3
#2 lore raw

Re: [PATCH v6 1/3] rust: i2c: add basic I2C device and driver abstractions

Quoted 4 lines · Igor Korotin

Implement the core abstractions needed for I2C drivers, including:

* i2c::Driver — the trait drivers must implement, including probe

Quoted 8 lines

* i2c::I2cClient — a safe wrapper around struct i2c_client

* i2c::Adapter — implements driver::RegistrationOps to hook into the generic driver::Registration machinery

* i2c::DeviceId — a RawDeviceIdIndex implementation for I2C device IDs

#3 lore raw

PATCH v6 2/3 rust: i2c: add manual I2C device creation abstractions

In reply to Igor Korotin

In addition to the basic I2C device support, add rust abstractions upon i2c_new_client_device/i2c_unregister_device C functions.

Implement the core abstractions needed for manual creation/deletion of I2C devices, including:

* i2c::Registration — a NonNull pointer created by the function

                          `i2c_new_client_device`

* i2c::I2cAdapter — a ref counted wrapper around struct i2c_adapter

* i2c::I2cBoardInfo — a safe wrapper around struct i2c_board_info

---
 rust/kernel/i2c.rs | 144 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 143 insertions(+), 1 deletion(-)
rust/kernel/i2c.rs +143 -1
index c5a8a5791523..73858aecc131 100644
@@ -13,7 +13,12 @@
     types::{AlwaysRefCounted, Opaque},
 };
 
-use core::{marker::PhantomData, ptr::NonNull};
+use core::{
+    marker::PhantomData,
+    ptr::{from_ref, NonNull},
+};
+
+use kernel::types::ARef;
 
 /// An I2C device id table.
 #[repr(transparent)]
@@ -343,6 +348,101 @@ fn unbind(dev: &I2cClient<device::Core>, this: Pin<&Self>) {
     }
 }
 
+/// The i2c adapter representation.
+///
+/// This structure represents the Rust abstraction for a C `struct i2c_adapter`. The
+/// implementation abstracts the usage of an existing C `struct i2c_adapter` that
+/// gets passed from the C side
+///
+/// # Invariants
+///
+/// A [`I2cAdapter`] instance represents a valid `struct i2c_adapter` created by the C portion of
+/// the kernel.
+#[repr(transparent)]
+pub struct I2cAdapter<Ctx: device::DeviceContext = device::Normal>(
+    Opaque<bindings::i2c_adapter>,
+    PhantomData<Ctx>,
+);
+
+impl<Ctx: device::DeviceContext> I2cAdapter<Ctx> {
+    fn as_raw(&self) -> *mut bindings::i2c_adapter {
+        self.0.get()
+    }
+}
+
+impl I2cAdapter {
+    /// Returns the I2C Adapter index.
+    #[inline]
+    pub fn get_nr(&self) -> i32 {
+        // SAFETY: `self.as_raw` is a valid pointer to a `struct i2c_adapter`.
+        unsafe { (*self.as_raw()).nr }
+    }
+    /// Gets pointer to an `i2c_adapter` by index.
+    pub fn get(index: i32) -> Result<ARef<Self>> {
+        // SAFETY: `index` must refer to a valid I2C adapter; the kernel
+        // guarantees that `i2c_get_adapter(index)` returns either a valid
+        // pointer or NULL. `NonNull::new` guarantees the correct check.
+        let adapter = NonNull::new(unsafe { bindings::i2c_get_adapter(index) }).ok_or(ENODEV)?;
+
+        // SAFETY: `adapter` is non-null and points to a live `i2c_adapter`.
+        // `I2cAdapter` is #[repr(transparent)], so this cast is valid.
+        Ok(unsafe { (&*adapter.as_ptr().cast::<I2cAdapter<device::Normal>>()).into() })
+    }
+}
+
+// SAFETY: `I2cAdapter` is a transparent wrapper of a type that doesn't depend on `I2cAdapter`'s generic
+// argument.
+kernel::impl_device_context_deref!(unsafe { I2cAdapter });
+kernel::impl_device_context_into_aref!(I2cAdapter);
+
+// SAFETY: Instances of `I2cAdapter` are always reference-counted.
+unsafe impl crate::types::AlwaysRefCounted for I2cAdapter {
+    fn inc_ref(&self) {
+        // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
+        unsafe { bindings::i2c_get_adapter(self.get_nr()) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+        unsafe { bindings::i2c_put_adapter(obj.as_ref().as_raw()) }
+    }
+}
+
+/// The i2c board info representation
+///
+/// This structure represents the Rust abstraction for a C `struct i2c_board_info` structure,
+/// which is used for manual I2C client creation.
+#[repr(transparent)]
+pub struct I2cBoardInfo(bindings::i2c_board_info);
+
+impl I2cBoardInfo {
+    const I2C_TYPE_SIZE: usize = 20;
+    /// Create a new [`I2cBoardInfo`] for a kernel driver.
+    #[inline(always)]
+    pub const fn new(type_: &'static CStr, addr: u16) -> Self {
+        build_assert!(
+            type_.len_with_nul() <= Self::I2C_TYPE_SIZE,
+            "Type exceeds 20 bytes"
+        );
+        let src = type_.as_bytes_with_nul();
+        // Replace with `bindings::acpi_device_id::default()` once stabilized for `const`.
+        // SAFETY: FFI type is valid to be zero-initialized.
+        let mut i2c_board_info: bindings::i2c_board_info = unsafe { core::mem::zeroed() };
+        let mut i: usize = 0;
+        while i < src.len() {
+            i2c_board_info.type_[i] = src[i];
+            i += 1;
+        }
+
+        i2c_board_info.addr = addr;
+        Self(i2c_board_info)
+    }
+
+    fn as_raw(&self) -> *const bindings::i2c_board_info {
+        from_ref(&self.0)
+    }
+}
+
 /// The i2c client representation.
 ///
 /// This structure represents the Rust abstraction for a C `struct i2c_client`. The
@@ -421,3 +521,45 @@ unsafe impl Send for I2cClient {}
 // SAFETY: `I2cClient` can be shared among threads because all methods of `I2cClient`
 // (i.e. `I2cClient<Normal>) are thread safe.
 unsafe impl Sync for I2cClient {}
+
+/// The registration of an i2c client device.
+///
+/// This type represents the registration of a [`struct i2c_client`]. When an instance of this
+/// type is dropped, its respective i2c client device will be unregistered from the system.
+///
+/// # Invariants
+///
+/// `self.0` always holds a valid pointer to an initialized and registered
+/// [`struct i2c_client`].
+#[repr(transparent)]
+pub struct Registration(NonNull<bindings::i2c_client>);
+
+impl Registration {
+    /// The C `i2c_new_client_device` function wrapper for manual I2C client creation.
+    pub fn new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result<Self> {
+        // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid
+        // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` checks for NULL.
+        let raw_dev = from_err_ptr(unsafe {
+            bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw())
+        })?;
+
+        let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?;
+
+        Ok(Self(dev_ptr))
+    }
+}
+
+impl Drop for Registration {
+    fn drop(&mut self) {
+        // SAFETY: `Drop` is only called for a valid `Registration`, which by invariant
+        // always contains a non-null pointer to an `i2c_client`.
+        unsafe { bindings::i2c_unregister_device(self.0.as_ptr()) }
+    }
+}
+
+// SAFETY: A `Registration` of a `struct i2c_client` can be released from any thread.
+unsafe impl Send for Registration {}
+
+// SAFETY: `Registration` offers no interior mutability (no mutation through &self
+// and no mutable access is exposed)
+unsafe impl Sync for Registration {}
Signature · 1 line
2.43.0
#4 lore raw

Re: [PATCH v6 2/3] rust: i2c: add manual I2C device creation abstractions

Quoted 4 lines · Igor Korotin
rust/kernel/i2c.rs +4 -0
+impl Registration {
+    /// The C `i2c_new_client_device` function wrapper for manual I2C client creation.
+    pub fn new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result<Self> {
+        // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid

rust/kernel/i2c.rs unsafe impl Send for I2cClient {} · line 550

Quoted 10 lines
rust/kernel/i2c.rs +10 -0
+        // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` checks for NULL.
+        let raw_dev = from_err_ptr(unsafe {
+            bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw())
+        })?;
+
+        let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?;
+
+        Ok(Self(dev_ptr))
+    }
+}

I wonder if we want to ensure that a Registration can't out-live the driver that registers the I2C client device.

This should only ever be called by drivers bound to more complex devices, so if the parent driver is unbound I don't think I2C client device registered by this driver should be able to survive.

Hence, I think Registration::new() should return
impl PinInit<Devres<Self>, Error> instead.

#5 lore raw

PATCH v6 2/3 rust: i2c: Add basic I2C driver abstractions

Hello Danilo

Quoted 19 lines · Igor Korotin
Quoted 4 lines
rust/kernel/i2c.rs +4 -0
+impl Registration {
+    /// The C `i2c_new_client_device` function wrapper for manual I2C client creation.
+    pub fn new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result<Self> {
+        // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid

rust/kernel/i2c.rs unsafe impl Send for I2cClient {} · line 550

Quoted 10 lines
rust/kernel/i2c.rs +10 -0
+        // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` checks for NULL.
+        let raw_dev = from_err_ptr(unsafe {
+            bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw())
+        })?;
+
+        let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?;
+
+        Ok(Self(dev_ptr))
+    }
+}

I wonder if we want to ensure that a Registration can't out-live the driver that registers the I2C client device.

Quoted 6 lines

This should only ever be called by drivers bound to more complex devices, so if the parent driver is unbound I don't think I2C client device registered by this driver should be able to survive.

Hence, I think Registration::new() should return
impl PinInit<Devres<Self>, Error> instead.

Maybe I'm missing something here, but as far as I understand, Devres is bound to an existing device. However Registration::new creates new device and registers new i2c_client using function i2c_new_client_device. Created i2c_client uses i2c_adapter as its parent.

The driver that declares Registration doesn't own that i2c_adapter. Registration itself is not part of the new client’s managed resources, so returning impl PinInit<Devres<Self>, Error> wouldn’t make sense here.

Drop for Registration calls i2c_unregister_client(), which gracefully unregisters and deallocates the i2c_client.

Cheers
Igor

#6 lore raw
Quoted 25 lines · Igor Korotin

Hello Danilo

Quoted 23 lines · Igor Korotin
Quoted 4 lines
rust/kernel/i2c.rs +4 -0
+impl Registration {
+    /// The C `i2c_new_client_device` function wrapper for manual I2C client creation.
+    pub fn new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result<Self> {
+        // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid

rust/kernel/i2c.rs unsafe impl Send for I2cClient {} · line 550

Quoted 10 lines
rust/kernel/i2c.rs +10 -0
+        // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` checks for NULL.
+        let raw_dev = from_err_ptr(unsafe {
+            bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw())
+        })?;
+
+        let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?;
+
+        Ok(Self(dev_ptr))
+    }
+}

I wonder if we want to ensure that a Registration can't out-live the driver that registers the I2C client device.

This should only ever be called by drivers bound to more complex devices, so if the parent driver is unbound I don't think I2C client device registered by this driver should be able to survive.

Quoted 7 lines
Quoted 2 lines

Hence, I think Registration::new() should return
impl PinInit<Devres<Self>, Error> instead.

Maybe I'm missing something here, but as far as I understand, Devres is bound to an existing device. However Registration::new creates new device and registers new i2c_client using function i2c_new_client_device. Created i2c_client uses i2c_adapter as its parent.

Correct, but the question is what's the correct lifetime boundary for this i2c:Registration.

Quoted 3 lines

The driver that declares Registration doesn't own that i2c_adapter. Registration itself is not part of the new client’s managed resources, so returning impl PinInit<Devres<Self>, Error> wouldn’t make sense here.

It does make sense, it's just not required for safety reasons.

This is an API that should be used by drivers operating complicated devices (DRM, NET, etc.) where there is no point in keeping an i2c::Registration alive after the driver that registered the I2C client has been unbound itself.

For instance, a GPU driver may call this in probe() to register an I2C device for some redriver, repeater, multiplexer, etc. So, it makes no sense to allow a corresponding i2c::Registration to still exist beyond the GPU driver being unbound.

Hence, besides not really being necessary for safety reasons, it still seems reasonable to enforce this for semantic reasons.

Quoted 2 lines

Drop for Registration calls i2c_unregister_client(), which gracefully unregisters and deallocates the i2c_client.

Not quite, it unregisters the I2C client (which is why we call the object Registration), but it does not necessarily free the I2C client. Someone else can still hold a reference count of the I2C client.

#7 lore raw

Hello Danilo

Quoted 46 lines · Danilo Krummrich
Quoted 25 lines · Igor Korotin

Hello Danilo

Quoted 23 lines · Igor Korotin
Quoted 4 lines
rust/kernel/i2c.rs +4 -0
+impl Registration {
+    /// The C `i2c_new_client_device` function wrapper for manual I2C client creation.
+    pub fn new(i2c_adapter: &I2cAdapter, i2c_board_info: &I2cBoardInfo) -> Result<Self> {
+        // SAFETY: the kernel guarantees that `i2c_new_client_device()` returns either a valid

rust/kernel/i2c.rs unsafe impl Send for I2cClient {} · line 550

Quoted 10 lines
rust/kernel/i2c.rs +10 -0
+        // pointer or NULL. `from_err_ptr` separates errors. Following `NonNull::new` checks for NULL.
+        let raw_dev = from_err_ptr(unsafe {
+            bindings::i2c_new_client_device(i2c_adapter.as_raw(), i2c_board_info.as_raw())
+        })?;
+
+        let dev_ptr = NonNull::new(raw_dev).ok_or(ENODEV)?;
+
+        Ok(Self(dev_ptr))
+    }
+}

I wonder if we want to ensure that a Registration can't out-live the driver that registers the I2C client device.

This should only ever be called by drivers bound to more complex devices, so if the parent driver is unbound I don't think I2C client device registered by this driver should be able to survive.

Quoted 7 lines
Quoted 2 lines

Hence, I think Registration::new() should return
impl PinInit<Devres<Self>, Error> instead.

Maybe I'm missing something here, but as far as I understand, Devres is bound to an existing device. However Registration::new creates new device and registers new i2c_client using function i2c_new_client_device. Created i2c_client uses i2c_adapter as its parent.

Correct, but the question is what's the correct lifetime boundary for this i2c:Registration.

Quoted 3 lines

The driver that declares Registration doesn't own that i2c_adapter. Registration itself is not part of the new client’s managed resources, so returning impl PinInit<Devres<Self>, Error> wouldn’t make sense here.

It does make sense, it's just not required for safety reasons.

This is an API that should be used by drivers operating complicated devices (DRM, NET, etc.) where there is no point in keeping an i2c::Registration alive after the driver that registered the I2C client has been unbound itself.

Quoted 6 lines

For instance, a GPU driver may call this in probe() to register an I2C device for some redriver, repeater, multiplexer, etc. So, it makes no sense to allow a corresponding i2c::Registration to still exist beyond the GPU driver being unbound.

Hence, besides not really being necessary for safety reasons, it still seems reasonable to enforce this for semantic reasons.

I might be misunderstanding your point, but as I see it, Devres cannot apply here because we can't bind it to i2c_adapter. There's no guarantee that driver
owns it. Registration can't be bound to i2c_client, cause it's kind of chicken and egg situation.

Even if we returned impl PinInit<Self, Error>, it wouldn’t prevent other code from holding an additional reference to the client.

Quoted 6 lines
Quoted 2 lines

Drop for Registration calls i2c_unregister_client(), which gracefully unregisters and deallocates the i2c_client.

Not quite, it unregisters the I2C client (which is why we call the object Registration), but it does not necessarily free the I2C client. Someone else can still hold a reference count of the I2C client.

You’re right, it doesn’t necessarily free the client immediately. However, i2c_unregister_client() calls device_unregister(), which triggers device_del() and put_device().

So while the memory may remain allocated until the last reference is dropped,
the device itself is already unregistered and deactivated at that point.
This aligns with the standard lifetime guarantees of the C driver model:
the structure remains valid only until its refcount reaches zero, after
which it’s released automatically.
That said, could you elaborate a bit more on what exactly you’d like to achieve by returning impl PinInit<Devres<Self>, Error> here?
Is the idea to explicitly tie the Registration lifetime to the parent device (for example, the i2c_adapter), or is this more about defining a general pattern for device-managed helper types in the Rust driver API? I just want to be sure I’m following the intent of your suggestion correctly.

Cheers,
Igor

#8 lore raw
Quoted 9 lines · Igor Korotin
Quoted 8 lines · Danilo Krummrich

This is an API that should be used by drivers operating complicated devices (DRM, NET, etc.) where there is no point in keeping an i2c::Registration alive after the driver that registered the I2C client has been unbound itself.

For instance, a GPU driver may call this in probe() to register an I2C device for some redriver, repeater, multiplexer, etc. So, it makes no sense to allow a corresponding i2c::Registration to still exist beyond the GPU driver being unbound.

Quoted 6 lines
Quoted 2 lines

Hence, besides not really being necessary for safety reasons, it still seems reasonable to enforce this for semantic reasons.

I might be misunderstanding your point, but as I see it, Devres cannot apply here
because we can't bind it to i2c_adapter.

Yes, you do misunderstand.

I'm not saying to use the I2C adapter device for this, that makes no sense. In fact, it wouldn't even work, because the underlying device can not be guaranteed to be in a bound state.

Quoted 3 lines

There's no guarantee that driver
owns it. Registration can't be bound to i2c_client, cause it's kind of chicken and egg situation.

I'm also not saying to use the the I2C client, which indeed makes even less sense (and wouldn't work for the same reason mentioned above).

Think of the bigger picture, i.e. where is i2c:Registration::new() called from?

It's called from other drivers (e.g. DRM drivers [1] or network drivers [2]) that are bound to some bus device themselves, e.g. a platform device or a PCI device.

This is the device that we can give to i2c:Registration::new() and use for the internal call to devres.

With that we ensure that the i2c:Registration will be dropped, when the driver that originally called i2c:Registration::new() is unbound; it makes no sense to allow a driver to keep an i2c:Registration alive when it is unbound.

In fact, quite some C drivers are already doing exactly this by hand. For instance, see [3]. Four lines after [3], raa215300_rtc_unregister_device() is registered with devm_add_action_or_reset(), which calls i2c_unregister_device().

Having that said, I'm a bit curious now: What is your use-case for i2c:Registration?

[1] https://elixir.bootlin.com/linux/v6.17.5/source/drivers/gpu/drm/xe/xe_i2c.c#L72
[2] https://elixir.bootlin.com/linux/v6.17.5/source/drivers/net/ethernet/intel/igb/igb_hwmon.c#L201
[3] https://elixir.bootlin.com/linux/v6.17.5/source/drivers/regulator/raa215300.c#L160

#9 lore raw

Hello Danilo

Quoted 3 lines · Danilo Krummrich

In fact, quite some C drivers are already doing exactly this by hand. For instance, see [3]. Four lines after [3], raa215300_rtc_unregister_device() is registered with devm_add_action_or_reset(), which calls i2c_unregister_device().

Now that you’ve mentioned the parent device, I finally understand what you mean. Originally, I started my Rust-for-Linux journey with the goal of rewriting one of our platform drivers that has I2C children — and yes, it has a root platform_device. For now, I’ve put that rewrite on hold and focused on the I2C part instead, which is why I was thinking from the perspective of rust_driver_i2c.rs: it’s a purely artificial sample driver that creates an I2C client for a non-existent physical device using an I2C adapter. That’s why the puzzle didn’t fit together for me earlier.

Quoted 2 lines

Having that said, I'm a bit curious now: What is your use-case for i2c:Registration?

As for my view of i2c::Registration, I see it as a safe wrapper around i2c_new_client_device that holds a pointer to the newly created I2C client and releases it automatically when dropped.

Thanks for the explanation, I'll drop a new version when ready

Best Regards
Igor

#10 lore raw
In reply to Danilo Krummrich

Hello Danilo

Quoted 6 lines · Danilo Krummrich

It's called from other drivers (e.g. DRM drivers [1] or network drivers [2]) that are bound to some bus device themselves, e.g. a platform device or a PCI device.

This is the device that we can give to i2c:Registration::new() and use for the internal call to devres.

After the recent change where i2c::Registration::new() returns impl PinInit<Devres<Self>, Error> instead of Result<Self>, I’m unsure how to adapt the Rust I2C sample driver. The sample doesn’t have a parent device available — previously it just used the returned I2cClient.

The current patch series includes a Rust I2C sample driver that creates a new i2c_client using i2c::Registration::new() and then attaches an I2C driver to it. Since this is no longer possible, I’m evaluating how the sample should be structured.

The only option I see is to split it into two parts:
1. A minimal I2C driver sample demonstrating only driver code.
2. A separate I2C client registration sample based on an existing PCI or platform sample driver, using its device as the parent for Devres::new() inside i2c::Registration::new().

Does this approach make sense, or is there a better way to handle it?

Thanks,
Igor

#11 lore raw
Quoted 7 lines · Igor Korotin

Hello Danilo

Quoted 4 lines · Danilo Krummrich

It's called from other drivers (e.g. DRM drivers [1] or network drivers [2]) that are bound to some bus device themselves, e.g. a platform device or a PCI device.

Quoted 7 lines
Quoted 2 lines

This is the device that we can give to i2c:Registration::new() and use for the internal call to devres.

After the recent change where i2c::Registration::new() returns impl PinInit<Devres<Self>, Error> instead of Result<Self>, I’m unsure how to adapt the Rust I2C sample driver. The sample doesn’t have a parent device available.

Indeed, and that's a good thing that this doesn't work now. :)

I think the best course of action is to make the sample driver closer to a real driver.

For this you can do what the debugfs sample (samples/rust/rust_debugfs.rs) does and use a platform driver with either ACPI

	kernel::acpi_device_table!(
	    ACPI_TABLE,
	    MODULE_ACPI_TABLE,
	    <RustDebugFs as platform::Driver>::IdInfo,
	    [(acpi::DeviceId::new(c_str!("LNUXBEEF")), ())]
	);

or OF

	kernel::of_device_table!(
	    OF_TABLE,
	    MODULE_OF_TABLE,
	    <SampleDriver as platform::Driver>::IdInfo,
	    [(of::DeviceId::new(c_str!("test,rust-device")), Info(42))]
	);

and then create an i2c::Registration from platform::Driver::probe().

- Danilo

#12 lore raw

PATCH v6 3/3 samples: rust: add Rust I2C sample driver

In reply to Igor Korotin

Add a new rust_driver_i2c sample, showing how to create a new i2c client using i2c::Registration and bind a driver to it via legacy I2C-ID table.

---
 MAINTAINERS                     |   1 +
 samples/rust/Kconfig            |  11 +++
 samples/rust/Makefile           |   1 +
 samples/rust/rust_driver_i2c.rs | 126 ++++++++++++++++++++++++++++++++
 4 files changed, 139 insertions(+)
 create mode 100644 samples/rust/rust_driver_i2c.rs
MAINTAINERS +1 -0
index c44c7ac317b1..2654a7ea0c80 100644
@@ -11523,6 +11523,7 @@ R:	Daniel Almeida <[email protected]>
 L:	[email protected]
 S:	Maintained
 F:	rust/kernel/i2c.rs
+F:	samples/rust/rust_driver_i2c.rs
 
 I2C SUBSYSTEM HOST DRIVERS
 M:	Andi Shyti <[email protected]>
samples/rust/Kconfig +11 -0
index 7f7371a004ee..28dae070b365 100644
@@ -62,6 +62,17 @@ config SAMPLE_RUST_DMA
 
 	  If unsure, say N.
 
+config SAMPLE_RUST_DRIVER_I2C
+	tristate "I2C Driver"
+	depends on I2C=y
+	help
+	  This option builds the Rust I2C driver sample.
+
+	  To compile this as a module, choose M here:
+	  the module will be called rust_driver_i2c.
+
+	  If unsure, say N.
+
 config SAMPLE_RUST_DRIVER_PCI
 	tristate "PCI Driver"
 	depends on PCI
samples/rust/Makefile +1 -0
index bd2faad63b4f..141d8f078248 100644
@@ -5,6 +5,7 @@ obj-$(CONFIG_SAMPLE_RUST_MINIMAL)		+= rust_minimal.o
 obj-$(CONFIG_SAMPLE_RUST_MISC_DEVICE)		+= rust_misc_device.o
 obj-$(CONFIG_SAMPLE_RUST_PRINT)			+= rust_print.o
 obj-$(CONFIG_SAMPLE_RUST_DMA)			+= rust_dma.o
+obj-$(CONFIG_SAMPLE_RUST_DRIVER_I2C)		+= rust_driver_i2c.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PCI)		+= rust_driver_pci.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_PLATFORM)	+= rust_driver_platform.o
 obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX)		+= rust_driver_faux.o
samples/rust/rust_driver_i2c.rs new file +126 -0

126 lines not on this page · raw message

Signature · 1 line
2.43.0
#13 lore raw

Re: [PATCH v6 3/3] samples: rust: add Rust I2C sample driver

samples/rust/rust_driver_i2c.rs line 92

Quoted 5 lines · Igor Korotin
samples/rust/rust_driver_i2c.rs new file +5 -0
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_info!(self.idev.as_ref(), "Remove Rust I2C driver sample.\n");
+    }
+}

NIT: Please use the i2c::Driver::unbind() callback instead.

#14 lore raw

Re: [PATCH v6 3/3] samples: rust: add Rust I2C sample driver

Hello Danilo

Quoted 8 lines · Danilo Krummrich

samples/rust/rust_driver_i2c.rs line 92

Quoted 5 lines · Igor Korotin
samples/rust/rust_driver_i2c.rs new file +5 -0
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_info!(self.idev.as_ref(), "Remove Rust I2C driver sample.\n");
+    }
+}

NIT: Please use the i2c::Driver::unbind() callback instead.

Thanks for the feedback.
I’ll move this into the i2c::Driver::unbind() callback.

Should I send v7?

Best,
Igor

#15 lore raw

Re: [PATCH v6 3/3] samples: rust: add Rust I2C sample driver

Quoted 12 lines · Igor Korotin

Hello Danilo

Quoted 8 lines · Danilo Krummrich

samples/rust/rust_driver_i2c.rs line 92

Quoted 5 lines · Igor Korotin
samples/rust/rust_driver_i2c.rs new file +5 -0
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_info!(self.idev.as_ref(), "Remove Rust I2C driver sample.\n");
+    }
+}

NIT: Please use the i2c::Driver::unbind() callback instead.

Quoted 4 lines

Thanks for the feedback.
I’ll move this into the i2c::Driver::unbind() callback.

Should I send v7?

For such a minor thing I can usually do it when applying the patch, but given that for the other patch the change is a bit more significant, I'd say please send a v7.

#16 lore raw

Re: [PATCH v6 3/3] samples: rust: add Rust I2C sample driver

Quoted 13 lines · Danilo Krummrich
Quoted 12 lines · Igor Korotin

Hello Danilo

Quoted 8 lines · Danilo Krummrich

samples/rust/rust_driver_i2c.rs line 92

Quoted 5 lines · Igor Korotin
samples/rust/rust_driver_i2c.rs new file +5 -0
+impl Drop for SampleDriver {
+    fn drop(&mut self) {
+        dev_info!(self.idev.as_ref(), "Remove Rust I2C driver sample.\n");
+    }
+}

NIT: Please use the i2c::Driver::unbind() callback instead.

Quoted 8 lines
Quoted 4 lines

Thanks for the feedback.
I’ll move this into the i2c::Driver::unbind() callback.

Should I send v7?

For such a minor thing I can usually do it when applying the patch, but given that for the other patch the change is a bit more significant, I'd say please send a v7.

Just for the clarification: by "the change for the other patch" you mean rebase and update this patch series based on [1], right?

Thanks
Igor

[1] https://lore.kernel.org/all/[email protected]/

#17 lore raw

Re: [PATCH v6 3/3] samples: rust: add Rust I2C sample driver

Quoted 2 lines · Igor Korotin

Just for the clarification: by "the change for the other patch" you mean rebase and update this patch series based on [1], right?

Yes, plus the i2c:Registration change.

Quoted 1 line

[1] https://lore.kernel.org/all/[email protected]/

#18 lore raw
In reply to Igor Korotin

Hi all,

Gentle ping on this series:
[PATCH v6 0/3] rust: i2c: Add basic I2C driver abstractions (2025-10-05)

Just checking if there’s any feedback or if it’s still pending review. Thanks!

#19 lore raw
Quoted 2 lines · Igor Korotin

Just checking if there’s any feedback or if it’s still pending review. Thanks!

One note, if taken through the I2C tree, this patch series will have a conflict with [1] from the driver-core tree.

Igor, Wolfram: If you rather want to avoid the conflict, I can also take this initial series through the driver-core tree.

Thanks,
Danilo

[1] https://lore.kernel.org/all/[email protected]/

#20 lore raw
Quoted 9 lines · Danilo Krummrich
Quoted 2 lines · Igor Korotin

Just checking if there’s any feedback or if it’s still pending review. Thanks!

One note, if taken through the I2C tree, this patch series will have a conflict with [1] from the driver-core tree.

Igor, Wolfram: If you rather want to avoid the conflict, I can also take this initial series through the driver-core tree.

I'm fine with either option. I can rebase and update the series based on [1] if needed.

Quoted 4 lines

Thanks,
Danilo

[1] https://lore.kernel.org/all/[email protected]/

Thanks
Igor