Integrate libfdt and add rust wrappers

Booting Android/Linux requires to pass additional platform specific
information such as ramdisk load address range and bootargs via FDT.
Port and integrate third party libfdt C library for this task.

Bug: 305093905

Change-Id: I2fdc6665795674188aefd45e02658817681cba7c
diff --git a/gbl/efi/BUILD b/gbl/efi/BUILD
index 034cc40..a17be8e 100644
--- a/gbl/efi/BUILD
+++ b/gbl/efi/BUILD
@@ -25,6 +25,7 @@
     srcs = [
         "src/main.rs",
         "src/riscv64.rs",
+        "src/utils.rs",
     ],
     linker_script = select({
         "@gbl//toolchain:gbl_rust_elf_riscv64": "@gbl//efi/arch/riscv64:riscv64_efi.lds",
@@ -36,6 +37,7 @@
     ],
     deps = [
         "@gbl//libefi",
+        "@gbl//libfdt",
         "@gbl//libstorage",
         "@gbl//third_party/libzbi",
     ] + select({
diff --git a/gbl/efi/src/main.rs b/gbl/efi/src/main.rs
index 6a0c569..1523bba 100644
--- a/gbl/efi/src/main.rs
+++ b/gbl/efi/src/main.rs
@@ -18,87 +18,24 @@
 #[cfg(target_arch = "riscv64")]
 mod riscv64;
 
+use core::ffi::CStr;
 use core::fmt::Write;
-use gbl_storage::{required_scratch_size, BlockDevice, Gpt, StorageError};
+use core::str::from_utf8;
+use gbl_storage::{required_scratch_size, BlockDevice, Gpt};
 
 use efi::defs::EfiSystemTable;
-use efi::{
-    initialize, BlockIoProtocol, DeviceHandle, DevicePathProtocol, DevicePathText,
-    DevicePathToTextProtocol, EfiEntry, EfiError, LoadedImageProtocol, Protocol,
-};
+use efi::{initialize, BlockIoProtocol, LoadedImageProtocol};
+use fdt::Fdt;
 
 // For the `vec!` macro
 #[macro_use]
 extern crate alloc;
 
-// Implement a block device on top of BlockIoProtocol
-struct EfiBlockDevice<'a>(Protocol<'a, BlockIoProtocol>);
+#[macro_use]
+mod utils;
+use utils::{get_device_path, get_efi_fdt, EfiBlockDevice, Result};
 
-impl BlockDevice for EfiBlockDevice<'_> {
-    fn block_size(&mut self) -> u64 {
-        self.0.media().unwrap().block_size as u64
-    }
-
-    fn num_blocks(&mut self) -> u64 {
-        (self.0.media().unwrap().last_block + 1) as u64
-    }
-
-    fn alignment(&mut self) -> u64 {
-        core::cmp::max(1, self.0.media().unwrap().io_align as u64)
-    }
-
-    fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool {
-        self.0.read_blocks(blk_offset, out).is_ok()
-    }
-
-    fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool {
-        self.0.write_blocks(blk_offset, data).is_ok()
-    }
-}
-
-/// A top level error type that consolidates errors from different libraries.
-#[derive(Debug)]
-enum GblEfiError {
-    StorageError(gbl_storage::StorageError),
-    EfiError(efi::EfiError),
-}
-
-impl From<StorageError> for GblEfiError {
-    fn from(error: StorageError) -> GblEfiError {
-        GblEfiError::StorageError(error)
-    }
-}
-
-impl From<EfiError> for GblEfiError {
-    fn from(error: EfiError) -> GblEfiError {
-        GblEfiError::EfiError(error)
-    }
-}
-
-/// Helper function to get the `DevicePathText` from a `DeviceHandle`.
-fn get_device_path<'a>(
-    entry: &'a EfiEntry,
-    handle: DeviceHandle,
-) -> Result<DevicePathText<'a>, GblEfiError> {
-    let bs = entry.system_table().boot_services();
-    let path = bs.open_protocol::<DevicePathProtocol>(handle)?;
-    let path_to_text = bs.find_first_and_open::<DevicePathToTextProtocol>()?;
-    Ok(path_to_text.convert_device_path_to_text(&path, false, false)?)
-}
-
-/// Helper macro for printing message via `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL` in
-/// `EFI_SYSTEM_TABLE.ConOut`.
-#[macro_export]
-macro_rules! efi_print {
-    ( $efi_entry:expr, $( $x:expr ),* ) => {
-        write!($efi_entry.system_table().con_out().unwrap(), $($x,)*).unwrap()
-    };
-}
-
-fn main(
-    image_handle: *mut core::ffi::c_void,
-    systab_ptr: *mut EfiSystemTable,
-) -> Result<(), GblEfiError> {
+fn main(image_handle: *mut core::ffi::c_void, systab_ptr: *mut EfiSystemTable) -> Result<()> {
     let entry = initialize(image_handle, systab_ptr);
 
     efi_print!(entry, "\n\n****Rust EFI Application****\n\n");
@@ -137,6 +74,28 @@
             }
         }
     }
+
+    // Check if we have a device tree.
+    match get_efi_fdt(&entry) {
+        Some((_, bytes)) => {
+            let fdt = Fdt::new(bytes)?;
+            efi_print!(entry, "Device tree found\n");
+            let print_property = |node: &str, name: &CStr| -> Result<()> {
+                efi_print!(
+                    entry,
+                    "{}:{} = {}\n",
+                    node,
+                    name.to_str().unwrap(),
+                    from_utf8(fdt.get_property(node, name).unwrap_or("NA".as_bytes())).unwrap()
+                );
+                Ok(())
+            };
+            print_property("/", CStr::from_bytes_with_nul(b"compatible\0").unwrap())?;
+            print_property("/chosen", CStr::from_bytes_with_nul(b"u-boot,version\0").unwrap())?;
+            print_property("/chosen", CStr::from_bytes_with_nul(b"bootargs\0").unwrap())?;
+        }
+        _ => {}
+    }
     Ok(())
 }
 
