// SPDX-License-Identifier: GPL-2.0-only //! Abstractions for the fwctl subsystem. //! //! C header: `include/linux/fwctl.h` use crate::{ bindings, container_of, device, prelude::*, sync::aref::{ ARef, AlwaysRefCounted, // }, types::Opaque, // }; use core::{ alloc::Layout, cell::UnsafeCell, marker::PhantomData, ptr::NonNull, slice, // }; /// Returns a kmalloc-compatible allocation size for `T`. const fn kmalloc_aligned_size() -> usize { Layout::new::().pad_to_align().size() } /// Represents a fwctl device type. /// /// Corresponds to the C `enum fwctl_device_type`. All non-error UAPI values are represented so /// Rust drivers can select a device type without passing an untyped integer, while /// `FWCTL_DEVICE_TYPE_ERROR` remains unrepresentable. #[repr(u32)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum DeviceType { /// Mellanox ConnectX (mlx5) device. Mlx5 = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_MLX5, /// CXL (Compute Express Link) device. Cxl = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_CXL, /// AMD/Pensando PDS device. Pds = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_PDS, /// Broadcom NetXtreme (bnxt) device. Bnxt = bindings::fwctl_device_type_FWCTL_DEVICE_TYPE_BNXT, } /// Scope of access for an RPC request. /// /// Corresponds to the C `enum fwctl_rpc_scope`. #[repr(u32)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum RpcScope { /// Read/write access to device configuration. Configuration = bindings::fwctl_rpc_scope_FWCTL_RPC_CONFIGURATION, /// Read-only access to debug information. DebugReadOnly = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_READ_ONLY, /// Write access to lockdown-compatible debug information. DebugWrite = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_WRITE, /// Full read/write access to all debug information (requires `CAP_SYS_RAWIO`). DebugWriteFull = bindings::fwctl_rpc_scope_FWCTL_RPC_DEBUG_WRITE_FULL, } impl TryFrom for RpcScope { type Error = Error; #[inline] fn try_from(value: u32) -> Result { match value { v if v == Self::Configuration as u32 => Ok(Self::Configuration), v if v == Self::DebugReadOnly as u32 => Ok(Self::DebugReadOnly), v if v == Self::DebugWrite as u32 => Ok(Self::DebugWrite), v if v == Self::DebugWriteFull as u32 => Ok(Self::DebugWriteFull), _ => Err(EINVAL), } } } /// Response from a [`Operations::fw_rpc`] call. pub enum FwRpcResponse { /// Reuse the input buffer as the output, with the given output length. /// /// The callback returns `EINVAL` if the output length exceeds the input buffer length. InPlace(usize), /// Return a newly allocated buffer as the output. NewBuffer(KVVec), } /// Trait implemented by each Rust driver that integrates with the fwctl subsystem. /// /// The implementing type **is** the per-FD user context: one instance is /// created for each `open()` call and dropped when the FD is closed. /// /// 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 Operations: Sized + Send + Sync + 'static { /// Data owned by the [`Registration`] and accessible during callbacks. /// /// The lifetime `'a` is tied to the [`Registration`] scope (which lives within the parent bus /// device binding scope). Drivers use it to store references to resources bound to this scope, /// such as PCI BARs or typed bus device references. type RegistrationData<'a>: Send + Sync + 'a where Self: 'a; /// fwctl device type identifier. const DEVICE_TYPE: DeviceType; /// Called when a new user context is opened. /// /// Returns a [`PinInit`] initializer for `Self`. The instance is dropped /// automatically when the FD is closed (after [`close`](Self::close)). fn open<'a>( device: &Device, reg_data: &Self::RegistrationData<'a>, ) -> impl PinInit; /// Called when the user context is closed. /// /// The driver may perform additional cleanup here that requires access /// to the owning [`Device`]. `Self` is dropped automatically after this /// returns. fn close<'a>( _this: Pin<&mut Self>, _device: &Device, _reg_data: &Self::RegistrationData<'a>, ) { } /// Return device information to userspace. /// /// The default implementation returns no device-specific data. fn info<'a>( _this: Pin<&Self>, _device: &Device, _reg_data: &Self::RegistrationData<'a>, ) -> Result, Error> { Ok(KVec::new()) } /// Handle a userspace RPC request. /// /// `max_output_len` is the size of the userspace output buffer. A driver may return a larger /// response to report the required size; the fwctl core copies only the bytes that fit and /// reports the full response length to userspace. fn fw_rpc<'a>( this: Pin<&Self>, device: &Device, reg_data: &Self::RegistrationData<'a>, scope: RpcScope, rpc_buf: &mut [u8], max_output_len: usize, ) -> Result; } /// A fwctl device. /// /// `#[repr(C)]` with the `fwctl_device` at offset 0, matching the C `fwctl_alloc_device()` layout /// convention. Contains a pointer to the [`Registration`]'s data, set at registration time and /// cleared on unregistration. /// /// # Invariants /// /// - `dev` is embedded at offset 0 and is initialised by fwctl. /// - The fwctl refcount owns the allocation lifetime. /// - `registration_data` is either [`NonNull::dangling()`] (before registration / after /// unregistration) or points to valid data owned by the [`Registration`]. #[repr(C)] pub struct Device { dev: Opaque, registration_data: UnsafeCell>>, } impl Device { /// Allocate a new fwctl device. /// /// Returns an [`ARef`] that can be passed to [`Registration::new()`] /// to make the device visible to userspace. pub fn new(parent: &device::Device) -> Result> { const_assert!( core::mem::offset_of!(Self, dev) == 0, "struct fwctl_device must be at offset 0" ); let size = kmalloc_aligned_size::(); let ops = core::ptr::from_ref::(&VTable::::VTABLE).cast_mut(); // SAFETY: `ops` is static, `parent` is bound, and `size` is padded so the allocation made // by `_fwctl_alloc_device` satisfies the size and alignment required by `Device`. let raw = unsafe { bindings::_fwctl_alloc_device(parent.as_raw(), ops, size) }; let this = NonNull::new(raw.cast::()).ok_or(ENOMEM)?; // INVARIANT: Set `registration_data` to dangling (no registration yet). // SAFETY: `this` points to the allocation just returned by fwctl. unsafe { (&raw mut (*this.as_ptr()).registration_data) .write(UnsafeCell::new(NonNull::dangling())); }; // SAFETY: `this` owns the initial reference. Ok(unsafe { ARef::from_raw(this) }) } /// Returns the underlying `fwctl_device` pointer. #[inline] fn as_raw(&self) -> *mut bindings::fwctl_device { self.dev.get() } /// Borrows a Rust fwctl device from its raw C pointer. /// /// # Safety /// /// `ptr` must point to a valid `fwctl_device` embedded in a [`Device`]. #[inline] unsafe fn from_raw<'a>(ptr: *mut bindings::fwctl_device) -> &'a Self { // SAFETY: The caller upholds the offset-0 `Device` invariant. unsafe { &*ptr.cast() } } /// Invokes `f` with the registration data. /// /// The higher-ranked callback prevents the erased registration lifetime from escaping and /// permits registration data that is invariant over its lifetime parameter. /// /// # Safety /// /// The caller must ensure that the device is registered and that this is called from a fwctl /// callback protected by `registration_lock`. #[inline] unsafe fn with_registration_data( &self, f: impl for<'a> FnOnce(&Device, &'a T::RegistrationData<'a>) -> R, ) -> R { // SAFETY: Caller guarantees the device is registered, so the pointer is valid. // Lifetimes do not affect layout. The higher-ranked callback prevents the shortened // lifetime from escaping or being selected by the caller. let reg_data = unsafe { (*self.registration_data.get()) .cast::>() .as_ref() }; f(self, reg_data) } } impl AsRef for Device { #[inline] fn as_ref(&self) -> &device::Device { // SAFETY: `self` contains a live fwctl_device. let dev = unsafe { &raw mut (*self.as_raw()).dev }; // SAFETY: The embedded device is initialised by fwctl. unsafe { device::Device::from_raw(dev) } } } // SAFETY: `fwctl_get` increments the refcount of a valid fwctl_device. // `fwctl_put` decrements it and frees the device when it reaches zero. unsafe impl AlwaysRefCounted for Device { #[inline] fn inc_ref(&self) { // SAFETY: `self` holds a live reference. unsafe { bindings::fwctl_get(self.as_raw()) }; } #[inline] unsafe fn dec_ref(obj: NonNull) { // SAFETY: The caller owns a live reference. unsafe { bindings::fwctl_put(obj.cast().as_ptr()) }; } } // SAFETY: `Device` is refcounted by the fwctl core and may be released from any thread. unsafe impl Send for Device {} // SAFETY: Shared access to the embedded `fwctl_device` is protected by the fwctl core. The // `registration_data` field is only mutated before registration and after unregistration (both // single-threaded with respect to callbacks). unsafe impl Sync for Device {} /// A registered fwctl device. /// /// Owns the [`RegistrationData`](Operations::RegistrationData) made available to driver callbacks. /// The parent device lifetime ensures that [`fwctl_unregister`] runs before the parent driver /// unbinds. /// /// On drop the device is unregistered (all user contexts are closed and `ops` is set to `NULL`) /// and the registration data is dropped. /// /// [`fwctl_unregister`]: srctree/drivers/fwctl/main.c pub struct Registration<'a, T: Operations> { dev: ARef>, _reg_data: Pin>>, } impl<'a, T: Operations> Registration<'a, T> { /// Register a previously allocated fwctl device with the given registration data. /// /// The `reg_data` is owned by the registration and accessible during callbacks. /// /// # Safety /// /// Callers must not `mem::forget()` the returned [`Registration`] or otherwise prevent its /// [`Drop`] implementation from running, since `fwctl_unregister` must be called before the /// parent device is unbound. /// /// `dev` must be an unregistered [`Device`] that is not associated with any live /// [`Registration`], and no other thread may attempt to register the same device concurrently. pub unsafe fn new( parent: &'a device::Device, dev: &Device, reg_data: impl PinInit, Error>, ) -> Result { let actual_parent = dev.as_ref().parent().ok_or(EINVAL)?; let parent_device: &device::Device = parent; if !core::ptr::eq(actual_parent, parent_device) { return Err(EINVAL); } let reg_data: Pin>> = KBox::pin_init(reg_data, GFP_KERNEL)?; // Store the registration data pointer in the device before registration, so that it is // visible once callbacks can be invoked. The `'static` type is only an erased storage // handle; callbacks access the pointer through a higher-ranked closure. let ptr: NonNull> = NonNull::from(Pin::get_ref(reg_data.as_ref())).cast(); // SAFETY: No concurrent access; the device is not yet registered. unsafe { *dev.registration_data.get() = ptr }; // SAFETY: `dev` is a valid fwctl_device backed by an ARef. let ret = unsafe { bindings::fwctl_register(dev.as_raw()) }; if ret != 0 { // SAFETY: No concurrent readers; registration failed. unsafe { *dev.registration_data.get() = NonNull::dangling() }; return Err(Error::from_errno(ret)); } Ok(Self { dev: dev.into(), _reg_data: reg_data, }) } } impl Drop for Registration<'_, T> { fn drop(&mut self) { // SAFETY: The Registration lifetime guarantees that the parent device is still bound. // `fwctl_unregister` takes the write lock, closes all user contexts, and sets ops=NULL. // After it returns, no callbacks can be running or will run. unsafe { bindings::fwctl_unregister(self.dev.as_raw()) }; // SAFETY: `fwctl_unregister` guarantees no concurrent readers. unsafe { *self.dev.registration_data.get() = NonNull::dangling() }; // `self._reg_data` is dropped here, after callbacks have stopped. } } /// Internal per-FD user context wrapping `struct fwctl_uctx` and `T`. /// /// Not exposed to drivers; they work with `&T` / `Pin<&mut T>` directly. #[repr(C)] #[pin_data] struct UserCtx { #[pin] fwctl_uctx: Opaque, #[pin] uctx: T, } impl UserCtx { /// Borrows a pinned Rust user context from its raw C pointer. /// /// # Safety /// /// `ptr` must point to a `fwctl_uctx` embedded in a live, pinned `UserCtx` that remains /// valid and does not move for the duration of `'a`. #[inline] unsafe fn from_raw<'a>(ptr: *mut bindings::fwctl_uctx) -> Pin<&'a Self> { // SAFETY: The caller upholds the `UserCtx` embedding, lifetime, and pinning invariants. unsafe { Pin::new_unchecked(&*container_of!(Opaque::cast_from(ptr), Self, fwctl_uctx)) } } /// Mutably borrows a pinned Rust user context from its raw C pointer. /// /// # Safety /// /// - `ptr` must point to a `fwctl_uctx` embedded in a live, pinned `UserCtx` that remains /// valid and does not move for the duration of `'a`. /// - The caller must ensure exclusive access to the `UserCtx` for the duration of `'a`. #[inline] unsafe fn from_raw_mut<'a>(ptr: *mut bindings::fwctl_uctx) -> Pin<&'a mut Self> { // SAFETY: The caller upholds the embedding, lifetime, pinning, and exclusivity invariants. unsafe { Pin::new_unchecked( &mut *container_of!(Opaque::cast_from(ptr), Self, fwctl_uctx).cast_mut(), ) } } /// Returns a reference to the fwctl [`Device`] that owns this context. #[inline] fn device(self: Pin<&Self>) -> &Device { // SAFETY: fwctl initialises this pointer before any driver callback. let raw_fwctl = unsafe { (*self.fwctl_uctx.get()).fwctl }; // SAFETY: Rust fwctl devices use the offset-0 `Device` layout. unsafe { Device::from_raw(raw_fwctl) } } /// Returns a pinned reference to the driver context. #[inline] fn uctx(self: Pin<&Self>) -> Pin<&T> { ::pin_init::assert_pinned!(UserCtx, uctx, T, inline); // SAFETY: `uctx` is structurally pinned. unsafe { self.map_unchecked(|ctx| &ctx.uctx) } } } /// Static vtable mapping Rust trait methods to C callbacks. struct VTable(PhantomData); impl VTable { /// The fwctl operations vtable for this driver type. const VTABLE: bindings::fwctl_ops = bindings::fwctl_ops { // CAST: `DeviceType` has the same `u32` representation as the C enum field. device_type: T::DEVICE_TYPE as u32, uctx_size: kmalloc_aligned_size::>(), 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), }; /// Initialises a newly opened Rust user context. /// /// # Safety /// /// `uctx` must be a valid `fwctl_uctx` embedded in a `UserCtx` with /// sufficient allocated space for the uctx field. unsafe extern "C" fn open_uctx_callback(uctx: *mut bindings::fwctl_uctx) -> ffi::c_int { const_assert!( core::mem::offset_of!(UserCtx, fwctl_uctx) == 0, "struct fwctl_uctx must be at offset 0" ); // SAFETY: fwctl sets this pointer before calling `open_uctx`. let raw_fwctl = unsafe { (*uctx).fwctl }; // SAFETY: Rust fwctl devices use the offset-0 `Device` layout. let device = unsafe { Device::::from_raw(raw_fwctl) }; let uctx_offset = core::mem::offset_of!(UserCtx, uctx); // SAFETY: `uctx_size` reserves space for the full `UserCtx`. let uctx_ptr: *mut T = unsafe { uctx.byte_add(uctx_offset).cast() }; // SAFETY: `open_uctx` is called under `registration_lock` read, so the device is // registered. `uctx_ptr` addresses the uninitialised pinned context reserved by // `uctx_size`. unsafe { device.with_registration_data(|device, reg_data| { match pin_init::raw_try_init(uctx_ptr, T::open(device, reg_data)) { Ok(()) => 0, Err(e) => e.to_errno(), } }) } } /// Closes and drops an opened Rust user context. /// /// # Safety /// /// `uctx` must point to a fully initialised `UserCtx`. unsafe extern "C" fn close_uctx_callback(uctx: *mut bindings::fwctl_uctx) { // SAFETY: fwctl keeps the owning device live for this callback. let device = unsafe { Device::::from_raw((*uctx).fwctl) }; // SAFETY: close is called for an opened Rust user context. let mut ctx = unsafe { UserCtx::::from_raw_mut(uctx) }; // SAFETY: `close_uctx` is called under `registration_lock` write (from // `fwctl_unregister`) or read (from `fwctl_fops_release`), so the device is registered. unsafe { device.with_registration_data(|device, reg_data| { T::close(ctx.as_mut().project().uctx, device, reg_data); }); } // SAFETY: close is the last callback before fwctl frees the allocation. unsafe { core::ptr::drop_in_place(ctx.project().uctx.get_unchecked_mut()) }; } /// Returns device-specific information for an opened Rust user context. /// /// # Safety /// /// - `uctx` must point to a fully initialised `UserCtx`. /// - `length` must be a valid pointer. unsafe extern "C" fn info_callback( uctx: *mut bindings::fwctl_uctx, length: *mut usize, ) -> *mut ffi::c_void { // SAFETY: info is called for an opened Rust user context. let ctx = unsafe { UserCtx::::from_raw(uctx) }; let device = ctx.device(); // SAFETY: `info` is called under `registration_lock` read, so the device is registered. let result = unsafe { device.with_registration_data(|device, reg_data| T::info(ctx.uctx(), device, reg_data)) }; match result { Ok(kvec) if kvec.is_empty() => { // SAFETY: `length` is a valid out-parameter. unsafe { *length = 0 }; // Return NULL for empty data; kfree(NULL) is safe. core::ptr::null_mut() } Ok(kvec) => { let (ptr, len, _cap) = kvec.into_raw_parts(); // SAFETY: `length` is a valid out-parameter. unsafe { *length = len }; ptr.cast::() } Err(e) => Error::to_ptr(e), } } /// Dispatches a firmware RPC for an opened Rust user context. /// /// # Safety /// /// - `uctx` must point to a fully initialised `UserCtx`. /// - `rpc_in` must be valid, initialised, and exclusively accessible for `in_len` bytes. /// - `out_len` must be valid for reading and writing an initialised `usize`. 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 { let scope = match RpcScope::try_from(scope) { Ok(s) => s, Err(e) => return Error::to_ptr(e), }; // SAFETY: `out_len` points to an initialised `usize` supplied by fwctl. let max_output_len = unsafe { *out_len }; // SAFETY: RPC is called for an opened Rust user context. let ctx = unsafe { UserCtx::::from_raw(uctx) }; let device = ctx.device(); // SAFETY: fwctl passes an exclusively owned buffer that is valid and initialised for // `in_len` bytes. It remains live for the duration of this callback. let rpc_buf = unsafe { slice::from_raw_parts_mut(rpc_in.cast::(), in_len) }; // SAFETY: `fw_rpc` is called under `registration_lock` read, so the device is registered. let result = unsafe { device.with_registration_data(|device, reg_data| { T::fw_rpc(ctx.uctx(), device, reg_data, scope, rpc_buf, max_output_len) }) }; let (response, response_len) = match result { Ok(FwRpcResponse::InPlace(len)) => { if len > in_len { return Error::to_ptr(EINVAL); } (rpc_in, len) } Ok(FwRpcResponse::NewBuffer(kvec)) if kvec.is_empty() => { // Return NULL for empty data; kvfree(NULL) is safe. (core::ptr::null_mut(), 0) } Ok(FwRpcResponse::NewBuffer(kvec)) => { let (ptr, len, _cap) = kvec.into_raw_parts(); (ptr.cast::(), len) } Err(e) => return Error::to_ptr(e), }; // SAFETY: `out_len` is a valid out-parameter. unsafe { *out_len = response_len }; response } }