1use flowey::node::prelude::RustRuntimeServices;
10use flowey::node::prelude::*;
11use std::env;
12use std::path::Path;
13use std::path::PathBuf;
14use std::thread;
15
16const SHRINKWRAP_REPO: &str = "https://git.gitlab.arm.com/tooling/shrinkwrap.git";
17const PLANE0_LINUX_REPO: &str = "https://github.com/jiong-microsoft/OHCL-Linux-Kernel.git";
19const PLANE0_LINUX_BRANCH: &str = "cca-dev";
20const CCA_CONFIGS: &[&str] = &["CONFIG_VIRT_DRIVERS", "CONFIG_ARM_CCA_GUEST"];
22const NINEP_CONFIGS: &[&str] = &[
23 "CONFIG_NET_9P",
24 "CONFIG_NET_9P_FD",
25 "CONFIG_NET_9P_VIRTIO",
26 "CONFIG_NET_9P_FS",
27];
28const HYPERV_CONFIGS: &[&str] = &[
29 "CONFIG_HYPERV",
30 "CONFIG_HYPERV_MSHV",
31 "CONFIG_MSHV",
32 "CONFIG_MSHV_VTL",
33 "CONFIG_HYPERV_VTL_MODE",
34];
35
36flowey_request! {
37 pub struct Params {
38 pub test_root: PathBuf,
40 pub openvmm_root: PathBuf,
41 pub done: WriteVar<SideEffect>,
42 }
43}
44
45new_simple_flow_node!(struct Node);
46
47fn enable_kernel_configs(
48 rt: &RustRuntimeServices<'_>,
49 group: &str,
50 configs: &[&str],
51) -> anyhow::Result<()> {
52 for config in configs {
54 flowey::shell_cmd!(rt, "./scripts/config --file .config --enable {config}")
55 .run()
56 .with_context(|| format!("Failed to enable {} kernel config {}", group, config))?;
57 }
58
59 Ok(())
60}
61
62fn make_target(
63 rt: &RustRuntimeServices<'_>,
64 arch: &str,
65 target: &str,
66 jobs: &str,
67) -> anyhow::Result<()> {
68 flowey::shell_cmd!(
69 rt,
70 "make ARCH={arch} CROSS_COMPILE=aarch64-linux-gnu- {target} -j{jobs}"
71 )
72 .run()
73 .with_context(|| format!("Failed to run `make {}`", target))?;
74 Ok(())
75}
76
77pub(crate) fn build_plane0_linux(
78 rt: &RustRuntimeServices<'_>,
79 plane0_linux: &Path,
80 plane0_image: &Path,
81) -> anyhow::Result<()> {
82 log::info!("Compiling Plane0 Linux kernel...");
83 rt.sh.change_dir(plane0_linux);
84
85 const ARCH: &str = "arm64";
86 const SINGLE_JOB: &str = "1";
87
88 log::info!("Running make defconfig...");
89 make_target(rt, ARCH, "defconfig", SINGLE_JOB)?;
90
91 log::info!("Enabling required kernel configurations...");
92 for (name, configs) in [
93 ("CCA", CCA_CONFIGS),
94 ("9P", NINEP_CONFIGS),
95 ("Hyper-V", HYPERV_CONFIGS),
96 ] {
97 enable_kernel_configs(rt, name, configs)?;
98 }
99
100 log::info!("Running make olddefconfig...");
101 make_target(rt, ARCH, "olddefconfig", SINGLE_JOB)?;
102
103 let jobs =
104 thread::available_parallelism().map_or_else(|_| "1".to_string(), |n| n.get().to_string());
105
106 log::info!("Building plane0 kernel image...");
107 make_target(rt, ARCH, "Image", &jobs)?;
108
109 anyhow::ensure!(
110 plane0_image.exists(),
111 "Plane0 kernel compilation appeared to succeed but image file was not found at {}",
112 plane0_image.display()
113 );
114
115 log::info!("Plane0 Linux kernel compiled successfully");
116 log::info!("Kernel image at: {}", plane0_image.display());
117 Ok(())
118}
119
120pub(crate) fn sync_shrinkwrap_overlay_assets(
127 openvmm_root: &Path,
128 shrinkwrap_dir: &Path,
129) -> anyhow::Result<()> {
130 let overlay_assets = [
131 (
132 openvmm_root.join("vmm_tests/cca_tests/test_data/cca_planes.yaml"),
133 shrinkwrap_dir.join("config/cca_planes.yaml"),
134 "planes.yaml",
135 ),
136 (
137 openvmm_root.join("vmm_tests/cca_tests/test_data/cca_realm_overlay.yaml"),
138 shrinkwrap_dir.join("config/cca_realm_overlay.yaml"),
139 "realm overlay config",
140 ),
141 (
142 openvmm_root.join("vmm_tests/cca_tests/test_data/cca_start_tmk.sh"),
143 shrinkwrap_dir.join("config/cca_start_tmk.sh"),
144 "Plane0 TMK launcher",
145 ),
146 ];
147
148 fs_err::create_dir_all(shrinkwrap_dir.join("config"))?;
149
150 for (src, dest, label) in overlay_assets {
151 if dest.is_file() {
152 if fs_err::read(&src)? == fs_err::read(&dest)? {
153 log::info!(
154 "{label} already exists at {} and matches source",
155 dest.display()
156 );
157 continue;
158 }
159
160 log::info!(
161 "{label} already exists at {} but differs from source; replacing it",
162 dest.display()
163 );
164 }
165
166 log::info!(
167 "Copying {label} from {} to {}",
168 src.display(),
169 dest.display()
170 );
171
172 fs_err::copy(&src, &dest)?;
173 }
174
175 Ok(())
176}
177
178pub(crate) fn build_cca_rootfs(
179 rt: &RustRuntimeServices<'_>,
180 test_root: &Path,
181 shrinkwrap_dir: &Path,
182 venv_dir: &Path,
183) -> anyhow::Result<()> {
184 let shrinkwrap_exe = shrinkwrap_dir.join("shrinkwrap/shrinkwrap");
185 anyhow::ensure!(
186 shrinkwrap_exe.exists(),
187 "shrinkwrap installation is missing or broken at {}, try --install-emu first",
188 shrinkwrap_exe.display()
189 );
190 anyhow::ensure!(
191 venv_dir.exists(),
192 "shrinkwrap venv is missing at {}, try --install-emu first",
193 venv_dir.display()
194 );
195
196 let log_dir = test_root.join("logs");
197 fs_err::create_dir_all(&log_dir)?;
198 let log_file = log_dir.join("shrinkwrap.build.log");
199
200 let path = format!(
201 "{}:{}",
202 venv_dir.join("bin").display(),
203 env::var("PATH").unwrap_or_default()
204 );
205
206 let rootfs = "${artifact:BUILDROOT}";
207 let tfa_revision = "8dae0862c502e08568a61a1050091fa9357f1240";
208 let cmd = format!(
209 "{} build cca-3world.yaml \
210 --overlay buildroot.yaml \
211 --overlay cca_realm_overlay.yaml \
212 --overlay cca_planes.yaml \
213 --btvar GUEST_ROOTFS={rootfs} \
214 --btvar TFA_REVISION={tfa_revision} \
215 2>&1 | tee {}",
216 shrinkwrap_exe.display(),
217 log_file.display()
218 );
219
220 flowey::shell_cmd!(rt, "bash -c {cmd}")
221 .env("VIRTUAL_ENV", venv_dir)
222 .env("PATH", path)
223 .run()
224 .with_context(|| "failed to do shrinkwrap build")?;
225
226 log::info!("shrinkwrap build finished, emulation env have been setup");
227 Ok(())
228}
229
230impl SimpleFlowNode for Node {
231 type Request = Params;
232
233 fn imports(ctx: &mut ImportCtx<'_>) {
234 ctx.import::<flowey_lib_common::git_checkout::Node>();
235 }
236
237 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
238 let Params {
239 test_root,
240 openvmm_root,
241 done,
242 } = request;
243 let plane0_linux = test_root.join("plane0-linux");
244 let shrinkwrap_dir = test_root.join("shrinkwrap");
245
246 let plane0_linux = if plane0_linux.exists() {
247 ReadVar::from_static(plane0_linux)
248 } else {
249 ctx.req(flowey_lib_common::git_checkout::Request::RegisterRepo {
250 repo_id: "cca-plane0-linux".into(),
251 repo_src: flowey_lib_common::git_checkout::RepoSource::LocalOnlyNewClone {
252 url: PLANE0_LINUX_REPO.into(),
253 path: plane0_linux,
254 ignore_existing_clone: false,
255 },
256 allow_persist_credentials: false,
257 depth: None,
258 pre_run_deps: Vec::new(),
259 });
260 ctx.reqv(|v| flowey_lib_common::git_checkout::Request::CheckoutRepo {
261 repo_id: ReadVar::from_static("cca-plane0-linux".into()),
262 repo_path: v,
263 persist_credentials: false,
264 })
265 };
266
267 let shrinkwrap_dir = if shrinkwrap_dir.exists() {
268 ReadVar::from_static(shrinkwrap_dir)
269 } else {
270 ctx.req(flowey_lib_common::git_checkout::Request::RegisterRepo {
271 repo_id: "shrinkwrap".into(),
272 repo_src: flowey_lib_common::git_checkout::RepoSource::LocalOnlyNewClone {
273 url: SHRINKWRAP_REPO.into(),
274 path: shrinkwrap_dir,
275 ignore_existing_clone: false,
276 },
277 allow_persist_credentials: false,
278 depth: None,
279 pre_run_deps: Vec::new(),
280 });
281 ctx.reqv(|v| flowey_lib_common::git_checkout::Request::CheckoutRepo {
282 repo_id: ReadVar::from_static("shrinkwrap".into()),
283 repo_path: v,
284 persist_credentials: false,
285 })
286 };
287
288 ctx.emit_rust_step("install cca emulation environment", |ctx| {
289 done.claim(ctx);
290 let plane0_linux = plane0_linux.claim(ctx);
291 let shrinkwrap_dir = shrinkwrap_dir.claim(ctx);
292 move |rt| {
293 fs_err::create_dir_all(&test_root)?;
295
296 let plane0_linux = rt.read(plane0_linux);
299 let plane0_image = plane0_linux
300 .join("arch")
301 .join("arm64")
302 .join("boot")
303 .join("Image");
304 rt.sh.change_dir(&plane0_linux);
305 flowey::shell_cmd!(rt, "git checkout {PLANE0_LINUX_BRANCH}").run()?;
306
307 if plane0_image.exists() {
309 log::info!(
310 "plane0 Linux image also has been built and found at: {}",
311 plane0_image.display()
312 );
313 } else {
314 build_plane0_linux(rt, &plane0_linux, &plane0_image)?;
315 }
316
317 let shrinkwrap_dir = rt.read(shrinkwrap_dir);
321 let venv_dir = shrinkwrap_dir.join("venv");
322 if !venv_dir.exists() {
323 log::info!(
324 "Creating Python virtual environment at {}",
325 venv_dir.display()
326 );
327 flowey::shell_cmd!(rt, "python3 -m venv")
328 .arg(&venv_dir)
329 .run()?;
330
331 log::info!("Installing Python dependencies...");
332 let pip = venv_dir.join("bin/pip");
333
334 flowey::shell_cmd!(rt, "{pip} install --upgrade pip").run()?;
335 flowey::shell_cmd!(rt, "{pip} install pyyaml termcolor tuxmake").run()?;
336 }
337
338 sync_shrinkwrap_overlay_assets(&openvmm_root, &shrinkwrap_dir)?;
339
340 let home_dir = env::var("HOME").map(PathBuf::from).expect("HOME not set");
341 let rootfs_file = home_dir.join(".shrinkwrap/package/cca-3world/rootfs.ext2");
342 if rootfs_file.exists() {
343 log::info!(
344 "cca emulation rootfs is already generated at: {}",
345 rootfs_file.display()
346 );
347 } else {
348 build_cca_rootfs(rt, &test_root, &shrinkwrap_dir, &venv_dir)?;
349 }
350
351 Ok(())
352 }
353 });
354
355 Ok(())
356 }
357}