@@ -145,3 +104,6 @@
     main(image_handle, systab_ptr).unwrap();
     loop {}
 }
+
+#[no_mangle]
+pub extern "C" fn __chkstk() {}
diff --git a/gbl/efi/src/utils.rs b/gbl/efi/src/utils.rs
new file mode 100644
index 0000000..8e5494f
--- /dev/null
+++ b/gbl/efi/src/utils.rs
@@ -0,0 +1,111 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use efi::defs::EfiGuid;
+use efi::{
+    BlockIoProtocol, DeviceHandle, DevicePathProtocol, DevicePathText, DevicePathToTextProtocol,
+    EfiEntry, EfiError, Protocol,
+};
+use fdt::{FdtError, FdtHeader};
+use gbl_storage::{BlockDevice, StorageError};
+
+pub const EFI_DTB_TABLE_GUID: EfiGuid =
+    EfiGuid::new(0xb1b621d5, 0xf19c, 0x41a5, [0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0]);
+
+/// Helper macro for printing message via `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL` in
+/// `EFI_SYSTEM_TABLE.ConOut`.
+#[macro_export]
+macro_rules! efi_print {
+    ( $efi_entry:expr, $( $x:expr ),* ) => {
+        write!($efi_entry.system_table().con_out().unwrap(), $($x,)*).unwrap()
+    };
+}
+
+/// GBL EFI application error type.
+pub type Result<T> = core::result::Result<T, GblEfiError>;
+
+/// A top level error type that consolidates errors from different libraries.
+#[derive(Debug)]
+pub enum GblEfiError {
+    StorageError(gbl_storage::StorageError),
+    EfiError(efi::EfiError),
+    FdtError(fdt::FdtError),
+}
+
+impl From<StorageError> for GblEfiError {
+    fn from(error: StorageError) -> GblEfiError {
+        GblEfiError::StorageError(error)
+    }
+}
+
+impl From<EfiError> for GblEfiError {
+    fn from(error: EfiError) -> GblEfiError {
+        GblEfiError::EfiError(error)
+    }
+}
+
+impl From<FdtError> for GblEfiError {
+    fn from(error: FdtError) -> GblEfiError {
+        GblEfiError::FdtError(error)
+    }
+}
+
+// Implement a block device on top of BlockIoProtocol
+pub struct EfiBlockDevice<'a>(pub Protocol<'a, BlockIoProtocol>);
+
+impl BlockDevice for EfiBlockDevice<'_> {
+    fn block_size(&mut self) -> u64 {
+        self.0.media().unwrap().block_size as u64
+    }
+
+    fn num_blocks(&mut self) -> u64 {
+        (self.0.media().unwrap().last_block + 1) as u64
+    }
+
+    fn alignment(&mut self) -> u64 {
+        core::cmp::max(1, self.0.media().unwrap().io_align as u64)
+    }
+
+    fn read_blocks(&mut self, blk_offset: u64, out: &mut [u8]) -> bool {
+        self.0.read_blocks(blk_offset, out).is_ok()
+    }
+
+    fn write_blocks(&mut self, blk_offset: u64, data: &[u8]) -> bool {
+        self.0.write_blocks(blk_offset, data).is_ok()
+    }
+}
+
+/// Helper function to get the `DevicePathText` from a `DeviceHandle`.
+pub fn get_device_path<'a>(
+    entry: &'a EfiEntry,
+    handle: DeviceHandle,
+) -> Result<DevicePathText<'a>> {
+    let bs = entry.system_table().boot_services();
+    let path = bs.open_protocol::<DevicePathProtocol>(handle)?;
+    let path_to_text = bs.find_first_and_open::<DevicePathToTextProtocol>()?;
+    Ok(path_to_text.convert_device_path_to_text(&path, false, false)?)
+}
+
+/// Find FDT from EFI configuration table.
+pub fn get_efi_fdt<'a>(entry: &'a EfiEntry) -> Option<(&FdtHeader, &[u8])> {
+    if let Some(config_tables) = entry.system_table().configuration_table() {
+        for table in config_tables {
+            if table.vendor_guid == EFI_DTB_TABLE_GUID {
+                // SAFETY: Buffer provided by EFI configuration table.
+                return unsafe { FdtHeader::from_raw(table.vendor_table as *const _).ok() };
+            }
+        }
+    }
+    None
+}
diff --git a/gbl/integration/aosp_u-boot-mainline/workspace.bzl b/gbl/integration/aosp_u-boot-mainline/workspace.bzl
index 425e76c..57ee8e9 100644
--- a/gbl/integration/aosp_u-boot-mainline/workspace.bzl
+++ b/gbl/integration/aosp_u-boot-mainline/workspace.bzl
@@ -81,6 +81,12 @@
 """,
     )
 
+    native.new_local_repository(
+        name = "libfdt_c",
+        path = "external/dtc/libfdt",
+        build_file = "@gbl//libfdt:BUILD.libfdt_c.bazel",
+    )
+
     # Following are third party rust crates dependencies.
 
     THIRD_PARTY_CRATES = [
diff --git a/gbl/libc/BUILD b/gbl/libc/BUILD
new file mode 100644
index 0000000..d2d7d23
--- /dev/null
+++ b/gbl/libc/BUILD
@@ -0,0 +1,34 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+rust_library(
+    name = "libc",
+    srcs = glob(["**/*.rs"]),
+    edition = "2021",
+)
+
+cc_library(
+    name = "headers",
+    hdrs = [
+        "include/stdlib.h",
+        "include/string.h",
+    ],
+    includes = ["include"],
+)
diff --git a/gbl/libc/include/stdlib.h b/gbl/libc/include/stdlib.h
new file mode 100644
index 0000000..f9e0726
--- /dev/null
+++ b/gbl/libc/include/stdlib.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDLIB_H__
+#define __STDLIB_H__
+
+int memcmp(const void* ptr1, const void* ptr2, size_t num);
+void* memmove(void* destination, const void* source, size_t num);
+
+#endif
diff --git a/gbl/libc/include/string.h b/gbl/libc/include/string.h
new file mode 100644
index 0000000..bee1457
--- /dev/null
+++ b/gbl/libc/include/string.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __STDLIB_STRING_H__
+#define __STDLIB_STRING_H__
+
+#include <stddef.h>
+
+size_t strlen(const char * str);
+void *memchr(const void *ptr, int ch, size_t count);
+char *strrchr(const char *str, int c);
+size_t strnlen(const char *s, size_t maxlen);
+
+#endif
diff --git a/gbl/libc/src/lib.rs b/gbl/libc/src/lib.rs
new file mode 100644
index 0000000..ab19c49
--- /dev/null
+++ b/gbl/libc/src/lib.rs
@@ -0,0 +1,69 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This library provides implementation for a few libc functions for building third party C
+//! libraries.
+
+#![no_std]
+
+use core::ptr::null_mut;
+use core::slice::from_raw_parts;
+
+/// void *memchr(const void *ptr, int ch, size_t count);
+#[no_mangle]
+pub extern "C" fn memchr(
+    ptr: *const core::ffi::c_void,
+    ch: core::ffi::c_int,
+    count: core::ffi::c_ulong,
+) -> *mut core::ffi::c_void {
+    assert!(!ptr.is_null());
+    let start = ptr as *const u8;
+    let target = (ch & 0xff) as u8;
+    for i in 0..count {
+        // SAFETY: Buffer is assumed valid and bounded by count.
+        let curr = unsafe { start.add(i.try_into().unwrap()) };
+        // SAFETY: Buffer is assumed valid and bounded by count.
+        if *unsafe { curr.as_ref().unwrap() } == target {
+            return curr as *mut _;
+        }
+    }
+    null_mut()
+}
+
+/// char *strrchr(const char *str, int c);
+#[no_mangle]
+pub extern "C" fn strrchr(
+    ptr: *const core::ffi::c_char,
+    ch: core::ffi::c_int,
+) -> *mut core::ffi::c_char {
+    assert!(!ptr.is_null());
+    // SAFETY: Input is a valid null terminated string.
+    let bytes = unsafe { core::ffi::CStr::from_ptr(ptr).to_bytes_with_nul() };
+    let target = (ch & 0xff) as u8;
+    for c in bytes.iter().rev() {
+        if *c == target {
+            return c as *const _ as *mut _;
+        }
+    }
+    null_mut()
+}
+
+/// size_t strnlen(const char *s, size_t maxlen);
+#[no_mangle]
+pub fn strnlen(s: *const core::ffi::c_char, maxlen: usize) -> usize {
+    match memchr(s as *const _, 0, maxlen.try_into().unwrap()) {
+        p if p.is_null() => maxlen,
+        p => (p as usize) - (s as usize),
+    }
+}
diff --git a/gbl/libefi/src/lib.rs b/gbl/libefi/src/lib.rs
index baac2f3..39d6014 100644
--- a/gbl/libefi/src/lib.rs
+++ b/gbl/libefi/src/lib.rs
@@ -51,6 +51,7 @@
 #![cfg_attr(not(test), no_std)]
 
 use core::ptr::null_mut;
+use core::slice::from_raw_parts;
 #[cfg(not(test))]
 use core::{fmt::Write, panic::PanicInfo};
 
@@ -150,7 +151,6 @@
 
 /// `SystemTable` provides methods for accessing fields in `EFI_SYSTEM_TABLE`.
 #[derive(Clone, Copy)]
-//pub struct SystemTable<'a>(&'a EfiEntry);
 pub struct SystemTable<'a> {
     efi_entry: &'a EfiEntry,
     table: &'a EfiSystemTable,
@@ -174,6 +174,20 @@
             self.efi_entry,
         ))
     }
+
+    /// Get the `EFI_SYSTEM_TABLE.ConfigurationTable` array.
+    pub fn configuration_table(&self) -> Option<&[EfiConfigurationTable]> {
+        match self.table.configuration_table.is_null() {
+            true => None,
+            // SAFETY: Non-null pointer to EFI configuration table.
+            false => unsafe {
+                Some(from_raw_parts(
+                    self.table.configuration_table,
+                    self.table.number_of_table_entries,
+                ))
+            },
+        }
+    }
 }
 
 /// `BootServices` provides methods for accessing various EFI_BOOT_SERVICES interfaces.
@@ -295,7 +309,7 @@
         Self {
             // SAFETY: Given correct UEFI firmware, non-null pointer points to valid memory.
             // The memory is owned by the objects.
-            handles: unsafe { core::slice::from_raw_parts(handles as *mut DeviceHandle, len) },
+            handles: unsafe { from_raw_parts(handles as *mut DeviceHandle, len) },
             efi_entry: efi_entry,
         }
     }
diff --git a/gbl/libfdt/BUILD b/gbl/libfdt/BUILD
new file mode 100644
index 0000000..6b833c4
--- /dev/null
+++ b/gbl/libfdt/BUILD
@@ -0,0 +1,90 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+load("@gbl//toolchain:gbl_toolchain.bzl", "link_static_cc_library")
+load("@gbl_llvm_prebuilts//:info.bzl", "LLVM_PREBUILTS_C_INCLUDE")
+load("@rules_rust//bindgen:bindgen.bzl", "rust_bindgen")
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+rust_bindgen(
+    name = "libfdt_c_bindgen",
+    bindgen_flags = [
+        "--ctypes-prefix",
+        "core::ffi",
+        "--use-core",
+        "--with-derive-custom-struct=fdt_header=AsBytes,FromBytes,FromZeroes,PartialEq",
+        "--allowlist-function",
+        "(fdt_.*)",
+        "--allowlist-type",
+        "(fdt_.*)",
+        "--raw-line",
+        """
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+#![allow(dead_code)]
+#![cfg_attr(not(test), no_std)]
+
+use zerocopy::{AsBytes, FromBytes, FromZeroes};
+""",
+    ],
+    cc_lib = "@libfdt_c",
+    # For x86_32, we need to explicitly specify 32bit architecture.
+    clang_flags = select({
+        "@gbl//toolchain:gbl_rust_uefi_x86_32": ["-m32"],
+        "//conditions:default": ["-m64"],
+    }) + ["-I{}".format(LLVM_PREBUILTS_C_INCLUDE)],
+    header = "@libfdt_c//:libfdt.h",
+)
+
+genrule(
+    name = "bindgen_source",
+    srcs = [":libfdt_c_bindgen"],
+    outs = ["src/libfdt_c_Def.rs"],
+    cmd = "cp $(SRCS) $(OUTS)",
+)
+
+rust_library(
+    name = "libfdt_c_def",
+    srcs = [":bindgen_source"],
+    crate_root = ":bindgen_source",
+    deps = ["@zerocopy"],
+)
+
+rust_library(
+    name = "libfdt",
+    srcs = ["src/lib.rs"],
+    crate_name = "fdt",
+    edition = "2021",
+    deps = [
+        ":libfdt_c_def",
+        ":libfdt_c_static",
+        "@gbl//libc",
+        "@zerocopy",
+    ],
+)
+
+rust_test(
+    name = "libfdt_test",
+    compile_data = ["@gbl//libfdt/test:test.dtb"],
+    crate = ":libfdt",
+)
+
+link_static_cc_library(
+    name = "libfdt_c_static",
+    cc_library = "@libfdt_c",
+)
diff --git a/gbl/libfdt/BUILD.libfdt_c.bazel b/gbl/libfdt/BUILD.libfdt_c.bazel
new file mode 100644
index 0000000..6a8fed4
--- /dev/null
+++ b/gbl/libfdt/BUILD.libfdt_c.bazel
@@ -0,0 +1,50 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+exports_files(glob(["**/*"]))
+
+cc_library(
+    name = "libfdt_c",
+    srcs = [
+        "acpi.c",
+        "fdt.c",
+        "fdt_addresses.c",
+        "fdt_check.c",
+        "fdt_empty_tree.c",
+        "fdt_overlay.c",
+        "fdt_ro.c",
+        "fdt_rw.c",
+        "fdt_strerror.c",
+        "fdt_sw.c",
+        "fdt_wip.c",
+    ],
+    hdrs = [
+        "fdt.h",
+        "libacpi.h",
+        "libfdt.h",
+        "libfdt_env.h",
+        "libfdt_internal.h",
+    ],
+    copts = [
+        "-Wno-int-conversion",
+        "-Wno-implicit-function-declaration",
+        "-Wno-pointer-integer-compare",
+    ],
+    includes = ["."],
+    deps = ["@gbl//libc:headers"],
+)
diff --git a/gbl/libfdt/src/lib.rs b/gbl/libfdt/src/lib.rs
new file mode 100644
index 0000000..da424f9
--- /dev/null
+++ b/gbl/libfdt/src/lib.rs
@@ -0,0 +1,394 @@
+// Copyright 2023, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! This library provides a few wrapper APIs for libfdt_c
+
+#![cfg_attr(not(test), no_std)]
+
+extern crate libc;
+
+use core::ffi::CStr;
+use core::mem::size_of;
+use core::slice::{from_raw_parts, from_raw_parts_mut};
+
+use libfdt_c_def::{
+    fdt_add_subnode_namelen, fdt_header, fdt_setprop, fdt_setprop_placeholder, fdt_strerror,
+    fdt_subnode_offset_namelen,
+};
+
+use zerocopy::{AsBytes, FromBytes, FromZeroes, Ref};
+
+/// libfdt error type.
+#[derive(Debug)]
+pub enum FdtError {
+    CLibError(&'static str),
+    FdtBytesNotMutable,
+    InvalidInput,
+    IntegerOverflow,
+}
+
+/// libfdt result type,
+pub type Result<T> = core::result::Result<T, FdtError>;
+
+/// Convert libfdt_c error code to Result
+fn map_result(code: core::ffi::c_int) -> Result<core::ffi::c_int> {
+    match code {
+        // SAFETY: Static null terminated string returned from libfdt_c API.
+        v if v < 0 => Err(FdtError::CLibError(unsafe {
+            core::ffi::CStr::from_ptr(fdt_strerror(v)).to_str().unwrap()
+        })),
+        v => Ok(v),
+    }
+}
+
+/// Check header and verified that totalsize does not exceed buffer size.
+fn fdt_check_header(fdt: &[u8]) -> Result<()> {
+    map_result(unsafe { libfdt_c_def::fdt_check_header(fdt.as_ptr() as *const _) })?;
+    match FdtHeader::from_bytes_ref(fdt)?.totalsize() <= fdt.len() {
+        true => Ok(()),
+        _ => Err(FdtError::InvalidInput),
+    }
+}
+
+/// Wrapper of fdt_add_subnode_namelen()
+fn fdt_add_subnode(
+    fdt: &mut [u8],
+    parent: core::ffi::c_int,
+    name: &str,
+) -> Result<core::ffi::c_int> {
+    // SAFETY: API from libfdt_c.
+    map_result(unsafe {
+        fdt_add_subnode_namelen(
+            fdt.as_mut_ptr() as *mut _,
+            parent,
+            name.as_ptr() as *const _,
+            name.len().try_into().map_err(|_| FdtError::IntegerOverflow)?,
+        )
+    })
+}
+
+/// Wrapper of fdt_subnode_offset_namelen()
+fn fdt_subnode_offset(
+    fdt: &[u8],
+    parent: core::ffi::c_int,
+    name: &str,
+) -> Result<core::ffi::c_int> {
+    // SAFETY: API from libfdt_c.
+    map_result(unsafe {
+        fdt_subnode_offset_namelen(
+            fdt.as_ptr() as *const _,
+            parent,
+            name.as_ptr() as *const _,
+            name.len().try_into().map_err(|_| FdtError::IntegerOverflow)?,
+        )
+    })
+}
+
+#[repr(transparent)]
+#[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes, PartialEq)]
+pub struct FdtHeader(fdt_header);
+
+impl FdtHeader {
+    /// Return the totalsize field.
+    pub fn totalsize(&self) -> usize {
+        u32::from_be(self.0.totalsize) as usize
+    }
+
+    /// Return the minimal size of the FDT. Disregard trailing free space.
+    pub fn actual_size(&self) -> usize {
+        u32::from_be(self.0.off_dt_strings)
+            .checked_add(u32::from_be(self.0.size_dt_strings))
+            .unwrap() as usize
+    }
+
+    /// Update the totalsize field.
+    pub fn set_totalsize(&mut self, value: u32) {
+        self.0.totalsize = value.to_be();
+    }
+
+    /// Cast a bytes into a reference of FDT header
+    pub fn from_bytes_ref(buffer: &[u8]) -> Result<&FdtHeader> {
+        Ok(Ref::<_, FdtHeader>::new_from_prefix(buffer)
+            .ok_or_else(|| FdtError::InvalidInput)?
+            .0
+            .into_ref())
+    }
+
+    /// Cast a bytes into a mutable reference of FDT header.
+    pub fn from_bytes_mut(buffer: &mut [u8]) -> Result<&mut FdtHeader> {
+        Ok(Ref::<_, FdtHeader>::new_from_prefix(buffer)
+            .ok_or_else(|| FdtError::InvalidInput)?
+            .0
+            .into_mut())
+    }
+
+    /// Get FDT header and raw bytes from a raw pointer.
+    ///
+    /// Caller should guarantee that
+    ///   1. `ptr` contains a valid FDT.
+    ///   2. The buffer remains valid as long as the returned references are in use.
+    pub unsafe fn from_raw(ptr: *const u8) -> Result<(&'static FdtHeader, &'static [u8])> {
+        map_result(unsafe { libfdt_c_def::fdt_check_header(ptr as *const _) })?;
+        let header_bytes = from_raw_parts(ptr, size_of::<FdtHeader>());
+        let header = Self::from_bytes_ref(header_bytes)?;
+        Ok((header, from_raw_parts(ptr, header.totalsize())))
+    }
+}
+
+/// Object for managing an FDT.
+pub struct Fdt<T>(T);
+
+/// Read only APIs.
+impl<T: AsRef<[u8]>> Fdt<T> {
+    pub fn new(init: T) -> Result<Self> {
+        fdt_check_header(init.as_ref())?;
+        Ok(Fdt(init))
+    }
+
+    pub fn header_ref(&self) -> Result<&FdtHeader> {
+        FdtHeader::from_bytes_ref(self.0.as_ref())
+    }
+
+    /// Returns the totalsize according to FDT header. Trailing free space is included.
+    pub fn size(&self) -> Result<usize> {
+        Ok(self.header_ref()?.totalsize())
+    }
+
+    /// Get a property from an existing node.
+    pub fn get_property<'a>(&'a self, path: &str, name: &CStr) -> Result<&'a [u8]> {
+        let node = self.find_node(path)?;
+        let mut len: core::ffi::c_int = 0;
+        // SAFETY: API from libfdt_c.
+        let ptr = unsafe {
+            libfdt_c_def::fdt_get_property(
+                self.0.as_ref().as_ptr() as *const _,
+                node,
+                name.to_bytes_with_nul().as_ptr() as *const _,
+                &mut len as *mut _,
+            )
+        };
+        // SAFETY: Buffer returned by API from libfdt_c.
+        match unsafe { ptr.as_ref() } {
+            // SAFETY: Buffer returned by API from libfdt_c.
+            Some(v) => Ok(unsafe {
+                from_raw_parts(
+                    v.data.as_ptr() as *const u8,
+                    u32::from_be(v.len).try_into().map_err(|_| FdtError::IntegerOverflow)?,
+                )
+            }),
+            _ => Err(map_result(len).unwrap_err()),
+        }
+    }
+
+    /// Find the offset of a node by a given node path.
+    fn find_node(&self, path: &str) -> Result<core::ffi::c_int> {
+        let mut curr: core::ffi::c_int = 0;
+        for name in path.split('/') {
+            if name.len() == 0 {
+                continue;
+            }
+            curr = fdt_subnode_offset(self.0.as_ref(), curr, name)?;
+        }
+        Ok(curr)
+    }
+}
+
+/// APIs when data can be modified.
+impl<T: AsMut<[u8]> + AsRef<[u8]>> Fdt<T> {
+    pub fn new_from_init(mut fdt: T, init: &[u8]) -> Result<Self> {
+        fdt_check_header(init)?;
+        // SAFETY: API from libfdt_c.
+        map_result(unsafe {
+            libfdt_c_def::fdt_move(
+                init.as_ptr() as *const _,
+                fdt.as_mut().as_ptr() as *mut _,
+                fdt.as_mut().len().try_into().map_err(|_| FdtError::IntegerOverflow)?,
+            )
+        })?;
+        let new_size: u32 = fdt.as_mut().len().try_into().map_err(|_| FdtError::IntegerOverflow)?;
+        let mut ret = Fdt::new(fdt)?;
+        ret.header_mut()?.set_totalsize(new_size);
+        Ok(ret)
+    }
+
+    /// Parse and get the FDT header.
+    fn header_mut(&mut self) -> Result<&mut FdtHeader> {
+        FdtHeader::from_bytes_mut(self.0.as_mut())
+    }
+
+    /// Reduce the total size field in the header to minimum that will fit existing content.
+    /// No more data can be added to the FDT. This should be called after all modification is
+    /// done and before passing to the kernel. This is to prevent kernel hang when FDT size is too
+    /// big.
+    pub fn shrink_to_fit(&mut self) -> Result<()> {
+        let actual = self.header_ref()?.actual_size();
+        self.header_mut()?.set_totalsize(actual.try_into().unwrap());
+        Ok(())
+    }
+
+    /// Set the value of a node's property. Create the node and property if it doesn't exist.
+    pub fn set_property(&mut self, path: &str, name: &CStr, val: &[u8]) -> Result<()> {
+        let node = self.find_or_add_node(path)?;
+        // SAFETY: API from libfdt_c.
+        map_result(unsafe {
+            fdt_setprop(
+                self.0.as_mut().as_mut_ptr() as *mut _,
+                node,
+                name.to_bytes_with_nul().as_ptr() as *const _,
+                val.as_ptr() as *const _,
+                val.len().try_into().map_err(|_| FdtError::IntegerOverflow)?,
+            )
+        })?;
+        Ok(())
+    }
+
+    /// Wrapper/equivalent of fdt_setprop_placeholder.
+    /// It creates/resizes a node's property to the given size and returns the buffer for caller
+    /// to modify content.
+    pub fn set_property_placeholder(
+        &mut self,
+        path: &str,
+        name: &CStr,
+        len: usize,
+    ) -> Result<&mut [u8]> {
+        let node = self.find_or_add_node(path)?;
+        let mut out_ptr: *mut u8 = core::ptr::null_mut();
+        // SAFETY: API from libfdt_c.
+        map_result(unsafe {
+            fdt_setprop_placeholder(
+                self.0.as_mut().as_mut_ptr() as *mut _,
+                node,
+                name.to_bytes_with_nul().as_ptr() as *const _,
+                len.try_into().map_err(|_| FdtError::IntegerOverflow)?,
+                &mut out_ptr as *mut *mut u8 as *mut _,
+            )
+        })?;
+        assert!(!out_ptr.is_null());
+        // SAFETY: Buffer returned by API from libfdt_c.
+        Ok(unsafe { from_raw_parts_mut(out_ptr, len) })
+    }
+
+    /// Find the offset of a node by a given node path. Add if node does not exist.
+    fn find_or_add_node(&mut self, path: &str) -> Result<core::ffi::c_int> {
+        let mut curr: core::ffi::c_int = 0;
+        for name in path.split('/') {
+            if name.len() == 0 {
+                continue;
+            }
+            curr = match fdt_subnode_offset(self.0.as_ref(), curr, name) {
+                Ok(v) => v,
+                _ => fdt_add_subnode(self.0.as_mut(), curr, name)?,
+            };
+        }
+        Ok(curr)
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::ffi::CString;
+
+    fn to_cstr(s: &str) -> CString {
+        CString::new(s).unwrap()
+    }
+
+    #[test]
+    fn test_new_from_invalid_fdt() {
+        let mut init = include_bytes!("../test/test.dtb").to_vec();
+        let mut fdt_buf = vec![0u8; init.len()];
+        // Invalid total size
+        assert!(Fdt::new_from_init(&mut fdt_buf[..], &init[..init.len() - 1]).is_err());
+        // Invalid FDT
+        init[..4].fill(0);
+        assert!(Fdt::new_from_init(&mut fdt_buf[..], &init[..]).is_err());
+    }
+
+    #[test]
+    fn test_get_property() {
+        let init = include_bytes!("../test/test.dtb").to_vec();
+        let mut fdt_buf = vec![0u8; init.len()];
+        let fdt = Fdt::new_from_init(&mut fdt_buf[..], &init[..]).unwrap();
+
+        assert_eq!(
+            CStr::from_bytes_with_nul(fdt.get_property("/", &to_cstr("info")).unwrap())
+                .unwrap()
+                .to_str()
+                .unwrap(),
+            "test device tree"
+        );
+        assert_eq!(
+            CStr::from_bytes_with_nul(
+                fdt.get_property("/dev-2/dev-2.2/dev-2.2.1", &to_cstr("property-1")).unwrap()
+            )
+            .unwrap()
+            .to_str()
+            .unwrap(),
+            "dev-2.2.1-property-1"
+        );
+
+        // Non eixsts
+        assert!(fdt.get_property("/", &to_cstr("non-exist")).is_err());
+    }
+
+    #[test]
+    fn test_set_property() {
+        let init = include_bytes!("../test/test.dtb").to_vec();
+        let mut fdt_buf = vec![0u8; init.len() + 512];
+        let mut fdt = Fdt::new_from_init(&mut fdt_buf[..], &init[..]).unwrap();
+        let data = vec![0x11u8, 0x22u8, 0x33u8];
+        fdt.set_property("/new-node", &to_cstr("custom"), &data).unwrap();
+        assert_eq!(fdt.get_property("/new-node", &to_cstr("custom")).unwrap().to_vec(), data);
+    }
+
+    #[test]
+    fn test_set_property_placeholder() {
+        let init = include_bytes!("../test/test.dtb").to_vec();
+        let mut fdt_buf = vec![0u8; init.len() + 512];
+        let mut fdt = Fdt::new_from_init(&mut fdt_buf[..], &init[..]).unwrap();
+        let data = vec![0x11u8, 0x22u8, 0x33u8, 0x44u8, 0x55u8];
+        let payload =
+            fdt.set_property_placeholder("/new-node", &to_cstr("custom"), data.len()).unwrap();
+        payload.clone_from_slice(&data[..]);
+        assert_eq!(fdt.get_property("/new-node", &to_cstr("custom")).unwrap().to_vec(), data);
+    }
+
+    #[test]
+    fn test_header_from_raw() {
+        let init = include_bytes!("../test/test.dtb").to_vec();
+        // Pointer points to `init`
+        let (header, bytes) = unsafe { FdtHeader::from_raw(init.as_ptr()).unwrap() };
+        assert_eq!(header.totalsize(), init.len());
+        assert_eq!(bytes.to_vec(), init);
+    }
+
+    #[test]
+    fn test_header_from_raw_invalid() {
+        let mut init = include_bytes!("../test/test.dtb").to_vec();
+        init[..4].fill(0);
+        // Pointer points to `init`
+        assert!(unsafe { FdtHeader::from_raw(init.as_ptr()).is_err() });
+    }
+
+    #[test]
+    fn test_fdt_shrink_to_fit() {
+        let init = include_bytes!("../test/test.dtb").to_vec();
+        let mut fdt_buf = vec![0u8; init.len() + 512];
+        let fdt_buf_len = fdt_buf.len();
+        let mut fdt = Fdt::new_from_init(&mut fdt_buf[..], &init[..]).unwrap();
+        assert_eq!(fdt.size().unwrap(), fdt_buf_len);
+        fdt.shrink_to_fit().unwrap();
+        assert_eq!(fdt.size().unwrap(), init.len());
+    }
+}
diff --git a/gbl/libfdt/test/BUILD b/gbl/libfdt/test/BUILD
new file mode 100644
index 0000000..6325cad
--- /dev/null
+++ b/gbl/libfdt/test/BUILD
@@ -0,0 +1,15 @@
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+exports_files(glob(["**/*"]))
diff --git a/gbl/libfdt/test/gen_test_dtb.sh b/gbl/libfdt/test/gen_test_dtb.sh
new file mode 100755
index 0000000..a514265
--- /dev/null
+++ b/gbl/libfdt/test/gen_test_dtb.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Copyright (C) 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e
+
+readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
+
+dtc -I dts -O dtb -o ${SCRIPT_DIR}/test.dtb ${SCRIPT_DIR}/test.dts
diff --git a/gbl/libfdt/test/test.dtb b/gbl/libfdt/test/test.dtb
new file mode 100644
index 0000000..4aa1685
--- /dev/null
+++ b/gbl/libfdt/test/test.dtb
Binary files differ
diff --git a/gbl/libfdt/test/test.dts b/gbl/libfdt/test/test.dts
new file mode 100644
index 0000000..f4df645
--- /dev/null
+++ b/gbl/libfdt/test/test.dts
@@ -0,0 +1,21 @@
+/dts-v1/;
+
+/ {
+	info = "test device tree";
+
+    dev-1 {};
+
+    dev-2 {
+        dev-2.1 {};
+
+        dev-2.2 {
+            dev-2.2.1 {
+                property-1 = "dev-2.2.1-property-1";
+            };
+        };
+
+        dev-1.3 {};
+    };
+
+    dev-3 {};
+};
diff --git a/gbl/tests/BUILD b/gbl/tests/BUILD
index 0529101..6cad017 100644
--- a/gbl/tests/BUILD
+++ b/gbl/tests/BUILD
@@ -16,6 +16,7 @@
     name = "tests",
     tests = [
         "@gbl//libefi:libefi_test",
+        "@gbl//libfdt:libfdt_test",
         "@gbl//libstorage:gbl_libstorage_test",
         "@gbl//third_party/libzbi:libzbi_test",
 
diff --git a/gbl/toolchain/BUILD b/gbl/toolchain/BUILD
index 2f9d5e3..1e4c741 100644
--- a/gbl/toolchain/BUILD
+++ b/gbl/toolchain/BUILD
@@ -173,7 +173,7 @@
     name = "x86_64_uefi_clang_cc_toolchain",
     cc_flags = cc_flags_common,
     target_cpu = "x86_64",
-    target_system_triple = "x86_64-windows-msvc",
+    target_system_triple = "x86_64-unknown-windows-msvc",
 )
 
 toolchain(
@@ -189,7 +189,7 @@
     name = "x86_32_uefi_clang_cc_toolchain",
     cc_flags = cc_flags_common,
     target_cpu = "x86_32",
-    target_system_triple = "x86-windows-msvc",
+    target_system_triple = "i686-unknown-windows-gnu",
 )
 
 toolchain(
diff --git a/gbl/toolchain/gbl_toolchain.bzl b/gbl/toolchain/gbl_toolchain.bzl
index 14583c0..41167da 100644
--- a/gbl/toolchain/gbl_toolchain.bzl
+++ b/gbl/toolchain/gbl_toolchain.bzl
@@ -25,7 +25,7 @@
     "tool_path",
 )
 load("@gbl_llvm_prebuilts//:info.bzl", "LLVM_PREBUILTS_C_INCLUDE", "gbl_llvm_tool_path")
-load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "ALL_CC_COMPILE_ACTION_NAMES")
+load("@rules_cc//cc:action_names.bzl", "ACTION_NAMES", "ALL_CPP_COMPILE_ACTION_NAMES")
 
 def _flag_set(flags):
     """Convert a list of compile/link flags to a flag_set type."""
@@ -40,6 +40,15 @@
 def _gbl_clang_cc_toolchain_config_impl(ctx):
     """Implementation for rule _gbl_clang_cc_toolchain_config()"""
     builtin_includes = ctx.attr.builtin_includes or [LLVM_PREBUILTS_C_INCLUDE]
+    common_compile_flagset = [
+        _flag_set([
+            "--target={}".format(ctx.attr.target_system_triple),
+            "-nostdinc",
+            "-no-canonical-prefixes",
+            "-Werror",
+            "-Wall",
+        ] + ["-I{}".format(inc) for inc in builtin_includes] + ctx.attr.cc_flags),
+    ]
     return cc_common.create_cc_toolchain_config_info(
         ctx = ctx,
         cxx_builtin_include_directories = builtin_includes,
@@ -56,17 +65,17 @@
                 action_name = action_name,
                 enabled = True,
                 tools = [tool(path = gbl_llvm_tool_path("clang++"))],
-                flag_sets = [
-                    _flag_set([
-                        "--target={}".format(ctx.attr.target_system_triple),
-                        "-nostdinc",
-                        "-no-canonical-prefixes",
-                        "-Werror",
-                        "-Wall",
-                    ] + ["-I{}".format(inc) for inc in builtin_includes] + ctx.attr.cc_flags),
-                ],
+                flag_sets = common_compile_flagset,
             )
-            for action_name in ALL_CC_COMPILE_ACTION_NAMES
+            for action_name in ALL_CPP_COMPILE_ACTION_NAMES +
+                               [ACTION_NAMES.assemble, ACTION_NAMES.preprocess_assemble]
+        ] + [
+            action_config(
+                action_name = ACTION_NAMES.c_compile,
+                enabled = True,
+                tools = [tool(path = gbl_llvm_tool_path("clang"))],
+                flag_sets = common_compile_flagset,
+            ),
         ] + [
             action_config(
                 action_name = ACTION_NAMES.cpp_link_executable,
@@ -248,3 +257,44 @@
         "deps": attr.label_list(allow_files = True, mandatory = True),
     },
 )
+
+# This rule creates symlink for a static library in both Linux/GNU and MSVC naming styles so that
+# rust linker is able to find it when building for them.
+#
+# When flag "-Clink-arg=-l<libname>" is passed to rustc, for Linux/GNU target platforms, the linker
+# looks for library named "lib<libname>.a", for MSVC target plaforms (i.e. UEFI), the linker looks
+# for library named "<libname>.lib". When bazel builds a cc_library target, it always outputs the
+# Linux/GNU naming style and therefore fails linking when building for UEFI targets.
+def _link_static_cc_library_impl(ctx):
+    # Put an underscore so that we don't need to deal with potential "lib" prefix from user
+    # provided name.
+    libname = "_{}".format(ctx.label.name)
+
+    # Create symlink for both naming styles.
+    out_msvc_style = ctx.actions.declare_file("{}.lib".format(libname))
+    ctx.actions.symlink(output = out_msvc_style, target_file = ctx.files.cc_library[0])
+    out_linux_style = ctx.actions.declare_file("lib{}.a".format(libname))
+    ctx.actions.symlink(output = out_linux_style, target_file = ctx.files.cc_library[0])
+
+    # Construct and return a `CcInfo` for this rule that includes the library to link, so that
+    # other rust_library/cc_library can depend directly on this target.
+    library_to_link = cc_common.create_library_to_link(
+        actions = ctx.actions,
+        # Either is fine, since both yield the same linker option by Bazel.
+        static_library = out_linux_style,
+    )
+    linking_input = cc_common.create_linker_input(
+        owner = ctx.label,
+        libraries = depset([library_to_link]),
+        # Make sure both symlinks are generated.
+        additional_inputs = depset([out_msvc_style, out_linux_style]),
+    )
+    linking_context = cc_common.create_linking_context(linker_inputs = depset([linking_input]))
+    return [CcInfo(linking_context = linking_context)]
+
+link_static_cc_library = rule(
+    implementation = _link_static_cc_library_impl,
+    attrs = {
+        "cc_library": attr.label(),  # The cc_library() target for the static library.
+    },
+)