From bfe5df2016074a52a3262a4d5632f37786af4368 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 10:40:33 +0000 Subject: [PATCH 01/13] docs: full documentation of error.rs Signed-off-by: Artie Poole --- daemon/src/error.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/daemon/src/error.rs b/daemon/src/error.rs index 9866c5f0..5f725044 100644 --- a/daemon/src/error.rs +++ b/daemon/src/error.rs @@ -10,20 +10,70 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Error types for the fpgad daemon. +//! +//! This module defines the `FpgadError` enum, which represents all possible error +//! conditions that can occur during FPGA management operations. The error types are +//! designed to provide detailed context about failures, including file paths, data +//! values, and underlying system errors. +//! +//! # Error Categories +//! +//! - **I/O Errors** - File system operations (read, write, create, delete, directory listing) +//! - **State Errors** - FPGA device state validation failures +//! - **Argument Errors** - Invalid parameters or paths +//! - **Softener Errors** - Platform-specific operation failures (feature-gated) +//! - **Internal Errors** - Unexpected internal conditions +//! +//! # DBus Integration +//! +//! Errors are automatically converted to `zbus::fdo::Error` types for transmission over +//! DBus. The error message always includes the `FpgadError:::` prefix to allow +//! CLI clients to distinguish between application errors and DBus communication errors. +//! +//! # Examples +//! +//! ```rust,no_run +//! # use daemon::error::FpgadError; +//! # use std::path::Path; +//! # +//! fn read_config(path: &Path) -> Result { +//! // Will produce: FpgadError::IORead: An IO error occurred when reading from ... +//! daemon::system_io::fs_read(path) +//! } +//! ``` + use log::error; use std::path::PathBuf; use zbus::fdo; +/// Application-level errors for FPGA management operations. +/// +/// This enum represents all possible error conditions in the fpgad daemon. Each variant +/// includes detailed context about the failure, such as file paths, data being processed, +/// and the underlying system error when applicable. +/// +/// All errors implement `Display` and will be formatted with the `FpgadError:::` +/// prefix, making them easily identifiable in logs and error messages sent over DBus. #[derive(Debug, thiserror::Error)] pub enum FpgadError { + /// Failed to read FPGA programming flags from sysfs. #[error("FpgadError::Flag: Failed to read flags: {0}")] Flag(String), + + /// Device tree overlay was not successfully applied. #[error("FpgadError::OverlayStatus: Overlay was not applied: {0}")] OverlayStatus(String), + + /// FPGA device is not in the expected state for the requested operation. #[error("FpgadError::FPGAState: FPGA state is not as expected: {0}")] FPGAState(String), + + /// Invalid argument provided to a function (e.g., invalid path, bad device handle). #[error("FpgadError::Argument: {0}")] Argument(String), + + /// Failed to read from a file system path - wrapper around std::io::Error #[error("FpgadError::IORead: An IO error occurred when reading from {file:?}: {e}")] IORead { file: PathBuf, e: std::io::Error }, /// Failed to write data to a file system path. @@ -32,17 +82,36 @@ pub enum FpgadError { /// Failed to create a file or directory - wrapper around std::io::Error #[error("FpgadError::IOCreate: An IO error occurred when creating {file:?}: {e}")] IOCreate { file: PathBuf, e: std::io::Error }, + + /// Failed to delete a file or directory - wrapper around std::io::Error #[error("FpgadError::IODelete: An IO error occurred when deleting {file:?}: {e}")] IODelete { file: PathBuf, e: std::io::Error }, + + /// Failed to list directory contents. #[error("FpgadError::IOReadDir: An IO error occurred when reading directory {dir:?}: {e}")] IOReadDir { dir: PathBuf, e: std::io::Error }, + + /// Platform-specific softener operation failed (only available with softeners feature). #[cfg(feature = "softeners")] #[error("FpgadError::Softener: An error occurred using softener: {0}")] Softener(crate::softeners::error::FpgadSoftenerError), + + /// Any other unexpected internal error occurred. #[error("FpgadError::Internal: An Internal error occurred: {0}")] Internal(String), } +/// Convert FpgadError to DBus-compatible fdo::Error. +/// +/// This implementation maps application-level errors to appropriate DBus error types +/// and logs the error before conversion. The error message retains the `FpgadError:::` +/// prefix to allow clients to distinguish between different error types. +/// +/// # Error Mapping +/// +/// - `Argument` โ†’ `InvalidArgs` - Invalid parameters +/// - `IORead`, `IOWrite`, `IOCreate`, `IODelete`, `IOReadDir` โ†’ `IOError` - I/O failures +/// - All others โ†’ `Failed` - General failures impl From for fdo::Error { fn from(err: FpgadError) -> Self { error!("{err}"); From c8382859a85a5087ce58aa3cdb3adc9f18058e70 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 10:41:02 +0000 Subject: [PATCH 02/13] docs: full documentation of config.rs Signed-off-by: Artie Poole --- daemon/src/config.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/daemon/src/config.rs b/daemon/src/config.rs index 1f08ad8d..d930ee61 100644 --- a/daemon/src/config.rs +++ b/daemon/src/config.rs @@ -10,6 +10,23 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Configuration constants for the fpgad daemon. +//! +//! This module contains compile-time constants that define the file system paths used by +//! the daemon to interact with the Linux kernel's FPGA subsystem, device tree overlays, +//! and firmware loading mechanisms. +//! +//! These paths are typically stable across Linux distributions but may vary in some +//! embedded or specialized configurations. The constants are defined at compile time +//! and cannot be changed at runtime because FPGAd aims to remain stateless. +//! +//! # Kernel Subsystems +//! +//! The daemon interacts with three main kernel subsystems: +//! - **FPGA Manager** - The Linux FPGA subsystem for bitstream loading +//! - **Device Tree Overlays** - Dynamic device tree modification via configfs +//! - **Firmware Loading** - Kernel firmware loader search path configuration + /// The driver-decided location of fpga_manager objects. Typically `/sys/class/fpga_manager/`. pub static FPGA_MANAGERS_DIR: &str = "/sys/class/fpga_manager/"; From fe0b1f3e7137833dfdd66968a8c8b36125b8a4e0 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 10:49:56 +0000 Subject: [PATCH 03/13] docs: full documentation of system_io.rs Signed-off-by: Artie Poole --- daemon/src/system_io.rs | 193 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 187 insertions(+), 6 deletions(-) diff --git a/daemon/src/system_io.rs b/daemon/src/system_io.rs index c30a0c39..5c0f5909 100644 --- a/daemon/src/system_io.rs +++ b/daemon/src/system_io.rs @@ -10,6 +10,30 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Error Wrapping File System I/O Helpers +//! +//! This module provides convenient wrappers around standard Rust file system operations, +//! with automatic conversion to `FpgadError` types. All functions include trace logging +//! for debugging and provide detailed error context including file paths and operation types. +//! +//! Includes: read, write, and directory operations. +//! +//! # Examples +//! +//! ```rust,no_run +//! # use daemon::system_io::{fs_read, fs_write}; +//! # use std::path::Path; +//! +//! # fn example() -> Result<(), daemon::error::FpgadError> { +//! // Read a file +//! let content = fs_read(Path::new("/sys/class/fpga_manager/fpga0/state"))?; +//! +//! // Write to a file +//! fs_write(Path::new("/sys/class/fpga_manager/fpga0/flags"), false, "0")?; +//! # Ok(()) +//! # } +//! ``` + use crate::error::FpgadError; use log::trace; use std::fs::OpenOptions; @@ -17,7 +41,31 @@ use std::fs::{create_dir_all, remove_dir}; use std::io::{Read, Write}; use std::path::Path; -/// Convenient wrapper for reading the contents of `file_path` to String +/// Read the contents of a file to a String. +/// +/// This is a convenient wrapper around `std::fs::File::read_to_string` that provides +/// trace logging and automatic error conversion to `FpgadError::IORead`. +/// +/// # Arguments +/// +/// * `file_path` - Path to the file to read +/// +/// # Returns: `Result` +/// * `Ok(String)` - The complete contents of the file +/// * `Err(FpgadError::IORead)` - If the file cannot be read (doesn't exist, permissions, etc.) +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_read; +/// # use std::path::Path; +/// +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let state = fs_read(Path::new("/sys/class/fpga_manager/fpga0/state"))?; +/// println!("FPGA state: {}", state.trim()); +/// # Ok(()) +/// # } +/// ``` pub fn fs_read(file_path: &Path) -> Result { trace!("Attempting to read from {file_path:?}"); let mut buf: String = String::new(); @@ -38,7 +86,36 @@ pub fn fs_read(file_path: &Path) -> Result { } } -/// Convenient wrapper for writing `value` to `file_path` +/// Write a string value to a file. +/// +/// This is a convenient wrapper around file write operations that provides trace logging +/// and automatic error conversion to `FpgadError::IOWrite`. +/// +/// # Arguments +/// +/// * `file_path` - Path to the file to write +/// * `create` - If `true`, create the file if it doesn't exist; if `false`, file must already exist +/// * `value` - The string value to write (implements `AsRef`) +/// +/// # Returns: `Result<(), FpgadError>` +/// * `Ok(())` - Write succeeded +/// * `Err(FpgadError::IOWrite)` - If the write fails (permissions, file doesn't exist when create=false, etc.) +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_write; +/// # use std::path::Path; +/// # +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // Write to an existing file +/// fs_write(Path::new("/sys/class/fpga_manager/fpga0/flags"), false, "0")?; +/// +/// // Create and write to a new file +/// fs_write(Path::new("/tmp/myfile.txt"), true, "Hello, world!")?; +/// # Ok(()) +/// # } +/// ``` pub fn fs_write(file_path: &Path, create: bool, value: impl AsRef) -> Result<(), FpgadError> { trace!( "Attempting to write {:?} to {:?}", @@ -63,7 +140,33 @@ pub fn fs_write(file_path: &Path, create: bool, value: impl AsRef) -> Resul } } -/// Convenient wrapper for writing a vector of integers as bytes to a file +/// Write binary data to a file. +/// +/// This is a convenient wrapper for writing raw bytes to a file, with automatic truncation +/// of existing content, trace logging, and error conversion to `FpgadError::IOWrite`. +/// +/// # Arguments +/// +/// * `file_path` - Path to the file to write +/// * `create` - If `true`, create the file if it doesn't exist; if `false`, file must already exist +/// * `data` - The binary data to write as a byte slice +/// +/// # Returns: `Result<(), FpgadError>` +/// * `Ok(())` - Write succeeded +/// * `Err(FpgadError::IOWrite)` - If the write fails +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_write_bytes; +/// # use std::path::Path; +/// # +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; +/// fs_write_bytes(Path::new("/tmp/binary_file"), true, &data)?; +/// # Ok(()) +/// # } +/// ``` pub fn fs_write_bytes(file_path: &Path, create: bool, data: &[u8]) -> Result<(), FpgadError> { // Open the file let result = OpenOptions::new() @@ -85,7 +188,32 @@ pub fn fs_write_bytes(file_path: &Path, create: bool, data: &[u8]) -> Result<(), } } -/// Convenient wrapper for recursively creating directories up to `path` +/// Recursively create directories up to the specified path. +/// +/// This is a convenient wrapper around `std::fs::create_dir_all` that provides trace +/// logging and automatic error conversion to `FpgadError::IOCreate`. It will create all +/// missing parent directories in the path. +/// +/// # Arguments +/// +/// * `path` - The directory path to create (including all parents) +/// +/// # Returns: `Result<(), FpgadError>` +/// * `Ok(())` - Directory created (or already existed) +/// * `Err(FpgadError::IOCreate)` - If directory creation fails (permissions, etc.) +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_create_dir; +/// # use std::path::Path; +/// # +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // Create nested directories +/// fs_create_dir(Path::new("/sys/kernel/config/device-tree/overlays/my_overlay"))?; +/// # Ok(()) +/// # } +/// ``` pub fn fs_create_dir(path: &Path) -> Result<(), FpgadError> { trace!("Attempting to Create '{path:?}'"); let result = create_dir_all(path); @@ -101,7 +229,32 @@ pub fn fs_create_dir(path: &Path) -> Result<(), FpgadError> { } } -/// Convenient wrapper for deleting an "empty" directory - works for overlayfs +/// Remove an empty directory. +/// +/// This is a convenient wrapper around `std::fs::remove_dir` that provides trace logging +/// and automatic error conversion to `FpgadError::IODelete`. The directory must be empty +/// for the operation to succeed. This works correctly with overlayfs directories. +/// +/// # Arguments +/// +/// * `path` - The directory path to remove +/// +/// # Returns: `Result<(), FpgadError>` +/// * `Ok(())` - Directory removed successfully +/// * `Err(FpgadError::IODelete)` - If removal fails (not empty, doesn't exist, permissions, etc.) +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_remove_dir; +/// # use std::path::Path; +/// # +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // Remove an overlay directory +/// fs_remove_dir(Path::new("/sys/kernel/config/device-tree/overlays/my_overlay"))?; +/// # Ok(()) +/// # } +/// ``` pub fn fs_remove_dir(path: &Path) -> Result<(), FpgadError> { trace!("Attempting to delete '{path:?}'"); let result = remove_dir(path); @@ -117,7 +270,35 @@ pub fn fs_remove_dir(path: &Path) -> Result<(), FpgadError> { } } -/// Convenient wrapper for reading contents of a directory +/// Read the contents of a directory and return entry names. +/// +/// This is a convenient wrapper around `std::fs::read_dir` that provides trace logging, +/// automatic error conversion to `FpgadError::IOReadDir`, and returns a vector of entry +/// names (not full paths). Entries that cannot be read are silently skipped. +/// +/// # Arguments +/// +/// * `dir` - The directory path to list +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Vec)` - List of entry names in the directory (files and subdirectories) +/// * `Err(FpgadError::IOReadDir)` - If the directory cannot be read (doesn't exist, permissions, etc.) +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::system_io::fs_read_dir; +/// # use std::path::Path; +/// +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // List all FPGA devices +/// let devices = fs_read_dir(Path::new("/sys/class/fpga_manager"))?; +/// for device in devices { +/// println!("Found device: {}", device); +/// } +/// # Ok(()) +/// # } +/// ``` pub fn fs_read_dir(dir: &Path) -> Result, FpgadError> { trace!("Attempting to read directory '{dir:?}'"); std::fs::read_dir(dir).map_or_else( From 4194b1c891f1f7440fa4dc41ca8a66888aa6922a Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 11:17:50 +0000 Subject: [PATCH 04/13] docs: add readme for softeners providing a tutorial for creating a softener Signed-off-by: Artie Poole --- daemon/src/softeners.rs | 2 + daemon/src/softeners/README.md | 415 +++++++++++++++++++++++++++++++++ 2 files changed, 417 insertions(+) diff --git a/daemon/src/softeners.rs b/daemon/src/softeners.rs index 79642571..514ea193 100644 --- a/daemon/src/softeners.rs +++ b/daemon/src/softeners.rs @@ -9,6 +9,8 @@ // fpgad is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +#![doc = include_str!("softeners/README.md")] + pub mod error; #[cfg(feature = "xilinx-dfx-mgr")] diff --git a/daemon/src/softeners/README.md b/daemon/src/softeners/README.md index 52cef9ed..97462d76 100644 --- a/daemon/src/softeners/README.md +++ b/daemon/src/softeners/README.md @@ -1 +1,416 @@ # FPGAd Softeners + +## Overview + +Softeners are platform-specific extensions to FPGAd that provide integration with vendor-specific FPGA management tools and frameworks. While FPGAd's core platform system handles standard Linux FPGA subsystem operations, softeners enable fpgad to work with proprietary or vendor-specific tools that sit alongside or above the kernel FPGA subsystem. + +> **Note on Naming**: FPGA devices are often referred to as "fabric" (as in "programmable logic fabric"), so naturally FPGAd provides "softeners" for the fabric ๐Ÿงบ. + +### When to Use a Softener vs. a Platform + +- **Use a Platform** when implementing direct interactions with the Linux FPGA subsystem (`/sys/class/fpga_manager`) and device tree overlays. Platforms handle the standard kernel interface. + +- **Use a Softener** when integrating with vendor-specific userspace tools, SDKs, or frameworks that provide additional functionality beyond what the kernel exposes. Softeners typically wrap external binaries or libraries. + +## Architecture + +Softeners extend platforms by providing vendor-specific functionality. A softener platform: +1. Implements the `Platform` trait (like any other platform) +2. Uses the `#[platform(compat_string = "...")]` macro to register itself +3. Provides additional vendor-specific functions beyond the standard `Platform` trait +4. Is conditionally compiled using Cargo feature flags + +### Example: Xilinx DFX Manager + +The `xilinx_dfx_mgr` softener integrates with AMD/Xilinx's `dfx-mgr` tool, which provides vendor specific optimizations on top of the standard FPGA subsystem and, e.g., enables "partial reconfiguration" of the device, which isn't supported by the Universal platform. See [dfx-mgr on GitHub](https://github.com/Xilinx/dfx-mgr) for more information on dfx-mgr. + +## Adding a New Softener Platform + +### Step 1: Add a Cargo Feature + +Edit `daemon/Cargo.toml` and add your softener as a feature: + +```toml +[features] +default = ["softeners-all"] +softeners-all = ["softeners", "xilinx-dfx-mgr", "your-new-softener"] +softeners = [] +xilinx-dfx-mgr = ["softeners"] +your-new-softener = ["softeners"] # Add this line +``` + +**Important**: Each softener should depend on the `softeners` base feature. + +### Step 2: Create the Softener Module + +Create a new file at `daemon/src/softeners/your_softener_name.rs`: + +```rust +use std::sync::OnceLock; + +use crate::platforms::platform::Platform; +use crate::platforms::universal_components::universal_fpga::UniversalFPGA; +use crate::platforms::universal_components::universal_overlay_handler::UniversalOverlayHandler; +use crate::softeners::error::FpgadSoftenerError; +use fpgad_macros::platform; + +/// Your softener platform implementation. +/// +/// This platform integrates with [vendor tool name] to provide [brief description]. +/// +/// # Compatibility +/// +/// Compatible with devices matching: `your,compat-string` +#[platform(compat_string = "your,compat-string")] +pub struct YourSoftenerPlatform { + fpga: OnceLock, + overlay_handler: OnceLock, +} + +impl Default for YourSoftenerPlatform { + fn default() -> Self { + Self::new() + } +} + +impl YourSoftenerPlatform { + pub fn new() -> Self { + YourSoftenerPlatform { + fpga: OnceLock::new(), + overlay_handler: OnceLock::new(), + } + } +} + +impl Platform for YourSoftenerPlatform { + fn fpga( + &self, + device_handle: &str, + ) -> Result<&dyn crate::platforms::platform::Fpga, crate::error::FpgadError> { + Ok(self.fpga.get_or_init(|| YourPlatformFPGA::new(device_handle))) + // See step (7) about implementing custom Fpga + } + + fn overlay_handler( + &self, + overlay_handle: &str, + ) -> Result<&dyn crate::platforms::platform::OverlayHandler, crate::error::FpgadError> { + Ok(self + .overlay_handler + .get_or_init(|| YourPlatformOverlayHandler::new(overlay_handle))) + // See step (7) about implementing custom OverlayHandler + } +} + +// Add your vendor-specific functions below +// These are public functions that can be called by the daemon or exposed via DBus + +/// Example vendor-specific function +pub fn do_vendor_thing(param: &str) -> Result { + // Implementation here + todo!() +} +``` + +### Step 3: Register the Module + +Edit `daemon/src/softeners.rs` to include your new softener: + +```rust +pub mod error; + +#[cfg(feature = "xilinx-dfx-mgr")] +pub mod xilinx_dfx_mgr; + +#[cfg(feature = "your-new-softener")] +pub mod your_softener_name; // Add this line +``` + +### Step 4: Register the Platform at Startup + +Edit `daemon/src/main.rs` to register your platform: + +```rust +#[cfg(feature = "xilinx-dfx-mgr")] +use softeners::xilinx_dfx_mgr::XilinxDfxMgrPlatform; + +#[cfg(feature = "your-new-softener")] +use softeners::your_softener_name::YourSoftenerPlatform; // Add this + +fn register_platforms() { + #[cfg(feature = "xilinx-dfx-mgr")] + XilinxDfxMgrPlatform::register_platform(); + + #[cfg(feature = "your-new-softener")] + YourSoftenerPlatform::register_platform(); // Add this + + UniversalPlatform::register_platform(); +} +``` + +**Important**: The Universal platform should always be registered last as it serves as the fallback. + +### Step 5: Implement Vendor-Specific Functions + +Add functions that wrap your vendor's tools or APIs. These typically: +- Use `std::process::Command` to call external binaries +- Return `Result` for error handling +- Are marked with `#[allow(dead_code)]` if not yet exposed via DBus + +Example pattern: + +```rust +use std::process::Command; +use crate::softeners::error::FpgadSoftenerError; + +/// Brief description of what this does +pub fn vendor_operation(arg: &str) -> Result { + let output = Command::new("vendor-tool") + .arg("--option") + .arg(arg) + .output() + .map_err(FpgadSoftenerError::YourError)?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(FpgadSoftenerError::YourError(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string(), + ))) + } +} +``` + +### Step 6: Add Error Types (If Needed) + +If your softener needs custom error types beyond what's in `error.rs`, add them: + +```rust +// In daemon/src/softeners/error.rs +#[derive(Debug, thiserror::Error)] +pub enum FpgadSoftenerError { + #[error("FpgadSoftenerError::DfxMgr: {0}")] + DfxMgr(std::io::Error), + + #[error("FpgadSoftenerError::YourError: {0}")] + YourError(std::io::Error), // Add your error variant +} +``` + +### Step 7: Implement Custom FPGA/OverlayHandler (Advanced, Optional) + +If your vendor-specific platform requires custom behavior for FPGA or overlay operations, you can implement your own, otherwise just use the Universal platform's `UniversalFPGA` and `UniversalOverlayHandler` implementations. + + +#### Creating Custom FPGA Implementation + +Create a struct that implements the `Fpga` trait: + +```rust +use crate::platforms::platform::Fpga; +use crate::error::FpgadError; + +pub struct YourPlatformFPGA { + device_handle: String, +} + +impl YourPlatformFPGA { + pub fn new(device_handle: &str) -> Self { + Self { + device_handle: device_handle.to_string(), + } + } +} + +impl Fpga for YourPlatformFPGA { + fn device_handle(&self) -> &str { + &self.device_handle + } + + fn state(&self) -> Result { + // Custom implementation - maybe call vendor tool + // or fall back to reading sysfs directly + todo!() + } + + fn flags(&self) -> Result { + // Custom implementation + todo!() + } + + fn set_flags(&self, flags: u32) -> Result<(), FpgadError> { + // Custom implementation + todo!() + } + + fn load(&self, bitstream_path: &str) -> Result<(), FpgadError> { + // Custom implementation - perhaps using vendor-specific loading + todo!() + } + + fn name(&self) -> Result { + // Custom implementation + todo!() + } +} +``` + +#### Creating Custom OverlayHandler Implementation + +Create a struct that implements the `OverlayHandler` trait: + +```rust +use crate::platforms::platform::OverlayHandler; +use crate::error::FpgadError; + +pub struct YourPlatformOverlayHandler { + overlay_handle: String, +} + +impl YourPlatformOverlayHandler { + pub fn new(overlay_handle: &str) -> Self { + Self { + overlay_handle: overlay_handle.to_string(), + } + } +} + +impl OverlayHandler for YourPlatformOverlayHandler { + fn overlay_handle(&self) -> &str { + &self.overlay_handle + } + + fn load(&self, overlay_path: &str) -> Result<(), FpgadError> { + // Custom implementation - maybe use vendor-specific overlay mechanism + todo!() + } + + fn remove(&self) -> Result<(), FpgadError> { + // Custom implementation + todo!() + } + + fn status(&self) -> Result { + // Custom implementation + todo!() + } + +} +``` + +#### Using Your Custom Implementations + +Update your platform's `fpga()` and `overlay_handler()` methods to use your custom implementations - note that this was +already included in the above example code.: + +```rust +impl Platform for YourSoftenerPlatform { + fn fpga( + &self, + device_handle: &str, + ) -> Result<&dyn crate::platforms::platform::Fpga, crate::error::FpgadError> { + Ok(self.fpga.get_or_init(|| YourPlatformFPGA::new(device_handle))) + } + + fn overlay_handler( + &self, + overlay_handle: &str, + ) -> Result<&dyn crate::platforms::platform::OverlayHandler, crate::error::FpgadError> { + Ok(self + .overlay_handler + .get_or_init(|| YourPlatformOverlayHandler::new(overlay_handle))) + } +} +``` + +And update your struct fields to use the custom types: + +```rust +pub struct YourSoftenerPlatform { + fpga: OnceLock, + overlay_handler: OnceLock, +} +``` + +#### When to Implement Custom Components + +Consider implementing custom FPGA or OverlayHandler when: +- The vendor tooling provides optimized loading mechanisms +- The standard sysfs interface doesn't expose all necessary functionality +- The sysfs interface is different on your platform (instead of changing [config.rs](../config.rs)) + +For most cases, the Universal components are sufficient and recommended for simplicity. + +## Key Concepts + +### Compatibility Strings + +The `compat_string` parameter in the `#[platform]` macro can contain multiple comma-separated values: + +```rust +#[platform(compat_string = "xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0")] +``` + +This means the platform will be selected if the device's `/sys/class/fpga_manager//of_node/compatible` file contains ANY of these strings. + +### OnceLock for Lazy Initialization + +Use `OnceLock` to lazily initialize components only when they're first accessed: + +```rust +pub struct YourPlatform { + fpga: OnceLock, + overlay_handler: OnceLock, +} + +impl Platform for YourPlatform { + fn fpga(&self, device_handle: &str) -> Result<&dyn Fpga, FpgadError> { + Ok(self.fpga.get_or_init(|| UniversalFPGA::new(device_handle))) + } +} +``` + +## Testing Your Softener + +### Unit Testing + +If possible, add unit tests which can be run on any host to the relevant files: + +```no_run +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_something() { + // Your tests here + } +} +``` + +### Integration Testing + +Add your integration tests to `daemon/tests/` to test FPGAd and your softener on your actual hardware. + +[//]: # (TODO -artie-: describe testing steps) + +### Manual testing + +Do some manual testing to ensure that everything is working as expected. Don't forget to build with the appropriate feature flag: + +```bash +cargo build --features your-new-softener +``` + + +## Best Practices + +1. **Document Everything**: Add comprehensive doc comments to all public functions +2. **Use Feature Gates**: Always guard your softener code with `#[cfg(feature = "...")]` +3. **Error Handling**: Use `FpgadSoftenerError` for all softener-specific errors. Do not allow fpgad to panic. +4. **Logging**: Use the `log` crate (`trace!`, `debug!`, `info!`, `warn!`, `error!`) liberally +5. **Fail Gracefully**: If vendor tools aren't installed, provide helpful error messages. Do not allow fpgad to panic. + +[//]: # (## Example: Xilinx DFX Manager Structure) + +[//]: # (TODO -artie-: describe all files edited by dfx-mgr approach, and how they were edited.) From 7e965be6c87f732f1d75360283fba5cedc8eaa06 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 11:54:03 +0000 Subject: [PATCH 05/13] docs: full documentation of main.rs Signed-off-by: Artie Poole --- daemon/src/main.rs | 78 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/daemon/src/main.rs b/daemon/src/main.rs index 94e4ce12..f0eaee50 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -10,6 +10,46 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! FPGA daemon (fpgad) - System service for managing FPGA devices. +//! +//! This is the main entry point for the fpgad daemon, which provides a DBus service for +//! managing FPGA devices on Linux systems. The daemon: +//! - Exposes two DBus interfaces: `control` and `status` +//! - Manages FPGA bitstream loading through the Linux FPGA subsystem +//! - Handles device tree overlay application and removal +//! - Provides platform-specific FPGA management capabilities +//! - Runs as a system service with appropriate privileges +//! +//! # DBus Service +//! +//! - **Service Name**: `com.canonical.fpgad` +//! - **Status Interface**: `/com/canonical/fpgad/status` - Read-only operations +//! - **Control Interface**: `/com/canonical/fpgad/control` - Write operations +//! +//! # Environment Variables +//! +//! - `RUST_LOG` - Controls logging level (`trace`, `debug`, `info`, `warn`, `error` +//! or `off`). Defaults to `info` +//! +//! # Architecture +//! +//! The daemon uses a platform abstraction approach that allows different FPGA vendors +//! and platforms to be supported through registered platform implementations. At startup, +//! the daemon: +//! 1. Registers all available platform implementations +//! 2. Creates DBus interface objects +//! 3. Connects to the system DBus and advertises the service +//! 4. Waits indefinitely for incoming DBus requests +//! +//! # Platform Support +//! +//! - **Universal Platform**: Generic FPGA support for standard Linux FPGA subsystem +//! - **Xilinx DFX Manager** (optional, feature-gated): Xilinx-specific softener which uses dfx-mgr +//! - **Additional Platforms**: Can be added via feature flags and custom implementations +//! +//! See the [`platforms`] module for details on the platform abstraction system and +//! [`softeners`] for more information on vendor-specific implementations. + use log::info; use std::error::Error; use std::future::pending; @@ -32,12 +72,50 @@ use crate::{ platforms::universal::UniversalPlatform, }; +/// Register all available FPGA platform implementations. +/// +/// This function is called at daemon startup to register platform handlers. Each +/// platform implementation provides vendor or hardware-specific logic for FPGA +/// management operations. Platforms are registered in order of priority, with more +/// specific platforms registered before generic ones. +/// +/// # Platform Registration Order +/// +/// 1. Xilinx DFX Manager (if feature enabled) - Handles Xilinx-specific devices +/// 2. Universal Platform - Fallback for standard Linux FPGA subsystem devices fn register_platforms() { #[cfg(feature = "xilinx-dfx-mgr")] XilinxDfxMgrPlatform::register_platform(); UniversalPlatform::register_platform(); } +/// Main entry point for the fpgad daemon. +/// +/// Initializes the daemon by: +/// 1. Setting up logging via `env_logger` (defaults to "info" level) +/// 2. Registering platform implementations +/// 3. Creating DBus interface instances +/// 4. Connecting to the system DBus and advertising the service +/// 5. Running indefinitely to serve DBus requests +/// +/// # Returns: `Result<(), Box>` +/// * `Ok(())` - Never returns under normal operation (runs until terminated) +/// * `Err(Box)` - Initialization error (DBus connection failed, etc.) +/// +/// # Environment Variables +/// +/// - `RUST_LOG` - Controls logging level (`trace`, `debug`, `info`, `warn`, `error` +/// or `off`). Defaults to `info` +/// +/// # Examples +/// +/// ```bash +/// # Run with default logging (info level) +/// fpgad +/// +/// # Run with debug logging +/// RUST_LOG=debug fpgad +/// ``` #[tokio::main] async fn main() -> Result<(), Box> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); From 2874a6b8584719d4be4d872944b335bf0c91308c Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 13:04:36 +0000 Subject: [PATCH 06/13] docs: mostly full documentation of platform.rs a todo: - add an example of fetching the correct platform object Signed-off-by: Artie Poole --- daemon/src/platforms/platform.rs | 422 ++++++++++++++++++++++++++++--- 1 file changed, 384 insertions(+), 38 deletions(-) diff --git a/daemon/src/platforms/platform.rs b/daemon/src/platforms/platform.rs index c75eb964..d7e73045 100644 --- a/daemon/src/platforms/platform.rs +++ b/daemon/src/platforms/platform.rs @@ -10,6 +10,59 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Platform abstraction layer for FPGA management. +//! +//! This module defines the core trait system that enables vendor and hardware-specific +//! FPGA management implementations. The platform system uses a registry-based approach +//! where platform implementations register themselves with compatibility strings that +//! match Linux device tree compatible strings. This module also provides functions for +//! retrieving the appropriate platform implementation for a given FPGA device at runtime. +//! +//! # Architecture +//! +//! The platform abstraction consists of three main traits: +//! - [`Platform`] - Top-level platform interface that provides access to FPGA and overlay handlers +//! - [`Fpga`] - Interface for interacting with FPGA devices through the Linux FPGA subsystem +//! - [`OverlayHandler`] - Interface for managing device tree overlays +//! +//! # Platform Discovery +//! +//! At runtime, the daemon discovers which platform to use for each FPGA device by: +//! 1. Reading the device's `/sys/class/fpga_manager//of_node/compatible` string +//! 2. Matching this against registered platform compatibility strings +//! 3. Falling back to the Universal platform if no match is found +//! +//! # Platform Registration +//! +//! Platforms register themselves at daemon startup using the [`register_platform`] function +//! and are defined using the `#[platform]` macro. Compatibility strings can include +//! comma-separated components, all of which must match for a platform to be selected. +//! Platforms and softeners are included or not excluded using cargo "features". +//! See [`softeners`](../../softeners/index.html) for more details. +//! +//! TODO(Artie): Add examples of how to use the getters for platforms with and without knowing the platform string? - could be called "# Fetching platforms" +//! # Examples +//! +//! in [main.rs]: +//! ```rust,no_run +//! #[cfg(feature = "xilinx-dfx-mgr")] +//! use softeners::xilinx_dfx_mgr::XilinxDfxMgrPlatform; +//! +//! #[cfg(feature = "your-new-softener")] +//! use softeners::your_softener_name::YourSoftenerPlatform; // Add this +//! +//! fn register_platforms() { +//! #[cfg(feature = "xilinx-dfx-mgr")] +//! XilinxDfxMgrPlatform::register_platform(); +//! +//! #[cfg(feature = "your-new-softener")] +//! YourSoftenerPlatform::register_platform(); // Add this +//! +//! UniversalPlatform::register_platform(); +//! } +//! ``` +//! + use crate::config; use crate::error::FpgadError; use crate::platforms::universal::UniversalPlatform; @@ -20,72 +73,212 @@ use std::collections::{HashMap, HashSet}; use std::path::Path; use std::sync::{Mutex, OnceLock}; +/// Type alias for platform constructor functions. +/// +/// Platform constructors take no arguments and return a boxed Platform trait object. +/// These functions are stored in the platform registry and called when a matching +/// platform is discovered. type PlatformConstructor = fn() -> Box; +/// Global registry of platform implementations. +/// +/// This static variable holds a thread-safe registry mapping compatibility strings +/// to platform constructor functions. It is initialized once at daemon startup via +/// [`init_platform_registry`] and accessed through [`register_platform`] and +/// [`match_platform_string`]. +/// +/// The registry uses `OnceLock` to ensure thread-safe lazy initialization and `Mutex` +/// to protect concurrent access to the internal HashMap. pub static PLATFORM_REGISTRY: OnceLock>> = OnceLock::new(); -/// A sysfs map of an fpga in fpga_manager class. -/// See the example below (not all sysfs files are implemented as methods): -/// ubuntu@kria:~$ tree /sys/class/fpga_manager/fpga0 -/// /sys/class/fpga_manager/fpga0 -/// โ”œโ”€โ”€ device -> ../../../firmware:zynqmp-firmware:pcap -/// โ”œโ”€โ”€ firmware -/// โ”œโ”€โ”€ flags -/// โ”œโ”€โ”€ key -/// โ”œโ”€โ”€ name -/// โ”œโ”€โ”€ of_node -> ../../../../../../firmware/devicetree/base/firmware/zynqmp-firmware/pcap -/// โ”œโ”€โ”€ power -/// โ”‚ย ย  โ”œโ”€โ”€ async -/// โ”‚ย ย  โ”œโ”€โ”€ autosuspend_delay_ms -/// โ”‚ย ย  โ”œโ”€โ”€ control -/// โ”‚ย ย  โ”œโ”€โ”€ runtime_active_kids -/// โ”‚ย ย  โ”œโ”€โ”€ runtime_active_time -/// โ”‚ย ย  โ”œโ”€โ”€ runtime_enabled -/// โ”‚ย ย  โ”œโ”€โ”€ runtime_status -/// โ”‚ย ย  โ”œโ”€โ”€ runtime_suspended_time -/// โ”‚ย ย  โ””โ”€โ”€ runtime_usage -/// โ”œโ”€โ”€ state -/// โ”œโ”€โ”€ status -/// โ”œโ”€โ”€ subsystem -> ../../../../../../class/fpga_manager -/// โ””โ”€โ”€ uevent -/// +/// Trait for managing an FPGA device pub trait Fpga { + /// Get the device handle for this FPGA device. + /// + /// Returns the device handle (e.g., "fpga0") that identifies this FPGA device. + /// + /// # Returns: `&str` + /// * Device handle string (e.g., "fpga0", "fpga1") #[allow(dead_code)] - /// get the device handle for this fpga device fn device_handle(&self) -> &str; - /// get the state of the fpga device + + /// Get the current state of the FPGA device. + /// + /// # Returns: `Result` + /// * `Ok(String)` - Current state of the FPGA + /// * `Err(FpgadError::IORead)` - Failed to read state file fn state(&self) -> Result; - /// get the current flags of the fpga device + + /// Get the current programming flags for the FPGA device. + /// + /// # Returns: `Result` + /// * `Ok(u32)` - Current flags value + /// * `Err(FpgadError::IORead)` - Failed to read flags file + /// * `Err(FpgadError::Flag)` - Failed to parse flags value fn flags(&self) -> Result; - /// attempt to set the flags of an fpga device + + /// Set the programming flags for the FPGA device. + /// + /// # Arguments + /// + /// * `flags` - The flags value to set + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Flags set successfully + /// * `Err(FpgadError::IOWrite)` - Failed to write flags file fn set_flags(&self, flags: u32) -> Result<(), FpgadError>; + + /// Load a bitstream firmware file to the FPGA device. + /// + /// # Arguments + /// + /// * `bitstream_path_rel` - Path to the bitstream file relative to whatever path the lookup starts in. For universal, this is the firmware search path + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Bitstream loaded successfully + /// * `Err(FpgadError::IOWrite)` - Failed to write firmware file + /// * `Err(FpgadError::FPGAState)` - FPGA not in correct state for loading #[allow(dead_code)] - /// Directly load the firmware stored in bitstream_path to the device fn load_firmware(&self, bitstream_path_rel: &Path) -> Result<(), FpgadError>; } +/// Trait for managing device tree overlays. pub trait OverlayHandler { - /// Applies an overlay to an already existing overlayfs dir, - /// which may or may not also write a bitstream to an fpga device. + /// Apply a device tree overlay from a source file. + /// + /// # Arguments + /// + /// * `source_path` - Path to the `.dtbo` overlay binary file + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Overlay applied successfully + /// * `Err(FpgadError::IOWrite)` - Failed to write overlay + /// * `Err(FpgadError::OverlayStatus)` - Overlay application failed fn apply_overlay(&self, source_path: &Path) -> Result<(), FpgadError>; - /// Removes an overlayfs directory from the configfs. + + /// Remove a device tree overlay. + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Overlay removed successfully + /// * `Err(FpgadError::IODelete)` - Failed to remove overlay directory fn remove_overlay(&self) -> Result<(), FpgadError>; - /// Gets the required fpga flags from an overlay file + + /// Get the required FPGA flags, however they may be provided. + /// + /// # Returns: `Result` + /// * `Ok(isize)` - Required flags value + /// * `Err(FpgadError)` - Failed to parse overlay or extract flags #[allow(dead_code)] fn required_flags(&self) -> Result; - /// gets the overlay application status + + /// Get the current status of the overlay. + /// + /// # Returns: `Result` + /// * `Ok(String)` - Overlay status string (e.g., "applied", "") + /// * `Err(FpgadError::IORead)` - Failed to read status fn status(&self) -> Result; + + /// Get the filesystem path to the overlay directory. + /// + /// # Returns: `Result<&Path, FpgadError>` + /// * `Ok(&Path)` - Path to overlay directory in configfs + /// * `Err(FpgadError)` - Overlay path not initialized fn overlay_fs_path(&self) -> Result<&Path, FpgadError>; } +/// Trait representing a complete FPGA platform implementation. +/// +/// This trait is the top-level interface for platform-specific FPGA management. +/// Implementations provide factory methods for creating FPGA device and overlay +/// handler instances. +/// +/// The trait extends `Any` to allow for runtime type checking and downcasting, +/// which can be useful for platform-specific functionality. +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::Platform; +/// # +/// # fn example(platform: &dyn Platform) -> Result<(), daemon::error::FpgadError> { +/// // Get an FPGA device instance +/// let fpga = platform.fpga("fpga0")?; +/// let state = fpga.state()?; +/// +/// // Get an overlay handler instance +/// let overlay = platform.overlay_handler("my_overlay")?; +/// # Ok(()) +/// # } +/// ``` pub trait Platform: Any { - /// creates and inits an Fpga if not present otherwise gets the instance + /// Get or create an FPGA device instance. + /// + /// Returns a reference to an [`Fpga`] trait object for the specified device. + /// Implementations typically cache instances and return the same instance for + /// repeated calls with the same device handle. + /// + /// # Arguments + /// + /// * `device_handle` - The device handle (e.g., "fpga0") + /// + /// # Returns: `Result<&dyn Fpga, FpgadError>` + /// * `Ok(&dyn Fpga)` - FPGA device instance + /// * `Err(FpgadError::Argument)` - Invalid device handle fn fpga(&self, device_handle: &str) -> Result<&dyn Fpga, FpgadError>; - /// creates and inits an OverlayHandler if not present otherwise gets the instance + + /// Get or create an overlay handler instance. + /// + /// Returns a reference to an [`OverlayHandler`] trait object for the specified + /// overlay. Implementations typically cache instances and return the same instance + /// for repeated calls with the same overlay handle. + /// + /// # Arguments + /// + /// * `overlay_handle` - The overlay handle (directory name in configfs) + /// + /// # Returns: `Result<&dyn OverlayHandler, FpgadError>` + /// * `Ok(&dyn OverlayHandler)` - Overlay handler instance + /// * `Err(FpgadError::Argument)` - Invalid overlay handle or configfs not available fn overlay_handler(&self, overlay_handle: &str) -> Result<&dyn OverlayHandler, FpgadError>; } +/// Match a platform compatibility string to a registered platform. +/// +/// This function implements the platform matching algorithm that searches the registry +/// for a platform whose compatibility string matches all components in the provided +/// string. The matching is done by splitting both strings on commas and ensuring ***all*** +/// components in the query string are present in the registered compatibility string. +/// +/// # Algorithm +/// +/// 1. Split the registered compatibility string into components: `"xlnx,zynqmp-pcap-fpga"` โ†’ `["xlnx", "zynqmp-pcap-fpga"]` +/// 2. Split the query string into components: `"xlnx"` โ†’ `["xlnx"]` +/// 3. Check if all query components exist in the registered components +/// 4. Return the first matching platform constructor +/// +/// # Arguments +/// +/// * `platform_string` - Comma-separated compatibility string to match +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Box)` - Newly constructed platform instance +/// * `Err(FpgadError::Internal)` - Registry not initialized or lock failure +/// * `Err(FpgadError::Argument)` - No matching platform found +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::match_platform_string; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // Match a single component +/// let platform = match_platform_string("xlnx")?; +/// +/// // Match multiple components +/// let platform = match_platform_string("xlnx,zynqmp-pcap-fpga")?; +/// # Ok(()) +/// # } +/// ``` fn match_platform_string(platform_string: &str) -> Result, FpgadError> { let registry = PLATFORM_REGISTRY .get() @@ -108,6 +301,30 @@ fn match_platform_string(platform_string: &str) -> Result, Fpg ))) } +/// Discover the appropriate platform for a device by reading its compatibility string. +/// +/// This function reads the device tree compatible string from the device's sysfs +/// `of_node/compatible` file and attempts to match it to a registered platform. +/// If no match is found, it falls back to the Universal platform with a warning. +/// +/// # Arguments +/// +/// * `device_handle` - The device handle (e.g., "fpga0") +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Box)` - Platform instance (matched or Universal fallback) +/// * `Err(FpgadError::Argument)` - Failed to read compatibility string +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::discover_platform; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let platform = discover_platform("fpga0")?; +/// let fpga = platform.fpga("fpga0")?; +/// # Ok(()) +/// # } +/// ``` fn discover_platform(device_handle: &str) -> Result, FpgadError> { let compat_string = read_compatible_string(device_handle)?; trace!("Found compatibility string: '{compat_string}'"); @@ -118,6 +335,30 @@ fn discover_platform(device_handle: &str) -> Result, FpgadErro })) } +/// Read the device tree compatible string for an FPGA device. +/// +/// Reads the compatibility string from `/sys/class/fpga_manager//of_node/compatible`. +/// This string identifies the hardware and is used for platform matching. The function +/// handles null-terminated strings that some drivers write to sysfs. +/// +/// # Arguments +/// +/// * `device_handle` - The device handle (e.g., "fpga0") +/// +/// # Returns: `Result` +/// * `Ok(String)` - Compatibility string (null terminators removed) +/// * `Err(FpgadError::Argument)` - Device not found or compatible string unreadable +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::read_compatible_string; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let compat = read_compatible_string("fpga0")?; +/// println!("Compatibility: {}", compat); +/// # Ok(()) +/// # } +/// ``` pub fn read_compatible_string(device_handle: &str) -> Result { let compat_string = match fs_read( &Path::new(config::FPGA_MANAGERS_DIR) @@ -137,6 +378,34 @@ pub fn read_compatible_string(device_handle: &str) -> Result Ok(compat_string) } +/// Get a platform instance from either a compatibility string or by device discovery. +/// +/// This is a helper function that chooses between [`discover_platform`] (if +/// `platform_string` is empty) or [`platform_for_known_platform`] (if a platform +/// string is provided). This is commonly used by DBus interface methods. +/// +/// # Arguments +/// +/// * `platform_string` - Compatibility string (empty for auto-discovery) +/// * `device_handle` - The device handle (e.g., "fpga0") +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Box)` - Platform instance +/// * `Err(FpgadError)` - Platform discovery or matching failed +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::platform_from_compat_or_device; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// // Auto-discover from device +/// let platform = platform_from_compat_or_device("", "fpga0")?; +/// +/// // Use known platform string +/// let platform = platform_from_compat_or_device("xlnx,zynqmp-pcap-fpga", "fpga0")?; +/// # Ok(()) +/// # } +/// ``` pub fn platform_from_compat_or_device( platform_string: &str, device_handle: &str, @@ -147,14 +416,70 @@ pub fn platform_from_compat_or_device( } } +/// Get a platform instance for a known compatibility string. +/// +/// Directly matches the provided compatibility string against registered platforms +/// without attempting device discovery. This requires the caller to know the correct +/// platform string. +/// +/// # Arguments +/// +/// * `platform_string` - Compatibility string to match +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Box)` - Matched platform instance +/// * `Err(FpgadError::Argument)` - No matching platform found +/// * `Err(FpgadError::Internal)` - Registry not initialized +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::platform_for_known_platform; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let platform = platform_for_known_platform("xlnx,zynqmp-pcap-fpga")?; +/// # Ok(()) +/// # } +/// ``` pub fn platform_for_known_platform(platform_string: &str) -> Result, FpgadError> { match_platform_string(platform_string) } +/// Initialize the platform registry. +/// +/// Creates a new empty mutex-protected HashMap for storing platform constructors. +/// This function is called automatically by [`register_platform`] via `OnceLock::get_or_init`. +/// +/// # Returns: `Mutex>` +/// * Empty mutex-protected HashMap ready for platform registration pub fn init_platform_registry() -> Mutex> { Mutex::new(HashMap::new()) } +/// Register a platform implementation in the global registry. +/// +/// Adds a platform constructor to the registry with an associated compatibility string. +/// The compatibility string should be a comma-separated list of components that match +/// the device tree compatible property. Platforms are typically registered at daemon +/// startup before any devices are discovered. +/// +/// # Arguments +/// +/// * `compatible` - Compatibility string (e.g., "xlnx,zynqmp-pcap-fpga") +/// * `constructor` - Function that creates a new platform instance +/// +/// # Panics +/// +/// Panics if the registry lock is poisoned (should never happen in normal operation). +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::register_platform; +/// # use daemon::platforms::universal::UniversalPlatform; +/// register_platform("new_platform,compatibility-string", || { +/// Box::new(NewPlatform::new()) +/// }); +/// ``` pub fn register_platform(compatible: &'static str, constructor: PlatformConstructor) { let mut registry = PLATFORM_REGISTRY .get_or_init(init_platform_registry) @@ -164,7 +489,28 @@ pub fn register_platform(compatible: &'static str, constructor: PlatformConstruc registry.insert(compatible, constructor); } -/// Scans /sys/class/fpga_manager/ for all present device nodes and returns a Vec of their handles +/// List all FPGA manager device handles present in the system. +/// +/// Scans `/sys/class/fpga_manager/` and returns the names of all FPGA device +/// directories found. These handles can be used to interact with the devices +/// through the platform abstraction. +/// +/// # Returns: `Result, FpgadError>` +/// * `Ok(Vec)` - List of device handles (e.g., ["fpga0", "fpga1"]) +/// * `Err(FpgadError::IOReadDir)` - Failed to read fpga_manager directory +/// +/// # Examples +/// +/// ```rust,no_run +/// # use daemon::platforms::platform::list_fpga_managers; +/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// let devices = list_fpga_managers()?; +/// for device in devices { +/// println!("Found FPGA device: {}", device); +/// } +/// # Ok(()) +/// # } +/// ``` pub fn list_fpga_managers() -> Result, FpgadError> { fs_read_dir(config::FPGA_MANAGERS_DIR.as_ref()) } From e966c93a0780618ff8aa34c67c20bd21593af36c Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 13:14:02 +0000 Subject: [PATCH 07/13] docs: full documentation of universal.rs Signed-off-by: Artie Poole --- daemon/src/platforms/universal.rs | 116 +++++++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/daemon/src/platforms/universal.rs b/daemon/src/platforms/universal.rs index 8d4a6aa5..ade84594 100644 --- a/daemon/src/platforms/universal.rs +++ b/daemon/src/platforms/universal.rs @@ -10,6 +10,47 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Universal platform implementation for standard Linux FPGA subsystem. +//! +//! This module provides the Universal platform, which is a generic implementation +//! that works with tested FPGA devices using one of the common Linux FPGA subsystems, +//! without requiring vendor-specific extensions. It serves as the fallback platform +//! when no more specific platform matches a device's compatibility string. +//! +//! # Features +//! +//! - **Generic FPGA Support** - Works with any FPGA manager driver in the Linux kernel +//! - **Device Tree Overlays** - Full support for overlay application and removal via configfs +//! - **No Vendor Dependencies** - Pure Linux kernel API usage without vendor SDKs +//! - **Automatic Fallback** - Used when device compatibility string doesn't match registered +//! platforms, or no softeners are included. +//! +//! # Architecture +//! +//! The Universal platform uses lazy initialization via `OnceLock` to create component +//! instances on first access: +//! - [`UniversalFPGA`] - Manages FPGA device operations +//! - [`UniversalOverlayHandler`] - Manages overlay operations +//! +//! # Registration +//! +//! The platform automatically registers itself with the compatibility string "universal" +//! via the `#[platform]` procedural macro. This ensures it's always available as a fallback. +//! +//! # Examples +//! +//! ```rust,no_run +//! # use daemon::platforms::universal::UniversalPlatform; +//! # use daemon::platforms::platform::Platform; +//! +//! # fn example() -> Result<(), daemon::error::FpgadError> { +//! let platform = platform_for_known_platform("universal"); +//! let fpga = platform.fpga("fpga0")?; +//! let state = fpga.state()?; +//! # Ok(()) +//! # } +//! ``` + use crate::error::FpgadError; use crate::platforms::platform::{Fpga, OverlayHandler, Platform}; use crate::platforms::universal_components::universal_fpga::UniversalFPGA; @@ -18,6 +59,26 @@ use fpgad_macros::platform; use log::trace; use std::sync::OnceLock; +/// Universal platform implementation for generic FPGA management. +/// +/// This struct provides a platform implementation that works with any FPGA device +/// using the standard Linux FPGA subsystem. It uses lazy initialization to create +/// FPGA and overlay handler instances on first access, ensuring efficient resource +/// usage. +/// +/// The `#[platform]` macro automatically registers this platform with the compatibility +/// string "universal", making it available as a fallback for devices without specific +/// platform support. +/// +/// # Fields +/// +/// * `fpga` - Lazily initialized FPGA device instance +/// * `overlay_handler` - Lazily initialized overlay handler instance +/// +/// # Thread Safety +/// +/// This struct is thread-safe thanks to `OnceLock`, which ensures that initialization +/// happens exactly once even with concurrent access. #[platform(compat_string = "universal")] #[derive(Debug)] pub struct UniversalPlatform { @@ -32,7 +93,22 @@ impl Default for UniversalPlatform { } impl UniversalPlatform { - /// Creates a new [`UniversalPlatform`]. + /// Create a new Universal platform instance. + /// + /// Creates an empty platform with uninitialized FPGA and overlay handler instances. + /// The actual components will be lazily initialized on first access through the + /// [`Platform`] trait methods. + /// + /// # Returns: `Self` + /// * New UniversalPlatform instance ready for use + /// + /// # Examples + /// + /// ```rust,no_run + /// use daemon::platforms::universal::UniversalPlatform; + /// + /// let platform = platform_for_known_platform("universal"); + /// ``` pub fn new() -> Self { trace!("creating new universal_platform"); UniversalPlatform { @@ -43,12 +119,46 @@ impl UniversalPlatform { } impl Platform for UniversalPlatform { - /// Initialises or get the fpga object called `name` + /// Get or initialize the FPGA device instance. + /// + /// Returns a reference to the [`UniversalFPGA`] instance for the specified device. + /// On first call, this creates and initializes the FPGA instance. Subsequent calls + /// return the same cached instance. + /// + /// # Arguments + /// + /// * `device_handle` - The device handle (e.g., "fpga0") + /// + /// # Returns: `Result<&dyn Fpga, FpgadError>` + /// * `Ok(&dyn Fpga)` - Reference to the FPGA device instance + /// + /// # Note + /// + /// This implementation currently never returns an error, but the Result type + /// is required by the Platform trait to support platform-specific validation. fn fpga(&self, device_handle: &str) -> Result<&dyn Fpga, FpgadError> { Ok(self.fpga.get_or_init(|| UniversalFPGA::new(device_handle))) } - /// Gets the `overlay_handler` associated with this device. + /// Get or initialize the overlay handler instance. + /// + /// Returns a reference to the [`UniversalOverlayHandler`] instance for the specified + /// overlay. On first call, this creates and initializes the handler. This method also + /// validates that the configfs overlay directory exists. + /// + /// # Arguments + /// + /// * `overlay_handle` - The overlay handle (directory name in configfs) + /// + /// # Returns: `Result<&dyn OverlayHandler, FpgadError>` + /// * `Ok(&dyn OverlayHandler)` - Reference to the overlay handler instance + /// * `Err(FpgadError::Argument)` - Overlay path has no parent or parent doesn't exist + /// + /// # Implementation Note + /// + /// This method includes a workaround for the unstable `get_or_try_init` feature. + /// Once that feature is stable, the error handling can be improved. See: + /// fn overlay_handler(&self, overlay_handle: &str) -> Result<&dyn OverlayHandler, FpgadError> { // TODO: replace the return type of UniversalOverlayHandler to Result and use // get_or_try_init instead here when stable: From 511e2b14ab3662b2203eda0215821d8ed7d5901c Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 13:20:59 +0000 Subject: [PATCH 08/13] docs: full documentation of universal_fpga.rs Signed-off-by: Artie Poole --- .../universal_components/universal_fpga.rs | 201 ++++++++++++++++-- 1 file changed, 187 insertions(+), 14 deletions(-) diff --git a/daemon/src/platforms/universal_components/universal_fpga.rs b/daemon/src/platforms/universal_components/universal_fpga.rs index 2dc7da4a..698c9670 100644 --- a/daemon/src/platforms/universal_components/universal_fpga.rs +++ b/daemon/src/platforms/universal_components/universal_fpga.rs @@ -10,6 +10,71 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Universal FPGA device implementation. +//! +//! This module provides the [`UniversalFPGA`] struct, which implements the [`Fpga`] trait +//! for generic FPGA devices using the standard Linux FPGA subsystem. It provides direct +//! access to sysfs attributes without vendor-specific logic. +//! +//! # A sysfs map of an fpga in fpga_manager class. +//! +//! Below is an example sysfs layout for an FPGA device managed by the standard Linux FPGA subsystem for a xilinx kria board: +//! ```text +//! ubuntu@kria:~$ tree /sys/class/fpga_manager/fpga0 +//! /sys/class/fpga_manager/fpga0 +//! โ”œโ”€โ”€ device -> ../../../firmware:zynqmp-firmware:pcap +//! โ”œโ”€โ”€ firmware +//! โ”œโ”€โ”€ flags +//! โ”œโ”€โ”€ key +//! โ”œโ”€โ”€ name +//! โ”œโ”€โ”€ of_node -> ../../../../../../firmware/devicetree/base/firmware/zynqmp-firmware/pcap +//! โ”œโ”€โ”€ power +//! โ”‚ย ย  โ”œโ”€โ”€ async +//! โ”‚ย ย  โ”œโ”€โ”€ autosuspend_delay_ms +//! โ”‚ย ย  โ”œโ”€โ”€ control +//! โ”‚ย ย  โ”œโ”€โ”€ runtime_active_kids +//! โ”‚ย ย  โ”œโ”€โ”€ runtime_active_time +//! โ”‚ย ย  โ”œโ”€โ”€ runtime_enabled +//! โ”‚ย ย  โ”œโ”€โ”€ runtime_status +//! โ”‚ย ย  โ”œโ”€โ”€ runtime_suspended_time +//! โ”‚ย ย  โ””โ”€โ”€ runtime_usage +//! โ”œโ”€โ”€ state +//! โ”œโ”€โ”€ status +//! โ”œโ”€โ”€ subsystem -> ../../../../../../class/fpga_manager +//! โ””โ”€โ”€ uevent +//! ``` +//! Of these files, only the following are interacted with by this implementation: +//! - `state` - Current FPGA state (operating, unknown, write error, etc.) +//! - `flags` - Programming flags (hexadecimal format: "0x...") +//! - `firmware` - Trigger bitstream loading by writing filename +//! +//! with any other files being controllable using the +//! [`write_property_bytes`](../../../../daemon/comm/dbus/control_interface/struct.ControlInterface.html#method.write_property_bytes) +//! and +//! [`write_property`](../../../../daemon/comm/dbus/control_interface/struct.ControlInterface.html#method.write_property) +//! DBus methods. +//! See the [`control_interface`](../../../../daemon/comm/dbus/control_interface/index.html) documentation for more details. +//! +//! # Examples +//! +//! ```rust,no_run +//! # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; +//! # use daemon::platforms::platform::platform_for_known_platform; +//! # +//! # fn example() -> Result<(), daemon::error::FpgadError> { +//! let fpga = platform_for_known_platform("universal").fpga("fpga0")?; +//! +//! // Check state +//! let state = fpga.state()?; +//! println!("FPGA state: {}", state); +//! +//! // Get flags +//! let flags = fpga.flags()?; +//! println!("Flags: 0x{:X}", flags); +//! # Ok(()) +//! # } +//! ``` + use crate::config; use crate::error::FpgadError; use crate::platforms::platform::Fpga; @@ -17,23 +82,68 @@ use crate::system_io::{fs_read, fs_write}; use log::{error, info, trace, warn}; use std::path::Path; +/// Universal FPGA device implementation using standard Linux FPGA subsystem. +/// +/// This struct represents a single FPGA device and provides methods to interact +/// with it through sysfs. It stores only the device handle (e.g., "fpga0") and +/// constructs paths to sysfs files on demand. +/// +/// # Fields +/// +/// * `device_handle` - The device identifier (e.g., "fpga0") used to locate the device in sysfs +/// #[derive(Debug)] pub struct UniversalFPGA { pub(crate) device_handle: String, } impl UniversalFPGA { - /// Constructor simply stores an owned version of the provided name. - /// This should probably be where we actually check if the device exists in the sysfs + /// Create a new UniversalFPGA instance for the specified device. + /// + /// This constructor simply stores the device handle. It does not verify that + /// the device exists in sysfs - validation occurs when methods are called. + /// + /// # Arguments + /// + /// * `device_handle` - The device handle (e.g., "fpga0") + /// + /// # Returns: `UniversalFPGA` + /// * New UniversalFPGA instance + /// + /// # Examples + /// + /// ```rust,no_run + /// use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; + /// + /// let fpga = platform_for_known_platform("universal").fpga("fpga0")?; + /// ``` pub(crate) fn new(device_handle: &str) -> UniversalFPGA { UniversalFPGA { device_handle: device_handle.to_owned(), } } - /// Reads the current fpga state file. - /// Only succeeds if the state is 'operating'. - /// Should only be used after bitstream loading. + /// Verify that the FPGA is in the "operating" state. + /// + /// Reads the FPGA state and checks if it equals "operating". This method should + /// be called after bitstream loading to ensure the FPGA successfully configured. + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - FPGA is in "operating" state + /// * `Err(FpgadError::FPGAState)` - FPGA is in a different state + /// * `Err(FpgadError::IORead)` - Failed to read state file + /// + /// # Examples + /// + /// ```rust,no_run + /// # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; + /// # fn example(fpga: &UniversalFPGA) -> Result<(), daemon::error::FpgadError> { + /// // After loading a bitstream + /// fpga.assert_state()?; + /// println!("FPGA is operating correctly"); + /// # Ok(()) + /// # } + /// ``` pub(crate) fn assert_state(&self) -> Result<(), FpgadError> { match self.state() { Ok(state) => match state.to_string().as_str() { @@ -52,14 +162,25 @@ impl UniversalFPGA { } impl Fpga for UniversalFPGA { - /// Get the name of this FPGA device e.g. fpga0. + /// Get the device handle for this FPGA. + /// + /// Returns the stored device handle string. + /// + /// # Returns: `&str` + /// * Device handle (e.g., "fpga0") fn device_handle(&self) -> &str { &self.device_handle } - /// Reads and returns contents of `/sys/class/fpga_manager/self.name/state` or FpgadError::IO. + /// Read the current FPGA state from sysfs. + /// + /// Reads `/sys/class/fpga_manager//state` and returns the state string + /// with trailing newlines removed. Common states include "operating", "unknown", + /// or a string representing an error state. /// - /// returns: Result + /// # Returns: `Result` + /// * `Ok(String)` - Current state (newlines trimmed) + /// * `Err(FpgadError::IORead)` - Failed to read state file fn state(&self) -> Result { let state_path = Path::new(config::FPGA_MANAGERS_DIR) .join(self.device_handle.clone()) @@ -68,8 +189,15 @@ impl Fpga for UniversalFPGA { fs_read(&state_path).map(|s| s.trim_end_matches('\n').to_string()) } - /// Gets the flags from the hex string stored in the sysfs flags file - /// e.g. sys/class/fpga_manager/fpga0/flags + /// Read the current programming flags from sysfs. + /// + /// Reads `/sys/class/fpga_manager//flags`, parses the hexadecimal string + /// (format: "0x...", or undecorated), and returns the flags as u32. + /// + /// # Returns: `Result` + /// * `Ok(u32)` - Current flags value + /// * `Err(FpgadError::IORead)` - Failed to read flags file + /// * `Err(FpgadError::Flag)` - Failed to parse hexadecimal value fn flags(&self) -> Result { let flag_path = Path::new(config::FPGA_MANAGERS_DIR) .join(self.device_handle.clone()) @@ -80,8 +208,22 @@ impl Fpga for UniversalFPGA { .map_err(|_| FpgadError::Flag("Parsing flags failed".into())) } - /// Sets the flags in the sysfs flags file (e.g. sys/class/fpga_manager/fpga0/flags) - /// and verifies the write command stuck by reading it back. + /// Set the programming flags in sysfs. + /// + /// Writes the flags to `/sys/class/fpga_manager//flags` in undecorated + /// hexadecimal (decimal `32` -> undecorated hex `20`) and verifies that the write + /// succeeded by reading the value back. + /// Also checks and logs the FPGA state after setting flags. + /// + /// # Arguments + /// + /// * `flags` - The flags value to set + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Flags set and verified successfully + /// * `Err(FpgadError::IOWrite)` - Failed to write flags file + /// * `Err(FpgadError::IORead)` - Failed to read back flags or state + /// * `Err(FpgadError::Flag)` - Read-back value doesn't match written value fn set_flags(&self, flags: u32) -> Result<(), FpgadError> { let flag_path = Path::new(config::FPGA_MANAGERS_DIR) .join(self.device_handle.clone()) @@ -123,8 +265,39 @@ impl Fpga for UniversalFPGA { } } - /// This can be used to manually load a firmware if the overlay does not trigger the load. - /// Note: always load firmware before overlay. + /// Load a bitstream firmware file directly to the FPGA. + /// + /// Writes the firmware filename (relative to the kernel firmware search path) to + /// `/sys/class/fpga_manager//firmware`. This triggers the kernel to load + /// and program the bitstream. After writing, the method verifies the FPGA enters + /// the "operating" state. + /// + /// # Arguments + /// + /// * `bitstream_path_rel` - Path to bitstream file relative to firmware search path + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Bitstream loaded and FPGA is operating + /// * `Err(FpgadError::IOWrite)` - Failed to write firmware file + /// * `Err(FpgadError::FPGAState)` - FPGA not in "operating" state after loading + /// + /// # Note + /// + /// This method can be used to manually load firmware when an overlay doesn't + /// trigger automatic loading. Always load firmware before applying overlays. + /// + /// # Examples + /// + /// ```rust,no_run + /// # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; + /// # use daemon::platforms::platform::Fpga; + /// # use std::path::Path; + /// # fn example(fpga: &UniversalFPGA) -> Result<(), daemon::error::FpgadError> { + /// fpga.load_firmware(Path::new("design.bit.bin"))?; + /// println!("Bitstream loaded successfully"); + /// # Ok(()) + /// # } + /// ``` fn load_firmware(&self, bitstream_path_rel: &Path) -> Result<(), FpgadError> { let control_path = Path::new(config::FPGA_MANAGERS_DIR) .join(self.device_handle()) From e9bca8380715fbd17ee03bb9d630a4d0d110dbfd Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 22 Jan 2026 13:39:19 +0000 Subject: [PATCH 09/13] docs: full documentation of universal_overlay_handler.rs Signed-off-by: Artie Poole --- .../universal_overlay_handler.rs | 213 ++++++++++++++++-- 1 file changed, 192 insertions(+), 21 deletions(-) diff --git a/daemon/src/platforms/universal_components/universal_overlay_handler.rs b/daemon/src/platforms/universal_components/universal_overlay_handler.rs index e8acf79f..0eb4b290 100644 --- a/daemon/src/platforms/universal_components/universal_overlay_handler.rs +++ b/daemon/src/platforms/universal_components/universal_overlay_handler.rs @@ -10,6 +10,54 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Universal device tree overlay handler implementation. +//! +//! This module provides the [`UniversalOverlayHandler`] struct, which implements the +//! [`OverlayHandler`] trait for generic device tree overlay management using the Linux +//! configfs mechanism. It handles overlay application, removal, and status checking +//! without vendor-specific logic. +//! +//! # Configfs Interface +//! +//! ## Overlay Directory Structure +//! +//! Overlays are managed in `/sys/kernel/config/device-tree/overlays//`: +//! ```text +//! /sys/kernel/config/device-tree/overlays/my_overlay/ +//! โ”œโ”€โ”€ dtbo # Device tree blob (appears unused in current implementation) +//! โ”œโ”€โ”€ path # Write overlay source path here to apply; read to verify application +//! โ””โ”€โ”€ status # Read to check if overlay was applied successfully +//!``` +//! +//! # Overlay Application Flow +//! +//! 1. Create overlay directory in configfs +//! 2. Write overlay source path to the `path` file +//! 3. Verify overlay applied by checking both `path` and `status` files +//! +//! # Examples +//! +//! ```rust,no_run +//! use daemon::platforms::universal_components::universal_overlay_handler::UniversalOverlayHandler; +//! use daemon::platforms::platform::OverlayHandler; +//! use std::path::Path; +//! +//! # fn example() -> Result<(), daemon::error::FpgadError> { +//! let handler = platform_for_known_platform("universal").overlay_handler("my_overlay")?; +//! +//! // Apply overlay +//! handler.apply_overlay(Path::new("/lib/firmware/design.dtbo"))?; +//! +//! // Check status +//! let status = handler.status()?; +//! println!("Overlay status: {}", status); +//! +//! // Remove overlay +//! handler.remove_overlay()?; +//! # Ok(()) +//! # } +//! ``` + use crate::config; use crate::error::FpgadError; use crate::platforms::platform::OverlayHandler; @@ -17,18 +65,47 @@ use crate::system_io::{fs_create_dir, fs_read, fs_remove_dir, fs_write}; use log::{info, trace}; use std::path::{Path, PathBuf}; -/// Takes a handle and creates and stores an appropriate overlay_fs_path in this object. -/// The overlay_fs_path is static apart from the handle associated with each -/// device, overlay or bitstream, and so the handle is specified by the user here and the rest -/// is fixed. +/// Construct the configfs path for an overlay directory. +/// +/// Creates the full path to an overlay's configfs directory by combining the +/// base overlay control directory with the provided handle. +/// See [config::OVERLAY_CONTROL_DIR] for information about the base path. +/// +/// # Arguments +/// +/// * `overlay_handle` - The overlay handle (directory name) +/// +/// # Returns: `PathBuf` +/// * Full path to overlay directory in configfs +/// +/// # Examples +/// +/// ```rust,no_run +/// # use std::path::PathBuf; +/// # fn construct_overlay_fs_path(overlay_handle: &str) -> PathBuf { +/// # PathBuf::from("/sys/kernel/config/device-tree/overlays").join(overlay_handle) +/// # } +/// let path = construct_overlay_fs_path("my_overlay"); +/// assert!(path.to_string_lossy().contains("my_overlay")); +/// ``` fn construct_overlay_fs_path(overlay_handle: &str) -> PathBuf { let overlay_fs_path = PathBuf::from(config::OVERLAY_CONTROL_DIR).join(overlay_handle); trace!("overlay_fs_path will be {overlay_fs_path:?}"); overlay_fs_path } -/// Stores the three relevant paths: source files for dtbo/bitstream and the overlayfs dir to which -/// the dtbo path was written. +/// Universal device tree overlay handler using Linux configfs. +/// +/// This struct manages a single device tree overlay through the Linux configfs +/// mechanism. It stores the overlay's configfs directory path and provides methods +/// to apply, remove, and check the status of the overlay. +/// +/// # Fields +/// +/// * `overlay_fs_path` - Path to the overlay's directory in configfs +/// (e.g., `/sys/kernel/config/device-tree/overlays/my_overlay`) +/// +/// see [config::OVERLAY_CONTROL_DIR] for the base overlay control directory. #[derive(Debug)] pub struct UniversalOverlayHandler { /// The path which points to the overlay virtual filesystem's dir which contains @@ -36,14 +113,30 @@ pub struct UniversalOverlayHandler { overlay_fs_path: PathBuf, } +/// Implementation of helper methods for UniversalOverlayHandler. impl UniversalOverlayHandler { + /// Read the overlay status from the configfs `status` file. + /// + /// Reads from the `/status` file and returns + /// the status string with trailing newlines removed. + /// + /// # Returns: `Result` + /// * `Ok(String)` - Status string (typically "applied" or empty) + /// * `Err(FpgadError::IORead)` - Failed to read status file fn get_vfs_status(&self) -> Result { let status_path = self.overlay_fs_path()?.join("status"); trace!("Reading from {status_path:?}"); fs_read(&status_path).map(|s| s.trim_end_matches('\n').to_string()) } - /// Read path from /path file and verify that what was meant to be applied - /// was applied. + + /// Read the overlay path from the configfs `path` file. + /// + /// Reads from the `/path` file which contains + /// the device tree path where the overlay was applied. + /// + /// # Returns: `Result` + /// * `Ok(PathBuf)` - Device tree path from the path file + /// * `Err(FpgadError::IORead)` - Failed to read path file fn get_vfs_path(&self) -> Result { let path_path = self.overlay_fs_path()?.join("path"); trace!("Reading from {path_path:?}"); @@ -51,8 +144,20 @@ impl UniversalOverlayHandler { Ok(PathBuf::from(path_string)) } - /// When an overlay fails to be applied, it may show as "applied" status but the path will - /// be empty. Therefore, this checks both match what is expected. + /// Verify that an overlay was successfully applied. + /// + /// Checks both the `path` and `status` files within `overlay_fs_path` to ensure that + /// the overlay was correctly applied. Sometimes an overlay may show "applied" status + /// but have an empty path, indicating a failure. + /// + /// # Arguments + /// + /// * `source_path_rel` - The source path that was written to apply the overlay + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Overlay successfully applied (path matches and status is "applied") + /// * `Err(FpgadError::OverlayStatus)` - Path doesn't match or status not "applied" + /// * `Err(FpgadError::IORead)` - Failed to read path or status file fn vfs_check_applied(&self, source_path_rel: &Path) -> Result<(), FpgadError> { let path_file_contents = &self.get_vfs_path()?; if path_file_contents.ends_with(source_path_rel) { @@ -79,11 +184,32 @@ impl UniversalOverlayHandler { } } +/// Implementation of the OverlayHandler trait for UniversalOverlayHandler. impl OverlayHandler for UniversalOverlayHandler { - /// Attempts to apply a device tree overlay which should trigger a firmware load. - /// There are multiple ways to trigger a firmware load so this is not valid if the - /// dtbo doesn't contain a firmware to load. - /// Calls prepare_for_load to ensure paths are valid etc. beforehand. + /// Apply a device tree overlay through configfs. + /// + /// Creates the overlay directory in configfs, writes the overlay source path to the + /// `path` file, and verifies successful application. This may trigger automatic + /// firmware loading if the overlay specifies a bitstream. + /// + /// # Arguments + /// + /// * `source_path_rel` - Path to the overlay file (can be absolute or relative to firmware path) + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Overlay applied and verified successfully + /// * `Err(FpgadError::Argument)` - Overlay with this handle already exists + /// * `Err(FpgadError::IOCreate)` - Failed to create overlay directory + /// * `Err(FpgadError::Internal)` - configfs didn't create `path` file (not mounted?) + /// * `Err(FpgadError::IOWrite)` - Failed to write overlay path + /// * `Err(FpgadError::OverlayStatus)` - Overlay didn't apply correctly + /// + /// # Notes + /// + /// - There are multiple ways device tree overlays can trigger firmware loading + /// - This method is not valid if the dtbo doesn't contain firmware to load + /// - The overlay directory must not already exist. [`OverlayHandler::remove_overlay`] can be called + /// to remove it first. fn apply_overlay(&self, source_path_rel: &Path) -> Result<(), FpgadError> { let overlay_fs_path = self.overlay_fs_path()?; if overlay_fs_path.exists() { @@ -115,20 +241,42 @@ impl OverlayHandler for UniversalOverlayHandler { self.vfs_check_applied(source_path_rel) } - /// Attempts to delete overlay_fs_path + /// Remove a device tree overlay from configfs. + /// + /// Removes the overlay directory from configfs, which deactivates the overlay and + /// restores the original device tree state. + /// + /// # Returns: `Result<(), FpgadError>` + /// * `Ok(())` - Overlay directory removed successfully + /// * `Err(FpgadError::IODelete)` - Failed to remove directory (not empty, doesn't exist, etc.) fn remove_overlay(&self) -> Result<(), FpgadError> { let overlay_fs_path = self.overlay_fs_path()?; fs_remove_dir(overlay_fs_path) } - /// WARNING NOT IMPLEMENTED: - /// This is where the required fpga flags will be determined from the dtbo, - /// such as compressed or encrypted. + /// Get the required FPGA programming flags from the overlay. + /// + /// # Warning: Not Implemented + /// + /// This method is intentionally unimplemented for the universal overlay handler. + /// In a platform specific implementation (i.e. a softener) this would implement + /// logic to parse the overlay/relevant package and determine any required FPGA + /// programming flags. + /// + /// # Returns: `Result` + /// * `Ok(0)` - Always returns 0 (no flags) fn required_flags(&self) -> Result { Ok(0) } - /// Read status from /status file and verify that it is "applied" + /// Get the current status of the overlay. + /// + /// Reads both the `path` and `status` files from configfs and returns a combined + /// status string. If the overlay directory doesn't exist, returns "not present". + /// + /// # Returns: `Result` + /// * `Ok(String)` - Status string (e.g., "/path/in/tree applied" or "not present") + /// * `Err(FpgadError::IORead)` - Failed to read status or path file fn status(&self) -> Result { if !self.overlay_fs_path()?.exists() { return Ok("not present".into()); @@ -138,14 +286,37 @@ impl OverlayHandler for UniversalOverlayHandler { Ok(format!("{path:?} {status}")) } - /// Checks that the overlay_fs_path is stored at time of call and returns it if so (unwraps Option into Result) + /// Get the filesystem path to the overlay directory. + /// + /// Returns the stored overlay configfs path. + /// + /// # Returns: `Result<&Path, FpgadError>` + /// * `Ok(&Path)` - Path to overlay directory in configfs fn overlay_fs_path(&self) -> Result<&Path, FpgadError> { Ok(self.overlay_fs_path.as_path()) } } impl UniversalOverlayHandler { - /// Scans the package dir for required files + /// Create a new UniversalOverlayHandler for the specified overlay. + /// + /// Constructs the configfs path for the overlay but doesn't create the directory + /// or validate its existence. Directory creation happens in [`apply_overlay`](OverlayHandler::apply_overlay). + /// + /// # Arguments + /// + /// * `overlay_handle` - The overlay handle (directory name in configfs) + /// + /// # Returns: `Self` + /// * New UniversalOverlayHandler instance + /// + /// # Examples + /// + /// ```rust,no_run + /// # use daemon::platforms::platform::platform_for_known_platform; + /// + /// let handler = platform_for_known_platform("universal").overlay_handler("my_overlay")?; + /// ``` pub(crate) fn new(overlay_handle: &str) -> Self { UniversalOverlayHandler { overlay_fs_path: construct_overlay_fs_path(overlay_handle), From a902619d63718b4f1d9584a9a4f3b24219c989bf Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Fri, 20 Mar 2026 12:38:21 +0000 Subject: [PATCH 10/13] docs: added documentation for how to implement a softener includes - adding new softener error to enum - adding custom fpga and custom overlayhandler files Signed-off-by: Artie Poole --- daemon/src/comm.rs | 18 + daemon/src/platforms.rs | 1 + daemon/src/platforms/README.md | 122 ++--- daemon/src/platforms/universal_components.rs | 1 - daemon/src/softeners/README.md | 449 +++++++++++-------- daemon/src/softeners/error.rs | 24 + fpgad_macros/src/lib.rs | 35 ++ 7 files changed, 410 insertions(+), 240 deletions(-) diff --git a/daemon/src/comm.rs b/daemon/src/comm.rs index 053bbd8d..0390ad04 100644 --- a/daemon/src/comm.rs +++ b/daemon/src/comm.rs @@ -10,4 +10,22 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Communication interfaces for the fpgad daemon. +//! +//! This module provides communication interfaces that allow external clients to +//! interact with the fpgad daemon. Currently, the only communication method is +//! DBus, which provides a system bus service for privileged FPGA operations. +//! +//! # Submodules +//! +//! - [`dbus`] - DBus interface implementation for the daemon +//! +//! # Architecture +//! +//! The daemon exposes two DBus interfaces: +//! - **Control Interface** - Write operations (loading, applying, removing) +//! - **Status Interface** - Read-only operations (querying state, listing devices) +//! +//! For more information, see the [`dbus`] module documentation. + pub mod dbus; diff --git a/daemon/src/platforms.rs b/daemon/src/platforms.rs index 906c2eb1..f23654a4 100644 --- a/daemon/src/platforms.rs +++ b/daemon/src/platforms.rs @@ -11,6 +11,7 @@ // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. #![doc = include_str!("platforms/README.md")] + pub mod platform; pub mod universal; pub mod universal_components; diff --git a/daemon/src/platforms/README.md b/daemon/src/platforms/README.md index 9771a686..2d74da75 100644 --- a/daemon/src/platforms/README.md +++ b/daemon/src/platforms/README.md @@ -1,62 +1,70 @@ -# FPGAd Platforms - -- **Universal** - Only uses the linux kernel implementations for bitstream loading. Relies on linux drivers. - -# Trait - -```mermaid -classDiagram - class Platform { - platform_type() <- get - fgpa() <- init/get - overlay_handler() <- init/get - } - class FPGA { - device_handle() <- get - get_state() - get_flags() - set_flags() - load_firmware() - } - class OverlayHandler { - new(overlay_handle) - get_required_flags() <- FPGA requires? - get_status() - set_source_path(source_path) - get_overlay_fs_path() - apply_overlay() - remove_overlay() - } - Platform <-- FPGA - Platform <-- OverlayHandler -``` +# Platform Abstractions for FPGA Hardware Management + +This module provides a platform registry system that allows fpgad to support multiple +FPGA platforms through a common interface. Each platform implementation handles the +specific details of interacting with different FPGA hardware and toolchains. + +## Architecture + +The platform system uses a registration pattern where platform implementations register +themselves with a global registry using device tree compatibility strings. When fpgad +discovers FPGA devices during initialization, it queries the registry to find the +appropriate platform implementation based on the device's compatibility string. + +Once a platform is selected, it is instantiated and used to manage the FPGA device. +Platforms handle operations like loading firmware, removing overlays, and querying device status. + +## Workflow + +1. Platform implementations register themselves with the platform registry during initialization +2. fpgad discovers FPGA devices by scanning the system +3. For each device, fpgad looks up the matching platform by compatibility string +4. The platform is instantiated and used to manage the device +5. Vendor-specific "softener" platforms can provide enhanced functionality beyond the standard interface + +## Built-in Platforms + +- **Universal Platform** ([`universal`]): A generic platform that provides basic FPGA management + functionality for devices that don't require platform-specific handling. + +## Optional Softener Platforms (using Features) -# Universal - -```mermaid -classDiagram - class UniversalPlatform { - device_handle: "Universal" - fpga: Option<UniversalFpga> - overlay_handler: Option<UniversalOverlayHandler> - + new() - } - class UniversalFPGA { - + device_handle: String e.g. "fpga0" - + new(device_handle) <- takes e.g. "fpga0" - + assert_state() - } - class UniversalOverlayHandler { - + overlay_source_path: Option<PathBuf> - + overlay_fs_path: Option<PathBuf> - + prepare_for_load() - + get_vfs_status() - + get_vfs_path() - + vfs_check_applied() - } - UniversalPlatform <-- UniversalFPGA - UniversalPlatform <-- UniversalOverlayHandler +These platforms are vendor-specific implementations called "softeners" that provide enhanced +functionality beyond the universal platform. They are enabled via Cargo feature flags. +- **Xilinx DFX Manager** ([`softeners::xilinx_dfx_mgr`](crate::softeners::xilinx_dfx_mgr)) - + **Feature** `xilinx-dfx-mgr` + Custom support for AMD-AECG (formerly Xilinx) FPGA devices that integrates with the Xilinx + dfx-mgr daemon. Implements dfx-mgr as a backend to support advanced features like partial + reconfiguration. + + + **Supported devices:** + - `xlnx,zynqmp-pcap-fpga` - Zynq UltraScale+ MPSoC + - `versal-fpga` - Versal ACAP devices + - `zynq-devcfg-1.0` - Zynq-7000 devices + + **Requirements:** The dfx-mgrd daemon must be running (automatically started in snap + environment). + + +## Platform Registration + +Platform implementations use the `#[platform]` macro from `fpgad_macros` to automatically +generate registration code: + +```rust,ignore +use fpgad_macros::platform; + +#[platform(compat_string = "xlnx,zynqmp-pcap-fpga")] +pub struct XilinxPlatform { + // ... +} ``` +## See Also + +- [`softeners`](crate::softeners) - Vendor-specific platform extensions (feature-gated) +- [`platform`] - Core platform registry and trait definitions + diff --git a/daemon/src/platforms/universal_components.rs b/daemon/src/platforms/universal_components.rs index 16b13b14..051a1955 100644 --- a/daemon/src/platforms/universal_components.rs +++ b/daemon/src/platforms/universal_components.rs @@ -1,4 +1,3 @@ -#![doc = include_str!("README.md")] // This file is part of fpgad, an application to manage FPGA subsystem together with device-tree and kernel modules. // // Copyright 2025 Canonical Ltd. diff --git a/daemon/src/softeners/README.md b/daemon/src/softeners/README.md index 97462d76..6a6c6913 100644 --- a/daemon/src/softeners/README.md +++ b/daemon/src/softeners/README.md @@ -2,27 +2,53 @@ ## Overview -Softeners are platform-specific extensions to FPGAd that provide integration with vendor-specific FPGA management tools and frameworks. While FPGAd's core platform system handles standard Linux FPGA subsystem operations, softeners enable fpgad to work with proprietary or vendor-specific tools that sit alongside or above the kernel FPGA subsystem. +Softeners are platform-specific extensions to FPGAd that provide integration with vendor-specific FPGA management tools +and frameworks. While FPGAd's core platform system handles standard Linux FPGA subsystem operations, softeners enable +fpgad to work with proprietary or vendor-specific tools that sit alongside or above the kernel FPGA subsystem. -> **Note on Naming**: FPGA devices are often referred to as "fabric" (as in "programmable logic fabric"), so naturally FPGAd provides "softeners" for the fabric ๐Ÿงบ. +> **Note on Naming**: FPGA devices are often referred to as "fabric" (as in "programmable logic fabric"), so naturally +> FPGAd provides "softeners" for the fabric ๐Ÿงบ. ### When to Use a Softener vs. a Platform -- **Use a Platform** when implementing direct interactions with the Linux FPGA subsystem (`/sys/class/fpga_manager`) and device tree overlays. Platforms handle the standard kernel interface. - -- **Use a Softener** when integrating with vendor-specific userspace tools, SDKs, or frameworks that provide additional functionality beyond what the kernel exposes. Softeners typically wrap external binaries or libraries. +- **Use the Universal Platform** when implementing direct interactions with the Linux FPGA subsystem ( + `/sys/class/fpga_manager`) and device tree overlays using standard kernel interfaces. The Universal platform handles + the standard kernel interface through generic sysfs operations. + +- **Create a Softener Platform** when you need to integrate with vendor-specific userspace tools, SDKs, or frameworks + that provide additional functionality beyond what the kernel exposes. Softeners typically wrap external binaries or + libraries and implement custom `Fpga` and `OverlayHandler` types that use vendor-specific tools instead of generic + sysfs operations. ## Architecture Softeners extend platforms by providing vendor-specific functionality. A softener platform: + 1. Implements the `Platform` trait (like any other platform) 2. Uses the `#[platform(compat_string = "...")]` macro to register itself -3. Provides additional vendor-specific functions beyond the standard `Platform` trait -4. Is conditionally compiled using Cargo feature flags +3. Provides custom `Fpga` and `OverlayHandler` implementations that wrap vendor tools +4. May provide additional vendor-specific functions beyond the standard `Platform` trait +5. Is conditionally compiled using Cargo feature flags + +### Why Custom FPGA and OverlayHandler Implementations? + +While you *could* reuse the Universal platform's components (`UniversalFPGA` and `UniversalOverlayHandler`), softeners +exist specifically to provide vendor-specific functionality. Custom implementations allow you to: + +- **Wrap vendor tools**: Instead of using generic sysfs operations, call vendor-specific binaries or libraries +- **Provide enhanced features**: Expose functionality not available through the standard Linux FPGA subsystem +- **Optimize performance**: Use vendor-optimized loading mechanisms or caching strategies +- **Handle vendor quirks**: Work around device-specific behaviors or limitations +- **Integrate with vendor ecosystems**: Connect with other vendor tools, licensing systems, or management frameworks + +If you find yourself not needing any custom behavior, you probably don't need a softener platform at all - the Universal +platform should suffice. ### Example: Xilinx DFX Manager -The `xilinx_dfx_mgr` softener integrates with AMD/Xilinx's `dfx-mgr` tool, which provides vendor specific optimizations on top of the standard FPGA subsystem and, e.g., enables "partial reconfiguration" of the device, which isn't supported by the Universal platform. See [dfx-mgr on GitHub](https://github.com/Xilinx/dfx-mgr) for more information on dfx-mgr. +The `xilinx_dfx_mgr` softener integrates with AMD/Xilinx's `dfx-mgr` tool, which provides vendor specific optimizations +on top of the standard FPGA subsystem and, e.g., enables "partial reconfiguration" of the device, which isn't supported +by the Universal platform. See [dfx-mgr on GitHub](https://github.com/Xilinx/dfx-mgr) for more information on dfx-mgr. ## Adding a New Softener Platform @@ -41,30 +67,40 @@ your-new-softener = ["softeners"] # Add this line **Important**: Each softener should depend on the `softeners` base feature. -### Step 2: Create the Softener Module +### Step 2: Create the Softener Module Structure -Create a new file at `daemon/src/softeners/your_softener_name.rs`: +Create your softener module files. You'll need at least: + +- `your_softener_name.rs` - Main platform struct +- `your_softener_name_fpga.rs` - Custom FPGA implementation +- `your_softener_name_overlay_handler.rs` - Custom overlay handler implementation + +**Important**: While you *could* reuse the Universal platform's components, softeners exist to provide vendor-specific +functionality, so you should implement your own FPGA and OverlayHandler types that wrap or integrate with your vendor's +tools. + +#### Main Platform File: `daemon/src/softeners/your_softener_name.rs` ```rust use std::sync::OnceLock; use crate::platforms::platform::Platform; -use crate::platforms::universal_components::universal_fpga::UniversalFPGA; -use crate::platforms::universal_components::universal_overlay_handler::UniversalOverlayHandler; use crate::softeners::error::FpgadSoftenerError; +use crate::softeners::your_softener_name_fpga::YourSoftenerFPGA; +use crate::softeners::your_softener_name_overlay_handler::YourSoftenerOverlayHandler; use fpgad_macros::platform; /// Your softener platform implementation. /// /// This platform integrates with [vendor tool name] to provide [brief description]. -/// +/// /// # Compatibility -/// +/// /// Compatible with devices matching: `your,compat-string` #[platform(compat_string = "your,compat-string")] pub struct YourSoftenerPlatform { - fpga: OnceLock, - overlay_handler: OnceLock, + fpga: OnceLock, + overlay_handler: OnceLock, } impl Default for YourSoftenerPlatform { @@ -87,18 +123,14 @@ impl Platform for YourSoftenerPlatform { &self, device_handle: &str, ) -> Result<&dyn crate::platforms::platform::Fpga, crate::error::FpgadError> { - Ok(self.fpga.get_or_init(|| YourPlatformFPGA::new(device_handle))) - // See step (7) about implementing custom Fpga + Ok(self.fpga.get_or_init(|| YourSoftenerFPGA::new(device_handle))) } fn overlay_handler( &self, overlay_handle: &str, ) -> Result<&dyn crate::platforms::platform::OverlayHandler, crate::error::FpgadError> { - Ok(self - .overlay_handler - .get_or_init(|| YourPlatformOverlayHandler::new(overlay_handle))) - // See step (7) about implementing custom OverlayHandler + Ok(self.overlay_handler.get_or_init(|| YourSoftenerOverlayHandler::new(overlay_handle))) } } @@ -107,114 +139,36 @@ impl Platform for YourSoftenerPlatform { /// Example vendor-specific function pub fn do_vendor_thing(param: &str) -> Result { - // Implementation here + // vendor specific functionality, example later todo!() } ``` -### Step 3: Register the Module - -Edit `daemon/src/softeners.rs` to include your new softener: - -```rust -pub mod error; - -#[cfg(feature = "xilinx-dfx-mgr")] -pub mod xilinx_dfx_mgr; - -#[cfg(feature = "your-new-softener")] -pub mod your_softener_name; // Add this line -``` - -### Step 4: Register the Platform at Startup - -Edit `daemon/src/main.rs` to register your platform: - -```rust -#[cfg(feature = "xilinx-dfx-mgr")] -use softeners::xilinx_dfx_mgr::XilinxDfxMgrPlatform; - -#[cfg(feature = "your-new-softener")] -use softeners::your_softener_name::YourSoftenerPlatform; // Add this - -fn register_platforms() { - #[cfg(feature = "xilinx-dfx-mgr")] - XilinxDfxMgrPlatform::register_platform(); - - #[cfg(feature = "your-new-softener")] - YourSoftenerPlatform::register_platform(); // Add this - - UniversalPlatform::register_platform(); -} -``` - -**Important**: The Universal platform should always be registered last as it serves as the fallback. - -### Step 5: Implement Vendor-Specific Functions - -Add functions that wrap your vendor's tools or APIs. These typically: -- Use `std::process::Command` to call external binaries -- Return `Result` for error handling -- Are marked with `#[allow(dead_code)]` if not yet exposed via DBus - -Example pattern: - -```rust -use std::process::Command; -use crate::softeners::error::FpgadSoftenerError; - -/// Brief description of what this does -pub fn vendor_operation(arg: &str) -> Result { - let output = Command::new("vendor-tool") - .arg("--option") - .arg(arg) - .output() - .map_err(FpgadSoftenerError::YourError)?; - - if output.status.success() { - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } else { - Err(FpgadSoftenerError::YourError(std::io::Error::other( - String::from_utf8_lossy(&output.stderr).to_string(), - ))) - } -} -``` - -### Step 6: Add Error Types (If Needed) - -If your softener needs custom error types beyond what's in `error.rs`, add them: - -```rust -// In daemon/src/softeners/error.rs -#[derive(Debug, thiserror::Error)] -pub enum FpgadSoftenerError { - #[error("FpgadSoftenerError::DfxMgr: {0}")] - DfxMgr(std::io::Error), - - #[error("FpgadSoftenerError::YourError: {0}")] - YourError(std::io::Error), // Add your error variant -} -``` - -### Step 7: Implement Custom FPGA/OverlayHandler (Advanced, Optional) - -If your vendor-specific platform requires custom behavior for FPGA or overlay operations, you can implement your own, otherwise just use the Universal platform's `UniversalFPGA` and `UniversalOverlayHandler` implementations. - - -#### Creating Custom FPGA Implementation - -Create a struct that implements the `Fpga` trait: +#### FPGA Implementation: `daemon/src/softeners/your_softener_name_fpga.rs` ```rust use crate::platforms::platform::Fpga; use crate::error::FpgadError; +use std::process::Command; +use log::{debug, trace}; -pub struct YourPlatformFPGA { +/// Your vendor-specific FPGA device implementation. +/// +/// This struct wraps interactions with [vendor tool] for FPGA operations. +pub struct YourSoftenerFPGA { device_handle: String, } -impl YourPlatformFPGA { +impl YourSoftenerFPGA { + /// Create a new FPGA device instance. + /// + /// # Arguments + /// + /// * `device_handle` - The device handle (e.g., "fpga0") + /// + /// # Returns: `Self` + /// + /// New FPGA instance pub fn new(device_handle: &str) -> Self { Self { device_handle: device_handle.to_string(), @@ -222,52 +176,89 @@ impl YourPlatformFPGA { } } -impl Fpga for YourPlatformFPGA { +impl Fpga for YourSoftenerFPGA { fn device_handle(&self) -> &str { &self.device_handle } - + fn state(&self) -> Result { - // Custom implementation - maybe call vendor tool - // or fall back to reading sysfs directly - todo!() + trace!("Getting state for device '{}'", self.device_handle); + // Custom implementation - call your vendor tool + let output = Command::new("vendor-tool").arg("status").output().map_err(|e| FpgadError::Io(e))?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(FpgadError::Io(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string() + ))) + } } - + fn flags(&self) -> Result { - // Custom implementation - todo!() + // You can read from sysfs or use vendor tool + // For standard flags, sysfs is fine: + crate::system_io::fs_read_u32(&format!( + "/sys/class/fpga_manager/{}/flags", + self.device_handle + )) } - + fn set_flags(&self, flags: u32) -> Result<(), FpgadError> { - // Custom implementation - todo!() + crate::system_io::fs_write( + &format!("/sys/class/fpga_manager/{}/flags", self.device_handle), + &flags.to_string() + ) } fn load(&self, bitstream_path: &str) -> Result<(), FpgadError> { - // Custom implementation - perhaps using vendor-specific loading - todo!() + debug!("Loading bitstream '{}' via vendor tool", bitstream_path); + // Use your vendor's tool for loading + let output = Command::new("vendor-tool").arg("load").arg(bitstream_path).output().map_err(|e| FpgadError::Io(e))?; + + if output.status.success() { + Ok(()) + } else { + Err(FpgadError::Io(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string() + ))) + } } fn name(&self) -> Result { - // Custom implementation - todo!() + crate::system_io::fs_read(&format!( + "/sys/class/fpga_manager/{}/name", + self.device_handle + )) } } ``` -#### Creating Custom OverlayHandler Implementation - -Create a struct that implements the `OverlayHandler` trait: +#### Overlay Handler: `daemon/src/softeners/your_softener_name_overlay_handler.rs` ```rust use crate::platforms::platform::OverlayHandler; use crate::error::FpgadError; +use std::process::Command; +use log::{debug, trace}; -pub struct YourPlatformOverlayHandler { +/// Your vendor-specific overlay handler implementation. +/// +/// This struct manages device tree overlays using [vendor approach]. +pub struct YourSoftenerOverlayHandler { overlay_handle: String, } -impl YourPlatformOverlayHandler { +impl YourSoftenerOverlayHandler { + /// Create a new overlay handler instance. + /// + /// # Arguments + /// + /// * `overlay_handle` - The overlay identifier + /// + /// # Returns: `Self` + /// + /// New overlay handler instance pub fn new(overlay_handle: &str) -> Self { Self { overlay_handle: overlay_handle.to_string(), @@ -275,71 +266,157 @@ impl YourPlatformOverlayHandler { } } -impl OverlayHandler for YourPlatformOverlayHandler { +impl OverlayHandler for YourSoftenerOverlayHandler { fn overlay_handle(&self) -> &str { &self.overlay_handle } fn load(&self, overlay_path: &str) -> Result<(), FpgadError> { - // Custom implementation - maybe use vendor-specific overlay mechanism - todo!() + debug!("Loading overlay '{}' via vendor tool", overlay_path); + // Use your vendor's method for applying overlays + let output = Command::new("vendor-tool").arg("apply-overlay").arg(overlay_path).output().map_err(|e| FpgadError::Io(e))?; + + if output.status.success() { + Ok(()) + } else { + Err(FpgadError::Io(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string() + ))) + } } fn remove(&self) -> Result<(), FpgadError> { - // Custom implementation - todo!() + debug!("Removing overlay '{}'", self.overlay_handle); + // Use your vendor's method for removing overlays + let output = Command::new("vendor-tool").arg("remove-overlay").arg(&self.overlay_handle).output().map_err(|e| FpgadError::Io(e))?; + + if output.status.success() { + Ok(()) + } else { + Err(FpgadError::Io(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string() + ))) + } } fn status(&self) -> Result { - // Custom implementation - todo!() + trace!("Getting overlay status for '{}'", self.overlay_handle); + // Query overlay status via vendor tool + let output = Command::new("vendor-tool").arg("overlay-status").arg(&self.overlay_handle).output().map_err(|e| FpgadError::Io(e))?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(FpgadError::Io(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string() + ))) + } } - } ``` -#### Using Your Custom Implementations +### Step 3: Add Your Softener Error Type -Update your platform's `fpga()` and `overlay_handler()` methods to use your custom implementations - note that this was -already included in the above example code.: +Add a custom error variant to the `FpgadSoftenerError` enum for your softener. This is used for vendor-specific errors +that occur when calling your vendor tools. + +Edit `daemon/src/softeners/error.rs` and add your error variant: ```rust -impl Platform for YourSoftenerPlatform { - fn fpga( - &self, - device_handle: &str, - ) -> Result<&dyn crate::platforms::platform::Fpga, crate::error::FpgadError> { - Ok(self.fpga.get_or_init(|| YourPlatformFPGA::new(device_handle))) - } +// In daemon/src/softeners/error.rs +#[derive(Debug, thiserror::Error)] +pub enum FpgadSoftenerError { + #[error("FpgadSoftenerError::DfxMgr: {0}")] + DfxMgr(std::io::Error), - fn overlay_handler( - &self, - overlay_handle: &str, - ) -> Result<&dyn crate::platforms::platform::OverlayHandler, crate::error::FpgadError> { - Ok(self - .overlay_handler - .get_or_init(|| YourPlatformOverlayHandler::new(overlay_handle))) - } + #[error("FpgadSoftenerError::YourSoftenerName: {0}")] + YourSoftenerName(std::io::Error), // Add your error variant } ``` -And update your struct fields to use the custom types: +**Naming Convention**: Use PascalCase matching your softener's name (e.g., `DfxMgr`). + +**Error Message Format**: Follow the pattern `FpgadSoftenerError::VariantName: {0}` to maintain consistency. + +This error type will be used throughout your softener implementation when vendor tool operations fail. + +### Step 4: Register the Modules + +Edit `daemon/src/softeners.rs` to include your new softener modules: ```rust -pub struct YourSoftenerPlatform { - fpga: OnceLock, - overlay_handler: OnceLock, +pub mod error; + +#[cfg(feature = "xilinx-dfx-mgr")] +pub mod xilinx_dfx_mgr; +#[cfg(feature = "xilinx-dfx-mgr")] +mod xilinx_dfx_mgr_fpga; +#[cfg(feature = "xilinx-dfx-mgr")] +mod xilinx_dfx_mgr_overlay_handler; + +#[cfg(feature = "your-new-softener")] +pub mod your_softener_name; // Main module (public) +#[cfg(feature = "your-new-softener")] +mod your_softener_name_fpga; // FPGA implementation (private) +#[cfg(feature = "your-new-softener")] +mod your_softener_name_overlay_handler; // Overlay handler (private) +``` + +**Note**: Only the main platform module needs to be `pub` - the FPGA and overlay handler modules are internal +implementation details. + +### Step 5: Register the Platform at Startup + +Edit `daemon/src/main.rs` to register your platform: + +```rust +#[cfg(feature = "xilinx-dfx-mgr")] +use softeners::xilinx_dfx_mgr::XilinxDfxMgrPlatform; + +#[cfg(feature = "your-new-softener")] +use softeners::your_softener_name::YourSoftenerPlatform; // Add this + +fn register_platforms() { + #[cfg(feature = "xilinx-dfx-mgr")] + XilinxDfxMgrPlatform::register_platform(); + + #[cfg(feature = "your-new-softener")] + YourSoftenerPlatform::register_platform(); // Add this + + UniversalPlatform::register_platform(); } ``` -#### When to Implement Custom Components +**Important**: The Universal platform should always be registered last as it serves as the fallback. + +### Step 6: Implement Vendor-Specific Functions + +Add functions that wrap your vendor's tools or APIs. +These might use `std::process::Command` to call external binaries. +These will return `Result` where `T` is typically `String`, and must be convertable to `String` +if not. + +Example pattern: -Consider implementing custom FPGA or OverlayHandler when: -- The vendor tooling provides optimized loading mechanisms -- The standard sysfs interface doesn't expose all necessary functionality -- The sysfs interface is different on your platform (instead of changing [config.rs](../config.rs)) +```rust +use std::process::Command; +use crate::softeners::error::FpgadSoftenerError; -For most cases, the Universal components are sufficient and recommended for simplicity. +/// Brief description of what this does +pub fn vendor_operation(arg: &str) -> Result { + // This example assumes an external binary `vendor-tool` is used + // The requirement of mapping the error is the key point made here + let output = Command::new("vendor-tool").arg("--option").arg(arg).output().map_err(FpgadSoftenerError::YourSoftenerName)?; + + if output.status.success() { + Ok(String::from_utf8_lossy(&output.stdout).to_string()) + } else { + Err(FpgadSoftenerError::YourSoftenerName(std::io::Error::other( + String::from_utf8_lossy(&output.stderr).to_string(), + ))) + } +} +``` ## Key Concepts @@ -351,25 +428,33 @@ The `compat_string` parameter in the `#[platform]` macro can contain multiple co #[platform(compat_string = "xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0")] ``` -This means the platform will be selected if the device's `/sys/class/fpga_manager//of_node/compatible` file contains ANY of these strings. +This means the platform will be selected if the device's `/sys/class/fpga_manager//of_node/compatible` file +contains ANY of these strings. ### OnceLock for Lazy Initialization Use `OnceLock` to lazily initialize components only when they're first accessed: ```rust -pub struct YourPlatform { - fpga: OnceLock, - overlay_handler: OnceLock, +pub struct YourSoftenerPlatform { + fpga: OnceLock, + overlay_handler: OnceLock, } -impl Platform for YourPlatform { +impl Platform for YourSoftenerPlatform { fn fpga(&self, device_handle: &str) -> Result<&dyn Fpga, FpgadError> { - Ok(self.fpga.get_or_init(|| UniversalFPGA::new(device_handle))) + Ok(self.fpga.get_or_init(|| YourSoftenerFPGA::new(device_handle))) + } + + fn overlay_handler(&self, overlay_handle: &str) -> Result<&dyn OverlayHandler, FpgadError> { + Ok(self.overlay_handler.get_or_init(|| YourSoftenerOverlayHandler::new(overlay_handle))) } } ``` +This pattern ensures that the FPGA and OverlayHandler instances are created only once, when first requested, and then +reused for all subsequent calls. + ## Testing Your Softener ### Unit Testing @@ -396,13 +481,13 @@ Add your integration tests to `daemon/tests/` to test FPGAd and y ### Manual testing -Do some manual testing to ensure that everything is working as expected. Don't forget to build with the appropriate feature flag: +Do some manual testing to ensure that everything is working as expected. Don't forget to build with the appropriate +feature flag: ```bash cargo build --features your-new-softener ``` - ## Best Practices 1. **Document Everything**: Add comprehensive doc comments to all public functions diff --git a/daemon/src/softeners/error.rs b/daemon/src/softeners/error.rs index 2c0e23af..2f7f6f0b 100644 --- a/daemon/src/softeners/error.rs +++ b/daemon/src/softeners/error.rs @@ -10,8 +10,32 @@ // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. +//! Error types for FPGA softener implementations. +//! +//! This module defines error types specific to vendor-specific softener implementations +//! (like Xilinx DFX Manager). These errors are converted to the main [`FpgadError`] type +//! for propagation through the daemon's DBus interfaces. +//! +//! # Error Types +//! +//! - [`FpgadSoftenerError`] - Errors from vendor-specific softener operations +//! +//! # Error Handling +//! +//! Softener errors implement conversion traits to both `fdo::Error` (for DBus responses) +//! and `FpgadError` (for internal error handling). All errors are logged when converted. + +/// Errors specific to FPGA softener implementations. +/// +/// This enum represents errors that occur in vendor-specific softener code, +/// such as failures in communication with external tools or daemons (e.g., dfx-mgr-client). #[derive(Debug, thiserror::Error)] pub enum FpgadSoftenerError { + /// Error from Xilinx DFX Manager operations. + /// + /// This variant represents failures when calling dfx-mgr-client or when + /// dfx-mgrd is not available or returns an error. The string contains + /// detailed error information from the dfx-mgr tooling. #[error("FpgadSoftenerError::DfxMgr: {0}")] DfxMgr(std::io::Error), } diff --git a/fpgad_macros/src/lib.rs b/fpgad_macros/src/lib.rs index 3c52afa5..6ce4e9f4 100644 --- a/fpgad_macros/src/lib.rs +++ b/fpgad_macros/src/lib.rs @@ -1,7 +1,42 @@ +#![doc = include_str!("../README.md")] + use proc_macro::TokenStream; use quote::quote; use syn::{Expr, ItemStruct, Lit, Meta, parse_macro_input, punctuated::Punctuated}; +/// Procedural macro to generate platform registration code. +/// +/// This macro adds a `register_platform()` method to a struct that registers it +/// with the global platform registry. The macro requires a `compat_string` parameter +/// that specifies the device tree compatibility string(s) the platform supports. +/// +/// # Arguments +/// +/// * `compat_string` - Comma-separated device tree compatibility strings +/// +/// # Generated Code +/// +/// The macro generates: +/// ```rust,ignore +/// impl YourStruct { +/// #[doc(hidden)] +/// pub fn register_platform() { +/// crate::platforms::platform::register_platform( +/// "compat_string", +/// || Box::new(Self::new()) +/// ); +/// } +/// } +/// ``` +/// +/// # Examples +/// +/// ```rust,ignore +/// #[platform(compat_string = "universal")] +/// pub struct UniversalPlatform { +/// // ... +/// } +/// ``` #[proc_macro_attribute] pub fn platform(args: TokenStream, input: TokenStream) -> TokenStream { // Parse the struct From fdb35052262040e36ce89dc7b3b7a0780d8aab26 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Thu, 26 Mar 2026 14:19:59 +0000 Subject: [PATCH 11/13] docs: address typos and mistakes suggested by copilot review Signed-off-by: Artie Poole --- daemon/src/error.rs | 6 +-- daemon/src/platforms/platform.rs | 45 +++++++++---------- daemon/src/platforms/universal.rs | 14 ++---- .../universal_components/universal_fpga.rs | 43 +++++++----------- .../universal_overlay_handler.rs | 19 +++----- daemon/src/softeners/error.rs | 2 +- daemon/src/system_io.rs | 28 ++++++------ fpgad_macros/README.md | 31 +++++++++++++ 8 files changed, 96 insertions(+), 92 deletions(-) create mode 100644 fpgad_macros/README.md diff --git a/daemon/src/error.rs b/daemon/src/error.rs index 5f725044..5219046c 100644 --- a/daemon/src/error.rs +++ b/daemon/src/error.rs @@ -34,12 +34,12 @@ //! # Examples //! //! ```rust,no_run -//! # use daemon::error::FpgadError; +//! # use crate::error::FpgadError; //! # use std::path::Path; -//! # +//! //! fn read_config(path: &Path) -> Result { //! // Will produce: FpgadError::IORead: An IO error occurred when reading from ... -//! daemon::system_io::fs_read(path) +//! crate::system_io::fs_read(path) //! } //! ``` diff --git a/daemon/src/platforms/platform.rs b/daemon/src/platforms/platform.rs index d7e73045..65a99136 100644 --- a/daemon/src/platforms/platform.rs +++ b/daemon/src/platforms/platform.rs @@ -199,9 +199,9 @@ pub trait OverlayHandler { /// # Examples /// /// ```rust,no_run -/// # use daemon::platforms::platform::Platform; +/// # use crate::platforms::platform::Platform; /// # -/// # fn example(platform: &dyn Platform) -> Result<(), daemon::error::FpgadError> { +/// # fn example(platform: &dyn Platform) -> Result<(), crate::error::FpgadError> { /// // Get an FPGA device instance /// let fpga = platform.fpga("fpga0")?; /// let state = fpga.state()?; @@ -267,17 +267,13 @@ pub trait Platform: Any { /// * `Err(FpgadError::Argument)` - No matching platform found /// /// # Examples -/// -/// ```rust,no_run -/// # use daemon::platforms::platform::match_platform_string; -/// # fn example() -> Result<(), daemon::error::FpgadError> { -/// // Match a single component +/// Match on one component: +/// ```rust,ignore /// let platform = match_platform_string("xlnx")?; -/// -/// // Match multiple components +/// ``` +/// Match on all of multiple components: +/// ```rust,ignore /// let platform = match_platform_string("xlnx,zynqmp-pcap-fpga")?; -/// # Ok(()) -/// # } /// ``` fn match_platform_string(platform_string: &str) -> Result, FpgadError> { let registry = PLATFORM_REGISTRY @@ -318,8 +314,8 @@ fn match_platform_string(platform_string: &str) -> Result, Fpg /// # Examples /// /// ```rust,no_run -/// # use daemon::platforms::platform::discover_platform; -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # use crate::platforms::platform::discover_platform; +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let platform = discover_platform("fpga0")?; /// let fpga = platform.fpga("fpga0")?; /// # Ok(()) @@ -339,7 +335,8 @@ fn discover_platform(device_handle: &str) -> Result, FpgadErro /// /// Reads the compatibility string from `/sys/class/fpga_manager//of_node/compatible`. /// This string identifies the hardware and is used for platform matching. The function -/// handles null-terminated strings that some drivers write to sysfs. +/// handles null-terminated strings that some drivers write to sysfs by trimming the final trailing +/// null byte. /// /// # Arguments /// @@ -352,8 +349,8 @@ fn discover_platform(device_handle: &str) -> Result, FpgadErro /// # Examples /// /// ```rust,no_run -/// # use daemon::platforms::platform::read_compatible_string; -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # use crate::platforms::platform::read_compatible_string; +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let compat = read_compatible_string("fpga0")?; /// println!("Compatibility: {}", compat); /// # Ok(()) @@ -396,8 +393,8 @@ pub fn read_compatible_string(device_handle: &str) -> Result /// # Examples /// /// ```rust,no_run -/// # use daemon::platforms::platform::platform_from_compat_or_device; -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # use crate::platforms::platform::platform_from_compat_or_device; +/// # fn example() -> Result<(), crate::error::FpgadError> { /// // Auto-discover from device /// let platform = platform_from_compat_or_device("", "fpga0")?; /// @@ -434,8 +431,8 @@ pub fn platform_from_compat_or_device( /// # Examples /// /// ```rust,no_run -/// # use daemon::platforms::platform::platform_for_known_platform; -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # use crate::platforms::platform::platform_for_known_platform; +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let platform = platform_for_known_platform("xlnx,zynqmp-pcap-fpga")?; /// # Ok(()) /// # } @@ -474,8 +471,8 @@ pub fn init_platform_registry() -> Mutex Result<(), daemon::error::FpgadError> { +/// # use crate::platforms::platform::list_fpga_managers; +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let devices = list_fpga_managers()?; /// for device in devices { /// println!("Found FPGA device: {}", device); diff --git a/daemon/src/platforms/universal.rs b/daemon/src/platforms/universal.rs index ade84594..fe39e676 100644 --- a/daemon/src/platforms/universal.rs +++ b/daemon/src/platforms/universal.rs @@ -39,16 +39,10 @@ //! //! # Examples //! -//! ```rust,no_run -//! # use daemon::platforms::universal::UniversalPlatform; -//! # use daemon::platforms::platform::Platform; -//! -//! # fn example() -> Result<(), daemon::error::FpgadError> { -//! let platform = platform_for_known_platform("universal"); +//! ```rust,ignore +//! let platform = platform_for_known_platform("universal")?; //! let fpga = platform.fpga("fpga0")?; //! let state = fpga.state()?; -//! # Ok(()) -//! # } //! ``` use crate::error::FpgadError; @@ -105,9 +99,9 @@ impl UniversalPlatform { /// # Examples /// /// ```rust,no_run - /// use daemon::platforms::universal::UniversalPlatform; + /// use crate::platforms::universal::UniversalPlatform; /// - /// let platform = platform_for_known_platform("universal"); + /// let platform = platform_for_known_platform("universal")?; /// ``` pub fn new() -> Self { trace!("creating new universal_platform"); diff --git a/daemon/src/platforms/universal_components/universal_fpga.rs b/daemon/src/platforms/universal_components/universal_fpga.rs index 698c9670..ec503d94 100644 --- a/daemon/src/platforms/universal_components/universal_fpga.rs +++ b/daemon/src/platforms/universal_components/universal_fpga.rs @@ -49,20 +49,18 @@ //! - `firmware` - Trigger bitstream loading by writing filename //! //! with any other files being controllable using the -//! [`write_property_bytes`](../../../../daemon/comm/dbus/control_interface/struct.ControlInterface.html#method.write_property_bytes) +//! [`write_property_bytes`](crate::comm::dbus::control_interface::ControlInterface::write_property_bytes) //! and -//! [`write_property`](../../../../daemon/comm/dbus/control_interface/struct.ControlInterface.html#method.write_property) +//! [`write_property`](crate::comm::dbus::control_interface::ControlInterface::write_property) //! DBus methods. -//! See the [`control_interface`](../../../../daemon/comm/dbus/control_interface/index.html) documentation for more details. +//! See the [`control_interface`](crate::comm::dbus::control_interface) documentation for more details. //! //! # Examples //! -//! ```rust,no_run -//! # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; -//! # use daemon::platforms::platform::platform_for_known_platform; -//! # -//! # fn example() -> Result<(), daemon::error::FpgadError> { -//! let fpga = platform_for_known_platform("universal").fpga("fpga0")?; +//! ```rust,ignore +//! // create platform +//! let platform = platform_for_known_platform("universal")?; +//! let fpga = platform.fpga("fpga0")?; //! //! // Check state //! let state = fpga.state()?; @@ -71,8 +69,6 @@ //! // Get flags //! let flags = fpga.flags()?; //! println!("Flags: 0x{:X}", flags); -//! # Ok(()) -//! # } //! ``` use crate::config; @@ -112,10 +108,9 @@ impl UniversalFPGA { /// /// # Examples /// - /// ```rust,no_run - /// use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; - /// - /// let fpga = platform_for_known_platform("universal").fpga("fpga0")?; + /// ```rust,ignore + /// let platform = platform_for_known_platform("universal")?; + /// let fpga = platform.fpga("fpga0")?; /// ``` pub(crate) fn new(device_handle: &str) -> UniversalFPGA { UniversalFPGA { @@ -135,14 +130,12 @@ impl UniversalFPGA { /// /// # Examples /// - /// ```rust,no_run - /// # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; - /// # fn example(fpga: &UniversalFPGA) -> Result<(), daemon::error::FpgadError> { + /// ```rust,ignore + /// let platform = platform_for_known_platform("universal")?; + /// let fpga = platform.fpga("fpga0")?; /// // After loading a bitstream /// fpga.assert_state()?; /// println!("FPGA is operating correctly"); - /// # Ok(()) - /// # } /// ``` pub(crate) fn assert_state(&self) -> Result<(), FpgadError> { match self.state() { @@ -288,15 +281,11 @@ impl Fpga for UniversalFPGA { /// /// # Examples /// - /// ```rust,no_run - /// # use daemon::platforms::universal_components::universal_fpga::UniversalFPGA; - /// # use daemon::platforms::platform::Fpga; - /// # use std::path::Path; - /// # fn example(fpga: &UniversalFPGA) -> Result<(), daemon::error::FpgadError> { + /// ```rust,ignore + /// let platform = platform_for_known_platform("universal")?; + /// let fpga = platform.fpga("fpga0")?; /// fpga.load_firmware(Path::new("design.bit.bin"))?; /// println!("Bitstream loaded successfully"); - /// # Ok(()) - /// # } /// ``` fn load_firmware(&self, bitstream_path_rel: &Path) -> Result<(), FpgadError> { let control_path = Path::new(config::FPGA_MANAGERS_DIR) diff --git a/daemon/src/platforms/universal_components/universal_overlay_handler.rs b/daemon/src/platforms/universal_components/universal_overlay_handler.rs index 0eb4b290..6ba2d0ff 100644 --- a/daemon/src/platforms/universal_components/universal_overlay_handler.rs +++ b/daemon/src/platforms/universal_components/universal_overlay_handler.rs @@ -37,13 +37,9 @@ //! //! # Examples //! -//! ```rust,no_run -//! use daemon::platforms::universal_components::universal_overlay_handler::UniversalOverlayHandler; -//! use daemon::platforms::platform::OverlayHandler; -//! use std::path::Path; -//! -//! # fn example() -> Result<(), daemon::error::FpgadError> { -//! let handler = platform_for_known_platform("universal").overlay_handler("my_overlay")?; +//! ```rust,ignore +//! let platform = platform_for_known_platform("universal")?; +//! let handler = platform.overlay_handler("my_overlay")?; //! //! // Apply overlay //! handler.apply_overlay(Path::new("/lib/firmware/design.dtbo"))?; @@ -54,8 +50,6 @@ //! //! // Remove overlay //! handler.remove_overlay()?; -//! # Ok(()) -//! # } //! ``` use crate::config; @@ -312,10 +306,9 @@ impl UniversalOverlayHandler { /// /// # Examples /// - /// ```rust,no_run - /// # use daemon::platforms::platform::platform_for_known_platform; - /// - /// let handler = platform_for_known_platform("universal").overlay_handler("my_overlay")?; + /// ```rust,ignore + /// let platform = platform_for_known_platform("universal")?; + /// let handler = platform.overlay_handler("my_overlay")?; /// ``` pub(crate) fn new(overlay_handle: &str) -> Self { UniversalOverlayHandler { diff --git a/daemon/src/softeners/error.rs b/daemon/src/softeners/error.rs index 2f7f6f0b..0f32b192 100644 --- a/daemon/src/softeners/error.rs +++ b/daemon/src/softeners/error.rs @@ -13,7 +13,7 @@ //! Error types for FPGA softener implementations. //! //! This module defines error types specific to vendor-specific softener implementations -//! (like Xilinx DFX Manager). These errors are converted to the main [`FpgadError`] type +//! (like Xilinx DFX Manager). These errors are converted to the main [`crate::error::FpgadError`] type //! for propagation through the daemon's DBus interfaces. //! //! # Error Types diff --git a/daemon/src/system_io.rs b/daemon/src/system_io.rs index 5c0f5909..80e073e1 100644 --- a/daemon/src/system_io.rs +++ b/daemon/src/system_io.rs @@ -21,10 +21,10 @@ //! # Examples //! //! ```rust,no_run -//! # use daemon::system_io::{fs_read, fs_write}; +//! # use crate::system_io::{fs_read, fs_write}; //! # use std::path::Path; //! -//! # fn example() -> Result<(), daemon::error::FpgadError> { +//! # fn example() -> Result<(), crate::error::FpgadError> { //! // Read a file //! let content = fs_read(Path::new("/sys/class/fpga_manager/fpga0/state"))?; //! @@ -57,10 +57,10 @@ use std::path::Path; /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_read; +/// # use crate::system_io::fs_read; /// # use std::path::Path; /// -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let state = fs_read(Path::new("/sys/class/fpga_manager/fpga0/state"))?; /// println!("FPGA state: {}", state.trim()); /// # Ok(()) @@ -104,10 +104,10 @@ pub fn fs_read(file_path: &Path) -> Result { /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_write; +/// # use crate::system_io::fs_write; /// # use std::path::Path; /// # -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// // Write to an existing file /// fs_write(Path::new("/sys/class/fpga_manager/fpga0/flags"), false, "0")?; /// @@ -158,10 +158,10 @@ pub fn fs_write(file_path: &Path, create: bool, value: impl AsRef) -> Resul /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_write_bytes; +/// # use crate::system_io::fs_write_bytes; /// # use std::path::Path; /// # -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// let data = vec![0xDE, 0xAD, 0xBE, 0xEF]; /// fs_write_bytes(Path::new("/tmp/binary_file"), true, &data)?; /// # Ok(()) @@ -205,10 +205,10 @@ pub fn fs_write_bytes(file_path: &Path, create: bool, data: &[u8]) -> Result<(), /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_create_dir; +/// # use crate::system_io::fs_create_dir; /// # use std::path::Path; /// # -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// // Create nested directories /// fs_create_dir(Path::new("/sys/kernel/config/device-tree/overlays/my_overlay"))?; /// # Ok(()) @@ -246,10 +246,10 @@ pub fn fs_create_dir(path: &Path) -> Result<(), FpgadError> { /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_remove_dir; +/// # use crate::system_io::fs_remove_dir; /// # use std::path::Path; /// # -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// // Remove an overlay directory /// fs_remove_dir(Path::new("/sys/kernel/config/device-tree/overlays/my_overlay"))?; /// # Ok(()) @@ -287,10 +287,10 @@ pub fn fs_remove_dir(path: &Path) -> Result<(), FpgadError> { /// # Examples /// /// ```rust,no_run -/// # use daemon::system_io::fs_read_dir; +/// # use crate::system_io::fs_read_dir; /// # use std::path::Path; /// -/// # fn example() -> Result<(), daemon::error::FpgadError> { +/// # fn example() -> Result<(), crate::error::FpgadError> { /// // List all FPGA devices /// let devices = fs_read_dir(Path::new("/sys/class/fpga_manager"))?; /// for device in devices { diff --git a/fpgad_macros/README.md b/fpgad_macros/README.md new file mode 100644 index 00000000..52de99cb --- /dev/null +++ b/fpgad_macros/README.md @@ -0,0 +1,31 @@ +# fpgad_macros + +Procedural macros for the FPGAd project. + +## Overview + +This crate provides procedural macros used by the FPGAd daemon to simplify platform registration and other compile-time code generation tasks. + +## Macros + +### `#[platform]` + +The `platform` attribute macro is used to register FPGA platform implementations with the daemon. + +**Usage:** + +```rust,ignore +use fpgad_macros::platform; + +#[platform(compat_string = "xlnx,zynqmp")] +pub struct ZynqMPPlatform { + // Platform-specific fields +} +``` + +This macro automatically implements the necessary registration code for the platform, making it discoverable by the FPGAd daemon at runtime. + +## License + +GPL-3.0 + From 328d4cd7602d2a45a21155771e0e3f690c2333c9 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Tue, 31 Mar 2026 11:25:48 +0100 Subject: [PATCH 12/13] docs: universal - re-add/fix readme file include Signed-off-by: Artie Poole --- daemon/src/platforms/universal_components.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daemon/src/platforms/universal_components.rs b/daemon/src/platforms/universal_components.rs index 051a1955..c49df111 100644 --- a/daemon/src/platforms/universal_components.rs +++ b/daemon/src/platforms/universal_components.rs @@ -9,5 +9,8 @@ // fpgad is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. + +#![doc = include_str!("universal_components/README.md")] + pub mod universal_fpga; pub mod universal_overlay_handler; From efd9b2bfe6ce0a425ebf2a53fcbebc1753be9f66 Mon Sep 17 00:00:00 2001 From: Artie Poole Date: Tue, 31 Mar 2026 11:26:28 +0100 Subject: [PATCH 13/13] docs: address linter issues - un-escapred angle brackets (treated as invalid HTML) - bad link to FPGA_MANAGERS_DIR Signed-off-by: Artie Poole --- cli/src/status.rs | 8 ++++---- daemon/src/comm/dbus/control_interface.rs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cli/src/status.rs b/cli/src/status.rs index d11ad0b9..e361c26d 100644 --- a/cli/src/status.rs +++ b/cli/src/status.rs @@ -34,13 +34,13 @@ use crate::proxies::status_proxy; use std::collections::HashMap; use zbus::Connection; -/// Parses a newline-separated string of overlays into a Vec +/// Parses a newline-separated string of overlays into a `Vec` /// /// # Arguments /// * `list_str` - The string containing overlay names separated by newlines. /// -/// # Returns: Vec -/// A Vec where each element is an overlay name from the input string. +/// # Returns: `Vec` +/// A `Vec` where each element is an overlay name from the input string. /// /// # Examples /// ```rust,no_run @@ -58,7 +58,7 @@ fn parse_overlay_lines(list_str: &str) -> Vec { /// # Arguments /// * `ret_str` - The string containing device-platform pairs separated by newlines. /// -/// # Returns: HashMap +/// # Returns: `HashMap` /// A HashMap where the key is the device and the value is the platform string. /// /// # Examples diff --git a/daemon/src/comm/dbus/control_interface.rs b/daemon/src/comm/dbus/control_interface.rs index 0cf5fb3d..c396c413 100644 --- a/daemon/src/comm/dbus/control_interface.rs +++ b/daemon/src/comm/dbus/control_interface.rs @@ -311,7 +311,7 @@ impl ControlInterface { /// /// # Arguments /// - /// * `property_path_str`: Full path under [FPGA_MANAGERS_DIR]. + /// * `property_path_str`: Full path under [crate::config::FPGA_MANAGERS_DIR]. /// * `data`: String data to write. /// /// # Returns: `Result` @@ -321,7 +321,7 @@ impl ControlInterface { /// other reason /// **Notes:** /// - /// * Path must be under [FPGA_MANAGERS_DIR] - determined at compile time. + /// * Path must be under [crate::config::FPGA_MANAGERS_DIR] - determined at compile time. /// /// # Examples /// @@ -349,7 +349,7 @@ impl ControlInterface { /// /// # Arguments /// - /// * `property_path_str`: Full path under [FPGA_MANAGERS_DIR]. + /// * `property_path_str`: Full path under [crate::config::FPGA_MANAGERS_DIR]. /// * `data`: Byte array to write. /// /// # Returns: `Result` @@ -360,7 +360,7 @@ impl ControlInterface { /// /// **Notes:** /// - /// * Path must be under [FPGA_MANAGERS_DIR] - determined at compile time. + /// * Path must be under [crate::config::FPGA_MANAGERS_DIR] - determined at compile time. /// /// # Examples ///