[RFC PATCH v5 1/3] i2c: rust: implement SMBus access via IoBackend and FallibleIoCapable

From: Muchamad Coirul Anwar

Date: Sat Aug 22 2026 - 02:28:37 EST


Implement SMBus byte and word read/write operations for I2cClient using
the FallibleIoCapable trait from the generic I/O backend infrastructure.

I2cClient now exposes an I2cBackend that implements FallibleIoCapable<u8>
and FallibleIoCapable<u16>, replacing the previous IoCapable approach.
I2C/SMBus bus transactions are inherently fallible (NACK, arbitration
loss, timeout), so the infallible IoCapable is not appropriate here.
FallibleIoCapable carries the errno from i2c_smbus_read_byte_data and
i2c_smbus_read_word_data directly to the caller via Result<T>.

The implementation is restricted to I2cClient<Bound> as I/O operations
require a live device context.

I2cClient<Bound>::smbus_io() returns an I2cView handle for use with the
generic try_read8/try_read16 methods. Two standalone methods are also
provided for odd-offset word access that bypasses the alignment check
in the Io trait:

smbus_read_word() - CPU-native byte order (SMBus LE wire format)
smbus_read_word_swapped() - byte-swapped result for big-endian devices

maxsize is 256, covering the SMBus command byte range 0x00-0xFF. This
is the command byte space, not the 7-bit device address which is handled
by the I2C core at adapter level.

Link: https://lore.kernel.org/rust-for-linux/20260131-i2c-adapter-v1-4-5a436e34cd1a@xxxxxxxxx/
Link: https://git.kernel.org/pub/scm/linux/kernel/git/driver-core/driver-core.git/commit/?h=driver-core-testing&id=121d87b28e1d9061d3aaa156c43a627d3cb5e620
Suggested-by: Danilo Krummrich <dakr@xxxxxxxxxx>
Signed-off-by: Muchamad Coirul Anwar <muchamadcoirulanwar@xxxxxxxxx>
---
rust/kernel/bits.rs | 29 +++++
rust/kernel/i2c.rs | 302 ++++++++++++++++++++++++++++++++++++++++++++
rust/kernel/io.rs | 66 +++++++---
3 files changed, 377 insertions(+), 20 deletions(-)

