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`
Signed-off-by: Igor Korotin <igor.korotin.linux@gmail.com>
---
rust/kernel/i2c.rs | 144 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 143 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index c5a8a5791523..73858aecc131 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -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 {}
--
2.43.0
On 10/5/25 12:23 PM, Igor Korotin wrote:
> +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))
> + }
> +}
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.
Hello Danilo
> On 10/5/25 12:23 PM, Igor Korotin wrote:
> > +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))
> > + }
> > +}
>
> 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.
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
On 10/26/25 7:41 PM, Igor Korotin wrote:
> Hello Danilo
>
>> On 10/5/25 12:23 PM, Igor Korotin wrote:
>>> +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))
>>> + }
>>> +}
>>
>> 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.
>
> 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.
> 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.
> 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.
Hello Danilo
On 10/26/2025 7:20 PM, Danilo Krummrich wrote:
> On 10/26/25 7:41 PM, Igor Korotin wrote:
>> Hello Danilo
>>
>>> On 10/5/25 12:23 PM, Igor Korotin wrote:
>>>> +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))
>>>> + }
>>>> +}
>>>
>>> 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.
>>
>> 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.
>> 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.
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.
>> 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
On Mon Oct 27, 2025 at 9:27 PM CET, Igor Korotin wrote: > On 10/26/2025 7:20 PM, Danilo Krummrich wrote: >> 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. > > 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. > 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
Hello Danilo On 10/27/2025 10:00 PM, Danilo Krummrich wrote: > 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
On Sun Nov 2, 2025 at 6:45 PM CET, Igor Korotin wrote:
> Hello Danilo
>
> On 10/27/2025 10:00 PM, Danilo Krummrich wrote:
>> 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.
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
Hello Danilo On 10/27/2025 10:00 PM, Danilo Krummrich wrote: > 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. > 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
© 2016 - 2026 Red Hat, Inc.