[kernel][physboot] Handle legacy x86 trampoline booting

This makes use_physboot mode handle the legacy x86 kernel
that needs to be loaded at the 1M fixed address.

Bug: 32414, 32255
Test: fx set ... --args=use_physboot=true && fx run-zbi-test core-tests
Change-Id: I80b9de32417fb6681929e7590b5102be4a8c3acf
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/489403
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
Fuchsia-Auto-Submit: Roland McGrath <mcgrathr@google.com>
Reviewed-by: Joshua Seaton <joshuaseaton@google.com>
diff --git a/zircon/kernel/arch/x86/phys/boot-shim/BUILD.gn b/zircon/kernel/arch/x86/phys/boot-shim/BUILD.gn
index f054255..3bb51e3 100644
--- a/zircon/kernel/arch/x86/phys/boot-shim/BUILD.gn
+++ b/zircon/kernel/arch/x86/phys/boot-shim/BUILD.gn
@@ -70,7 +70,10 @@
 if (toolchain.environment == "kernel.phys" ||
     toolchain.environment == "kernel.phys32") {
   source_set("trampoline-boot") {
-    visibility = [ ":*" ]
+    visibility = [
+      ":*",
+      "//zircon/kernel/phys/*",
+    ]
     sources = [ "trampoline-boot.cc" ]
     public = [ "trampoline-boot.h" ]
     public_deps = [ "//zircon/kernel/phys:boot-zbi" ]
diff --git a/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.cc b/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.cc
index 5724e3d..a46e764 100644
--- a/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.cc
+++ b/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.cc
@@ -168,6 +168,8 @@
 }
 
 [[noreturn]] void TrampolineBoot::Boot() {
+  ZX_ASSERT(!MustRelocateDataZbi());
+
   uintptr_t entry = static_cast<uintptr_t>(KernelEntryAddress());
   ZX_ASSERT(entry == KernelEntryAddress());
 
diff --git a/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.h b/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.h
index f2fcd74..78e2b5c6 100644
--- a/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.h
+++ b/zircon/kernel/arch/x86/phys/boot-shim/trampoline-boot.h
@@ -16,13 +16,17 @@
 
   using BootZbi::BootZbi;
 
+  // In the legacy fixed-address format, the entry address is always above 1M.
+  // In the new format, it's an offset and in practice it's never > 1M.  So
+  // this is a safe-enough heuristic to distinguish the new from the old.
+  bool Relocating() const { return KernelHeader()->entry > kFixedLoadAddress; }
+
   uint64_t KernelEntryAddress() const {
-    // In the legacy fixed-address format, the entry address is always
-    // above 1M.  In the new format, it's an offset and in practice it's
-    // never > 1M.  So this is a safe-enough heuristic to distinguish the
-    // new format from the old.
-    uint64_t entry = KernelHeader()->entry;
-    return entry < kFixedLoadAddress ? BootZbi::KernelEntryAddress() : entry;
+    return Relocating() ? KernelHeader()->entry : BootZbi::KernelEntryAddress();
+  }
+
+  bool MustRelocateDataZbi() const {
+    return Relocating() && FixedKernelOverlapsData(kFixedLoadAddress);
   }
 
   fitx::result<Error> Load(uint32_t extra_data_capacity = 0);
diff --git a/zircon/kernel/phys/BUILD.gn b/zircon/kernel/phys/BUILD.gn
index 11ae459..b91d879 100644
--- a/zircon/kernel/phys/BUILD.gn
+++ b/zircon/kernel/phys/BUILD.gn
@@ -293,4 +293,8 @@
     "//zircon/system/ulib/pretty",
     "//zircon/system/ulib/zbitl",
   ]
+  if (current_cpu == "x64") {
+    deps += [ "//zircon/kernel/arch/x86/phys/boot-shim:trampoline-boot" ]
+    include_dirs = [ "//zircon/kernel/arch/x86/phys/boot-shim" ]
+  }
 }
diff --git a/zircon/kernel/phys/boot-zbi.cc b/zircon/kernel/phys/boot-zbi.cc
index 5003172..7451300 100644
--- a/zircon/kernel/phys/boot-zbi.cc
+++ b/zircon/kernel/phys/boot-zbi.cc
@@ -131,6 +131,14 @@
   return in_place_space >= KernelMemorySize();
 }
 
+bool BootZbi::FixedKernelOverlapsData(uint64_t kernel_load_address) const {
+  uint64_t start1 = kernel_load_address;
+  uint64_t start2 = reinterpret_cast<uintptr_t>(data_.storage().data());
+  uint64_t end1 = start1 + KernelMemorySize();
+  uint64_t end2 = start2 + data_.storage().size();
+  return start1 <= start2 ? start2 < end1 : start1 < end2;
+}
+
 fitx::result<BootZbi::Error> BootZbi::Load(uint32_t extra_data_capacity,
                                            ktl::optional<uintptr_t> kernel_load_address) {
   auto input_address = reinterpret_cast<uintptr_t>(zbi_.storage().data());
@@ -276,17 +284,11 @@
     };
   }
 