diff --git a/rust/kernel/bits.rs b/rust/kernel/bits.rs
index 2daead125626..a6537a668dd6 100644
--- a/rust/kernel/bits.rs
+++ b/rust/kernel/bits.rs
@@ -41,6 +41,7 @@ pub const fn [<bit_ $ty>](n: u32) -> $ty {
impl_bit_fn!(u32);
impl_bit_fn!(u16);
impl_bit_fn!(u8);
+impl_bit_fn!(usize);

macro_rules! impl_genmask_fn {
(
@@ -203,3 +204,31 @@ pub const fn [<genmask_ $ty>](range: RangeInclusive<u32>) -> $ty {
/// assert_eq!(genmask_u8(0..=7), u8::MAX);
/// ```
);
+
+impl_genmask_fn!(
+ usize,
+ /// # Examples
+ ///
+ /// ```
+ /// # #![expect(clippy::reversed_empty_ranges)]
+ /// # use kernel::bits::genmask_checked_usize;
+ /// assert_eq!(genmask_checked_usize(0..=0), Some(0b1));
+ /// assert_eq!(genmask_checked_usize(0..=3), Some(0b1111));
+ /// assert_eq!(genmask_checked_usize(1..=3), Some(0b1110));
+ ///
+ /// // `200` is out of the supported bit range on all platforms.
+ /// assert_eq!(genmask_checked_usize(0..=200), None);
+ ///
+ /// // Invalid range where the start is bigger than the end.
+ /// assert_eq!(genmask_checked_usize(5..=2), None);
+ /// ```
+ ,
+ /// # Examples
+ ///
+ /// ```
+ /// # use kernel::bits::genmask_usize;
+ /// assert_eq!(genmask_usize(0..=0), 0b1);
+ /// assert_eq!(genmask_usize(0..=3), 0b1111);
+ /// assert_eq!(genmask_usize(1..=3), 0b1110);
+ /// ```
+);
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 624b971ca8b0..f939907573a6 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, //
@@ -601,3 +608,298 @@ 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 {}
+
+// SAFETY: `I2cClient<Bound>` wraps a kernel `struct i2c_client`. The I2C core
+// and bus locking mechanisms ensure that the underlying client structure can
+// be safely transferred between threads.
+unsafe impl Send for I2cClient<device::Bound> {}
+
+// SAFETY: `I2cClient<Bound>` wraps a kernel `struct i2c_client`. All methods
+// that access the client go through kernel I2C core functions that provide
+// their own synchronization. No &self method exposes interior mutability.
+unsafe impl Sync for I2cClient<device::Bound> {}
+
+// SAFETY: `I2cClient<Bound>` is always reference-counted via the embedded
+// `struct device`. `get_device`/`put_device` increment and decrement the
+// device refcount atomically. A separate impl is needed for `I2cClient<Bound>`
+// because `AlwaysRefCounted` is not implemented generically over all
+// `DeviceContext`s — only the specific contexts that are safe to refcount
+// from arbitrary threads.
+unsafe impl AlwaysRefCounted for I2cClient<device::Bound> {
+ fn inc_ref(&self) {
+ // SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
+ unsafe { bindings::get_device(self.as_ref().as_raw()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: The safety requirements guarantee that the refcount is non-zero.
+ unsafe { bindings::put_device(&raw mut (*obj.as_ref().as_raw()).dev) }
+ }
+}
+
+/// I/O backend for SMBus register access via I2C.
+///
+/// This backend implements only [`FallibleIoCapable`] and not [`IoCapable`],
+/// because I2C/SMBus bus transactions are inherently fallible — NACK,
+/// arbitration loss, and timeout can occur regardless of address validity.
+/// The infallible [`Io::read`], [`Io::write`], and [`Io::update`] methods
+/// are therefore compile-time unavailable for this backend.
+pub struct I2cBackend;
+
+/// View type for [`I2cBackend`], carrying a reference to an I2C client and
+/// a fake pointer that encodes the register offset and address-space size
+/// as fat-pointer metadata.
+///
+/// The pointer field is never dereferenced. After [`IoBackend::project_view`]
+/// projects an offset into the pointer, `addr()` yields that offset as the
+/// SMBus command byte. [`KnownSize::size()`] reads the fat-pointer metadata
+/// length (256 for the SMBus command space).
+///
+/// # Invariants
+///
+/// `ptr` is a non-dereferenceable fat pointer. Its address component encodes
+/// the SMBus register offset (0..=255) after [`IoBackend::project_view`]
+/// projection; its length metadata is 256 (the SMBus command byte address
+/// space). `client` points to a valid `I2cClient<Bound>` that remains live
+/// for `'a`.
+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;
+ // SAFETY: `view.client.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_read_byte_data` is safe to call with any valid client pointer
+ // and any u8 command byte.
+ let ret = unsafe { bindings::i2c_smbus_read_byte_data(view.client.as_raw(), reg) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as u8)
+ }
+ }
+
+ #[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;
+ // SAFETY: `view.client.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_write_byte_data` is safe to call with any valid client pointer
+ // and any u8 command byte and value.
+ let ret = unsafe { bindings::i2c_smbus_write_byte_data(view.client.as_raw(), reg, value) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+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;
+ // SAFETY: `view.client.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(view.client.as_raw(), reg) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as u16)
+ }
+ }
+
+ #[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;
+ // SAFETY: `view.client.as_raw()` returns a valid `*mut struct i2c_client`
+ // pointer as guaranteed by the type invariant of `I2cClient`.
+ // `i2c_smbus_write_word_data` is safe to call with any valid client pointer
+ // and any u8 command byte and u16 value.
+ let ret = unsafe { bindings::i2c_smbus_write_word_data(view.client.as_raw(), reg, value) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(())
+ }
+ }
+}
+
+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: `I2cView` contains `&'a I2cClient<Bound>` (which is `Send` because
+// `I2cClient<Bound>: Sync`) and `*mut T`. The raw pointer is never
+// dereferenced — it only encodes the SMBus register offset as its address.
+// With `T: Sync`, moving the view to another thread cannot cause data races.
+unsafe impl<T: ?Sized + Sync> Send for I2cView<'_, T> {}
+
+// SAFETY: `I2cView` contains `&'a I2cClient<Bound>` (which is `Sync`) and
+// `*mut T`. The raw pointer is never dereferenced; sharing an `&I2cView`
+// across threads is equivalent to sharing `&I2cClient<Bound>` and a
+// read-only address value. `T: Sync` ensures the addressed data is
+// safe to access from multiple threads.
+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/write methods for the
+ /// 256-byte SMBus command address space (0x00–0xFF). This is the SMBus
+ /// command byte range, NOT the 7-bit device address, which is handled
+ /// by the I2C core at the adapter level.
+ ///
+ /// Note: [`Io::try_read16`] and [`Io::try_write16`] on the returned handle
+ /// reject odd offsets. The underlying [`Region`] base address is 0, so
+ /// [`offset_valid`] checks `(0 + offset) % 2 == 0` — only even offsets
+ /// pass. For word-sized access to odd-offset registers use
+ /// [`smbus_read_word`] or [`smbus_read_word_swapped`] instead.
+ ///
+ /// The underlying pointer in the returned [`I2cView`] is never
+ /// dereferenced; it encodes the register address space size as
+ /// fat-pointer metadata and the register offset as the pointer address.
+ ///
+ /// [`smbus_read_word`]: Self::smbus_read_word
+ /// [`smbus_read_word_swapped`]: Self::smbus_read_word_swapped
+ #[inline]
+ pub fn smbus_io(&self) -> I2cView<'_, Region<256>> {
+ // INVARIANT: `client` is `self`, a valid `I2cClient<Bound>`.
+ //
+ // `ptr` is a "fake pointer" — it is constructed solely to carry two
+ // pieces of metadata through the `IoBase` machinery:
+ // - address component: 0 initially; after each `project_view` call,
+ // this becomes the register offset (the SMBus command byte).
+ // - length metadata: 256, encoding the SMBus command address space
+ // size so `io_view()` can bounds-check offsets.
+ //
+ // `without_provenance_mut(0)` produces a pointer with no memory
+ // provenance — it cannot be used to read or write memory. This is safe
+ // because `I2cBackend::as_ptr()` extracts the address as a `usize`
+ // offset and passes it to `i2c_smbus_*` functions, never dereferencing
+ // the pointer itself. Using a provenance-free base avoids accidentally
+ // creating a pointer that appears to alias real memory.
+ I2cView {
+ client: self,
+ ptr: Region::<256>::ptr_from_raw_parts_mut(core::ptr::without_provenance_mut(0), 256),
+ }
+ }
+
+ /// Reads a 16-bit word from an SMBus register in CPU-native byte order.
+ ///
+ /// Wraps `i2c_smbus_read_word_data`. The `reg` parameter is the SMBus
+ /// command byte (0x00–0xFF) — an instruction sent to the device over the
+ /// serial bus, not a memory address. There is no alignment requirement:
+ /// any command byte value is valid regardless of whether it is odd or even.
+ ///
+ /// SMBus transmits the low byte first (little-endian on the wire), and this
+ /// method returns the value in CPU-native byte order without further
+ /// conversion. Use [`Self::smbus_read_word_swapped`] for devices that store
+ /// multi-byte registers in big-endian (MSB-first) format.
+ ///
+ /// Returns `Err` if the bus transaction fails (e.g. 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) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as u16)
+ }
+ }
+
+ /// Reads a 16-bit word from an SMBus register with bytes unconditionally
+ /// swapped.
+ ///
+ /// Wraps `i2c_smbus_read_word_data` and applies [`u16::swap_bytes`] to the
+ /// result. Use this for devices that store multi-byte registers in
+ /// big-endian (MSB-first) format, which is common among I2C sensors whose
+ /// datasheets do not reference the SMBus specification.
+ ///
+ /// The swap is **unconditional** — it is not equivalent to `be16_to_cpu`.
+ /// On a big-endian CPU, `be16_to_cpu` would be a no-op, but this method
+ /// still swaps. The reason: SMBus always transmits the low byte first, so
+ /// the driver always receives data in little-endian wire order regardless
+ /// of CPU endianness. The swap corrects for the device's wire-level byte
+ /// order, not the CPU's native order.
+ ///
+ /// The `reg` parameter is the SMBus command byte (0x00–0xFF). There is no
+ /// alignment requirement; any command byte value is valid.
+ ///
+ /// Returns `Err` if the bus transaction fails (e.g. NACK, arbitration loss,
+ /// or timeout).
+ ///
+ /// # Example
+ ///
+ /// ```ignore
+ /// // AS5600 stores the 12-bit raw angle big-endian at register 0x0C.
+ /// let raw = client.smbus_read_word_swapped(0x0C)?;
+ /// let angle = raw & 0x0FFF;
+ /// ```
+ #[inline]
+ pub fn smbus_read_word_swapped(&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`.
+ let ret = unsafe { bindings::i2c_smbus_read_word_data(self.as_raw(), reg) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok((ret as u16).swap_bytes())
+ }
+ }
+}
diff --git a/rust/kernel/io.rs b/rust/kernel/io.rs
index 95f46bb75f9e..516895ca2082 100644
--- a/rust/kernel/io.rs
+++ b/rust/kernel/io.rs
@@ -276,6 +276,36 @@ pub trait IoCapable<T>: IoBackend {
fn io_write<'a>(view: Self::View<'a, T>, value: T);
}

+/// Fallible counterpart of [`IoCapable`] for I/O backends where operations can fail at the
+/// transport level (e.g. I2C, SPI).
+///
+/// Infallible backends ([`IoCapable`] implementors) get this for free via blanket implementation.
+/// Fallible-only backends implement this trait directly without implementing [`IoCapable`]; the
+/// infallible [`Io::read`], [`Io::write`], and [`Io::update`] methods will then be unavailable,
+/// enforcing that callers use the `try_*` variants instead.
+pub trait FallibleIoCapable<T>: IoBackend {
+ /// Performs an I/O read of type `T` at `view` and returns the result, or an error if the
+ /// transport-level operation fails.
+ fn io_try_read<'a>(view: Self::View<'a, T>) -> Result<T>;
+
+ /// Performs an I/O write of `value` at `view`, or returns an error if the transport-level
+ /// operation fails.
+ fn io_try_write<'a>(view: Self::View<'a, T>, value: T) -> Result;
+}
+
+impl<B: IoCapable<T>, T> FallibleIoCapable<T> for B {
+ #[inline(always)]
+ fn io_try_read<'a>(view: Self::View<'a, T>) -> Result<T> {
+ Ok(Self::io_read(view))
+ }
+
+ #[inline(always)]
+ fn io_try_write<'a>(view: Self::View<'a, T>, value: T) -> Result {
+ Self::io_write(view, value);
+ Ok(())
+ }
+}
+
/// Trait indicating that an I/O backend supports memory copy operations.
pub trait IoCopyable: IoBackend {
/// Copy contents of `view` to `buffer`.
@@ -645,7 +675,7 @@ fn copy_to_slice(self, data: &mut [u8])
fn try_read8(self, offset: usize) -> Result<u8>
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
- Self::Backend: IoCapable<u8>,
+ Self::Backend: FallibleIoCapable<u8>,
{
self.try_read(offset)
}
@@ -655,7 +685,7 @@ fn try_read8(self, offset: usize) -> Result<u8>
fn try_read16(self, offset: usize) -> Result<u16>
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
- Self::Backend: IoCapable<u16>,
+ Self::Backend: FallibleIoCapable<u16>,
{
self.try_read(offset)
}
@@ -665,7 +695,7 @@ fn try_read16(self, offset: usize) -> Result<u16>
fn try_read32(self, offset: usize) -> Result<u32>
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
- Self::Backend: IoCapable<u32>,
+ Self::Backend: FallibleIoCapable<u32>,
{
self.try_read(offset)
}
@@ -675,7 +705,7 @@ fn try_read32(self, offset: usize) -> Result<u32>
fn try_read64(self, offset: usize) -> Result<u64>
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
- Self::Backend: IoCapable<u64>,
+ Self::Backend: FallibleIoCapable<u64>,
{
self.try_read(offset)
}
@@ -685,7 +715,7 @@ fn try_read64(self, offset: usize) -> Result<u64>
fn try_write8(self, value: u8, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u8, IoType = u8>,
- Self::Backend: IoCapable<u8>,
+ Self::Backend: FallibleIoCapable<u8>,
{
self.try_write(offset, value)
}
@@ -695,7 +725,7 @@ fn try_write8(self, value: u8, offset: usize) -> Result
fn try_write16(self, value: u16, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u16, IoType = u16>,
- Self::Backend: IoCapable<u16>,
+ Self::Backend: FallibleIoCapable<u16>,
{
self.try_write(offset, value)
}
@@ -705,7 +735,7 @@ fn try_write16(self, value: u16, offset: usize) -> Result
fn try_write32(self, value: u32, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u32, IoType = u32>,
- Self::Backend: IoCapable<u32>,
+ Self::Backend: FallibleIoCapable<u32>,
{
self.try_write(offset, value)
}
@@ -715,7 +745,7 @@ fn try_write32(self, value: u32, offset: usize) -> Result
fn try_write64(self, value: u64, offset: usize) -> Result
where
usize: IoLoc<Self::Target, u64, IoType = u64>,
- Self::Backend: IoCapable<u64>,
+ Self::Backend: FallibleIoCapable<u64>,
{
self.try_write(offset, value)
}
@@ -827,10 +857,10 @@ fn write64(self, value: u64, offset: usize)
fn try_read<T, L>(self, location: L) -> Result<T>
where
L: IoLoc<Self::Target, T>,
- Self::Backend: IoCapable<L::IoType>,
+ Self::Backend: FallibleIoCapable<L::IoType>,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
- Ok(Self::Backend::io_read(view).into())
+ Ok(Self::Backend::io_try_read(view)?.into())
}

/// Generic fallible write with runtime bounds check.
@@ -860,12 +890,11 @@ fn try_read<T, L>(self, location: L) -> Result<T>
fn try_write<T, L>(self, location: L, value: T) -> Result
where
L: IoLoc<Self::Target, T>,
- Self::Backend: IoCapable<L::IoType>,
+ Self::Backend: FallibleIoCapable<L::IoType>,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
let io_value = value.into();
- Self::Backend::io_write(view, io_value);
- Ok(())
+ Self::Backend::io_try_write(view, io_value)
}

/// Generic fallible write of a fully-located register value.
@@ -905,7 +934,7 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
where
L: IoLoc<Self::Target, T>,
V: LocatedRegister<Self::Target, Location = L, Value = T>,
- Self::Backend: IoCapable<L::IoType>,
+ Self::Backend: FallibleIoCapable<L::IoType>,
{
let (location, value) = value.into_io_op();

@@ -938,16 +967,13 @@ fn try_write_reg<T, L, V>(self, value: V) -> Result
fn try_update<T, L, F>(self, location: L, f: F) -> Result
where
L: IoLoc<Self::Target, T>,
- Self::Backend: IoCapable<L::IoType>,
+ Self::Backend: FallibleIoCapable<L::IoType>,
F: FnOnce(T) -> T,
{
let view = io_view::<Self, L::IoType>(self, location.offset())?;
-
- let value: T = Self::Backend::io_read(view).into();
+ let value: T = Self::Backend::io_try_read(view)?.into();
let io_value = f(value).into();
- Self::Backend::io_write(view, io_value);
-
- Ok(())
+ Self::Backend::io_try_write(view, io_value)
}

/// Generic infallible read with compile-time bounds check.
--
2.50.0