consortium_builder/pipeline/
firmware.rs1use std::path::PathBuf;
25
26use consortium_cfg::Session;
27use consortium_cfg_common::config::{Party, VisibleCore};
28use consortium_cfg_common::database::{ChipDatabase, Coprocessor, CoprocessorVariant};
29
30use super::{CargoOptions, PipelineError, core_rust_triple, visible_core_of};
31use crate::compile::env::CstiWorkspace;
32use crate::compile::firmware::FirmwareConfiguration;
33
34pub struct FirmwareBuildPlan {
36 pub core: VisibleCore,
38 pub party: Party,
40 pub config: FirmwareConfiguration,
42 pub generated: String,
45}
46
47pub fn remote_coprocessors(database: &ChipDatabase) -> impl Iterator<Item = &Coprocessor> {
49 database
50 .cp
51 .iter()
52 .filter(|cp| cp.variant == CoprocessorVariant::Remote)
53}
54
55pub fn firmware_endpoints(
62 session: &Session,
63) -> Result<Vec<(VisibleCore, Party, &str)>, PipelineError> {
64 let mut endpoints = Vec::new();
65 for cp in remote_coprocessors(&session.database) {
66 let core = visible_core_of(cp)?;
67 if let Some((party, root)) = session
69 .config
70 .endpoints
71 .iter()
72 .find(|(party, _)| party.core() == core)
73 {
74 endpoints.push((core, *party, root.as_str()));
75 }
76 }
77 Ok(endpoints)
78}
79
80pub fn plan_firmware_builds(
87 session: &Session,
88 workspace: &CstiWorkspace,
89 cargo: CargoOptions,
90 ty_crate: &syn::Path,
91) -> Result<Vec<FirmwareBuildPlan>, PipelineError> {
92 let ir = session.lower()?;
93 let mut plans = Vec::new();
94
95 for (core, party, root) in firmware_endpoints(session)? {
96 let path = workspace.manifest_dir.join(root);
97 let target = core_rust_triple(core);
98
99 consortium_log::trace!("rendering firmware init module for core {}", core);
103 let generated = consortium_cfg::render(ir.initialize(core, ty_crate)).map_err(|e| {
104 consortium_log::trace!("firmware init codegen render failed for {}: {}", core, e);
105 PipelineError::Codegen(e.to_string())
106 })?;
107
108 plans.push(FirmwareBuildPlan {
109 core,
110 party,
111 config: FirmwareConfiguration {
112 target,
113 path,
114 release: cargo.release,
115 offline: cargo.offline,
116 remoteproc: ir.remoteproc.get(&core).cloned(),
117 strict_remoteproc: true,
118 workspace: Some(workspace.clone()),
119 },
120 generated,
121 });
122 }
123
124 Ok(plans)
125}
126
127pub async fn firmware_elf_path(config: &FirmwareConfiguration) -> Result<PathBuf, PipelineError> {
129 crate::compile::utils::elf_path(
130 &config.path,
131 config.workspace.as_ref(),
132 &config.target,
133 config.release,
134 )
135 .await
136 .map_err(PipelineError::Codegen)
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use std::path::Path;
143
144 const STM32: &str = include_str!("../../../consortium-cfg/Consortium.example.stm32mp257.toml");
145
146 fn session() -> Session {
147 Session::from_toml(STM32).expect("example manifest loads")
148 }
149
150 #[test]
151 fn remote_coprocessors_excludes_managers() {
152 let session = session();
153 let remote: Vec<_> = remote_coprocessors(&session.database)
154 .map(|cp| cp.name.clone())
155 .collect();
156 assert!(remote.iter().any(|n| n == "cm33"));
158 assert!(!remote.iter().any(|n| n == "cm0p"));
159 }
160
161 #[test]
162 fn plans_one_build_per_remote_endpoint() {
163 let session = session();
164 let ty_crate: syn::Path = syn::parse_str("::shared").unwrap();
165 let cargo = CargoOptions {
166 release: true,
167 offline: true,
168 };
169 let workspace = crate::pipeline::resolve_workspace(&session, Path::new("/proj"))
170 .expect("nested workspace");
171 let plans = plan_firmware_builds(&session, &workspace, cargo, &ty_crate).expect("plans");
172
173 assert_eq!(plans.len(), 1);
175 let plan = &plans[0];
176 assert_eq!(plan.core, VisibleCore::Mcore("cm33".parse().unwrap()));
177 assert_eq!(plan.config.target, "thumbv8m.main-none-eabihf");
178 assert_eq!(plan.config.path, Path::new("/proj/firmware"));
179 assert_eq!(
181 workspace.mcu_dir.as_deref(),
182 Some(Path::new("/proj/firmware"))
183 );
184 assert!(plan.config.release);
185 assert!(plan.config.offline);
187 assert!(plan.generated.contains("ConsortiumLogger::init("));
190 let remoteproc = plan.config.remoteproc.as_ref().expect("remoteproc layout");
191 assert_eq!(remoteproc.flash.origin, 0x8010_1000);
192 assert_eq!(remoteproc.ram.origin, 0x80a0_0000);
193 assert_eq!(remoteproc.vector_section, ".isr_vectors");
194 assert!(
199 plan.generated
200 .contains("fn init(config: Config) -> (Peripherals, Channels)"),
201 "{}",
202 plan.generated
203 );
204 assert!(
205 plan.generated.contains("channels!(SENSOR)"),
206 "{}",
207 plan.generated
208 );
209 }
210}