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 | 153 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 152 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index c17665cd5662..97865a9d8fea 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)]
@@ -316,6 +321,110 @@ fn shutdown(dev: &I2cClient<device::Core>) {
}
}
+/// 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 {
+ /// 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() })
+ }
+}
+
+impl<Ctx: device::DeviceContext> Drop for I2cAdapter<Ctx> {
+ fn drop(&mut self) {
+ // SAFETY: This `I2cAdapter` was obtained from `i2c_get_adapter`,
+ // and calling `i2c_put_adapter` exactly once will correctly release
+ // the reference count in the I2C core. It is safe to call from any context
+ unsafe { bindings::i2c_put_adapter(self.as_raw()) }
+ }
+}
+
+// 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.as_raw()).nr) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::i2c_put_adapter(&raw mut (*obj.as_ref().as_raw())) }
+ }
+}
+
+impl<Ctx: device::DeviceContext> AsRef<I2cAdapter<Ctx>> for I2cAdapter<Ctx> {
+ fn as_ref(&self) -> &I2cAdapter<Ctx> {
+ &self
+ }
+}
+
+/// 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
@@ -394,3 +503,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 Thu Sep 11, 2025 at 5:50 PM CEST, Igor Korotin wrote:
> +impl I2cAdapter {
> + /// Gets pointer to an `i2c_adapter` by index.
> + pub fn get(index: i32) -> Result<ARef<Self>> {
Where do we get this index usually from? OF, ACPI, etc. I assume? I feel like it
could make sense to wrap it into a new type. Even though it is not safety
relevant it eliminates a source for mistakes.
> + // 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() })
> + }
> +}
> +
> +impl<Ctx: device::DeviceContext> Drop for I2cAdapter<Ctx> {
> + fn drop(&mut self) {
> + // SAFETY: This `I2cAdapter` was obtained from `i2c_get_adapter`,
> + // and calling `i2c_put_adapter` exactly once will correctly release
> + // the reference count in the I2C core. It is safe to call from any context
> + unsafe { bindings::i2c_put_adapter(self.as_raw()) }
> + }
> +}
The Drop implementation is not needed, you only ever give out an
ARef<I2cAdapter>, but never a "raw" I2cAdapter, which is the correct thing to
do.
> +
> +// 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.as_raw()).nr) };
Please make accessing the nr field a separate inline function, or at least put
it in a separate unsafe block.
> + }
> +
> + unsafe fn dec_ref(obj: NonNull<Self>) {
> + // SAFETY: The safety requirements guarantee that the refcount is non-zero.
> + unsafe { bindings::i2c_put_adapter(&raw mut (*obj.as_ref().as_raw())) }
Same here, separate unsafe blocks please.
> + }
> +}
> +
> +impl<Ctx: device::DeviceContext> AsRef<I2cAdapter<Ctx>> for I2cAdapter<Ctx> {
> + fn as_ref(&self) -> &I2cAdapter<Ctx> {
> + &self
> + }
> +}
This AsRef implementation doesn't seem to do anything?
Hello Danilo
On 9/11/2025 9:34 PM, Danilo Krummrich wrote:
> On Thu Sep 11, 2025 at 5:50 PM CEST, Igor Korotin wrote:
>> +impl I2cAdapter {
>> + /// Gets pointer to an `i2c_adapter` by index.
>> + pub fn get(index: i32) -> Result<ARef<Self>> {
>
> Where do we get this index usually from? OF, ACPI, etc. I assume? I feel like it
> could make sense to wrap it into a new type. Even though it is not safety
> relevant it eliminates a source for mistakes.
It usually comes as predefined magic number in a platform driver info.
I'll define a specific type for this number for clearance.
>> + }
>> +}
>> +
>> +impl<Ctx: device::DeviceContext> AsRef<I2cAdapter<Ctx>> for I2cAdapter<Ctx> {
>> + fn as_ref(&self) -> &I2cAdapter<Ctx> {
>> + &self
>> + }
>> +}
>
> This AsRef implementation doesn't seem to do anything?
I misunderstood the concept of the ARef<T> a little bit and used this
as_ref() instead of just getting a reference by &adapter. I'll remove
this in the next drop
Thanks for the review. All other comments are noted. I'll address them
in the next drop as well
Cheers
Igor
© 2016 - 2026 Red Hat, Inc.