Skip to main content

fpgad_cli/
lib.rs

1// This file is part of fpgad, an application to manage FPGA subsystem together with device-tree and kernel modules.
2//
3// Copyright 2025 Canonical Ltd.
4//
5// SPDX-License-Identifier: GPL-3.0-only
6//
7// fpgad is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation.
8//
9// 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.
10//
11// You should have received a copy of the GNU General Public License along with this program.  If not, see http://www.gnu.org/licenses/.
12
13//! FPGA CLI (fpgad_cli) - Command-line interface for managing FPGA devices.
14//!
15//! This is FPGAd's commandline interface (CLI) . Due to strict confinement of the snap, this can
16//! only be used from a terminal or from a script which is not part of another snap.
17//! It is a useful helper for one-off control of the FPGA device or testing, and serves as an
18//! example implementation for the DBus interface.
19//!
20//! # Common Concepts
21//!
22//! The following concepts are shared across all CLI submodules ([`load`], [`remove`], [`set`], [`status`]).
23//!
24//! ## Device Handles
25//! [Device Handles]: #device-handles
26//!
27//! A "device handle" refers to the name of an FPGA device as it appears in
28//! `/sys/class/fpga_manager/`. Common examples include:
29//! - `fpga0` - The first FPGA device
30//! - `fpga1` - The second FPGA device (if multiple FPGAs are present)
31//!
32//! These handles uniquely identify FPGA devices in the system and are used throughout
33//! the CLI to specify which device to operate on.
34//!
35//! ## Overlay Handles
36//! [Overlay Handles]: #overlay-handles
37//!
38//! An "overlay handle" refers to the name of a device tree overlay as it appears in
39//! `/sys/kernel/config/device-tree/overlays/`. Common examples include:
40//! - `overlay0` - A generic overlay name
41//! - `fpga-design` - A custom overlay name specified during loading
42//!
43//! These handles are used to identify and manage loaded device tree overlays. When loading
44//! an overlay, you can specify a custom handle or let the system choose one based on the
45//! device handle.
46//!
47//! ## Error Handling
48//! [Error Handling]: #error-handling
49//!
50//! All CLI functions communicate with the fpgad daemon via DBus and return
51//! `Result<String, zbus::Error>` (or variants with `Vec<String>` or `HashMap<String, String>`).
52//!
53//! When the fpgad daemon returns an application-level error (not a DBus communication
54//! error), the error will be of type `zbus::Error::Failure` and the error message will
55//! begin with `FpgadError::<variant>:` followed by the error details. For example:
56//! ```text
57//! FpgadError::Argument: Device fpga0 not found.
58//! FpgadError::IOWrite: Failed to write bitstream: Permission denied
59//! FpgadError::IORead: Failed to read state: No such file or directory
60//! ```
61//!
62//! This allows callers to distinguish between:
63//! - **DBus communication errors** - Problems connecting to or communicating with the daemon
64//! - **Application errors** - Errors from the daemon itself (prefixed with `FpgadError::`)
65//!
66//! # Usage
67//!
68//! ```text
69//! Usage: [snap run] fpgad [OPTIONS] <COMMAND>
70//!
71//! OPTIONS:
72//!   -h, --help                      Print help
73//!   -p, --platform <PLATFORM>       Platform override string (bypasses platform detection logic).
74//!                                   When provided, this platform string is passed directly to the
75//!                                   daemon instead of auto-detecting from the device handle.
76//!                                   Examples: "xlnx-sys", "xlnx,zynqmp-pcap-fpga"
77//!   -d, --device <DEVICE_HANDLE>    FPGA device handle to be used for the operations.
78//!                                   Default value is calculated at runtime - the application
79//!                                   picks the first available FPGA device in the system
80//!                                   (under `/sys/class/fpga_manager/`).
81//!                                   Examples: "fpga0", "fpga1"
82//!
83//! SUBCOMMAND OPTIONS:
84//!   -n, --name <OVERLAY_NAME>       (Used with load/remove overlay subcommands)
85//!                                   Name for the overlay directory in configfs
86//!                                   (under `/sys/kernel/config/device-tree/overlays/`).
87//!                                   If not provided, defaults to the device handle or "overlay0".
88//!
89//! COMMANDS:
90//! ├── load                Load a bitstream or overlay
91//! │   ├── overlay <FILE> [--name <OVERLAY_HANDLE> --platform <PLATFORM>]
92//! │   │       Load overlay (.dtbo) into the system using the default OVERLAY_HANDLE
93//! │   │           (either the provided DEVICE_HANDLE or "overlay0") or provide
94//! │   │       --name: to name the overlay directory
95//! │   └── bitstream <FILE> [--platform <PLATFORM>]
96//! │           Load bitstream (e.g. `.bit.bin` file) into the FPGA
97//! │
98//! ├── set <ATTRIBUTE> <VALUE>
99//! │       Set an attribute/flag under `/sys/class/fpga_manager/<DEVICE_HANDLE>/<ATTRIBUTE>`
100//! │
101//! ├── status [--device <DEVICE_HANDLE> --platform <PLATFORM>]
102//! │       Show FPGA status (all devices and overlays) or provide
103//! │       --device: for a specific device status
104//! │
105//! └── remove              Remove an overlay or bitstream
106//!     ├── overlay [--name <OVERLAY_HANDLE> --platform <PLATFORM>]
107//!     │       Removes the first overlay found (call repeatedly to remove all) or provide
108//!     │       --name: to remove overlay previously loaded with given OVERLAY_HANDLE
109//!     └── bitstream [--name <BITSTREAM_HANDLE> --platform <PLATFORM>]
110//!             Remove active bitstream from FPGA (bitstream removal is vendor specific)
111//! ```
112//!
113//! ### Loading
114//!
115//! ```shell
116//! fpgad [--device=<device_handle>] [--platform=<platform>] load ( (overlay <file> [--name=<overlay_name>]) | (bitstream <file>) )
117//! ```
118//!
119//! ### Removing
120//!
121//! ```shell
122//! fpgad [--device=<device_handle>] [--platform=<platform>] remove ( ( overlay [--name=<overlay_name>] ) | ( bitstream ) )
123//! ```
124//!
125//! ### Set
126//!
127//! ```shell
128//! fpgad [--device=<device_handle>] set ATTRIBUTE VALUE
129//! ```
130//!
131//! ### Status
132//!
133//! ```shell
134//! fpgad [--device=<device_handle>] [--platform=<platform>] status
135//! ```
136//!
137//! ## examples (for testing)
138//!
139//! ### Load
140//!
141//! ```shell
142//! sudo ./target/debug/cli load bitstream /lib/firmware/k26-starter-kits.bit.bin
143//! sudo ./target/debug/cli --device=fpga0 load bitstream /lib/firmware/k26-starter-kits.bit.bin
144//! sudo ./target/debug/cli --platform=xlnx-sys load bitstream /lib/firmware/k26-starter-kits.bit.bin
145//! sudo ./target/debug/cli --platform=xlnx-sys load bitstream /lib/firmware/k26-starter-kits.bit.bin
146//!
147//! sudo ./target/debug/cli load overlay /lib/firmware/k26-starter-kits.dtbo
148//! sudo ./target/debug/cli load overlay /lib/firmware/k26-starter-kits.dtbo --name=overlay_handle
149//! sudo ./target/debug/cli --device=fpga0 load overlay /lib/firmware/k26-starter-kits.dtbo --name=overlay_handle
150//! sudo ./target/debug/cli --platform=xlnx-sys load overlay /lib/firmware/k26-starter-kits.dtbo --name=overlay_handle
151//! sudo ./target/debug/cli --platform=xlnx-sys --device=fpga0 load overlay /lib/firmware/k26-starter-kits.dtbo --name=overlay_handle
152//! ```
153//!
154//! ### Remove
155//!
156//! ```shell
157//! sudo ./target/debug/cli --device=fpga0 remove overlay
158//! sudo ./target/debug/cli --device=fpga0 remove overlay --name=overlay_handle
159//! ```
160//!
161//! ### Set
162//!
163//! ```shell
164//! sudo ./target/debug/cli set flags 0
165//! sudo ./target/debug/cli --device=fpga0 set flags 0
166//! ```
167//!
168//! ### Status
169//!
170//! ```shell
171//! ./target/debug/cli status
172//! ./target/debug/cli --device=fpga0 status
173//! ```
174
175// (xlnx and dfx-mgr subcommands are documented on their respective enum variants below)
176pub mod load;
177
178pub mod remove;
179
180pub mod status;
181
182pub mod set;
183
184pub mod xlnx_sys;
185
186pub mod dfx_mgr;
187
188use clap::{Parser, Subcommand};
189
190/// Command-line interface structure for FPGA management operations.
191///
192/// This structure represents the top-level CLI interface for interacting with FPGA devices
193/// through the fpgad daemon's DBus interface. It provides a unified interface for loading
194/// bitstreams and overlays, querying device status, setting attributes, and removing
195/// loaded components.
196///
197/// # Examples
198///
199/// ```shell
200///
201/// # Load a bitstream
202/// fpgad load bitstream /lib/firmware/design.bit.bin
203///
204/// # Check status of all FPGA devices
205/// fpgad status
206///
207/// # Load an overlay with a specific name
208/// fpgad load overlay /lib/firmware/overlay.dtbo --name=my_overlay
209///
210/// ```
211#[derive(Parser, Debug)]
212#[command(name = "fpgad")]
213#[command(bin_name = "fpgad")]
214pub struct Cli {
215    /// Platform override string (bypasses platform detection logic).
216    /// When provided, this platform string is passed directly to the daemon
217    /// instead of auto-detecting from the device handle.
218    /// Examples: "xlnx-sys", "xlnx,zynqmp-pcap-fpga"
219    #[arg(short = 'p', long = "platform")]
220    platform: Option<String>,
221
222    /// FPGA `device` handle to be used for the operations.
223    /// Default value is calculated at runtime - the application picks the first
224    /// available FPGA device in the system (under /sys/class/fpga_manager/).
225    /// Examples: "fpga0", "fpga1"
226    #[arg(short = 'd', long = "device")]
227    device: Option<String>,
228
229    #[command(subcommand)]
230    command: Commands,
231}
232
233impl Cli {
234    /// Returns the platform override string, if provided.
235    pub fn platform(&self) -> Option<&String> {
236        self.platform.as_ref()
237    }
238
239    /// Returns the device handle, if provided.
240    pub fn device(&self) -> Option<&String> {
241        self.device.as_ref()
242    }
243
244    /// Returns a reference to the command.
245    pub fn command(&self) -> &Commands {
246        &self.command
247    }
248}
249
250/// Subcommands for loading FPGA components.
251///
252/// This enum defines the types of components that can be loaded onto an FPGA device:
253/// - **Overlay**: Device tree overlays (.dtbo files) that describe hardware configuration
254/// - **Bitstream**: FPGA configuration bitstreams (.bit.bin files) containing the actual FPGA design
255///
256/// Device tree overlays are typically loaded before or after bitstreams to properly configure
257/// the kernel's view of the FPGA's hardware interfaces and peripherals.
258///
259/// # Examples
260///
261/// ```shell
262/// # Load a bitstream
263/// fpgad load bitstream [-d=<DEVICE_HANDLE> -p=<COMPAT_STR>] /lib/firmware/design.bit.bin
264///
265/// # Load an overlay with a custom name
266/// fpgad load overlay [-d=<DEVICE_HANDLE> -p=<COMPAT_STR>] /lib/firmware/overlay.dtbo [-n=my_overlay]
267/// ```
268#[derive(Subcommand, Debug)]
269pub enum LoadSubcommand {
270    /// Load overlay into the system
271    Overlay {
272        /// Overlay `FILE` to be loaded (typically .dtbo)
273        file: String,
274
275        /// Name for the overlay directory which will be created
276        /// under "/sys/kernel/config/device-tree/overlays/".
277        /// If not provided, defaults to the device handle or "overlay0".
278        #[arg(short = 'n', long = "name")]
279        name: Option<String>,
280    },
281    /// Load bitstream into the system
282    Bitstream {
283        /// Bitstream `FILE` to be loaded (typically .bit.bin)
284        file: String,
285    },
286}
287
288/// Subcommands for removing FPGA components.
289///
290/// This enum defines the types of components that can be removed from an FPGA device:
291/// - **Overlay**: Removes a device tree overlay by its name.
292/// - **Bitstream**: Intended to remove the currently loaded FPGA bitstream (vendor-specific
293///   operation that may use slot identifiers on platforms like DFX Manager)
294///
295/// Removing overlays is important for proper cleanup when reconfiguring the FPGA.
296/// Bitstream removal support depends on the FPGA vendor and platform capabilities.
297///
298/// # Examples
299///
300/// ```shell
301/// # Remove the first overlay found
302/// fpgad remove overlay
303///
304/// # Remove a specific overlay by name
305/// fpgad [-d=<DEVICE_HANDLE>] [-p=<COMPAT_STR>] remove overlay -n=my_overlay
306///
307/// # Remove a bitstream, if supported
308/// fpgad [-d=<DEVICE_HANDLE>] [-p=<COMPAT_STR>] remove bitstream -n=0 # for dfx-mgr slot 0
309/// ```
310#[derive(Subcommand, Debug)]
311pub enum RemoveSubcommand {
312    /// Remove overlay with the name provided
313    Overlay {
314        /// Name of the overlay to remove (as given during `load` operation).
315        /// If not provided, removes the first overlay found in the system.
316        /// This is different from device_handle which is used for platform detection.
317        #[arg(short = 'n', long = "name")]
318        name: Option<String>,
319    },
320    /// Remove bitstream loaded in the given device
321    Bitstream {
322        /// Handle/identifier for the bitstream to remove.
323        /// For DFX Manager platforms, this can be a slot ID.
324        /// Use empty string "" to remove the latest bitstream.
325        #[arg(long = "handle")]
326        handle: Option<String>,
327    },
328}
329
330/// Subcommands for the xlnx-sys platform interface.
331///
332/// Provides direct access to the daemon's `xlnx_sys` read/write DBus methods,
333/// allowing low-level control of FPGA manager sysfs properties and flags.
334///
335/// # Valid `sub_cmd` values
336///
337/// **Read** (`fpgad xlnx-sys read <sub_cmd> <path>`):
338///
339/// | `sub_cmd` | `path` | Description |
340/// |-----------|--------|-------------|
341/// | `read_flags` | Device handle or full sysfs path to flags, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags` | Read the current programming flags |
342/// | `read_property` | Full sysfs path e.g. `/sys/class/fpga_manager/fpga0/name` | Read a sysfs property string |
343///
344/// **Write** (`fpgad xlnx-sys write <sub_cmd> <path> <value>`):
345///
346/// | `sub_cmd` | `path` | `value` | Description |
347/// |-----------|--------|---------|-------------|
348/// | `write_flags` | Device handle or full sysfs path to flags, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags` | Hex `u32` with or without `0x` prefix (e.g. `0x20` or `20`, both = 32) | Set FPGA programming flags |
349/// | `write_property` | Full sysfs path | String payload | Write a string to a sysfs property |
350/// | `write_property_bytes` | Full sysfs path | Hex byte string | Write raw bytes to a sysfs property |
351///
352/// # Examples
353///
354/// ```shell
355/// fpgad xlnx-sys read read_flags fpga0
356/// fpgad xlnx-sys read read_property /sys/class/fpga_manager/fpga0/name
357/// fpgad xlnx-sys write write_flags fpga0 0x20
358/// fpgad xlnx-sys write write_property /sys/class/fpga_manager/fpga0/key VALUE
359/// fpgad xlnx-sys write write_property_bytes /sys/class/fpga_manager/fpga0/key deadbeef
360/// ```
361#[derive(Subcommand, Debug)]
362pub enum XlnxSysSubcommand {
363    /// Read an FPGA property using the xlnx interface
364    Read {
365        /// Read operation to perform: `read_flags` or `read_property`.
366        ///
367        /// * `read_flags` — `path` is a device handle or full sysfs path to flags, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags`.
368        ///
369        /// * `read_property` — `path` is the full sysfs path, e.g. `/sys/class/fpga_manager/fpga0/name`.
370        ///
371        /// See: <https://docs.rs/fpgad/latest/fpgad/platforms/xlnx_sys/enum.ReadSubCommand.html>
372        sub_cmd: String,
373        /// For `read_flags`: device handle or full sysfs path, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags`.
374        ///
375        /// For `read_property`: full sysfs path, e.g. `/sys/class/fpga_manager/fpga0/name`.
376        path: String,
377    },
378    /// Write an FPGA property using the xlnx_sys interface
379    Write {
380        /// Write operation to perform: `write_flags`, `write_property`, or `write_property_bytes`.
381        ///
382        /// * `write_flags` — `path` is a device handle or full sysfs path to flags, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags`; `value` is a hex `u32` with or without `0x` prefix
383        ///   (e.g. `0x20` or `20`, both = decimal 32).
384        ///
385        /// * `write_property` — `path` is a full sysfs path; `value` is a string payload.
386        ///
387        /// * `write_property_bytes` — `path` is a full sysfs path; `value` is a hex byte string, e.g. `deadbeef`.
388        ///
389        /// See: <https://docs.rs/fpgad/latest/fpgad/platforms/xlnx_sys/enum.WriteSubCommand.html>
390        sub_cmd: String,
391        /// For `write_flags`: device handle or full sysfs path to flags, e.g. `fpga0` or `/sys/class/fpga_manager/fpga0/flags`.
392        ///
393        /// For `write_property` / `write_property_bytes`: full sysfs path under
394        /// `/sys/class/fpga_manager/`.
395        path: String,
396        /// Value to write.
397        ///
398        /// For `write_flags`: hex `u32` with or without `0x` prefix (e.g. `0x20` or `20`, both = 32).
399        ///
400        /// For `write_property`: string payload.
401        ///
402        /// For `write_property_bytes`: hex-encoded byte string, e.g. `deadbeef`.
403        value: String,
404    },
405}
406
407/// Top-level commands supported by the CLI.
408///
409/// This enum represents all the primary operations available through the fpgad CLI:
410/// - **Load**: Load bitstreams or device tree overlays onto the FPGA
411/// - **Set**: Configure FPGA attributes and flags (e.g., programming flags)
412/// - **Status**: Query the current state of FPGA devices and loaded overlays
413/// - **Remove**: Unload bitstreams or device tree overlays from the FPGA
414/// - **XlnxSys**: Low-level read/write access to FPGA manager properties via the xlnx_sys interface
415/// - **DfxMgr**: Pass commands directly to `dfx-mgr-client` (requires dfx-mgr component)
416///
417/// Each command communicates with the fpgad daemon via DBus to perform privileged
418/// operations on FPGA devices managed through the Linux kernel's FPGA subsystem.
419///
420/// # Examples
421///
422/// ```shell
423/// # Load a bitstream to a specific device
424///
425/// fpgad --device=fpga0 load bitstream /lib/firmware/design.bit.bin
426///
427/// # Load an overlay with platform override
428///
429/// fpgad --platform=xlnx-sys load overlay /lib/firmware/overlay.dtbo --name=my_overlay
430///
431/// # Set flags for a device
432///
433/// fpgad --device=fpga0 set flags 0
434///
435/// # Get status for all devices
436///
437/// fpgad status
438///
439/// # Remove an overlay by name
440///
441/// fpgad remove overlay --name=my_overlay
442///
443/// # Read FPGA flags via xlnx_sys interface
444///
445/// fpgad xlnx-sys read read_flags fpga0
446///
447/// # Write flags via xlnx_sys interface
448///
449/// fpgad xlnx-sys write write_flags fpga0 0x20
450///
451/// # Invoke dfx-mgr-client
452///
453/// fpgad dfx-mgr "-listPackage"
454/// ```
455#[derive(Subcommand, Debug)]
456pub enum Commands {
457    /// Load a bitstream or an overlay for the given device handle
458    Load {
459        #[command(subcommand)]
460        command: LoadSubcommand,
461    },
462    /// Set an option (e.g. flags) to a specific value for a given device handle
463    Set { attribute: String, value: String },
464    /// Get the status information for the given device handle
465    Status,
466    /// Remove bitstream or an overlay
467    Remove {
468        #[command(subcommand)]
469        command: RemoveSubcommand,
470    },
471    /// Low-level read/write access to FPGA manager properties (xlnx_sys platform interface)
472    XlnxSys {
473        #[command(subcommand)]
474        command: XlnxSysSubcommand,
475    },
476    /// Pass a command directly to `dfx-mgr-client` (requires the `dfx-mgr` snap component).
477    ///
478    /// This is a thin passthrough to the Xilinx DFX Manager client binary.  The arguments are
479    /// forwarded verbatim to `dfx-mgr-client`, so any flag or option that the tool accepts can be
480    /// used here.
481    ///
482    /// The `dfx-mgr` component must be installed:
483    /// ```shell
484    /// sudo snap install fpgad+dfx-mgr
485    /// ```
486    ///
487    /// For a full list of `dfx-mgr-client` commands and options, see the upstream project:
488    /// <https://github.com/Xilinx/dfx-mgr>
489    ///
490    /// # Common commands
491    ///
492    /// | Example | Description |
493    /// |---------|-------------|
494    /// | `fpgad dfx-mgr -listPackage` | List all available acceleration packages |
495    /// | `fpgad dfx-mgr -listSlot`    | List all FPGA slots and their current state |
496    /// | `fpgad dfx-mgr -load 0 <package_name>` | Load a package into slot 0 |
497    /// | `fpgad dfx-mgr -remove 0`   | Remove the package loaded in slot 0 |
498    ///
499    /// # Notes
500    ///
501    /// * Arguments that begin with `-` (such as `-listPackage`) are supported directly — no `--`
502    ///   separator is required.
503    /// * Multiple tokens are accepted: `fpgad dfx-mgr -load 0 my_design`
504    DfxMgr {
505        /// One or more arguments to pass to `dfx-mgr-client`.
506        ///
507        /// Tokens beginning with `-` are accepted without needing a `--` separator, so commands
508        /// like `-listPackage` or `-load 0 my_design` work naturally:
509        ///
510        /// ```shell
511        /// fpgad dfx-mgr -listPackage
512        /// fpgad dfx-mgr -load 0 my_design
513        /// ```
514        #[arg(allow_hyphen_values = true, num_args = 1.., value_name = "CMD")]
515        cmd: Vec<String>,
516    },
517    /// Generate a shell completion script and print it to stdout.
518    ///
519    /// This is primarily used at packaging time (the snap wires the generated bash
520    /// script up via the `completer` keyword), but it can also be sourced manually:
521    ///
522    /// ```shell
523    /// # Enable completions for the current shell session
524    /// source <(fpgad completions bash)
525    ///
526    /// # Or install them permanently for the current user
527    /// fpgad completions bash > ~/.local/share/bash-completion/completions/fpgad
528    /// ```
529    Completions {
530        /// Shell to generate the completion script for (e.g. `bash`, `zsh`, `fish`).
531        shell: clap_complete::Shell,
532    },
533}