| // Copyright 2022 The ChromiumOS Authors. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use camino::{Utf8Path, Utf8PathBuf}; |
| use serde::Deserialize; |
| use std::{env, fs, process}; |
| |
| #[derive(Clone, Copy, Debug, PartialEq)] |
| enum Target { |
| UefiI686, |
| UefiX86_64, |
| Host, |
| } |
| |
| impl Target { |
| /// Read the target from the `TARGET` env var. |
| fn from_env() -> Target { |
| let target = env::var("TARGET").unwrap(); |
| match target.as_str() { |
| "i686-unknown-uefi" => Self::UefiI686, |
| "x86_64-unknown-uefi" => Self::UefiX86_64, |
| // For everything else, assume it's a host build |
| // (e.g. "cargo test"). |
| _ => Self::Host, |
| } |
| } |
| |
| /// True if this is a UEFI target, false if it's a host target. |
| fn is_uefi(self) -> bool { |
| match self { |
| Self::UefiI686 | Self::UefiX86_64 => true, |
| Self::Host => false, |
| } |
| } |
| |
| /// Get a target triple to override the default C compiler |
| /// target. Returns None if this is a host build so that the default |
| /// target is used in that case. |
| fn c_target_override(self) -> Option<&'static str> { |
| match self { |
| // UEFI target builds. There are a couple reasons why these are |
| // "-windows-gnu" rather than just "-windows": |
| // |
| // 1. The 32-bit target must be i686-unknown-windows-gnu rather than |
| // just i686-unknown-windows due to a missing intrinsic. See the |
| // long comment in |
| // compiler/rustc_target/src/spec/i686_unknown_uefi.rs in the |
| // rustlang repo for details. |
| // |
| // 2. It's easier to get the appropriate standard C headers for |
| // these targets with "-windows-gnu", see README.md for the apt |
| // packages containing these headers. |
| Self::UefiI686 => Some("i686-unknown-windows-gnu"), |
| Self::UefiX86_64 => Some("x86_64-unknown-windows-gnu"), |
| Self::Host => None, |
| } |
| } |
| |
| fn vboot_build_subdir(self) -> &'static str { |
| self.c_target_override().unwrap_or("host") |
| } |
| |
| fn fw_arch(self) -> &'static str { |
| match self { |
| Self::UefiI686 => "i386", |
| Self::UefiX86_64 | Self::Host => "amd64", |
| } |
| } |
| } |
| |
| fn rerun_if_changed<P: AsRef<Utf8Path>>(path: P) { |
| println!("cargo:rerun-if-changed={}", path.as_ref()); |
| } |
| |
| /// Build vboot_reference's fwlib. |
| fn build_vboot_fwlib(vboot_ref: &Utf8Path, target: Target) { |
| let out_dir = Utf8PathBuf::from(env::var("OUT_DIR").unwrap()); |
| let vboot_build_dir = out_dir |
| .join("vboot_fw_build") |
| .join(target.vboot_build_subdir()); |
| |
| let mut cflags = "-I../../vboot/src/libc".to_string(); |
| if let Some(target) = target.c_target_override() { |
| cflags = format!("{} --target={}", cflags, target); |
| } |
| if target == Target::Host { |
| cflags += " -fPIC"; |
| } |
| println!("CFLAGS={}", cflags); |
| |
| let mut make_cmd = process::Command::new("make"); |
| make_cmd |
| .env("CFLAGS", cflags) |
| .arg("-C") |
| .arg(vboot_ref) |
| .arg("V=1") |
| .arg("CC=clang") |
| .arg(format!("FIRMWARE_ARCH={}", target.fw_arch())) |
| .arg(format!("BUILD={}", vboot_build_dir)) |
| .arg("fwlib"); |
| println!("{:?}", make_cmd); |
| let status = make_cmd.status().expect("failed to launch make"); |
| if !status.success() { |
| panic!("make failed"); |
| } |
| |
| // Rename the vboot_fw library to match the pattern expected for the |
| // current target, then tell cargo to link that library in. |
| if target.is_uefi() { |
| fs::copy( |
| vboot_build_dir.join("vboot_fw.a"), |
| vboot_build_dir.join("vboot_fw.lib"), |
| ) |
| .unwrap(); |
| println!("cargo:rustc-link-lib={}", vboot_build_dir.join("vboot_fw")); |
| } else { |
| fs::copy( |
| vboot_build_dir.join("vboot_fw.a"), |
| vboot_build_dir.join("libvboot_fw.a"), |
| ) |
| .unwrap(); |
| println!("cargo:rustc-link-search={}", vboot_build_dir); |
| println!("cargo:rustc-link-lib=static=vboot_fw"); |
| } |
| } |
| |
| /// Build a small C library to help bridge the Rust code in this crate |
| /// with vboot_reference's fwlib. |
| fn build_bridge_lib( |
| vboot_ref: &Utf8Path, |
| include_dirs: &[Utf8PathBuf], |
| target: Target, |
| ) { |
| let firmware = vboot_ref.join("firmware"); |
| let source_files = [ |
| "src/bridge.c".into(), |
| // Stubs |
| firmware.join("2lib/2stub_hwcrypto.c"), |
| firmware.join("stub/vboot_api_stub_stream.c"), |
| ]; |
| |
| for path in &source_files { |
| rerun_if_changed(path); |
| } |
| |
| let mut builder = cc::Build::new(); |
| builder |
| .compiler("clang") |
| .flag("-Wno-address-of-packed-member") |
| .flag("-Wno-int-to-pointer-cast") |
| .flag("-Wno-sign-compare") |
| .flag("-Wno-unused-parameter") |
| .warnings_into_errors(true) |
| .includes(include_dirs) |
| .files(source_files); |
| if let Some(target) = target.c_target_override() { |
| builder.target(target); |
| } |
| builder.compile("vboot_bridge"); |
| } |
| |
| fn gen_fwlib_bindings(include_dirs: &[Utf8PathBuf], target: Target) { |
| let header_path = "src/bindgen.h"; |
| |
| rerun_if_changed(header_path); |
| |
| let mut builder = bindgen::Builder::default(); |
| builder = builder |
| .header(header_path) |
| .allowlist_function("crdyboot_set_kernel_key") |
| .allowlist_function("vb2_workbuf_alloc") |
| .allowlist_function("vb2_workbuf_from_ctx") |
| .allowlist_function("vb2api_init") |
| .allowlist_function("vb2api_inject_kernel_subkey") |
| .allowlist_function("vb2api_load_kernel") |
| .allowlist_type("vb2_context") |
| .allowlist_type("vb2_disk_info") |
| .allowlist_type("vb2_return_code") |
| .allowlist_type("vb2_workbuf") |
| .allowlist_var("CROS_CONFIG_SIZE") |
| .allowlist_var("CROS_PARAMS_SIZE") |
| .allowlist_var("VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE") |
| .default_enum_style(bindgen::EnumVariation::NewType { |
| is_bitfield: false, |
| }) |
| .translate_enum_integer_types(true) |
| // Block-listing these types avoids some unnecessary |
| // generation of ctype typedefs. |
| .blocklist_type("__uint8_t") |
| .blocklist_type("__uint16_t") |
| .blocklist_type("__uint32_t") |
| .blocklist_type("__uint64_t") |
| .use_core() |
| .ctypes_prefix("cty") |
| // Turn off a bunch of layout tests because they generate |
| // "reference to packed field is unaligned" warnings. |
| .layout_tests(false) |
| .parse_callbacks(Box::new(bindgen::CargoCallbacks)); |
| |
| if let Some(target) = target.c_target_override() { |
| builder = builder.clang_arg(format!("--target={}", target)); |
| } |
| |
| for include_dir in include_dirs { |
| builder = builder.clang_arg(format!("-I{}", include_dir)); |
| } |
| |
| // Not sure why, but setting the sysroot is needed for the clang |
| // windows targets. And it must not be set for the host target as |
| // it causes compilation to fail there. |
| if target.is_uefi() { |
| builder = builder.clang_args(&["--sysroot", "/usr"]); |
| } |
| |
| let bindings = builder.generate().expect("Unable to generate bindings"); |
| |
| let out_path = Utf8PathBuf::from(env::var("OUT_DIR").unwrap()); |
| bindings |
| .write_to_file(out_path.join("vboot_bindgen.rs")) |
| .expect("Couldn't write bindings!"); |
| } |
| |
| /// Clang AST node. |
| #[derive(Debug, Default, Deserialize)] |
| #[serde(default)] |
| struct AstNode { |
| kind: String, |
| name: Option<String>, |
| #[serde(default)] |
| inner: Vec<AstNode>, |
| } |
| |
| /// Generate a Rust function that converts from a vb2_return_code integer |
| /// value to a somewhat human-readable string. |
| fn gen_return_code_strings(vboot_ref: &Utf8Path) { |
| let header_path = vboot_ref.join("firmware/2lib/include/2return_codes.h"); |
| rerun_if_changed(&header_path); |
| |
| // Use clang to get an AST in JSON. |
| let output = process::Command::new("clang") |
| .args(&[ |
| "-Xclang", |
| "-ast-dump=json", |
| "-fsyntax-only", |
| header_path.as_str(), |
| ]) |
| .output() |
| .expect("failed to get AST (clang not found)"); |
| assert!(output.status.success(), "failed to get AST (clang failed)"); |
| |
| // Parse the JSON. |
| let ast: AstNode = serde_json::from_slice(&output.stdout) |
| .expect("failed to parse AST JSON"); |
| |
| // Find the vb2_return_code enum. |
| let enum_node = ast |
| .inner |
| .iter() |
| .find(|node| { |
| node.kind == "EnumDecl" |
| && node.name.as_deref() == Some("vb2_return_code") |
| }) |
| .expect("failed to find vb2_return_code"); |
| |
| // Get an iterator of enum member names. |
| let members = enum_node.inner.iter().map(|node| { |
| assert_eq!(node.kind, "EnumConstantDecl"); |
| node.name.as_ref().expect("missing node name") |
| }); |
| |
| // Generate conversion code. |
| let mut lines = vec![ |
| "pub fn return_code_to_str(code: vb2_return_code) -> &'static str {" |
| .to_string(), |
| ]; |
| for name in members { |
| lines.push(format!(" if code == vb2_return_code::{} {{", name)); |
| lines.push(format!(" return \"{}\";", name)); |
| lines.push(" }".to_string()); |
| } |
| lines.push(" \"unknown return code\"".to_string()); |
| lines.push("}".to_string()); |
| |
| // Write to a file. |
| let out_dir = Utf8PathBuf::from(env::var("OUT_DIR").unwrap()); |
| let out_path = out_dir.join("vboot_return_codes.rs"); |
| fs::write(&out_path, lines.join("\n")).unwrap(); |
| } |
| |
| fn main() { |
| let vboot_ref = Utf8Path::new("../third_party/vboot_reference"); |
| |
| let include_dirs = vec![ |
| Utf8PathBuf::from("src"), |
| Utf8PathBuf::from("src/libc"), |
| vboot_ref.to_path_buf(), |
| vboot_ref.join("firmware/2lib/include"), |
| vboot_ref.join("firmware/include"), |
| vboot_ref.join("firmware/lib/include"), |
| ]; |
| |
| let target = Target::from_env(); |
| |
| build_vboot_fwlib(vboot_ref, target); |
| build_bridge_lib(vboot_ref, &include_dirs, target); |
| |
| gen_fwlib_bindings(&include_dirs, target); |
| gen_return_code_strings(vboot_ref); |
| } |