Re: [RFC PATCH v5 2/3] rust: add minimal IIO subsystem abstractions

Jonathan Cameron <[email protected]>
Newsgroups org.kernel.vger.linux-i2c,org.kernel.vger.linux-iio,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux
Message-ID <20260824010726.2849f3d8@jic23-huawei>
On Sat, 22 Aug 2026 14:26:57 +0800
Muchamad Coirul Anwar <[email protected]> wrote:

> Add safe Rust wrappers for the Linux IIO (Industrial I/O) subsystem:
> 
> - IioChanInfo enum wrapping iio_chan_info_enum, with TryFrom<u32> for
>   type-safe dispatch in read_raw. The compiler enforces match
>   exhaustiveness, replacing the previous raw isize approach.
> - IioVal enum with NonZeroI32 for division-by-zero prevention on
>   IIO_VAL_FRACTIONAL.
> - IioDriver trait with read_raw callback (requires Send + Sync).
> - Device<T, State> with typestate (Unregistered -> Registered) to
>   prevent double-registration at compile time.
> - PinnedDrop for guaranteed cleanup sequence:
>     iio_device_unregister -> drop_in_place(T) -> iio_device_free
>   iio_device_unregister() calls cdev_device_del() which drains the
>   kernfs workqueue before returning. All in-flight read_raw callbacks
>   (which go through kernfs sysfs reads) complete before drop_in_place
>   proceeds. This covers the sysfs read path used by this driver.
> - Compile-time const VTABLE (iio_info).
> - C-to-Rust FFI trampoline for read_raw dispatch.
> 
> The abstraction uses iio_device_alloc (not devm_*) so that the Rust
> Drop implementation has full control over the cleanup sequence.
> Module ownership is enforced via __iio_device_register(indio_dev, module).
> 
> Signed-off-by: Muchamad Coirul Anwar <[email protected]>

Hi Muchamad

This looks fine to me subject to a few little things - see inline.

However I didn't take the time to decode every line of rust today so there were bits
I simply didn't understand yet.  So for this to be able to move forward I'm going
to need reviews from rust experts!

Jonathan

> diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
> index a56ba6309594..5dc917d92151 100644
> --- a/rust/kernel/error.rs
> +++ b/rust/kernel/error.rs
> @@ -86,6 +86,7 @@ macro_rules! declare_err {
>      declare_err!(EIOCBQUEUED, "iocb queued, will get completion event.");
>      declare_err!(ERECALLCONFLICT, "Conflict with recalled state.");
>      declare_err!(ENOGRACE, "NFS file lock reclaim refused.");
> +    declare_err!(ENODATA, "No data available.");

Do we have something says there must be a user in the same patch?
A really generic thing like this in C would definitely be a patch on its
own so that folk who care about maintaining a given file can easily see
it without reviewing the rest of the series.

So unless you can't do otherwise, break this out as a precursor patch.

>  }
>  
>  /// Generic integer kernel error.
> diff --git a/rust/kernel/iio.rs b/rust/kernel/iio.rs
> new file mode 100644
> index 000000000000..f1638160fed1
> --- /dev/null
> +++ b/rust/kernel/iio.rs
...

> +
> +build_iio_enum! {
> +    /// Raw unprocessed value from the channel (`IIO_CHAN_INFO_RAW`).
> +    ///
> +    /// For sensors, this is typically the ADC reading or register value
> +    /// before any scaling or offset correction.
> +    Raw = iio_chan_info_enum_IIO_CHAN_INFO_RAW,

I guess there may be a rust convention for this but from a human trying to
read the code point of view this need a blank line here and in similar places
where you have docs / thing documented repeated back to back.

> +    /// Scale factor to convert raw values to SI units (`IIO_CHAN_INFO_SCALE`).
> +    ///
> +    /// The processed value is `raw * scale`. The unit depends on the channel
> +    /// type (e.g. V for voltage, m/s² for acceleration, rad for angle).
> +    Scale = iio_chan_info_enum_IIO_CHAN_INFO_SCALE,
> +}


> +
> +/// C-compatible trampoline for the `iio_info.read_raw` callback.
> +///
> +/// # Safety
> +///
> +/// This function is only called by the IIO core via the `read_raw` function
> +/// pointer in `iio_info`. The IIO core guarantees:
> +/// - `indio_dev` is a valid `iio_dev` allocated by `iio_device_alloc`.
> +/// - `chan` points to a valid channel spec from the device's channel array.
> +/// - `val` is a valid non-null pointer to a writable `int`.
> +/// - `val2` is a valid non-null pointer to a writable `int`. The IIO core
> +///   always passes stack-allocated storage for both, regardless of whether
> +///   the driver uses `val2` (e.g. `IIO_VAL_INT` only writes `val`; `val2`

That val2 is always a valid pointer smells a bit like the c interface leaking
into the rust.  I'm not necessarily against that being a constraint we take
on but I'm not sure how we document that. Probably add something to the C docs.
Any C driver relying on this today is probably buggy for other reasons.


> +///   is provided but left unread by the caller for that return type).
> +unsafe extern "C" fn read_raw_callback<T: IioDriver>(
> +    indio_dev: *mut iio_dev,
> +    chan: *const iio_chan_spec,
> +    val: *mut c_int,
> +    val2: *mut c_int,
> +    info: isize,
> +) -> c_int {
> +    // SAFETY: `indio_dev` is valid and was allocated with space for `T` in its
> +    // private data area. The `priv_` field was initialized in `Device::build_device()`.
> +    let priv_ptr = unsafe { (*indio_dev).priv_ as *mut T };
> +    // SAFETY: `priv_ptr` points to a valid, initialized instance of `T` that
> +    // lives as long as the `iio_dev` allocation.
> +    let driver = unsafe { &*priv_ptr };
> +
> +    let info_enum = match IioChanInfo::try_from(info as u32) {
> +        Ok(valid) => valid,
> +        Err(e) => return e.to_errno(),
> +    };
> +
> +    match driver.read_raw(chan, info_enum) {
> +        Ok(IioVal::Int(v)) => {
> +            // SAFETY: `val` is valid per the function's Safety contract above.
> +            // `val2` is not written; `IIO_VAL_INT` signals to the IIO core
> +            // that only `val` carries meaningful data.
> +            unsafe {
> +                *val = v;
> +            }
> +            IIO_VAL_INT
> +        }
> +        Ok(IioVal::Fractional(v, v2)) => {
> +            // SAFETY: both `val` and `val2` are valid per the Safety contract.
> +            unsafe {
> +                *val = v;
> +                *val2 = v2.get();

Why get in some places and not others?  May well be a gap in my really limited
rust knowledge.

> +            }
> +            IIO_VAL_FRACTIONAL
> +        }
> +        Ok(IioVal::IntPlusMicro(v, v2)) => {
> +            // SAFETY: both `val` and `val2` are valid per the Safety contract.
> +            unsafe {
> +                *val = v;
> +                *val2 = v2;
> +            }
> +            IIO_VAL_INT_PLUS_MICRO
> +        }
> +        Ok(IioVal::IntPlusNano(v, v2)) => {
> +            // SAFETY: both `val` and `val2` are valid per the Safety contract.
> +            unsafe {
> +                *val = v;
> +                *val2 = v2;
> +            }
> +            IIO_VAL_INT_PLUS_NANO
> +        }
> +        Err(e) => e.to_errno(),
> +    }
> +}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.