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
1 change: 1 addition & 0 deletions .git_components.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ tests:
snapcraft:
owners:
- artiepoole
- talhaHavadar
13 changes: 10 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ resolver = "3"
members = ["daemon", "cli", "fpgad_macros"]

[workspace.package]
version = "0.1.0"
version = "0.2.0"
edition = "2024"
license = "GPL-3.0"
homepage = "https://github.com/canonical/fpgad"
repository = "https://github.com/canonical/fpgad"
authors = ["Talha Can Havadar <talha.can.havadar@canonical.com>", "Artie Poole <stuart.poole@canonical.com>"]

[workspace.dependencies]
fpgad_macros = { version = "0.1.0", path = "fpgad_macros" }
fpgad_macros = { version = "0.2.0", path = "fpgad_macros" }

15 changes: 12 additions & 3 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,8 @@ use std::error::Error;
///
/// # Examples
///
/// ```bash
/// ```shell
///
/// # Load a bitstream
/// fpgad load bitstream /lib/firmware/design.bit.bin
///
Expand All @@ -191,6 +192,7 @@ use std::error::Error;
///
/// # Load an overlay with a specific handle
/// fpgad load overlay /lib/firmware/overlay.dtbo --handle=my_overlay
///
/// ```
#[derive(Parser, Debug)]
#[command(name = "fpga")]
Expand Down Expand Up @@ -252,7 +254,14 @@ enum RemoveSubcommand {
handle: Option<String>,
},
/// Remove bitstream loaded in given `HANDLE` to fpga command
Bitstream,
Bitstream {
/// `HANDLE` is the handle that is given during `load` operation
/// TODO(Artie): document - for dfxmgr it will be the slot - use "" to allow remove latest
/// it is different than device_handle which is being used for platform
/// detection logic.
#[arg(long = "handle")]
handle: Option<String>,
},
}

