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 - 2025 Red Hat, Inc.