Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/integration_tests.yaml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
echo "test_path=job.yaml" >> $GITHUB_OUTPUT
- name: Submit TF job
id: submit
uses: canonical/testflinger/.github/actions/submit@main
uses: canonical/testflinger/.github/actions/submit@rev263
continue-on-error: true
with:
poll: true
Expand Down
97 changes: 97 additions & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,103 @@
//
// You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

//! 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.
//!
//! ```text
//! Usage: [snap run] fpgad [OPTIONS] <COMMAND>
//!
//! OPTIONS:
//! -h, --help Print help
//! --handle <DEVICE_HANDLE> fpga device `HANDLE` to be used for the operations.
//! Default value for this option is calculated in runtime
//! and the application picks the first available fpga device
//! in the system (under `/sys/class/fpga_manager/`)
//!
//! COMMANDS:
//! ├── load Load a bitstream or overlay
//! │ ├── overlay <FILE> [--handle <OVERLAY_HANDLE>]
//! │ │ Load overlay (.dtbo) into the system using the default OVERLAY_HANDLE
//! │ │ (either the provided DEVICE_HANDLE or "overlay0") or provide
//! │ │ --handle: to name the overlay directory
//! │ └── bitstream <FILE>
//! │ Load bitstream (e.g. `.bit.bin` file) into the FPGA
//! │
//! ├── set <ATTRIBUTE> <VALUE>
//! │ Set an attribute/flag under `/sys/class/fpga_manager/<DEVICE_HANDLE>/<ATTRIBUTE>`
//! │
//! ├── status [--handle <DEVICE_HANDLE>]
//! │ Show FPGA status (all devices and overlays) or provide
//! │ --handle: for a specific device status
//! │
//! └── remove Remove an overlay or bitstream
//! ├── overlay [--handle <OVERLAY_HANDLE>]
//! │ Removes the first overlay found (call repeatedly to remove all) or provide
//! │ --handle: to remove overlay previously loaded with given OVERLAY_HANDLE
//! └── bitstream
//! Remove active bitstream from FPGA (bitstream removal is vendor specific)
//! ```
//!
//! ### Loading
//!
//! ```shell
//! fpgad [--handle=<device_handle>] load ( (overlay <file> [--handle=<handle>]) | (bitstream <file>) )
//! ```
//!
//! ### Removing
//!
//! ```shell
//! fpgad [--handle=<device_handle>] remove ( ( overlay <HANDLE> ) | ( bitstream ) )
//! ```
//!
//! ### Set
//!
//! ```shell
//! fpgad [--handle=<device_handle>] set ATTRIBUTE VALUE
//! ```
//!
//! ### Status
//!
//! ```shell
//! fpgad [--handle=<device_handle>] status
//! ```
//!
//! ## examples (for testing)
//!
//! ### Load
//!
//! ```shell
//! sudo ./target/debug/cli load bitstream /lib/firmware/k26-starter-kits.bit.bin
//! sudo ./target/debug/cli --handle=fpga0 load bitstream /lib/firmware/k26-starter-kits.bit.bin
//!
//! sudo ./target/debug/cli load overlay /lib/firmware/k26-starter-kits.dtbo
//! sudo ./target/debug/cli load overlay /lib/firmware/k26-starter-kits.dtbo --handle=overlay_handle
//! sudo ./target/debug/cli --handle=fpga0 load overlay /lib/firmware/k26-starter-kits.dtbo --handle=overlay_handle
//! ```
//!
//! ### Remove
//!
//! ```shell
//! sudo ./target/debug/cli --handle=fpga0 remove overlay
//! sudo ./target/debug/cli --handle=fpga0 remove overlay --handle=overlay_handle
//! ```
//!
//! ### Set
//!
//! ```shell
//! sudo ./target/debug/cli set flags 0
//! sudo ./target/debug/cli --handle=fpga0 set flags 0
//! ```
//!
//! ### Status
//!
//! ```shell
//! ./target/debug/cli status
//! ./target/debug/cli --handle=fpga0 status
//! ```

mod proxies;

