use crate::config;
use crate::error::FpgadError;
use crate::platforms::xilinx_sys::XilinxSysPlatform;
use crate::system_io::{fs_read, fs_read_dir};
use log::{trace, warn};
use std::any::Any;
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::sync::{Mutex, OnceLock};
type PlatformConstructor = fn() -> Box<dyn Platform>;
pub static PLATFORM_REGISTRY: OnceLock<Mutex<HashMap<&'static str, PlatformConstructor>>> =
OnceLock::new();
pub trait Fpga {
#[allow(dead_code)]
fn device_handle(&self) -> &str;
fn state(&self) -> Result<String, FpgadError>;
#[allow(dead_code)]
fn load_firmware(
&self,
bitstream_path: &Path,
firmware_lookup_path: &Path,
) -> Result<String, FpgadError>;
fn remove_firmware(&self, handle: Option<&str>) -> Result<String, FpgadError>;
}
pub trait OverlayHandler {
fn apply_overlay(&self, source_path: &Path, lookup_path: &Path) -> Result<String, FpgadError>;
fn remove_overlay(&self, handle: Option<&str>) -> Result<String, FpgadError>;
fn status(&self) -> Result<String, FpgadError>;
fn overlay_fs_path(&self) -> Result<&Path, FpgadError>;
}
pub trait Platform: Any {
fn fpga(&self, device_handle: &str) -> Result<&dyn Fpga, FpgadError>;
fn overlay_handler(&self, overlay_handle: &str) -> Result<&dyn OverlayHandler, FpgadError>;
fn status_message(&self) -> Result<String, FpgadError>;
fn platform_compat_string(&self) -> String;
fn is_available(&self) -> bool;
}
pub fn match_platform_string(platform_string: &str) -> Result<Box<dyn Platform>, FpgadError> {
let registry = PLATFORM_REGISTRY
.get()
.ok_or(FpgadError::Internal(String::from(
"couldn't get PLATFORM_REGISTRY",
)))?
.lock()
.map_err(|_| FpgadError::Internal(String::from("couldn't lock PLATFORM_REGISTRY")))?;
let matching_platforms: Vec<_> = registry
.iter()
.filter(|(compat_string, _)| {
let compat_set: HashSet<&str> = compat_string.split(',').collect();
platform_string.split(',').all(|x| compat_set.contains(x))
})
.collect();
let (softeners, builtin_platforms): (Vec<_>, Vec<_>) = matching_platforms
.iter()
.partition(|(compat_string, _)| compat_string.contains("softener"));
for (compat_string, constructor) in softeners {
trace!("Using softener platform: {}", compat_string);
let platform = constructor();
if platform.is_available() {
return Ok(platform);
}
}
trace!(
"No softeners available for {}, trying built in platforms",
platform_string
);
for (compat_string, constructor) in builtin_platforms {
trace!("Using softener platform: {}", compat_string);
let platform = constructor();
if platform.is_available() {
return Ok(platform);
}
}
trace!("No built in platforms available for {}", platform_string);
Err(FpgadError::Argument(format!(
"FPGAd could not match {platform_string} to a known platform."
)))
}
pub fn discover_platform(device_handle: &str) -> Result<Box<dyn Platform>, FpgadError> {
let compat_string = read_compatible_string(device_handle)?;
trace!("Found compatibility string: '{compat_string}'");
match match_platform_string(&compat_string) {
Ok(platform) => {
trace!("Matched platform for compatibility string: '{compat_string}'");
Ok(platform)
}
Err(_) => {
warn!("{compat_string} not supported. Defaulting to XilinxSys platform.");
Ok(Box::new(XilinxSysPlatform::new()))
}
}
}
pub fn read_compatible_string(device_handle: &str) -> Result<String, FpgadError> {
let compat_string = match fs_read(
&Path::new(config::FPGA_MANAGERS_DIR)
.join(device_handle)
.join("of_node/compatible"),
) {
Err(e) => {
return Err(FpgadError::Argument(format!(
"Failed to read platform from {device_handle:?}: {e}"
)));
}
Ok(s) => {
s.trim_end_matches('\0').to_string()
}
};
Ok(compat_string)
}
pub fn platform_from_compat_or_device(
platform_string: &str,
device_handle: &str,
) -> Result<Box<dyn Platform>, FpgadError> {
match platform_string.is_empty() {
true => discover_platform(device_handle),
false => platform_for_known_platform(platform_string),
}
}
pub fn platform_for_known_platform(platform_string: &str) -> Result<Box<dyn Platform>, FpgadError> {
match_platform_string(platform_string)
}
pub fn init_platform_registry() -> Mutex<HashMap<&'static str, PlatformConstructor>> {
Mutex::new(HashMap::new())
}
pub fn register_platform(compatible: &'static str, constructor: PlatformConstructor) {
let mut registry = PLATFORM_REGISTRY
.get_or_init(init_platform_registry)
.lock()
.expect("couldnt get PLATFORM_REGISTRY");
registry.insert(compatible, constructor);
}
pub fn list_fpga_managers() -> Result<Vec<String>, FpgadError> {
fs_read_dir(config::FPGA_MANAGERS_DIR.as_ref())
}
#[cfg(test)]
mod platform_discovery_tests {
use super::*;
use googletest::prelude::*;
fn register_test_softener_available() {
register_platform(
"xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,dfx-mgr,softener",
|| Box::new(XilinxSysPlatform::new()), );
}
fn register_test_softener_unavailable() {
register_platform(
"xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,dfx-mgr,softener",
|| Box::new(XilinxSysPlatform::new()),
);
}
fn register_test_builtin() {
register_platform(
"xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,xlnx-sys,platform",
|| Box::new(XilinxSysPlatform::new()),
);
}
#[gtest]
fn test_built_in_platform_is_always_available() {
let platform = XilinxSysPlatform::new();
assert_that!(platform.is_available(), eq(true));
}
#[gtest]
fn test_built_in_platform_can_be_registered_and_matched() {
register_platform("test-platform,built-in", || {
Box::new(XilinxSysPlatform::new())
});
let result = match_platform_string("test-platform");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_platform_matching_requires_all_components() {
register_platform("test-multi,component,platform", || {
Box::new(XilinxSysPlatform::new())
});
let result = match_platform_string("test-multi,component,platform");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("test-multi,component");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("test-multi,component,platform,extra");
assert_that!(result.is_err(), eq(true));
}
#[gtest]
fn test_softener_preferred_when_available() {
register_test_softener_available();
register_test_builtin();
let result = match_platform_string("xlnx,zynqmp-pcap-fpga");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("xlnx");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_fallback_to_builtin_when_softener_unavailable() {
register_test_softener_unavailable();
register_test_builtin();
let result = match_platform_string("xlnx,zynqmp-pcap-fpga");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("versal-fpga");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_explicit_softener_request_requires_softener_component() {
register_test_softener_available();
register_test_builtin();
let result = match_platform_string("xlnx,softener");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("xlnx,platform,softener");
assert_that!(result.is_err(), eq(true));
}
#[gtest]
fn test_explicit_builtin_request_works() {
register_test_softener_available();
register_test_builtin();
let result = match_platform_string("xlnx,platform");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("xlnx-sys");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_only_unavailable_platforms_match_still_returns_one() {
register_platform("only-unavailable,test", || {
Box::new(XilinxSysPlatform::new())
});
let result = match_platform_string("only-unavailable");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_multiple_softeners_picks_first_available() {
register_platform("multi-soft,test,softener,first", || {
Box::new(XilinxSysPlatform::new())
});
register_platform("multi-soft,test,softener,second", || {
Box::new(XilinxSysPlatform::new())
});
register_platform("multi-soft,test,softener,third", || {
Box::new(XilinxSysPlatform::new())
});
let result = match_platform_string("multi-soft,test,softener");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_no_match_returns_error() {
let result = match_platform_string("nonexistent-platform-12345");
assert_that!(result.is_err(), eq(true));
if let Err(FpgadError::Argument(msg)) = result {
assert_that!(msg, contains_substring("could not match"));
} else {
panic!("Expected FpgadError::Argument");
}
}
#[gtest]
fn test_empty_string_returns_error() {
let result = match_platform_string("");
assert_that!(result.is_err(), eq(true));
}
#[gtest]
fn test_case_sensitivity() {
register_platform("case-test,lowercase", || Box::new(XilinxSysPlatform::new()));
let result = match_platform_string("case-test");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("CASE-TEST");
assert_that!(result.is_err(), eq(true));
}
#[gtest]
fn test_compat_string_constant_matches_registered() {
assert_that!(
XilinxSysPlatform::COMPAT_STRING,
eq("xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,xlnx-sys,platform")
);
}
#[gtest]
fn test_dfx_mgr_component_matching() {
register_test_softener_available();
let result = match_platform_string("dfx-mgr");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("xlnx,dfx-mgr");
assert_that!(result.is_ok(), eq(true));
}
#[gtest]
fn test_versal_and_zynqmp_components() {
register_test_builtin();
let result = match_platform_string("versal-fpga");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("zynqmp-pcap-fpga");
assert_that!(result.is_ok(), eq(true));
let result = match_platform_string("zynq-devcfg-1.0");
assert_that!(result.is_ok(), eq(true));
}
}
#[cfg(all(test, feature = "xilinx-dfx-mgr"))]
mod dfx_mgr_integration_tests {
use super::*;
use crate::softeners::xilinx_dfx_mgr::XilinxDfxMgrPlatform;
use googletest::prelude::*;
use std::any::Any;
fn setup_integrated_registry() {
register_platform(
"xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,dfx-mgr,softener",
|| Box::new(XilinxDfxMgrPlatform::new()),
);
register_platform(
"xlnx,zynqmp-pcap-fpga,versal-fpga,zynq-devcfg-1.0,xlnx-sys,platform",
|| Box::new(XilinxSysPlatform::new()),
);
}
fn assert_is_xlnx_sys_platform(platform: &dyn Platform) {
let as_xlnx_sys = (platform as &dyn Any).downcast_ref::<XilinxSysPlatform>();
assert_that!(as_xlnx_sys.is_some(), eq(true));
}
#[gtest]
fn test_dfx_mgr_platform_availability() {
let platform = XilinxDfxMgrPlatform::new();
let available = platform.is_available();
println!("DFX Manager available: {}", available);
}
#[gtest]
fn test_explicit_builtin_request_with_real_platforms() {
setup_integrated_registry();
let result = match_platform_string("xlnx-sys");
assert_that!(result.is_ok(), eq(true));
let platform = result.unwrap();
assert_is_xlnx_sys_platform(platform.as_ref());
}
#[gtest]
fn test_platform_type_assertion_methods() {
setup_integrated_registry();
let platform = match_platform_string("xlnx").unwrap();
let platform_any = platform.as_ref() as &dyn Any;
let type_name = std::any::type_name_of_val(platform_any);
println!("Platform type: {}", type_name);
let is_dfx_mgr = platform_any.is::<XilinxDfxMgrPlatform>();
let is_xlnx_sys = platform_any.is::<XilinxSysPlatform>();
assert_that!(is_dfx_mgr || is_xlnx_sys, eq(true));
}
#[gtest]
fn test_compat_string_constants() {
assert_that!(
XilinxDfxMgrPlatform::COMPAT_STRING,
contains_substring("dfx-mgr")
);
assert_that!(
XilinxDfxMgrPlatform::COMPAT_STRING,
contains_substring("softener")
);
assert_that!(
XilinxSysPlatform::COMPAT_STRING,
contains_substring("xlnx-sys")
);
assert_that!(
XilinxSysPlatform::COMPAT_STRING,
contains_substring("platform")
);
}
}