From: "Danilo Krummrich" <dakr@kernel.org>
To: "Zhi Wang" <zhiw@nvidia.com>
Cc: <rust-for-linux@vger.kernel.org>, <bhelgaas@google.com>,
<kwilczynski@kernel.org>, <ojeda@kernel.org>,
<alex.gaynor@gmail.com>, <boqun.feng@gmail.com>,
<gary@garyguo.net>, <bjorn3_gh@protonmail.com>,
<lossin@kernel.org>, <a.hindborg@kernel.org>,
<aliceryhl@google.com>, <tmgross@umich.edu>,
<linux-kernel@vger.kernel.org>, <cjia@nvidia.com>,
<smitra@nvidia.com>, <ankita@nvidia.com>, <aniketa@nvidia.com>,
<kwankhede@nvidia.com>, <targupta@nvidia.com>,
<zhiwang@kernel.org>, <alwilliamson@nvidia.com>,
<acourbot@nvidia.com>, <joelagnelf@nvidia.com>,
<jhubbard@nvidia.com>, <jgg@nvidia.com>
Subject: Re: [RFC 1/2] rust: introduce abstractions for fwctl
Date: Thu, 30 Oct 2025 17:47:26 +0100 [thread overview]
Message-ID: <DDVT5YA564C6.3HN9WCMQX49PC@kernel.org> (raw)
In-Reply-To: <20251030160315.451841-2-zhiw@nvidia.com>
On Thu Oct 30, 2025 at 5:03 PM CET, Zhi Wang wrote:
> diff --git a/rust/kernel/fwctl.rs b/rust/kernel/fwctl.rs
> new file mode 100644
> index 000000000000..21f8f7d11d6f
> --- /dev/null
> +++ b/rust/kernel/fwctl.rs
> @@ -0,0 +1,254 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +
> +//! Abstractions for the fwctl.
> +//!
> +//! This module provides bindings for working with fwctl devices in kernel modules.
> +//!
> +//! C header: [`include/linux/fwctl.h`]
> +
> +use crate::device::Device;
> +use crate::types::ARef;
> +use crate::{bindings, container_of, device, error::code::*, prelude::*};
> +
> +use core::marker::PhantomData;
> +use core::ptr::NonNull;
> +use core::slice;
Please use the import scheme as documented in [1].
[1] https://docs.kernel.org/rust/coding-guidelines.html#imports
> +/// The registration of a fwctl device.
> +///
> +/// This type represents the registration of a [`struct fwctl_device`]. When an instance of this
> +/// type is dropped, its respective fwctl device will be unregistered and freed.
> +///
> +/// [`struct fwctl_device`]: srctree/include/linux/device/fwctl.h
> +pub struct Registration<T: FwCtlOps> {
> + fwctl_dev: NonNull<bindings::fwctl_device>,
Given that this structure has to keep a reference count of the fwctl_device, I'd
prefer to have an abstraction of struct fwctl_device (fwctl::Device) which
implements AlwaysRefCounted.
This way the Registration can store an ARef<fwctl::Device> rather than a raw
pointer.
However, I wonder if we really need a reference count? Does fwctl_register() not
take a reference count itself?
> + _marker: PhantomData<T>,
> +}
> +
> +impl<T: FwCtlOps> Registration<T> {
> + /// Allocate and register a new fwctl device under the given parent device.
> + pub fn new(parent: &device::Device) -> Result<Self> {
AFAIK, fwctl_unregister() is synchronized against IOCTLs. Hence, if we guarantee
that a fwctl::Registration can not out-live parent device unbind, we can provide
a &Device<Bound> in the FwCtlOps callbacks, which allows us to do zero-cost
accesses of device resources with Devres::access().
In order to provide this guarantee, this function should return
impl PinInit<Devres<Self>, Error>.
> + let ops = &FwCtlVTable::<T>::VTABLE as *const _ as *mut _;
Please use cast() and cast_mut() when possible.
> +
> + // SAFETY: `_fwctl_alloc_device()` allocates a new `fwctl_device`
> + // and initializes its embedded `struct device`.
This safety comment should justify how you guarantee that the arguments you pass
in are valid, instead of describing what the called function does.
> + let dev = unsafe {
> + bindings::_fwctl_alloc_device(
> + parent.as_raw(),
> + ops,
> + core::mem::size_of::<bindings::fwctl_device>(),
> + )
> + };
> +
> + let dev = NonNull::new(dev).ok_or(ENOMEM)?;
> +
> + // SAFETY: `fwctl_register()` expects a valid device from `_fwctl_alloc_device()`.
> + let ret = unsafe { bindings::fwctl_register(dev.as_ptr()) };
> + if ret != 0 {
> + // SAFETY: If registration fails, release the allocated fwctl_device().
> + unsafe {
> + bindings::put_device(core::ptr::addr_of_mut!((*dev.as_ptr()).dev));
> + }
> + return Err(Error::from_errno(ret));
> + }
> +
> + Ok(Self {
> + fwctl_dev: dev,
> + _marker: PhantomData,
> + })
> + }
> +
> + fn as_raw(&self) -> *mut bindings::fwctl_device {
> + self.fwctl_dev.as_ptr()
> + }
> +}
> +
> +impl<T: FwCtlOps> Drop for Registration<T> {
> + fn drop(&mut self) {
> + // SAFETY: `fwctl_unregister()` expects a valid device from `_fwctl_alloc_device()`.
> + unsafe {
> + bindings::fwctl_unregister(self.as_raw());
> + bindings::put_device(core::ptr::addr_of_mut!((*self.as_raw()).dev));
> + }
> + }
> +}
> +
> +// SAFETY: The only action allowed in a `Registration` instance is dropping it, which is safe to do
> +// from any thread because `fwctl_unregister()/put_device()` can be called from any sleepible
> +// context.
> +unsafe impl<T: FwCtlOps> Send for Registration<T> {}
> +
> +/// Trait implemented by each Rust driver that integrates with the fwctl subsystem.
> +///
> +/// Each implementation corresponds to a specific device type and provides
> +/// the vtable used by the core `fwctl` layer to manage per-FD user contexts
> +/// and handle RPC requests.
> +pub trait FwCtlOps: Sized {
> + /// Driver UCtx type.
> + type UCtx;
> +
> + /// fwctl device type, matching the C enum `fwctl_device_type`.
> + const DEVICE_TYPE: u32;
> +
> + /// Called when a new user context is opened by userspace.
> + fn open_uctx(uctx: &mut FwCtlUCtx<Self::UCtx>) -> Result<(), Error>;
> +
> + /// Called when the user context is being closed.
> + fn close_uctx(uctx: &mut FwCtlUCtx<Self::UCtx>);
Why not just open() and close()?
> + /// Return device or context information to userspace.
> + fn info(uctx: &mut FwCtlUCtx<Self::UCtx>) -> Result<KVec<u8>, Error>;
> +
> + /// Called when a userspace RPC request is received.
> + fn fw_rpc(
> + uctx: &mut FwCtlUCtx<Self::UCtx>,
> + scope: u32,
> + rpc_in: &mut [u8],
> + out_len: *mut usize,
> + ) -> Result<Option<KVec<u8>>, Error>;
As mentioned above, if we ensure that a fwctl::Registration cannot out-live the
parent device being bound, we can provide a &Device<Bound> in those callbacks
for zero-cost accesses of device resources with Devres::access().
> +}
> +
> +/// Represents a per-FD user context (`struct fwctl_uctx`).
> +///
> +/// Each driver embeds `struct fwctl_uctx` as the first field of its own
> +/// context type and uses this wrapper to access driver-specific data.
> +#[repr(C)]
> +#[pin_data]
> +pub struct FwCtlUCtx<T> {
> + /// The core fwctl user context shared with the C implementation.
> + #[pin]
> + pub fwctl_uctx: bindings::fwctl_uctx,
This should be Opaque<bindings::fwctl_uctx> and should not be a public field.
> + /// Driver-specific data associated with this user context.
> + pub uctx: T,
I'd rather provide a Deref and DerefMut implementation for this.
> +}
> +
> +impl<T> FwCtlUCtx<T> {
> + /// Converts a raw C pointer to `struct fwctl_uctx` into a reference to the
> + /// enclosing `FwCtlUCtx<T>`.
> + ///
> + /// # Safety
> + /// * `ptr` must be a valid pointer to a `fwctl_uctx` that is embedded
> + /// inside an existing `FwCtlUCtx<T>` instance.
> + /// * The caller must ensure that the lifetime of the returned reference
> + /// does not outlive the underlying object managed on the C side.
> + pub unsafe fn from_raw<'a>(ptr: *mut bindings::fwctl_uctx) -> &'a mut Self {
Why does this need to be public?
> + // SAFETY: `ptr` was originally created from a valid `FwCtlUCtx<T>`.
> + unsafe { &mut *container_of!(ptr, FwCtlUCtx<T>, fwctl_uctx) }
> + }
> +
> + /// Returns the parent device of this user context.
> + ///
> + /// # Safety
> + /// The `fwctl_device` pointer inside `fwctl_uctx` must be valid.
> + pub fn get_parent_device(&self) -> ARef<Device> {
We the fwctl::Registration changes suggested above, this should return a
&Device<Bound>.
Regardless of this, it's better to return a &Device than an ARef<Device>. The
caller can always obtain a reference count, i.e. ARef<Device> from a &Device (or
a &Device<Bound>).
> + // SAFETY: `self.fwctl_uctx.fwctl` is initialized by the fwctl subsystem and guaranteed
> + // to remain valid for the lifetime of this `FwCtlUCtx`.
> + let raw_dev =
> + unsafe { (*(self.fwctl_uctx.fwctl)).dev.parent as *mut kernel::bindings::device };
> + // SAFETY: `raw_dev` points to a live device object.
> + unsafe { Device::get_device(raw_dev) }
> + }
> +
> + /// Returns a mutable reference to the driver-specific context.
> + pub fn to_driver_uctx_mut(&mut self) -> &mut T {
> + &mut self.uctx
> + }
As mentioned, I think Deref and DerefMut are a better fit for this.
> +}
> +
> +/// Static vtable mapping Rust trait methods to C callbacks.
> +pub struct FwCtlVTable<T: FwCtlOps>(PhantomData<T>);
> +
> +impl<T: FwCtlOps> FwCtlVTable<T> {
> + /// Static instance of `fwctl_ops` used by the C core to call into Rust.
> + pub const VTABLE: bindings::fwctl_ops = bindings::fwctl_ops {
> + device_type: T::DEVICE_TYPE,
> + uctx_size: core::mem::size_of::<FwCtlUCtx<T::UCtx>>(),
> + open_uctx: Some(Self::open_uctx_callback),
> + close_uctx: Some(Self::close_uctx_callback),
> + info: Some(Self::info_callback),
> + fw_rpc: Some(Self::fw_rpc_callback),
> + };
> +
> + /// Called when a new user context is opened by userspace.
> + unsafe extern "C" fn open_uctx_callback(uctx: *mut bindings::fwctl_uctx) -> ffi::c_int {
> + // SAFETY: `uctx` is guaranteed by the fwctl subsystem to be a valid pointer.
> + let ctx = unsafe { FwCtlUCtx::<T::UCtx>::from_raw(uctx) };
> + match T::open_uctx(ctx) {
> + Ok(()) => 0,
> + Err(e) => e.to_errno(),
> + }
> + }
> +
> + /// Called when the user context is being closed.
> + unsafe extern "C" fn close_uctx_callback(uctx: *mut bindings::fwctl_uctx) {
> + // SAFETY: `uctx` is guaranteed by the fwctl subsystem to be a valid pointer.
> + let ctx = unsafe { FwCtlUCtx::<T::UCtx>::from_raw(uctx) };
> + T::close_uctx(ctx);
> + }
> +
> + /// Returns device or context information.
> + unsafe extern "C" fn info_callback(
> + uctx: *mut bindings::fwctl_uctx,
> + length: *mut usize,
> + ) -> *mut ffi::c_void {
> + // SAFETY: `uctx` is guaranteed by the fwctl subsystem to be a valid pointer.
> + let ctx = unsafe { FwCtlUCtx::<T::UCtx>::from_raw(uctx) };
> +
> + match T::info(ctx) {
> + Ok(kvec) => {
> + // The ownership of the buffer is now transferred to the foreign
> + // caller. It must eventually be released by fwctl framework.
> + let (ptr, len, _cap) = kvec.into_raw_parts();
> +
> + // SAFETY: `length` is a valid out-parameter provided by the C
> + // caller. Write the number of bytes in the returned buffer.
> + unsafe {
> + *length = len;
> + }
> +
> + ptr.cast::<ffi::c_void>()
> + }
> +
> + Err(e) => Error::to_ptr(e),
> + }
> + }
> +
> + /// Called when a user-space RPC request is received.
> + unsafe extern "C" fn fw_rpc_callback(
> + uctx: *mut bindings::fwctl_uctx,
> + scope: u32,
> + rpc_in: *mut ffi::c_void,
> + in_len: usize,
> + out_len: *mut usize,
> + ) -> *mut ffi::c_void {
> + // SAFETY: `uctx` is guaranteed by the fwctl framework to be a valid pointer.
> + let ctx = unsafe { FwCtlUCtx::<T::UCtx>::from_raw(uctx) };
> +
> + // SAFETY: `rpc_in` points to a valid input buffer of size `in_len`
> + // provided by fwctl subsystem.
Please see the safety requirements of slice::from_raw_parts_mut() and justify
all of them.
> + let rpc_in_slice: &mut [u8] =
> + unsafe { slice::from_raw_parts_mut(rpc_in as *mut u8, in_len) };
> +
> + match T::fw_rpc(ctx, scope, rpc_in_slice, out_len) {
> + // Driver allocates a new output buffer.
> + Ok(Some(kvec)) => {
> + // The ownership of the buffer is now transferred to the foreign
> + // caller. It must eventually be released by fwctl subsystem.
> + let (ptr, len, _cap) = kvec.into_raw_parts();
> +
> + // SAFETY: `out_len` is a valid writable pointer provided by the C caller.
> + unsafe {
> + *out_len = len;
> + }
NIT: If you move the semicolon at the end of the unsafe block, this is formatted
in a single line.
> +
> + ptr.cast::<ffi::c_void>()
> + }
> +
> + // Driver re-uses the existing input buffer and writes the out_len.
> + Ok(None) => rpc_in,
> +
> + // Return an ERR_PTR-style encoded error pointer.
> + Err(e) => Error::to_ptr(e),
> + }
> + }
> +}
next prev parent reply other threads:[~2025-10-30 16:47 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-10-30 16:03 [RFC 0/2] rust: introduce abstractions for fwctl Zhi Wang
2025-10-30 16:03 ` [RFC 1/2] " Zhi Wang
2025-10-30 16:22 ` Jason Gunthorpe
2025-10-30 17:19 ` Zhi Wang
2025-10-30 17:24 ` Danilo Krummrich
2025-10-30 17:21 ` Danilo Krummrich
2025-10-30 16:47 ` Danilo Krummrich [this message]
2025-11-02 17:26 ` Danilo Krummrich
2025-11-02 22:52 ` Jason Gunthorpe
2025-11-02 18:33 ` Danilo Krummrich
2025-11-02 22:55 ` Jason Gunthorpe
2025-11-03 9:55 ` Zhi Wang
2025-11-03 10:36 ` Danilo Krummrich
2025-10-30 16:03 ` [RFC 2/2] samples: rust: fwctl: add sample code for FwCtl Zhi Wang
2025-10-30 17:29 ` [RFC 0/2] rust: introduce abstractions for fwctl Zhi Wang
2025-10-30 17:52 ` Danilo Krummrich
2025-10-30 17:54 ` Jason Gunthorpe
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=DDVT5YA564C6.3HN9WCMQX49PC@kernel.org \
--to=dakr@kernel.org \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=alex.gaynor@gmail.com \
--cc=aliceryhl@google.com \
--cc=alwilliamson@nvidia.com \
--cc=aniketa@nvidia.com \
--cc=ankita@nvidia.com \
--cc=bhelgaas@google.com \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun.feng@gmail.com \
--cc=cjia@nvidia.com \
--cc=gary@garyguo.net \
--cc=jgg@nvidia.com \
--cc=jhubbard@nvidia.com \
--cc=joelagnelf@nvidia.com \
--cc=kwankhede@nvidia.com \
--cc=kwilczynski@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=smitra@nvidia.com \
--cc=targupta@nvidia.com \
--cc=tmgross@umich.edu \
--cc=zhiw@nvidia.com \
--cc=zhiwang@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.