use std::path::PathBuf;
use consortium_cfg::Session;
use consortium_cfg_common::config::{Party, VisibleCore};
use consortium_cfg_common::database::{ChipDatabase, Coprocessor, CoprocessorVariant};
use super::{CargoOptions, PipelineError, core_rust_triple, visible_core_of};
use crate::compile::env::CstiWorkspace;
use crate::compile::firmware::FirmwareConfiguration;
pub struct FirmwareBuildPlan {
pub core: VisibleCore,
pub party: Party,
pub config: FirmwareConfiguration,
pub generated: String,
}
pub fn remote_coprocessors(database: &ChipDatabase) -> impl Iterator<Item = &Coprocessor> {
database
.cp
.iter()
.filter(|cp| cp.variant == CoprocessorVariant::Remote)
}
pub fn firmware_endpoints(
session: &Session,
) -> Result<Vec<(VisibleCore, Party, &str)>, PipelineError> {
let mut endpoints = Vec::new();
for cp in remote_coprocessors(&session.database) {
let core = visible_core_of(cp)?;
if let Some((party, root)) = session
.config
.endpoints
.iter()
.find(|(party, _)| party.core() == core)
{
endpoints.push((core, *party, root.as_str()));
}
}
Ok(endpoints)
}
pub fn plan_firmware_builds(
session: &Session,
workspace: &CstiWorkspace,
cargo: CargoOptions,
ty_crate: &syn::Path,
) -> Result<Vec<FirmwareBuildPlan>, PipelineError> {
let ir = session.lower()?;
let mut plans = Vec::new();
for (core, party, root) in firmware_endpoints(session)? {
let path = workspace.manifest_dir.join(root);
let target = core_rust_triple(core);
consortium_log::trace!("rendering firmware init module for core {}", core);
let generated = consortium_cfg::render(ir.initialize(core, ty_crate)).map_err(|e| {
consortium_log::trace!("firmware init codegen render failed for {}: {}", core, e);
PipelineError::Codegen(e.to_string())
})?;
plans.push(FirmwareBuildPlan {
core,
party,
config: FirmwareConfiguration {
target,
path,
release: cargo.release,
offline: cargo.offline,
remoteproc: ir.remoteproc.get(&core).cloned(),
strict_remoteproc: true,
workspace: Some(workspace.clone()),
},
generated,
});
}
Ok(plans)
}
pub async fn firmware_elf_path(config: &FirmwareConfiguration) -> Result<PathBuf, PipelineError> {
crate::compile::utils::elf_path(
&config.path,
config.workspace.as_ref(),
&config.target,
config.release,
)
.await
.map_err(PipelineError::Codegen)
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
fn session() -> Session {
Session::from_toml(STM32).expect("example manifest loads")
}
#[test]
fn remote_coprocessors_excludes_managers() {
let session = session();
let remote: Vec<_> = remote_coprocessors(&session.database)
.map(|cp| cp.name.clone())
.collect();
assert!(remote.iter().any(|n| n == "cm33"));
assert!(!remote.iter().any(|n| n == "cm0p"));
}
#[test]
fn plans_one_build_per_remote_endpoint() {
let session = session();
let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
let cargo = CargoOptions {
release: true,
offline: true,
};
let workspace = crate::pipeline::resolve_workspace(&session, Path::new("/proj"))
.expect("nested workspace");
let plans = plan_firmware_builds(&session, &workspace, cargo, &ty_crate).expect("plans");
assert_eq!(plans.len(), 1);
let plan = &plans[0];
assert_eq!(plan.core, VisibleCore::Mcore("cm33".parse().unwrap()));
assert_eq!(plan.config.target, "thumbv8m.main-none-eabihf");
assert_eq!(plan.config.path, Path::new("/proj/firmware"));
assert_eq!(
workspace.mcu_dir.as_deref(),
Some(Path::new("/proj/firmware"))
);
assert!(plan.config.release);
assert!(plan.config.offline);
assert!(plan.generated.contains("ConsortiumLogger::init("));
let remoteproc = plan.config.remoteproc.as_ref().expect("remoteproc layout");
assert_eq!(remoteproc.flash.origin, 0x8010_1000);
assert_eq!(remoteproc.ram.origin, 0x80a0_0000);
assert_eq!(remoteproc.vector_section, ".isr_vectors");
assert!(
plan.generated
.contains("fn init(config: Config) -> (Peripherals, Channels)"),
"{}",
plan.generated
);
assert!(
plan.generated.contains("channels!(SENSOR)"),
"{}",
plan.generated
);
}
}