[RFC PATCH v5 1/3] i2c: rust: implement SMBus access via IoBackend and FallibleIoCapable
Muchamad Coirul Anwar <[email protected]>
| Newsgroups | org.kernel.vger.rust-for-linux,org.kernel.vger.linux-i2c,org.kernel.vger.linux-iio,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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/[email protected]/ 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 <[email protected]> Signed-off-by: Muchamad Coirul Anwar <[email protected]> --- 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