From: Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
To: Jonathan Cameron <jic23@kernel.org>,
Miguel Ojeda <ojeda@kernel.org>,
Igor Korotin <igor.korotin@linux.dev>,
Danilo Krummrich <dakr@kernel.org>
Cc: David Lechner <dlechner@baylibre.com>,
Alice Ryhl <aliceryhl@google.com>,
Daniel Almeida <daniel.almeida@collabora.com>,
Mark Brown <broonie@kernel.org>,
Andi Shyti <andi.shyti@kernel.org>, Shuai Liu <847807867@qq.com>,
linux-iio@vger.kernel.org, linux-i2c@vger.kernel.org,
rust-for-linux@vger.kernel.org, driver-core@lists.linux.dev,
linux-kernel@vger.kernel.org
Subject: [RFC PATCH v7 2/4] i2c: rust: implement SMBus access via IoBackend
Date: Mon, 9 Nov 2026 22:57:41 +0700 [thread overview]
Message-ID: <20261109155743.83740-3-muchamadcoirulanwar@gmail.com> (raw)
In-Reply-To: <20261109155743.83740-1-muchamadcoirulanwar@gmail.com>
Implement fallible SMBus byte and word access for bound I2C clients
using the generic I/O backend infrastructure.
I2C transactions may fail due to NACK, arbitration loss, or timeout, so
use FallibleIoCapable rather than the infallible IoCapable interface.
Propagate errors returned by the I2C core to callers.
Provide smbus_io() for byte-addressed register access and standalone
word helpers for layouts not represented by the Region-based view.
Also provide smbus_read_word_swapped() for devices that transmit the
high byte of a multi-byte value first.
Restrict these operations to I2cClient<Bound> so bus I/O is only available
while the client remains bound.
Keep the raw SMBus FFI calls and errno conversion in safe bound-client
methods. Include a generic no_run example for the word-read helper.
Suggested-by: Shuai Liu <847807867@qq.com>
Link: https://lore.kernel.org/all/tencent_173B7731E90B9624F9F82613DC3031B70907@qq.com/
Signed-off-by: Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
---
rust/kernel/i2c.rs | 235 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 235 insertions(+)
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 0487bae811fb..6f63bb8c6f93 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -14,8 +14,15 @@
devres::Devres,
driver,
error::*,
+ io::{
+ FallibleIoCapable,
+ IoBackend,
+ IoBase,
+ Region, //
+ },
of,
prelude::*,
+ ptr::KnownSize,
sync::aref::{
ARef,
AlwaysRefCounted, //
@@ -594,3 +601,231 @@ 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 {}
+
+/// I/O backend for SMBus register access via I2C.
+///
+/// This backend implements only [`FallibleIoCapable`] and not
+/// [`crate::io::IoCapable`], because I2C/SMBus transactions can fail
+/// regardless of address validity. The infallible [`crate::io::Io::read`],
+/// [`crate::io::Io::write`], and [`crate::io::Io::update`] methods are
+/// therefore unavailable for this backend.
+pub struct I2cBackend;
+
+/// An SMBus view associated with a bound I2C client.
+///
+/// The pointer encodes a command offset and is never dereferenced as
+/// memory. Its type and, for dynamically sized targets, metadata describe
+/// the extent of the view.
+///
+/// # Invariants
+///
+/// The view is derived from the client's SMBus command region.
+/// Projections preserve the bounds of the source view.
+/// `client` remains valid and bound for the lifetime of the view.
+pub struct I2cView<'a, T: ?Sized> {
+ client: &'a I2cClient<device::Bound>,
+ ptr: *mut T,
+}
+
+impl<T: ?Sized> Copy for I2cView<'_, T> {}
+
+impl<T: ?Sized> Clone for I2cView<'_, T> {
+ #[inline]
+ fn clone(&self) -> Self {
+ *self
+ }
+}
+
+impl IoBackend for I2cBackend {
+ type View<'a, T: ?Sized + KnownSize> = I2cView<'a, T>;
+
+ #[inline]
+ fn as_ptr<'a, T: ?Sized + KnownSize>(view: Self::View<'a, T>) -> *mut T {
+ view.ptr
+ }
+
+ #[inline]
+ unsafe fn project_view<'a, T: ?Sized + KnownSize, U: ?Sized + KnownSize>(
+ view: Self::View<'a, T>,
+ ptr: *mut U,
+ ) -> Self::View<'a, U> {
+ // INVARIANT: Per safety requirement.
+ I2cView {
+ client: view.client,
+ ptr,
+ }
+ }
+}
+
+impl FallibleIoCapable<u8> for I2cBackend {
+ #[inline]
+ fn io_try_read<'a>(view: I2cView<'a, u8>) -> Result<u8> {
+ // `io_view()` ensures `offset + 1 <= 256`, so `addr()` is at most 255;
+ // the `as u8` cast below is therefore lossless.
+ let reg = Self::as_ptr(view).addr() as u8;
+ view.client.smbus_read_byte(reg)
+ }
+
+ #[inline]
+ fn io_try_write<'a>(view: I2cView<'a, u8>, value: u8) -> Result {
+ // `io_view()` ensures `offset + 1 <= 256`, so `addr()` is at most 255;
+ // the `as u8` cast below is therefore lossless.
+ let reg = Self::as_ptr(view).addr() as u8;
+ view.client.smbus_write_byte(reg, value)
+ }
+}
+
+/// The `Region<256>` view is byte-addressed. Consequently, its generic
+/// 16-bit operations reject odd command bytes and command byte `0xFF`.
+/// Devices whose command bytes each select an independent 16-bit register
+/// should use [`I2cClient::smbus_read_word`] or
+/// [`I2cClient::smbus_read_word_swapped`] directly.
+impl FallibleIoCapable<u16> for I2cBackend {
+ #[inline]
+ fn io_try_read<'a>(view: I2cView<'a, u16>) -> Result<u16> {
+ // `io_view()` ensures `offset + 2 <= 256`, so `addr()` is at most 254;
+ // the `as u8` cast below is therefore lossless.
+ let reg = Self::as_ptr(view).addr() as u8;
+ view.client.smbus_read_word(reg)
+ }
+
+ #[inline]
+ fn io_try_write<'a>(view: I2cView<'a, u16>, value: u16) -> Result {
+ // `io_view()` ensures `offset + 2 <= 256`, so `addr()` is at most 254;
+ // the `as u8` cast below is therefore lossless.
+ let reg = Self::as_ptr(view).addr() as u8;
+ view.client.smbus_write_word(reg, value)
+ }
+}
+
+impl<'a, T: ?Sized + KnownSize> IoBase<'a> for I2cView<'a, T> {
+ type Backend = I2cBackend;
+ type Target = T;
+
+ #[inline]
+ fn as_view(self) -> I2cView<'a, T> {
+ self
+ }
+}
+
+// SAFETY: The bound client borrow remains valid when the view is moved
+// between threads. The view exposes only SMBus transactions, which use
+// the I2C core's bus locking, and pointer projections that never
+// dereference the encoded address as memory. `T: Sync` preserves the
+// shared-view restriction.
+unsafe impl<T: ?Sized + Sync> Send for I2cView<'_, T> {}
+
+// SAFETY: Sharing a view permits concurrent SMBus transactions, which
+// use the I2C core's bus locking. The bound client remains valid for
+// the view's lifetime, and the encoded pointer is never dereferenced
+// as memory. `T: Sync` preserves the shared-view restriction.
+unsafe impl<T: ?Sized + Sync> Sync for I2cView<'_, T> {}
+
+impl I2cClient<device::Bound> {
+ /// Returns an I/O handle for SMBus register access on this I2C client.
+ ///
+ /// The returned handle provides fallible read and write methods for the
+ /// SMBus command byte range (0x00–0xFF). A command byte is a data byte which
+ /// often selects a register on the device. The 7-bit device address is handled
+ /// separately by the I2C core.
+ ///
+ /// [`crate::io::Io::try_read16`] and
+ /// [`crate::io::Io::try_write16`] on the returned handle reject
+ /// odd offsets because the [`Region`]-based bounds check requires
+ /// `offset % 2 == 0`. This model does not cover every SMBus register layout,
+ /// including layouts where auto-increment or auto-decrement results in command
+ /// bytes that appear unaligned. Use [`Self::smbus_read_word`] or
+ /// [`Self::smbus_read_word_swapped`] for word access at an odd command byte.
+ #[inline]
+ pub fn smbus_io(&self) -> I2cView<'_, Region<256>> {
+ // INVARIANT: The view borrows this bound client and covers the 256-byte
+ // SMBus command region.
+ //
+ // The provenance-free pointer encodes a command offset rather than a
+ // memory address. Its initial address is zero, and the region metadata
+ // supplies the length used for bounds checks. Projections select command
+ // offsets; SMBus accessors extract those offsets without dereferencing
+ // the pointer.
+ I2cView {
+ client: self,
+ ptr: Region::<256>::ptr_from_raw_parts_mut(core::ptr::without_provenance_mut(0), 256),
+ }
+ }
+
+ #[inline]
+ fn smbus_read_byte(&self, reg: u8) -> Result<u8> {
+ // SAFETY: `self.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_read_byte_data` accepts any u8 command byte.
+ let ret = unsafe { bindings::i2c_smbus_read_byte_data(self.as_raw(), reg) };
+ to_result(ret)?;
+ Ok(ret as u8)
+ }
+
+ #[inline]
+ fn smbus_write_byte(&self, reg: u8, value: u8) -> Result {
+ // SAFETY: `self.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_write_byte_data` accepts any u8 command byte and value.
+ let ret = unsafe { bindings::i2c_smbus_write_byte_data(self.as_raw(), reg, value) };
+ to_result(ret)
+ }
+
+ /// Reads the 16-bit SMBus word value returned for a command.
+ ///
+ /// Wraps `i2c_smbus_read_word_data`. The `reg` parameter is the SMBus
+ /// command byte (0x00–0xFF), a data byte which often selects a register on
+ /// the device. SMBus transmits the low byte first.
+ ///
+ /// Use [`Self::smbus_read_word_swapped`] for devices that transmit the
+ /// high byte of a multi-byte value first.
+ ///
+ /// Returns `Err` if the bus transaction fails, for example because of a
+ /// NACK, arbitration loss, or timeout.
+ #[inline]
+ pub fn smbus_read_word(&self, reg: u8) -> Result<u16> {
+ // SAFETY: `self.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_read_word_data` is safe to call with any valid client
+ // pointer and any u8 command byte.
+ let ret = unsafe { bindings::i2c_smbus_read_word_data(self.as_raw(), reg) };
+ to_result(ret)?;
+ Ok(ret as u16)
+ }
+
+ #[inline]
+ fn smbus_write_word(&self, reg: u8, value: u16) -> Result {
+ // SAFETY: `self.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_write_word_data` accepts any u8 command byte and u16 value.
+ let ret = unsafe { bindings::i2c_smbus_write_word_data(self.as_raw(), reg, value) };
+ to_result(ret)
+ }
+
+ /// Reads a 16-bit word from an SMBus command and swaps its two bytes.
+ ///
+ /// Wraps `i2c_smbus_read_word_data` and applies [`u16::swap_bytes`] to the
+ /// result. The `reg` parameter is the SMBus command byte (0x00–0xFF), a data
+ /// byte which often selects a register on the device.
+ ///
+ /// SMBus transmits the low byte first. This method swaps the returned bytes
+ /// for devices that transmit the high byte of a multi-byte value first.
+ ///
+ /// Returns `Err` if the bus transaction fails, for example because of a
+ /// NACK, arbitration loss, or timeout.
+ ///
+ /// # Examples
+ ///
+ /// ```no_run
+ /// use kernel::{device::Bound, i2c::I2cClient, prelude::*};
+ ///
+ /// # fn test(client: &I2cClient<Bound>) -> Result<u16> {
+ /// // Read a word from a device that transmits its high byte first.
+ /// client.smbus_read_word_swapped(0x0c)
+ /// # }
+ /// ```
+ #[inline]
+ pub fn smbus_read_word_swapped(&self, reg: u8) -> Result<u16> {
+ self.smbus_read_word(reg).map(u16::swap_bytes)
+ }
+}
next prev parent reply other threads:[~2026-11-09 15:58 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 15:57 [RFC PATCH v7 0/4] iio: position: add Rust driver for ams AS5600 Muchamad Coirul Anwar
2026-11-09 15:57 ` [RFC PATCH v7 1/4] rust: io: add FallibleIoCapable Muchamad Coirul Anwar
2026-11-09 15:57 ` Muchamad Coirul Anwar [this message]
2026-11-09 15:57 ` [RFC PATCH v7 3/4] rust: add minimal IIO subsystem abstractions Muchamad Coirul Anwar
2026-11-09 15:57 ` [RFC PATCH v7 4/4] iio: position: add Rust driver for ams AS5600 Muchamad Coirul Anwar
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261109155743.83740-3-muchamadcoirulanwar@gmail.com \
--to=muchamadcoirulanwar@gmail.com \
--cc=847807867@qq.com \
--cc=aliceryhl@google.com \
--cc=andi.shyti@kernel.org \
--cc=broonie@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=dlechner@baylibre.com \
--cc=driver-core@lists.linux.dev \
--cc=igor.korotin@linux.dev \
--cc=jic23@kernel.org \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®