use crate::comm::dbus::{validate_device_handle, validate_property_path};
use crate::error::map_error_io_to_fdo;
use crate::platforms::platform::{platform_for_known_platform, platform_from_compat_or_device};
use crate::softeners::error::FpgadSoftenerError;
use crate::system_io::{fs_write, fs_write_bytes};
use log::{info, trace};
use std::env;
use std::path::Path;
use std::sync::Arc;
use tokio::process::Command;
use tokio::sync::{Mutex, MutexGuard, OnceCell};
use zbus::{fdo, interface};
static WRITE_LOCK: OnceCell<Arc<Mutex<()>>> = OnceCell::const_new();
async fn get_write_lock_guard() -> MutexGuard<'static, ()> {
let lock = WRITE_LOCK
.get_or_init(|| async { Arc::new(Mutex::new(())) })
.await;
lock.lock().await
}
pub struct ControlInterface {}
#[interface(name = "com.canonical.fpgad.control")]
impl ControlInterface {
async fn set_fpga_flags(
&self,
platform_string: &str,
device_handle: &str,
flags: u32,
) -> Result<String, fdo::Error> {
info!("set_fpga_flags called with name: {device_handle} and flags: {flags}");
validate_device_handle(device_handle)?;
let platform = platform_from_compat_or_device(platform_string, device_handle)?;
Ok(platform.fpga(device_handle)?.set_flags(flags)?)
}
async fn write_bitstream_direct(
&self,
platform_string: &str,
device_handle: &str,
bitstream_path_str: &str,
firmware_lookup_path: &str,
) -> Result<String, fdo::Error> {
info!("load_firmware called with name: {device_handle} and path_str: {bitstream_path_str}");
validate_device_handle(device_handle)?;
let path = Path::new(bitstream_path_str);
let lookup = Path::new(firmware_lookup_path);
let _guard = get_write_lock_guard().await;
trace!("Got write lock.");
let platform = platform_from_compat_or_device(platform_string, device_handle)?;
Ok(platform.fpga(device_handle)?.load_firmware(path, lookup)?)
}
async fn apply_overlay(
&self,
platform_string: &str,
overlay_handle: &str,
overlay_source_path: &str,
firmware_lookup_path: &str,
) -> Result<String, fdo::Error> {
info!(
"apply_overlay called with platform_string: {platform_string}, overlay_handle: \
{overlay_handle} and overlay_path: {overlay_source_path}",
);
let _guard = get_write_lock_guard().await;
trace!("Got write lock.");
let platform = platform_for_known_platform(platform_string)?;
let overlay_handler = platform.overlay_handler(overlay_handle)?;
Ok(overlay_handler.apply_overlay(
Path::new(overlay_source_path),
Path::new(firmware_lookup_path),
)?)
}
async fn remove_overlay(
&self,
platform_string: &str,
overlay_handle: &str,
) -> Result<String, fdo::Error> {
info!(
"remove_overlay called with platform_string: {platform_string} and overlay_handle:\
{overlay_handle}"
);
let platform = platform_for_known_platform(platform_string)?;
let overlay_handler = platform.overlay_handler(overlay_handle)?;
let handle = match overlay_handle {
"" => None,
_ => Some(overlay_handle),
};
Ok(overlay_handler.remove_overlay(handle)?)
}
async fn remove_bitstream(
&self,
platform_string: &str,
device_handle: &str,
bitstream_handle: &str,
) -> Result<String, fdo::Error> {
info!(
"remove_bitstream called with platform_string: {platform_string}, device_handle:\
{device_handle} and bitstream_handle: {bitstream_handle}"
);
let platform = platform_from_compat_or_device(platform_string, device_handle)?;
let fpga = platform.fpga(device_handle)?;
let handle = match bitstream_handle {
"" => None,
_ => Some(bitstream_handle),
};
Ok(fpga.remove_firmware(handle)?)
}
async fn write_property(
&self,
property_path_str: &str,
data: &str,
) -> Result<String, fdo::Error> {
info!("write_property called with property_path_str: {property_path_str} and data: {data}");
let property_path = validate_property_path(Path::new(property_path_str))?;
fs_write(&property_path, false, data)?;
Ok(format!("{data} written to {property_path_str}"))
}
async fn write_property_bytes(
&self,
property_path_str: &str,
data: &[u8],
) -> Result<String, fdo::Error> {
info!(
"write_property called with property_path_str: {property_path_str} and data: {data:?}"
);
let property_path = validate_property_path(Path::new(property_path_str))?;
fs_write_bytes(&property_path, false, data)?;
Ok(format!(
"Byte string successfully written to {property_path_str}"
))
}
async fn dfx_mgr(&self, cmd_string: &str) -> Result<String, fdo::Error> {
if cfg!(feature = "xilinx-dfx-mgr") {
let snap_env = env::var("SNAP").unwrap_or("".to_string());
let dfx_mgr_client_path = format!("{}/usr/bin/dfx-mgr-client", snap_env);
if !Path::new(&dfx_mgr_client_path).exists() {
return Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr-client not detected.\n\
If using snap, please install the dfx-mgr component with \n\
`[sudo] snap install fpgad+dfx-mgr [options]` \n\
otherwise ensure that dfx-mgr-client exists at `{dfx_mgr_client_path}`"
))
.into());
}
let output = Command::new(&dfx_mgr_client_path)
.args(cmd_string.split_whitespace())
.output()
.await
.map_err(|e| {
map_error_io_to_fdo("dfx-mgr-client call failed to produce any output", e)
})?;
match output.status.success() {
true => {
info!("Command ran successfully!");
Ok(format!(
"dfx-mgr called with args {}.\nExit status: {}\nStdout:\n{}\nStderr:\n{}",
cmd_string,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
))
}
false => {
info!("Command failed with code: {:#?}", output.status.code());
Err(FpgadSoftenerError::DfxMgr(format!(
"dfx-mgr called with args {}.\nExit status: {}\nStdout:\n{}\nStderr:\n{}",
cmd_string,
output.status,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
))
.into())
}
}
} else {
use crate::error::FpgadError;
Err(FpgadError::Feature(
"Cannot use DfxMgr method - FPGAd was compiled without xilinx-dfx-mgr feature"
.into(),
)
.into())
}
}
}
#[cfg(test)]
mod test_get_write_lock_guard {
use crate::comm::dbus::control_interface::get_write_lock_guard;
#[tokio::test]
async fn test_get_write_lock_guard() {
let _guard = get_write_lock_guard().await;
}
}