From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=74.125.82.44 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1794239887; b=28L5eBV5DTcdLJ1+E4sx/8qt7GbIAjgdlhmXUov3OnDAy2kVf58DZ0bYjXV2BsDyyTDL iw7Ax3uxqQ8fOHNbyy+WuNbFMuYCxquf4slMFQoA92/b8JTbxyQLxAKRGoggRR8F+7RdK YdiEuG56i2cGXBcHt2Y3SblPoZkuqRM9tjGCj0mdAeSsKr1RePIOEriXtQzzcDgY5FZNQ lFY9CC++LJjya1yn7fWP2jVi3/bkrT8dOQ96LrxIP1J0o21U5P3Kr0cUaKh4TC5j1bofO pZSYAa9fikyzrUof+D8v7dwidZzru5NjHxTespBW6xdo87UP0cZFfQcOJBr9p0oojhQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1794239887; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:To:Cc:Subject:Date:Message-ID:X-Mailer:In-Reply-To:References: MIME-Version:Content-Transfer-Encoding; bh=jGfuhn/5tnyoSv44dvWh4ZEhzqKE79bI1dn8Un7FDkU=; b=KO4vNAg9OPfzlB9deYmyNnWZZvwY2yiyJH8FSG+ZDrW5lqGPZul9ochYXoXW1MFS6FfA mDJ5UCi/OK5dE9LyOg41Jjl6cayfl9+qAI3lEeHUKwAoBUCjUagqr2xJ40zotdzIY9KE7 zQLaT9UIshVsgUSxBS8MMznETSkgnhro2EEhuG3D0jLzhnqHj1Jn8XdnxtNHL8CobfarT cuIwnlvPThJB396+o3lq0B2T9QIy+HgiphwzlgFr9I5ThC0voWEtbIdHzmz8/1lIINsuv Jip49Mku+wDRo3tB4jd/dDLzVoQRr7thmLuxsWL0KIsr2XprIz4eB41li+9/L7w8HtA== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b="JauFfX2/"; arc=none smtp.remote-ip=74.125.82.44 Received: from mail-dl1-f44.google.com (mail-dl1-f44.google.com [74.125.82.44]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id E52E9606A325 for ; Mon, 9 Nov 2026 15:58:05 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org E52E9606A325 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=JauFfX2/ Received: by mail-dl1-f44.google.com with SMTP id a92af1059eb24-1a2761c69fbso873308c88.0 for ; Mon, 09 Nov 2026 07:58:05 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1794239885; x=1794844685; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=jGfuhn/5tnyoSv44dvWh4ZEhzqKE79bI1dn8Un7FDkU=; b=JauFfX2/kPwXo/3TGPnFd3DqgGyJgQVbytT2cNQaq/Jekjc/nysHnfTs3LMjfBWEj1 RUI2XRbXSlccMsPHtabv3BOkK4nS/aE9rkIm5vkQE21ZRK+98LS6tXooP9ufQe7Xch2h OJ/LISyib4xQOueQXWeC1oXSuAFt8bFNjk360B4D+PFezNKJhuvKiZHIEZcIqAOH9Fzo TaDj/o8ymr8wKOU1oXmZhYCsxMr4ycFnG9UwKyJbsMi+96nVt6YSFxR3bvDmNyBRAH8M ISJo/jVFvlOWtiPjhwmz8Lhk9saX57LcazyRfaBixMH6ZMBhEjFIZd74haYUuuPdAFHi GN5g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1794239885; x=1794844685; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=jGfuhn/5tnyoSv44dvWh4ZEhzqKE79bI1dn8Un7FDkU=; b=nUuvukElF/USQg/YeqWHogm8okDfts+TJJp8lzJW3N2fVyO1oci30gwBLEkVVSTd3a Y1g5N5ShK2oFV3yGeZed996Cv++u67DX5TEXr6VsPdEP9Os/S8h3atZ2irET7Rw0qxVz SB0lczvrleCi5rMOtmSdvpIhsG/98/MUesUZ0IB/c61DXGGGzSSpNuB0inuJvwVuWtA8 hOUGIimrpkAuKEO9GrODTQESKZfBtooFlHrv5BV3+2L7j76u0dJwO+qZXuJx8dGZQ5JE KgfzdK4iOjq2u51oIjw3PIzADLZ89PT91AAcC9fKpLVAiNQ/u+I2Qi+091Mo/77Ev5CJ VIzg== X-Forwarded-Encrypted: i=1; AL+hErFtI8aaAASPOD/bBz3rvbMch2+06y1wjh/xoaUEtZ5H7/znQU7KONRADWup9/K+ESVcWa5Ghaq0zofrS8E=@vger.kernel.org X-Gm-Message-State: APuU4Z+9bwubJWbfSEDr3hCW2DH4S9zTpqg+DruZG6If8JbDsC1no7Ql hAb13zKm3OGPkLHol2WesCruDwCihaH1pg/1CLzsrnJG7bVGOvnbZuWO X-Gm-Gg: AYBFou2AwXJzc97GHgLp2vRCpP7YReFzUAolsnjfq/PRiAvHeoTLVqiQYbNjB7HNG2f tgaJ/gHZOnDWMZJzrG87uZbQq8RNix/LqM4FQAvzdVIqGF/Zdj/ZSQOLS1SZAdRht2JyE/hnzrm Jh2RNH+rUyN/IbyZ/4Z5umhLIzH1CKDZ6ltbEcMP/+X/wbjvT2rzjQQsX/fYxNDS7CZ7Kto+xJO fm7G4b928C4ogaZ2ZDrF9GDuoyabQ3RLUs2npNJcq2k4tUZ35Hn0kKeH6aCFTmfExIwsTVQbeKs w8lKvu4vsQ1BFjeboZIUaIouTnxvs20otB4za/whiEvYuHhWBmhAXKaEJYMq1KVy5j43/p9ZdVD vltd8NsZ0HxQzRnmgdgg61v2sXDGnEXg5lv//8e66ei3toNyXp+yK+GmfwCpUe8QpKoPh0PfYm3 csuH6wlF/Ap6+64wOziedn5Xq7UismpaRRMY8cNU3l81lADBp2LVjH1bVRBVhxUY6GNwSCUEDgB AuGmSU+gt3STpKowRkNpLr28KUej6slictTGaRfWOf4hnY/CfLvelIlcvFzTtL/ubl21KZveUWZ EaogT+Dhjwd5uqaF/GsN5FgnNqz0I5NHwjbiiRS8fi1IpPVRq+rL48IElRqTfm4T2oBqJVs= X-Received: by 2002:a05:7022:ef07:b0:1b2:5128:df1b with SMTP id a92af1059eb24-1b39fed20f3mr7173796c88.45.1794239884680; Mon, 09 Nov 2026 07:58:04 -0800 (PST) Received: from localhost.localdomain ([125.166.179.225]) by smtp.gmail.com with ESMTPSA id a92af1059eb24-1b3757214cbsm13020828c88.13.2026.11.09.07.58.00 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Mon, 09 Nov 2026 07:58:04 -0800 (PST) From: Muchamad Coirul Anwar To: Jonathan Cameron , Miguel Ojeda , Igor Korotin , Danilo Krummrich Cc: David Lechner , Alice Ryhl , Daniel Almeida , Mark Brown , Andi Shyti , 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 3/4] rust: add minimal IIO subsystem abstractions Date: Mon, 9 Nov 2026 22:57:42 +0700 Message-ID: <20261109155743.83740-4-muchamadcoirulanwar@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20261109155743.83740-1-muchamadcoirulanwar@gmail.com> References: <20261109155743.83740-1-muchamadcoirulanwar@gmail.com> X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add minimal Rust abstractions needed to register direct-mode IIO devices exposing read_raw channel information. Provide a type-safe IioChanInfo selector, IioVal return types, and an IioDriver trait. Add a C callback trampoline that validates the channel information selector, invokes the Rust driver, and converts IioVal into the representation expected by the IIO core. Use typestate to distinguish unregistered and registered devices. Registration returns an owned registered device whose destructor unregisters it before destroying its Rust private data and releasing the allocation reference. Allow private data to borrow resources tied to the bound parent lifetime. This lets drivers capture bus-specific resources during probe and use them directly from subsystem callbacks without recovering them from a generic parent device. Support read_raw in INDIO_DIRECT_MODE only. Buffers, triggers, events, and write operations are not supported. The registration ownership and bound-parent lifetimes are based on Igor Korotin's rework. Link: https://lore.kernel.org/all/179286358623.55649.5397316745104390556@linux.dev/ Suggested-by: Shuai Liu <847807867@qq.com> Link: https://lore.kernel.org/all/tencent_173B7731E90B9624F9F82613DC3031B70907@qq.com/ Signed-off-by: Muchamad Coirul Anwar --- include/linux/iio/iio.h | 6 +- rust/bindings/bindings_helper.h | 2 + rust/kernel/iio.rs | 463 ++++++++++++++++++++++++++++++++ rust/kernel/lib.rs | 2 + 4 files changed, 471 insertions(+), 2 deletions(-) create mode 100644 rust/kernel/iio.rs diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 9b63785d5aec..e2ffb2b35126 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -450,8 +450,10 @@ struct iio_trigger; /* forward declaration */ * @read_raw: function to request a value from the device. * mask specifies which value. Note 0 means a reading of * the channel in question. Return value will specify the - * type of value returned by the device. val and val2 will - * contain the elements making up the returned value. + * type of value returned by the device. val and val2 + * point to writable integer storage valid for the + * duration of the callback. The return value determines + * which outputs contain meaningful data. * @read_raw_multi: function to return values from the device. * mask specifies which value. Note 0 means a reading of * the channel in question. Return value will specify the diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 3791af0aadc7..c2e2fb32ebe3 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -64,6 +64,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/rust/kernel/iio.rs b/rust/kernel/iio.rs new file mode 100644 index 000000000000..cd62b765b050 --- /dev/null +++ b/rust/kernel/iio.rs @@ -0,0 +1,463 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2026 Muchamad Coirul Anwar +//! IIO subsystem abstractions. +//! +//! Provides [`Device`] for allocating and registering an IIO device, and +//! [`IioDriver`] for implementing `read_raw` callbacks. Implementing the +//! trait requires validating the raw channel specifications. + +use crate::{ + bindings::{ + __iio_device_register, + iio_chan_spec, + iio_dev, + iio_device_alloc, + iio_device_free, + iio_device_unregister, + iio_info, + INDIO_DIRECT_MODE, // + }, + device::{ + self, + Bound, // + }, + error::{ + code::*, + to_result, + Result, // + }, + prelude::*, + ThisModule, // +}; + +use core::{ + ffi::c_int, + marker::PhantomData, + mem::{ + forget, + size_of, + zeroed, // + }, + num::NonZeroI32, + pin::Pin, + ptr::drop_in_place, // +}; + +use pin_init::{ + pin_data, + pinned_drop, // +}; + +/// IIO value type: single integer (`IIO_VAL_INT`). +pub const IIO_VAL_INT: c_int = crate::bindings::IIO_VAL_INT as c_int; + +/// IIO value type: integer plus micro part (`IIO_VAL_INT_PLUS_MICRO`). +pub const IIO_VAL_INT_PLUS_MICRO: c_int = crate::bindings::IIO_VAL_INT_PLUS_MICRO as c_int; + +/// IIO value type: integer plus nano part (`IIO_VAL_INT_PLUS_NANO`). +pub const IIO_VAL_INT_PLUS_NANO: c_int = crate::bindings::IIO_VAL_INT_PLUS_NANO as c_int; + +/// IIO value type: fractional (`IIO_VAL_FRACTIONAL`). +pub const IIO_VAL_FRACTIONAL: c_int = crate::bindings::IIO_VAL_FRACTIONAL as c_int; + +/// Generates a Rust enum wrapper for C `enum iio_chan_info_enum`. +/// +/// This macro creates a type-safe enum with automatic `TryFrom` +/// conversion. Drivers match directly on `IioChanInfo` variants in +/// `read_raw`, and the compiler enforces match exhaustiveness. +/// Additional variants can be added as drivers require them. +macro_rules! build_iio_chan_info_enum { + ( + $( + $(#[$meta:meta])* + $rust_name:ident = $c_const:ident + ),* $(,)? + ) => { + /// Channel information selector for [`IioDriver::read_raw`]. + /// + /// Wraps supported C `enum iio_chan_info_enum` values identifying + /// the requested channel information. + /// + /// Conversion from an unsupported value returns `EINVAL`. + #[repr(u32)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + pub enum IioChanInfo { + $( + $(#[$meta])* + $rust_name = bindings::$c_const, + )* + } + impl TryFrom for IioChanInfo { + type Error = Error; + fn try_from(value: u32) -> Result { + match value { + $( bindings::$c_const => Ok(IioChanInfo::$rust_name), )* + _ => Err(EINVAL), + } + } + } + }; +} + +build_iio_chan_info_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, + + /// Scale factor for converting raw values to channel units + /// (`IIO_CHAN_INFO_SCALE`). + /// + /// Apply any offset before scaling: `(raw + offset) * scale`. + /// The unit is defined by the IIO ABI for the channel type. + Scale = iio_chan_info_enum_IIO_CHAN_INFO_SCALE, +} + +/// Represents the return value of a `read_raw` operation. +/// +/// Each variant corresponds to an `IIO_VAL_*` constant and tells the +/// IIO core how to format `val` and `val2` for userspace. +pub enum IioVal { + /// A single integer value. + Int(i32), + + /// A fractional value represented as `val / val2`. + /// The denominator is `NonZeroI32` to prevent division-by-zero in + /// `iio_format_value()`. + Fractional(i32, NonZeroI32), + + /// An integer part and a fractional part in millionths + /// (`IIO_VAL_INT_PLUS_MICRO`). + /// + /// For negative values, put the sign on the integer part, or on the + /// fractional part when the integer part is zero. For example, + /// `IntPlusMicro(-1, 500_000)` represents -1.5, and + /// `IntPlusMicro(0, -500_000)` represents -0.5. + IntPlusMicro(i32, i32), + + /// An integer part and a fractional part in billionths + /// (`IIO_VAL_INT_PLUS_NANO`). + /// + /// Uses the same sign convention as [`IioVal::IntPlusMicro`]. + IntPlusNano(i32, i32), +} + +/// Trait implemented by IIO driver private data. +/// +/// # Safety +/// +/// The channel specifications returned by `channels()` must satisfy the +/// IIO core's requirements. Channel types, modifiers, strings, and +/// pointer/count pairs must be valid. Referenced storage must remain +/// valid for every access by the IIO core. +/// +/// Channels must not expose events, buffers, extended attributes, or +/// available-value attributes. This abstraction currently supports only +/// channel information handled by its `read_raw` callback. +pub unsafe trait IioDriver: Send + Sync { + /// Called by the IIO core to read channel information. + /// + /// `chan` identifies the channel, and `info` selects the requested + /// information, such as its raw value or scale. Implementors must + /// capture bus-specific resources during initialization rather than + /// deriving them from a generic parent reference. + fn read_raw(&self, chan: *const iio_chan_spec, info: IioChanInfo) -> Result; + + /// Returns the channel specifications for this driver. + /// + /// The default implementation returns an empty slice. + fn channels(&self) -> &'static [iio_chan_spec] { + &[] + } +} + +/// C-compatible trampoline for the `iio_info.read_raw` callback. +/// +/// # Safety +/// +/// Callers must ensure that: +/// - `indio_dev` points to a valid `iio_dev` allocated with space for `T`. +/// Its private data contains an initialized `T` that remains valid and +/// may be shared for the duration of this callback. +/// - `chan` points to a valid channel specification for `indio_dev`. +/// - `val` and `val2` are non-null, properly aligned pointers to writable +/// `c_int` storage valid for the duration of this callback. +/// - The parent of `indio_dev` remains bound for the entire callback. +/// Registration ownership and callback teardown must enforce this. +unsafe extern "C" fn read_raw_callback( + 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_.cast::() }; + // SAFETY: The callback contract guarantees that `priv_ptr` points to an + // initialized `T` that remains valid for the duration of this callback. + let driver = unsafe { &*priv_ptr }; + + // `info` is `long` from C. Negative values and values > u32::MAX + // are unsupported iio_chan_info variants; both map to EINVAL. + let info_enum = match u32::try_from(info) + .ok() + .and_then(|value| IioChanInfo::try_from(value).ok()) + { + Some(info) => info, + None => return EINVAL.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(); + } + 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(), + } +} + +// Device: IIO device wrapper with typestate. + +/// Marker type for an unregistered IIO device. +pub struct Unregistered; + +/// Marker type for a registered IIO device. +pub struct Registered; + +/// An initialized IIO device awaiting registration. +pub struct UnregisteredDevice { + device: Device, +} + +/// An owned IIO device with in-place initialized driver data. +/// +/// Dropping a registered device unregisters it before destroying the driver +/// data. Dropping either state then releases the allocation reference with +/// `iio_device_free`. +/// +/// # Invariants +/// +/// - `indio_dev` points to an `iio_dev` allocated by `iio_device_alloc`, +/// and this wrapper owns its allocation reference. +/// - Its private data contains an initialized `T` that remains at a fixed +/// address until it is destroyed. +/// - `registered` is true exactly when this wrapper owns a successful +/// registration that has not yet been undone. +#[pin_data(PinnedDrop)] +pub struct Device { + indio_dev: *mut iio_dev, + registered: bool, + _p: PhantomData<(T, State)>, +} + +// SAFETY: Moving the wrapper transfers ownership without moving the separately +// allocated `iio_dev` or its private data. `T: Send` permits transferring the +// private data to another thread. +unsafe impl Send for Device {} + +// SAFETY: All `&self` access to the `iio_dev` is read-only or goes through the +// IIO core which provides its own synchronization. `T` is required to be `Sync`. +unsafe impl Sync for Device {} + +#[pinned_drop] +impl PinnedDrop for Device { + fn drop(self: Pin<&mut Self>) { + if self.registered { + // SAFETY: `__iio_device_register` succeeded. + // + // iio_device_unregister() deletes the character device, disables + // buffers, clears `info` while holding `info_exist_lock`, and removes + // the remaining buffer sysfs state. Device removal also removes and + // drains its sysfs files. In-kernel read helpers that use + // `info_exist_lock` cannot race this `info` teardown. + // + // Any callback path not covered by those mechanisms requires a + // separate teardown proof before this abstraction can expose it. + unsafe { iio_device_unregister(self.indio_dev) }; + } + + // SAFETY: `priv_` was fully initialized in `build_device` via + // `pin_init::raw_try_init`. `drop_in_place` runs `T`'s destructor + // (including any pinned fields like Mutex). After that, `iio_device_free` + // calls `put_device` which decrements the kref. The underlying `iio_dev` + // memory is only freed when kref reaches 0. + unsafe { + let priv_ptr = (*self.indio_dev).priv_.cast::(); + drop_in_place(priv_ptr); + iio_device_free(self.indio_dev); + } + } +} + +impl Device { + // SAFETY: + // - `read_raw_callback::` is a valid function pointer whose signature + // matches the IIO core's `read_raw` contract. + // - All remaining fields are pointers or function pointers; zeroed values + // are NULL, and the IIO core checks for NULL before invoking any optional + // callback or dereferencing any optional attribute group. + const VTABLE: iio_info = iio_info { + read_raw: Some(read_raw_callback::), + ..unsafe { zeroed() } + }; + + /// Allocates an unregistered IIO device and initializes its driver data + /// in place. + /// + /// `parent` becomes the IIO device's parent. `name` supplies its name. + /// Only `INDIO_DIRECT_MODE` is supported. + /// + /// Returns `EINVAL` if `modes` is not `INDIO_DIRECT_MODE`, if the + /// private-data size or channel count cannot be represented by the + /// corresponding C type, if `T` is zero-sized, or if the private + /// storage is not suitably aligned. + /// Allocation and initialization errors are propagated. + /// + /// Call [`UnregisteredDevice::register`] to register the device. + pub fn build_device( + parent: &device::Device, + name: &'static CStr, + modes: u32, + init: impl PinInit, + ) -> Result> + where + Error: From, + { + if modes != INDIO_DIRECT_MODE { + return Err(EINVAL); + } + + let modes = c_int::try_from(modes).map_err(|_| EINVAL)?; + + let priv_size = i32::try_from(size_of::()).map_err(|_| EINVAL)?; + if priv_size == 0 { + return Err(EINVAL); + } + + // SAFETY: `parent.as_raw()` returns a valid `struct device` pointer. + // `iio_device_alloc` allocates an `iio_dev` with `sizeof(T)` bytes of + // private data. Returns NULL on failure. + let indio_dev = unsafe { iio_device_alloc(parent.as_raw(), priv_size) }; + if indio_dev.is_null() { + return Err(ENOMEM); + } + + // SAFETY: `indio_dev` is non-null and was just successfully allocated by + // `iio_device_alloc`. Accessing `priv_` yields a pointer to zeroed + // memory (kzalloc'd) sized for `T`. + let priv_ptr = unsafe { (*indio_dev).priv_.cast::() }; + if !priv_ptr.is_aligned() { + // SAFETY: `indio_dev` was allocated by `iio_device_alloc` and `priv_` + // has not been initialized, so no destructor needs to run. + unsafe { iio_device_free(indio_dev) }; + return Err(EINVAL); + } + + // SAFETY: `priv_ptr` is aligned and points to exclusively owned storage + // large enough for `T`. The private data remains at a fixed address in + // the separate IIO allocation until it is dropped. On initialization + // failure, the error path only frees the allocation. + let init_result = unsafe { pin_init::raw_try_init(priv_ptr, init) }; + if let Err(e) = init_result { + // SAFETY: `pin_init` guarantees partial-init rollback internally. + // `priv_` memory was not fully initialized, so we only free the + // container without running `T`'s destructor. + unsafe { iio_device_free(indio_dev) }; + return Err(Error::from(e)); + } + + let device = Self { + indio_dev, + registered: false, + _p: PhantomData, + }; + + // SAFETY: `priv_ptr` points to the initialized `T` owned by `device`. + // It remains valid for this shared borrow. + let channels = unsafe { (&*priv_ptr).channels() }; + let num_channels = c_int::try_from(channels.len()).map_err(|_| EINVAL)?; + + // SAFETY: `priv_ptr` is now fully initialized. We set up the IIO + // device fields: + // - `name` is a `'static` C string that outlives the device. + // - The reference to `VTABLE` is promoted to static storage. + // - `channels()` returns a static slice, so its backing storage + // outlives the `iio_dev` allocation. + // - `modes` was checked to be `INDIO_DIRECT_MODE`, so registration + // does not expose unsupported buffer or trigger paths. + // - `T`'s `IioDriver` safety contract guarantees that the channel + // specifications and their referenced storage satisfy the IIO + // core's requirements. + + unsafe { + (*indio_dev).name = name.as_char_ptr(); + (*indio_dev).info = &Self::VTABLE; + (*indio_dev).channels = channels.as_ptr(); + (*indio_dev).num_channels = num_channels; + (*indio_dev).modes = modes; + } + + Ok(UnregisteredDevice { device }) + } +} + +impl UnregisteredDevice { + /// Registers the IIO device. + /// + /// The caller owns the returned [`Device`] and must keep it + /// alive while the registration should remain visible. Dropping it + /// unregisters the device and tears down the private data. + /// + /// On failure, the device is released and any successful registration is + /// undone. + #[inline] + pub fn register(self, module: &'static ThisModule) -> Result> { + let Self { device } = self; + + // SAFETY: `device.indio_dev` is a valid, fully-initialized `iio_dev` + // allocated by `iio_device_alloc` with its name, info vtable, channels, + // modes, and private data all set. `module.as_ptr()` provides the owning + // module for proper reference counting. + to_result(unsafe { __iio_device_register(device.indio_dev, module.as_ptr()) })?; + + let registered: Device = Device { + indio_dev: device.indio_dev, + registered: true, + _p: PhantomData, + }; + forget(device); + + Ok(registered) + } +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 1ab41129d557..dd883c1b374e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -85,6 +85,8 @@ #[cfg(CONFIG_I2C = "y")] pub mod i2c; pub mod id_pool; +#[cfg(CONFIG_IIO)] +pub mod iio; #[doc(hidden)] pub mod impl_flags; pub mod init;