-  if (kernel_load_address) {
+  if (kernel_load_address && FixedKernelOverlapsData(*kernel_load_address)) {
     // There's a fixed kernel load address, so the data ZBI cannot be allowed
     // to reuse the memory where it will go.  This memory will already have
     // been reserved from the allocator, but the incoming data might be there.
-    uintptr_t start1 = *kernel_load_address;
-    size_t len1 = static_cast<size_t>(KernelMemorySize());
-    uintptr_t start2 = reinterpret_cast<uintptr_t>(data_.storage().data());
-    size_t len2 = data_.storage().size();
-    if (start1 <= start2 ? start1 + len1 > start2 : start1 < start2 + len2) {
-      data_.storage() = {};
-    }
+    data_.storage() = {};
   }
 
   // If we can reuse either the kernel image or the data ZBI items in place,
diff --git a/zircon/kernel/phys/include/phys/boot-zbi.h b/zircon/kernel/phys/include/phys/boot-zbi.h
index 65a68bf..6d42b65 100644
--- a/zircon/kernel/phys/include/phys/boot-zbi.h
+++ b/zircon/kernel/phys/include/phys/boot-zbi.h
@@ -51,6 +51,10 @@
   BootZbi(BootZbi&&) = default;
   BootZbi& operator=(BootZbi&&) = default;
 
+  // These are overridden in TrampolineBoot (see x86/phys/boot-shim).
+  bool Relocating() const { return false; }
+  bool MustRelocateDataZbi() const { return false; }
+
   // Suggest allocation parameters for a whole bootable ZBI image whose
   // incoming size is known but whose contents haven't been seen yet.  A
   // conforming allocation will be optimal for reuse by Load().
@@ -113,6 +117,8 @@
   void LogAddresses();
   void LogBoot(uint64_t entry) const;
 
+  bool FixedKernelOverlapsData(uint64_t kernel_load_address) const;
+
  private:
   // These are set on construction by Init().
   InputZbi zbi_;
diff --git a/zircon/kernel/phys/physboot.cc b/zircon/kernel/phys/physboot.cc
index 976b0a3..2ed4f09 100644
--- a/zircon/kernel/phys/physboot.cc
+++ b/zircon/kernel/phys/physboot.cc
@@ -22,6 +22,17 @@
 #include <phys/zbitl-allocation.h>
 #include <pretty/cpp/sizes.h>
 
+#ifdef __x86_64__
+#include "trampoline-boot.h"
+
+using ChainBoot = TrampolineBoot;
+
+#else
+
+using ChainBoot = BootZbi;
+
+#endif
+
 const char Symbolize::kProgramName_[] = "physboot";
 
 namespace {
@@ -32,7 +43,7 @@
 
 struct LoadedZircon {
   Allocation buffer;
-  BootZbi boot;
+  ChainBoot boot;
   arch::EarlyTicks decompress_ts;
 };
 
@@ -71,21 +82,28 @@
            pretty::FormattedBytes(kernel_zbi.size_bytes()).c_str());
   }
 
-  BootZbi boot;
+  ChainBoot boot;
   if (auto result = boot.Init(kernel_zbi); result.is_error()) {
     printf("physboot: Cannot read STORAGE_KERNEL item ZBI: ");
     zbitl::PrintViewCopyError(result.error_value());
     abort();
   }
 
+  if (auto result = boot.Load(reserve_memory_estimate); result.is_error()) {
+    printf("physboot: Cannot load decompressed kernel: ");
+    zbitl::PrintViewCopyError(result.error_value());
+    abort();
+  }
+
   return {std::move(buffer), std::move(boot), decompress_ts};
 }
 
 [[noreturn]] void BootZircon(BootZbi::InputZbi& zbi, BootZbi::InputZbi::iterator kernel_item,
                              arch::EarlyTicks entry_ts) {
   auto zircon = LoadZircon(zbi, kernel_item, kKernelBssEstimate);
-  if (!zircon.boot.KernelCanLoadInPlace() ||
-      zircon.buffer.size_bytes() < zircon.boot.KernelMemorySize()) {
+  if (!zircon.boot.Relocating() &&  //
+      (!zircon.boot.KernelCanLoadInPlace() ||
+       zircon.buffer.size_bytes() < zircon.boot.KernelMemorySize())) {
     printf("physboot: Kernel ZBI at %#" PRIx64 " cannot be loaded in place!\n",
            zircon.boot.KernelLoadAddress());
     uint64_t bss_size = zircon.boot.KernelHeader()->reserve_memory_size;
@@ -113,6 +131,28 @@
       zbi.storage().size(),
   };
 
+  Allocation relocated_zbi;
+  if (boot.MustRelocateDataZbi()) {
+    // Actually, the original data ZBI must be moved elsewhere since it
+    // overlaps the space where the fixed-address kernel will be loaded.
+    fbl::AllocChecker ac;
+    relocated_zbi = Allocation::New(ac, zbi.storage().size(), arch::kZbiBootDataAlignment);
+    if (!ac.check()) {
+      printf("physboot: Cannot allocate %#zx bytes aligned to %#zx for relocated data ZBI!\n",
+             zbi.storage().size(), arch::kZbiBootDataAlignment);
+      abort();
+    }
+    if (auto result = zbi.Copy(relocated_zbi.data(), zbi.begin(), zbi.end()); result.is_error()) {
+      zbi.ignore_error();
+      printf("physboot: Failed to relocate data ZBI: ");
+      zbitl::PrintViewCopyError(result.error_value());
+      printf("\n");
+      abort();
+    }
+    ZX_ASSERT(zbi.take_error().is_ok());
+    boot.DataZbi().storage() = relocated_zbi.data();
+  }
+
   boot.Boot();
 }