/// Top-level commands supported by the CLI.
Expand Down Expand Up @@ -320,7 +329,7 @@ enum Commands {
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();
let cli = Cli::parse();
debug!("parsed cli command with {cli:?}");
debug!("parsed cli command with {cli:#?}");
let result = match cli.command {
Commands::Load { command } => load_handler(&cli.handle, &command).await,
Commands::Remove { command } => remove_handler(&cli.handle, &command).await,
Expand Down
23 changes: 23 additions & 0 deletions cli/src/proxies/control_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,4 +165,27 @@ pub trait Control {
/// * `Err(zbus::Error)` - DBus error or FpgadError.
/// See [Error Handling](../../index.html#error-handling)
async fn write_property_bytes(&self, property_path_str: &str, data: &[u8]) -> Result<String>;

/// Remove a previously loaded bitstream, identifiable by its `bitstream_handle` or `slot`.
///
/// # Arguments
///
/// * `platform_string`: Platform compatibility string.
/// * `overlay_handle`: Handle of the overlay to remove.
///
/// # Returns: `Result<String, Error>`
/// * `Ok(String)` – Confirmation message including overlay filesystem path.
/// * `Err(fdo::Error)` if overlay or platform cannot be accessed.
///
/// # Examples
///
/// ```
/// assert!(remove_bitstream("xlnx,zynqmp-pcap-fpga", "").is_ok());
/// ```
async fn remove_bitstream(
&self,
platform_string: &str,
device_handle: &str,
bitstream_handle: &str,
) -> Result<String>;
}
2 changes: 2 additions & 0 deletions cli/src/proxies/status_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ use zbus::{Result, proxy};
default_path = "/com/canonical/fpgad/status"
)]
pub trait Status {
async fn get_status_message(&self, platform_string: &str) -> Result<String>;

/// Get the current state of an FPGA device.
///
/// Returns the device state such as "operating", "unknown", "write init", "write",
Expand Down
68 changes: 52 additions & 16 deletions cli/src/remove.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,34 @@

use crate::RemoveSubcommand;
use crate::proxies::control_proxy;
use crate::status::{call_get_platform_type, get_first_overlay, get_first_platform};
use crate::status::{
call_get_platform_type, get_first_device_handle, get_first_overlay, get_first_platform,
};
use zbus::Connection;

/// Removes a bitstream from an FPGA device.
/// Sends the DBus command to remove a bitstream.
///
/// # Note
/// Communicates with the fpgad daemon via DBus to remove a previously loaded
/// bitstream from the system.
///
/// This functionality is currently not implemented as bitstream removal is
/// vendor-specific and depends on platform capabilities that may be added
/// through softener implementations in the future.
/// # Arguments
///
/// * `device_handle` - Platform identifier for the [device](../index.html#device-handles)
/// * `bitstream_handle` - the identifier of the bitstream (can be slot ID for dfx-mgr) TODO(Artie): update docs
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Err(zbus::Error)` - Always returns "Not implemented" error
async fn remove_bitstream() -> Result<String, zbus::Error> {
// TODO: so this is confusing because we don't have a way to remove a bitstream but with
// softeners we might end up with this functionality.
Err(zbus::Error::Failure("Not implemented".to_string()))
/// * `Ok(String)` - Success message from the daemon
/// * `Err(zbus::Error)` - DBus communication error, invalid handle(s), or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn call_remove_bitstream(
device_handle: &str,
bitstream_handle: &str,
) -> Result<String, zbus::Error> {
let connection = Connection::system().await?;
let proxy = control_proxy::ControlProxy::new(&connection).await?;
proxy
.remove_bitstream("", device_handle, bitstream_handle)
.await
}

/// Sends the DBus command to remove a device tree overlay.
Expand All @@ -64,12 +75,12 @@ async fn remove_bitstream() -> Result<String, zbus::Error> {
/// * `Err(zbus::Error)` - DBus communication error, invalid handle(s), or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
async fn call_remove_overlay(
device_handle: &str,
platform_string: &str,
overlay_handle: &str,
) -> Result<String, zbus::Error> {
let connection = Connection::system().await?;
let proxy = control_proxy::ControlProxy::new(&connection).await?;
proxy.remove_overlay(device_handle, overlay_handle).await
proxy.remove_overlay(platform_string, overlay_handle).await
}

/// Removes a device tree overlay with automatic platform and handle detection.
Expand All @@ -93,15 +104,40 @@ async fn remove_overlay(
device_handle: &Option<String>,
overlay_handle: &Option<String>,
) -> Result<String, zbus::Error> {
let dev = match device_handle {
let platform_string = match device_handle {
None => get_first_platform().await?,
Some(dev) => call_get_platform_type(dev).await?,
};
let handle = match overlay_handle {
Some(handle) => handle.clone(),
None => get_first_overlay().await?,
};
call_remove_overlay(&dev, &handle).await
call_remove_overlay(&platform_string, &handle).await
}

/// Removes a bitstream from an FPGA device.
///
/// # Note
///
/// This functionality is currently not implemented as bitstream removal is
/// vendor-specific and depends on platform capabilities that may be added
/// through softener implementations in the future.
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Err(zbus::Error)` - Always returns "Not implemented" error
async fn remove_bitstream(
device_handle: &Option<String>,
bitstream_handle: &Option<String>,
) -> Result<String, zbus::Error> {
let dev = match device_handle {
None => &get_first_device_handle().await?,
Some(dev) => dev,
};
let handle = match bitstream_handle {
Some(handle) => handle,
None => "",
};
call_remove_bitstream(dev, handle).await
}

/// Main handler for the remove command.
Expand All @@ -125,6 +161,6 @@ pub async fn remove_handler(
) -> Result<String, zbus::Error> {
match sub_command {
RemoveSubcommand::Overlay { handle } => remove_overlay(dev_handle, handle).await,
RemoveSubcommand::Bitstream => remove_bitstream().await,
RemoveSubcommand::Bitstream { handle } => remove_bitstream(dev_handle, handle).await,
}
}
92 changes: 25 additions & 67 deletions cli/src/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ use crate::proxies::status_proxy;
use std::collections::HashMap;
use zbus::Connection;

/// Retrieve a platform-specific status message from the daemon.
///
/// Sends the DBus command to get a comprehensive status message for the specified
/// platform, which includes device and overlay information formatted for that platform.
///
/// # Arguments
///
/// * `platform` - Platform compatibility string (e.g., "xlnx,zynqmp-pcap-fpga", "universal")
///
/// # Returns: `Result<String, zbus::Error>`
/// * `Ok(String)` - Formatted status message from the platform
/// * `Err(zbus::Error)` - DBus communication error or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
pub async fn call_get_status_message(platform: &str) -> Result<String, zbus::Error> {
let connection = Connection::system().await?;
let proxy = status_proxy::StatusProxy::new(&connection).await?;
proxy.get_status_message(platform).await
}

/// Parses a newline-separated string of overlays into a Vec<String>
///
/// # Arguments
Expand Down Expand Up @@ -215,35 +234,6 @@ pub async fn call_get_platform_type(device_handle: &str) -> Result<String, zbus:
proxy.get_platform_type(device_handle).await
}