mod load;
Expand Down
4 changes: 2 additions & 2 deletions daemon/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ To get the state of an FPGA device:
busctl call --system com.canonical.fpgad /com/canonical/fpgad/status com.canonical.fpgad.status GetFpgaState ss "" "fpga0"
```

To get the currently set flags for an FPGA device in hex with `0x` prefix:
To get the currently set flags for an FPGA device in hex (but missing the `0x` prefix):

```shell
busctl call --system com.canonical.fpgad /com/canonical/fpgad/status com.canonical.fpgad.status GetFpgaFlags ss "" "fpga0"
Expand Down Expand Up @@ -106,7 +106,7 @@ sudo busctl call --system com.canonical.fpgad /com/canonical/fpgad/control com.c

#### write a bitstream

Using automated platform detectoin and default `fw_search_path` generation:
Using automated platform detection and default `fw_search_path` generation:

```shell
sudo busctl call --system com.canonical.fpgad /com/canonical/fpgad/control com.canonical.fpgad.control WriteBitstreamDirect ssss "" "fpga0" "/lib/firmware/k26-starter-kits.bit.bin" ""
Expand Down
139 changes: 133 additions & 6 deletions daemon/src/comm/dbus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,48 @@
//
// You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

//!
//! # FPGAd dbus interfaces
//!
//! There are two interfaces available: `status`, which contains methods to access read-only properties of the FPGA and overlay subsystems, and `control` which contains methods to access writable properties of the FPGA and overlay subsystems.
//! Directly following this paragraph is a summary of all available methods, with the rest of this document explaining each method in detail.
//! Please note that these interfaces are used in the `cli` part of FPGAd (e.g. in [load.rs](../../../../cli/src/load.rs)), and so those implementations can be used as examples with greater context than those provided here.
//! There is further usage documentation, including the use of these interfaces from cmdline by way of `dbusctl`, in [daemon/README.md](../../../README.md).
//!
//!
//! For both interfaces, all methods are asynchronous and should be awaited immediately to avoid async pollution.
//!
//! ## Custom Errors
//!
//! All functions are shown to return the `fdo::Error` type, which is a wrapper for "FreeDesktopOrg", i.e. these are DBus error types. In the case that FPGAd returns an error, and it is not a DBus communication error, this error will be of type `fdo::Error:Failure` and the error string with begin with the `FpgadError` identifier such as `"FpgadError::Argument:"` i.e. the full error will be reported (from `busctl`) as
//! ```text
//! Call failed: FpgadError::Argument: <error text>
//! ```
//! or in the case of an IOError, the rust error will appear like:
//! ```text
//! [2026-01-08T16:29:05Z ERROR cli] org.freedesktop.DBus.Error.IOError: FpgadError::IOWrite: <fpgad's error text>: No such file or directory (os error 2)
//! Error: MethodError(OwnedErrorName("org.freedesktop.DBus.Error.IOError"), Some("FpgadError::IOWrite: <fpgad's error text>: No such file or directory (os error 2)"), Msg { type: Error, serial: 11, sender: UniqueName(":1.77"), reply-serial: 4, body: Str, fds: [] })
//! ```
//! whereby the `FpgadError` type could be mapped to an equivalent `fdo::Error` type.
//!
//! ## FPGA API Summary
//!
//! | Interface | Method | Parameters | Returns / Notes |
//! |-----------|--------------------------|----------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
//! | [status](status_interface::StatusInterface) | [`get_fpga_state`](status_interface::StatusInterface::get_fpga_state) | `platform_string: &str`, `device_handle: &str` | `String` – Current FPGA state (`unknown`, `operating`, etc.) |
//! | [status](status_interface::StatusInterface) | [`get_fpga_flags`](status_interface::StatusInterface::get_fpga_flags) | `platform_string: &str`, `device_handle: &str` | `String` – Current FPGA flags from hexadecimal integer to string |
//! | [status](status_interface::StatusInterface) | [`get_overlay_status`](status_interface::StatusInterface::get_overlay_status) | `platform_string: &str`, `overlay_handle: &str` | `String` – Overlay status; errors if handle empty or invalid |
//! | [status](status_interface::StatusInterface) | [`get_overlays`](status_interface::StatusInterface::get_overlays) | None | `String` – List of available overlay handles (`\n` separated) |
//! | [status](status_interface::StatusInterface) | [`get_platform_type`](status_interface::StatusInterface::get_platform_type) | `device_handle: &str` | `String` – Compatibility string for device |
//! | [status](status_interface::StatusInterface) | [`get_platform_types`](status_interface::StatusInterface::get_platform_types) | None | `String` – List of all fpga devices and their compatibility strings (`<device>:<compat>\n`) |
//! | [status](status_interface::StatusInterface) | [`read_property`](status_interface::StatusInterface::read_property) | `property_path_str: &str` | `String` – Contents of arbitrary FPGA attribute value |
//! | [control](control_interface::ControlInterface) | [`set_fpga_flags`](control_interface::ControlInterface::set_fpga_flags) | `platform_string: &str`, `device_handle: &str`, `flags: u32` | `String` – Confirmation with new flags in hex |
//! | [control](control_interface::ControlInterface) | [`write_bitstream_direct`](control_interface::ControlInterface::write_bitstream_direct) | `platform_string: &str`, `device_handle: &str`, `bitstream_path_str: &str`, `firmware_lookup_path: &str` | `String` – Confirmation of bitstream load; acquires write lock |
//! | [control](control_interface::ControlInterface) | [`apply_overlay`](control_interface::ControlInterface::apply_overlay) | `platform_string: &str`, `overlay_handle: &str`, `overlay_source_path: &str`, `firmware_lookup_path: &str` | `String` – Overlay applied; confirmation including firmware prefix; write lock used to protect against firmware search path race conditions |
//! | [control](control_interface::ControlInterface) | [`remove_overlay`](control_interface::ControlInterface::remove_overlay) | `platform_string: &str`, `overlay_handle: &str` | `String` – Overlay removed; confirmation including overlay filesystem path |
//! | [control](control_interface::ControlInterface) | [`write_property`](control_interface::ControlInterface::write_property) | `property_path_str: &str`, `data: &str` | `String` – Confirmation of data written; path must be under `/sys/class/fpga_manager/` |
//! | [control](control_interface::ControlInterface) | [`write_property_bytes`](control_interface::ControlInterface::write_property_bytes) | `property_path_str: &str`, `data: &[u8]` | `String` – Confirmation of bytes written; path must be under `/sys/class/fpga_manager/` |

