Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 118 additions & 12 deletions cli/src/load.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,24 @@
//
// You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

//! Load command implementation for the FPGA CLI.
//!
//! This module handles the loading of FPGA bitstreams and device tree overlays through
//! the fpgad daemon's DBus interface. It provides functionality to:
//! - Load FPGA bitstreams onto FPGA devices
//! - Apply device tree overlays
//! - Automatically detect and use default FPGA devices when not specified
//!
//! The module communicates with the fpgad daemon via DBus to perform these privileged
//! operations on the FPGA subsystem.
//!
//! For information on [Device Handles], [Overlay Handles], and [Error Handling],
//! see the [Common Concepts](../index.html#common-concepts) section in the main CLI documentation.
//!
//! [Device Handles]: ../index.html#device-handles
//! [Overlay Handles]: ../index.html#overlay-handles
//! [Error Handling]: ../index.html#error-handling

use crate::LoadSubcommand;
use crate::proxies::control_proxy;
use crate::status::{
Expand All @@ -18,13 +36,41 @@ use crate::status::{
use std::path;
use zbus::Connection;

fn sanitize_path_str(in_str: &str) -> String {
path::absolute(in_str)
.expect("Failed to resolve path")
.to_string_lossy()
.to_string()
/// Sanitizes and converts a file path string to an absolute path.
///
/// # Arguments
///
/// * `in_str` - The input path string to sanitize
///
/// # Returns: `Result<String, zbus::Error>`
/// * `String` - Absolute path string resolved from the input
/// * `zbus::Error::Failure` - If the path cannot be resolved to an absolute path
fn sanitize_path_str(in_str: &str) -> Result<String, zbus::Error> {
match path::absolute(in_str) {
Ok(absolute_path) => Ok(absolute_path.to_string_lossy().to_string()),
Err(e) => Err(zbus::Error::Failure(format!(
"Failed to resolve path '{}': {}",
in_str, e
))),
}
}
/// Sends the dbus command to load a bitstream

/// Sends the DBus command to load a bitstream onto an FPGA device.
///
/// Communicates with the fpgad daemon via DBus to write a bitstream file to the
/// specified FPGA device. The bitstream configures the FPGA's logic fabric.
///
/// # Arguments
///
/// * `platform_str` - Platform identifier (empty string for auto-detection)
/// * `device_handle` - The [device handle](../index.html#device-handles) (e.g., "fpga0")
/// * `file_path` - Absolute path to the bitstream file
/// * `firmware_lookup_path` - Optional firmware lookup path (empty string for default)
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Success message from the daemon
/// * `Err(zbus::Error)` - DBus communication error or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn call_load_bitstream(
platform_str: &str,
device_handle: &str,
Expand All @@ -38,7 +84,22 @@ async fn call_load_bitstream(
.await
}

/// Sends the dbus command to apply an overlay
/// Sends the DBus command to apply a device tree overlay.
///
/// Communicates with the fpgad daemon via DBus to load a device tree overlay file.
/// Overlays describe hardware configuration and interfaces for the FPGA peripherals.
///
/// # Arguments
///
/// * `platform` - Platform identifier string
/// * `file_path` - Absolute path to the overlay file (.dtbo)
/// * `overlay_handle` - [Overlay handle](../index.html#overlay-handles) for the overlay directory in configfs
/// * `firmware_lookup_path` - Optional firmware lookup path (empty string for default)
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Success message from the daemon
/// * `Err(zbus::Error)` - DBus communication error or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn call_apply_overlay(
platform: &str,
file_path: &str,
Expand All @@ -52,7 +113,25 @@ async fn call_apply_overlay(
.await
}

/// Populates the platform and overlay handle appropriately before calling `call_apply_overlay`
/// Applies a device tree overlay with automatic platform and handle detection.
///
/// This function handles the logic for determining the appropriate platform and overlay
/// handle based on what the user has provided. It supports four scenarios:
/// 1. Both device and overlay handles provided - use both as-is
/// 2. Only device handle provided - use device name as overlay handle
/// 3. Only overlay handle provided - auto-detect first platform
/// 4. Neither provided - auto-detect both from first available device
///
/// # Arguments
///
/// * `dev_handle` - Optional [device handle](../index.html#device-handles) (e.g., "fpga0")
/// * `file_path` - Path to the overlay file (.dtbo)
/// * `overlay_handle` - Optional [overlay handle](../index.html#overlay-handles) for the overlay directory
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Success message from the daemon
/// * `Err(zbus::Error)` - DBus communication error, device detection failure, or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn apply_overlay(
dev_handle: &Option<String>,
file_path: &str,
Expand Down Expand Up @@ -93,14 +172,27 @@ async fn apply_overlay(
};
call_apply_overlay(
&platform,
sanitize_path_str(file_path).as_str(),
&sanitize_path_str(file_path)?,
&overlay_handle_to_use,
"",
)
.await
}

/// Populates the device_handle appropriately before calling `call_load_bitstream`
/// Loads a bitstream onto an FPGA device with automatic device detection.
///
/// If no device handle is provided, this function automatically detects and uses
/// the first available FPGA device in the system.
///
/// # Arguments
///
/// * `device_handle` - Optional [device handle](../index.html#device-handles) (e.g., "fpga0")
/// * `file_path` - Path to the bitstream file
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Success message from the daemon
/// * `Err(zbus::Error)` - DBus communication error, device detection failure, or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn load_bitstream(
device_handle: &Option<String>,
file_path: &str,
Expand All @@ -109,10 +201,24 @@ async fn load_bitstream(
None => get_first_device_handle().await?,
Some(dev) => dev.clone(),
};
call_load_bitstream("", &dev, sanitize_path_str(file_path).as_str(), "").await
call_load_bitstream("", &dev, &sanitize_path_str(file_path)?, "").await
}

/// Argument parser for the load command
/// Main handler for the load command.
///
/// Dispatches to the appropriate load function based on the subcommand type
/// (overlay or bitstream). This is the entry point called by the CLI's main
/// function when a load command is issued.
///
/// # Arguments
///
/// * `dev_handle` - Optional [device handle](../index.html#device-handles)
/// * `sub_command` - The load subcommand specifying what to load (overlay or bitstream)
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Success message from the operation
/// * `Err(zbus::Error)` - DBus communication error, operation failure, or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
pub async fn load_handler(
dev_handle: &Option<String>,
sub_command: &LoadSubcommand,
Expand Down
126 changes: 126 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,54 @@

//! This is FPGAd's commandline interface (CLI) . Due to strict confinement of the snap, this can only be used from a terminal or from a script which is not part of another snap. It is a useful helper for one-off control of the FPGA device or testing, and serves as an example implementation for the DBus interface.
//!
//! # Common Concepts
//!
//! The following concepts are shared across all CLI submodules ([`load`], [`remove`], [`set`], [`status`]).
//!
//! ## Device Handles
//! [Device Handles]: #device-handles
//!
//! A "device handle" refers to the name of an FPGA device as it appears in
//! `/sys/class/fpga_manager/`. Common examples include:
//! - `fpga0` - The first FPGA device
//! - `fpga1` - The second FPGA device (if multiple FPGAs are present)
//!
//! These handles uniquely identify FPGA devices in the system and are used throughout
//! the CLI to specify which device to operate on.
//!
//! ## Overlay Handles
//! [Overlay Handles]: #overlay-handles
//!
//! An "overlay handle" refers to the name of a device tree overlay as it appears in
//! `/sys/kernel/config/device-tree/overlays/`. Common examples include:
//! - `overlay0` - A generic overlay name
//! - `fpga-design` - A custom overlay name specified during loading
//!
//! These handles are used to identify and manage loaded device tree overlays. When loading
//! an overlay, you can specify a custom handle or let the system choose one based on the
//! device handle.
//!
//! ## Error Handling
//! [Error Handling]: #error-handling
//!
//! All CLI functions communicate with the fpgad daemon via DBus and return
//! `Result<String, zbus::Error>` (or variants with `Vec<String>` or `HashMap<String, String>`).
//!
//! When the fpgad daemon returns an application-level error (not a DBus communication
//! error), the error will be of type `zbus::Error::Failure` and the error message will
//! begin with `FpgadError::<variant>:` followed by the error details. For example:
//! ```text
//! FpgadError::Argument: Device fpga0 not found.
//! FpgadError::IOWrite: Failed to write bitstream: Permission denied
//! FpgadError::IORead: Failed to read state: No such file or directory
//! ```
//!
//! This allows callers to distinguish between:
//! - **DBus communication errors** - Problems connecting to or communicating with the daemon
//! - **Application errors** - Errors from the daemon itself (prefixed with `FpgadError::`)
//!
//! # Usage
//!
//! ```text
//!Usage: [snap run] fpgad [OPTIONS] <COMMAND>
//!
Expand Down Expand Up @@ -122,6 +170,25 @@ use clap::{Parser, Subcommand};
use log::{debug, error};
use std::error::Error;

/// Command-line interface structure for FPGA management operations.
///
/// This structure represents the top-level CLI interface for interacting with FPGA devices
/// through the fpgad daemon's DBus interface. It provides a unified interface for loading
/// bitstreams and overlays, querying device status, setting attributes, and removing
/// loaded components.
///
/// # Examples
///
/// ```bash
/// # Load a bitstream
/// fpgad load bitstream /lib/firmware/design.bit.bin
///
/// # Check status of all FPGA devices
/// fpgad status
///
/// # Load an overlay with a specific handle
/// fpgad load overlay /lib/firmware/overlay.dtbo --handle=my_overlay
/// ```
#[derive(Parser, Debug)]
#[command(name = "fpga")]
#[command(bin_name = "fpga")]
Expand All @@ -135,6 +202,14 @@ struct Cli {
command: Commands,
}

/// Subcommands for loading FPGA components.
///
/// This enum defines the types of components that can be loaded onto an FPGA device:
/// - **Overlay**: Device tree overlays (.dtbo files) that describe hardware configuration
/// - **Bitstream**: FPGA configuration bitstreams (.bit.bin files) containing the actual FPGA design
///
/// Device tree overlays are typically loaded before or after bitstreams to properly configure
/// the kernel's view of the FPGA's hardware interfaces and peripherals.
#[derive(Subcommand, Debug)]
enum LoadSubcommand {
/// Load overlay into the system
Expand All @@ -154,6 +229,14 @@ enum LoadSubcommand {
},
}

/// Subcommands for removing FPGA components.
///
/// This enum defines the types of components that can be removed from an FPGA device:
/// - **Overlay**: Removes a device tree overlay by its handle - no implementation currently available
/// - **Bitstream**: Removes the currently loaded FPGA bitstream (vendor-specific operation)
///
/// Removing overlays is important for proper cleanup when reconfiguring the FPGA.
/// Bitstream removal support depends on the FPGA vendor and platform capabilities.
#[derive(Subcommand, Debug)]
enum RemoveSubcommand {
/// Remove overlay with the `HANDLE` provided
Expand All @@ -168,6 +251,16 @@ enum RemoveSubcommand {
Bitstream,
}

/// Top-level commands supported by the CLI.
///
/// This enum represents all the primary operations available through the fpgad CLI:
/// - **Load**: Load bitstreams or device tree overlays onto the FPGA
/// - **Set**: Configure FPGA attributes and flags (e.g., programming flags)
/// - **Status**: Query the current state of FPGA devices and loaded overlays
/// - **Remove**: Unload bitstreams or device tree overlays from the FPGA
///
/// Each command communicates with the fpgad daemon via DBus to perform privileged
/// operations on FPGA devices managed through the Linux kernel's FPGA subsystem.
#[derive(Subcommand, Debug)]
enum Commands {
/// Load a bitstream or an overlay for the given device handle
Expand All @@ -186,6 +279,39 @@ enum Commands {
},
}

/// Main entry point for the FPGA CLI application.
///
/// Initializes the environment logger, parses command-line arguments, and dispatches
/// to the appropriate handler based on the specified command. All operations are
/// performed asynchronously using tokio.
///
/// # Flow
///
/// 1. Initializes logging via `env_logger`
/// 2. Parses CLI arguments into the `Cli` structure
/// 3. Dispatches to the appropriate handler:
/// - `load_handler` for loading bitstreams/overlays
/// - `remove_handler` for removing bitstreams/overlays
/// - `set_handler` for setting FPGA attributes
/// - `status_handler` for querying device status
/// 4. Prints success messages or logs errors
///
/// # Returns
///
/// * `Ok(())` - Command executed successfully
/// * `Err(Box<dyn Error>)` - An error occurred during command execution
///
/// # Examples
///
/// The application is typically invoked from the command line:
///
/// ```bash
/// # Load a bitstream
/// fpgad load bitstream /lib/firmware/design.bit.bin
///
/// # Query status
/// fpgad status
/// ```
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
Expand Down
Loading
Loading