/// Retrieve the status of a specific device tree overlay.
///
/// Queries the daemon for the status information of a loaded overlay.
///
/// # Arguments
///
/// * `platform` - Platform identifier string
/// * `overlay_handle` - [Overlay handle](../index.html#overlay-handles) of the overlay to query
///
/// # Returns: `Result<String, zbus::Error>`
/// * `String` - Status information for the overlay
/// * `zbus::Error` - DBus communication error, invalid overlay handle, or FpgadError.
/// See [Error Handling](../index.html#error-handling) for details.
///
/// # Examples
///
/// ```rust,ignore
/// let status = call_get_overlay_status("universal", "overlay0").await?;
/// println!("Overlay status: {}", status);
/// ```
async fn call_get_overlay_status(
platform: &str,
overlay_handle: &str,
) -> Result<String, zbus::Error> {
let connection = Connection::system().await?;
let proxy = status_proxy::StatusProxy::new(&connection).await?;
proxy.get_overlay_status(platform, overlay_handle).await
}

/// Retrieve all FPGA devices and their platform compatibility strings.
///
/// Parses the newline-separated string from the `get_platform_types` DBus interface
Expand Down Expand Up @@ -357,13 +347,7 @@ pub async fn get_first_device_handle() -> Result<String, zbus::Error> {
/// println!("{}", message);
/// ```
async fn get_fpga_state_message(device_handle: &str) -> Result<String, zbus::Error> {
let state = call_get_fpga_state(device_handle).await?;
let platform = call_get_platform_type(device_handle).await?;
Ok(format!(
"---- DEVICE ----\n\
| dev | platform | state |\n\
{device_handle} | {platform} | {state}"
))
call_get_fpga_state(device_handle).await
}

/// Format comprehensive status information for all FPGA devices and overlays.
Expand All @@ -387,38 +371,12 @@ async fn get_fpga_state_message(device_handle: &str) -> Result<String, zbus::Err
/// println!("{}", status);
/// ```
async fn get_full_status_message() -> Result<String, zbus::Error> {
let mut ret_string = String::from(
"---- DEVICES ----\n\
| dev | platform | state |\n",
);

for (dev, platform) in call_get_platform_types().await? {
let state = call_get_fpga_state(&dev).await?;
ret_string += format!("| {dev} | {platform} | {state} |\n").as_str();
}

// If overlayfs not enabled, or interface not connected this will be an error.
let overlays = match call_get_overlays().await {
Ok(overlays) => {
ret_string += "\n---- OVERLAYS ----\n\
| overlay | status |\n";
overlays
}
Err(e) => {
ret_string += "\n---- OVERLAYS NOT ACCESSIBLE ----\n\n\
errors encountered:\n";
ret_string += e.to_string().as_str();
Vec::new()
}
};
let mut ret_string = String::from("\n");

for overlay in overlays {
// TODO: overlays do not provide enough information to work out what platform to use.
// so maybe the status command can take a platform type instead or something.
// This is tricky.
let p = get_first_platform().await?;
let status = call_get_overlay_status(&p, &overlay).await?;
ret_string.push_str(format!("| {overlay} | {status} |\n").as_ref());
for (device, platform) in call_get_platform_types().await? {
ret_string += format!("----- device: {device} | platform: {platform} -----\n").as_str();
ret_string += call_get_status_message(&platform).await?.as_str();
ret_string += "\n";
}
Ok(ret_string)
}
Expand Down
3 changes: 2 additions & 1 deletion daemon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ env_logger = "0.11.8"
tokio = { version = "1.47.0", features = ["full"] }
zbus = { version = "5.9.0", default-features = false, features = ["tokio"] }
thiserror = "2.0.12"
fdt = "0.1.5"

[dev-dependencies]
googletest = "0.14.2"
rstest = "0.26.1"
rstest = "0.26.1"
Loading