pub mod control_interface;
pub mod status_interface;

Expand All @@ -19,6 +61,23 @@ use crate::system_io::{fs_read, fs_write};
use log::trace;
use std::path::{Component, Path, PathBuf};

/// Read the current contents of an FPGA device property, e.g. "name". The property path must be a subdirectory of the fpga manager directory (typically, /sys/class/fpga_manager/)
///
/// # Arguments
///
/// * `property_path_str`: path to the variable to read e.g. /sys/class/fpga_manager/fpga0/name
///
/// # Returns: `Result<String, FpgadError>`
/// * `String` - the contents of the property path
///
/// * `FpgadError::Argument` if the path is not found within the compile time [config::FPGA_MANAGERS_DIR]
///
/// # Examples
///
/// ```rust,no_run
/// let device_name = fs_read_property("/sys/class/fpga_manager/fpga0/name")?;
/// assert_eq!(device_name, "Xilinx ZynqMP FPGA Manager\n")
/// ```
pub fn fs_read_property(property_path_str: &str) -> Result<String, FpgadError> {
let property_path = Path::new(property_path_str);
if !property_path.starts_with(Path::new(config::FPGA_MANAGERS_DIR)) {
Expand All @@ -32,6 +91,20 @@ pub fn fs_read_property(property_path_str: &str) -> Result<String, FpgadError> {
}

#[allow(dead_code)]
/// Read the currently specified firmware search path.
/// See [these kernel docs](https://docs.kernel.org/driver-api/firmware/fw_search_path.html)
/// for more information on the process.
///
/// # Returns: `Result<String, FpgadError>`
/// * `String` - The contents of the firmware search path variable.
/// * `FpgadError::IOWrite` (or similar IO error) if writing fails for any reason.
///
/// # Examples
///
/// ```rust,no_run
/// let search_path_str = read_firmware_source_dir()?;
/// assert_eq!(search_path_str, "/lib/firmware/my_firmware_dir");
/// ```
pub fn read_firmware_source_dir() -> Result<String, FpgadError> {
trace!(
"Reading fw prefix from {}",
Expand All @@ -41,6 +114,23 @@ pub fn read_firmware_source_dir() -> Result<String, FpgadError> {
fs_read(fw_lookup_override)
}

/// Write a specified path to the systems firmware search path.
/// See [these kernel docs](https://docs.kernel.org/driver-api/firmware/fw_search_path.html)
/// for more information on the process.
///
/// # Arguments
///
/// * `new_path`: path inside which firmware can be found
///
/// # Returns: `Result<(), FpgadError>`
/// * `()` on success
/// * `FpgadError::IOWrite` (or similar IO error) if writing fails for any reason.
///
Comment thread
artiepoole marked this conversation as resolved.
/// # Examples
///
/// ```rust,no_run
/// assert!(write_firmware_source_dir("/lib/firmware/my_firmware_dir").is_ok());
/// ```
pub fn write_firmware_source_dir(new_path: &str) -> Result<(), FpgadError> {
trace!(
"Writing fw prefix {} to {}",
Expand All @@ -51,6 +141,22 @@ pub fn write_firmware_source_dir(new_path: &str) -> Result<(), FpgadError> {
fs_write(fw_lookup_override, false, new_path)
}

/// Splits a Path object into its parent directory and basename/filename
///
/// # Arguments
///
/// * `path`: path to be split
///
/// # Returns: `Result<(PathBuf, PathBuf), FpgadError>`
/// * `(PathBuf, PathBuf)` - Tuple of parent directory and basename/filename
/// * `FpgadError::Argument` on invalid `path` or `path` is a root directory (no parent)
/// # Examples
///
/// ```rust,no_run
/// let (parent, base) = extract_path_and_filename(Path::new("/lib/firmware/file.bin"));
/// assert_eq!(parent.to_string_lossy(), "/lib/firmware");
/// assert_eq!(base.to_string_lossy(), "file.bin");
/// ```
pub fn extract_path_and_filename(path: &Path) -> Result<(PathBuf, PathBuf), FpgadError> {
// Extract filename
let filename = path
Expand All @@ -72,6 +178,22 @@ pub fn extract_path_and_filename(path: &Path) -> Result<(PathBuf, PathBuf), Fpga
}

/// Helper function to check that a device with given handle does exist.
///
/// # Arguments
///
/// * `device_handle`: name of the device in sysfs, e.g. `fpga0`
///
/// # Returns: `Result<(), FpgadError>`
/// * `()` on success
/// * `FpgadError::Argument` in the case that `device_handle` is not found on the system or
/// device_handle contains invalid characters/is empty.
///
/// # Examples
///
/// ```rust,no_run
/// assert!(validate_device_handle("fpga0").is_ok())
/// assert!(validate_device_handle("").is_err())
/// ```
pub(crate) fn validate_device_handle(device_handle: &str) -> Result<(), FpgadError> {
if device_handle.is_empty() || !device_handle.is_ascii() {
return Err(FpgadError::Argument(format!(
Expand Down Expand Up @@ -102,22 +224,27 @@ pub(crate) fn validate_device_handle(device_handle: &str) -> Result<(), FpgadErr
/// * `source_path`: the full path to the target file (or containing directory?)
/// * `firmware_path`: the root common path for all files to be loaded by the FW subsystem
///
/// returns: Result<(PathBuf, PathBuf), FpgadError> A tuple of (prefix, suffix) where prefix is
/// typically used as the fw_lookup_path and the suffix is remaining relative path from that prefix
///
/// # Returns: `Result<(PathBuf, PathBuf), FpgadError>`
/// * `(PathBuf, PathBuf)` - A tuple of (prefix, suffix) where prefix is
/// typically used as the fw_lookup_path and the suffix is remaining relative path from that prefix
/// * `FpgadError::Argument` in case `firmware_path` is not within `source_path`, or for inputs
/// resulting in an empty suffix value
/// # Examples
///
/// ```
/// use std::path::Path;
/// ```rust
/// # use std::path::Path;
/// let (prefix, suffix) = make_firmware_pair(
/// Path::new("/lib/firmware/file.bin"),
/// Path::new("/lib/firmware/"),
/// )?; // returns prefix = "/lib/firmware/", suffix = "file.bin"
/// )?;
/// assert_eq!(prefix.to_string_lossy(), "/lib/firmware");
/// assert_eq!(suffix.to_string_lossy(), "file.bin");
/// ```
pub(crate) fn make_firmware_pair(
source_path: &Path,
firmware_path: &Path,
) -> Result<(PathBuf, PathBuf), FpgadError> {
// No firmware search path provided, so just try to use parent dir
if firmware_path.as_os_str().is_empty() {
return extract_path_and_filename(source_path);
}
Expand Down
Loading
Loading