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}