| // Copyright 2021 The Fuchsia Authors |
| // |
| // Use of this source code is governed by a MIT-style |
| // license that can be found in the LICENSE file or at |
| // https://opensource.org/licenses/MIT |
| |
| #include "handoff-prep.h" |
| |
| #include <ctype.h> |
| #include <lib/boot-options/boot-options.h> |
| #include <lib/instrumentation/debugdata.h> |
| #include <lib/llvm-profdata/llvm-profdata.h> |
| #include <lib/memalloc/pool-mem-config.h> |
| #include <lib/memalloc/pool.h> |
| #include <lib/memalloc/range.h> |
| #include <lib/trivial-allocator/new.h> |
| #include <lib/zbitl/error-stdio.h> |
| #include <stdio.h> |
| #include <string-file.h> |
| #include <zircon/assert.h> |
| |
| #include <ktl/algorithm.h> |
| #include <phys/allocation.h> |
| #include <phys/elf-image.h> |
| #include <phys/handoff.h> |
| #include <phys/kernel-package.h> |
| #include <phys/main.h> |
| #include <phys/new.h> |
| #include <phys/symbolize.h> |
| |
| #include "log.h" |
| #include "physboot.h" |
| |
| #include <ktl/enforce.h> |
| |
| namespace { |
| |
| // Carve out some physical pages requested for testing before handing off. |
| void FindTestRamReservation(RamReservation& ram) { |
| ZX_ASSERT_MSG(!ram.paddr, "Must use kernel.test.ram.reserve=SIZE without ,ADDRESS!"); |
| |
| memalloc::Pool& pool = Allocation::GetPool(); |
| |
| // Don't just use Pool::Allocate because that will use the first (lowest) |
| // address with space. The kernel's PMM initialization doesn't like the |
| // earliest memory being split up too small, and anyway that's not very |
| // representative of just a normal machine with some device memory elsewhere, |
| // which is what the test RAM reservation is really meant to simulate. |
| // Instead, find the highest-addressed, most likely large chunk that is big |
| // enough and just make it a little smaller, which is probably more like what |
| // an actual machine with a little less RAM would look like. |
| |
| auto it = pool.end(); |
| while (true) { |
| if (it == pool.begin()) { |
| break; |
| } |
| --it; |
| if (it->type == memalloc::Type::kFreeRam && it->size >= ram.size) { |
| uint64_t aligned_start = (it->addr + it->size - ram.size) & -uint64_t{ZX_PAGE_SIZE}; |
| uint64_t aligned_end = aligned_start + ram.size; |
| if (aligned_start >= it->addr && aligned_end <= aligned_start + ram.size) { |
| if (pool.UpdateFreeRamSubranges(memalloc::Type::kTestRamReserve, aligned_start, ram.size) |
| .is_ok()) { |
| ram.paddr = aligned_start; |
| if (gBootOptions->phys_verbose) { |
| // Dump out the memory usage again to show the reservation. |
| printf("%s: Physical memory after kernel.test.ram.reserve carve-out:\n", ProgramName()); |
| pool.PrintMemoryRanges(ProgramName()); |
| } |
| return; |
| } |
| // Don't try another spot if something went wrong. |
| break; |
| } |
| } |
| } |
| |
| printf("%s: ERROR: Cannot reserve %#" PRIx64 |
| " bytes of RAM for kernel.test.ram.reserve request!\n", |
| ProgramName(), ram.size); |
| } |
| |
| // Returns a pointer into the array that was passed by reference. |
| constexpr ktl::string_view VmoNameString(const PhysVmo::Name& name) { |
| ktl::string_view str(name.data(), name.size()); |
| return str.substr(0, str.find_first_of('\0')); |
| } |
| |
| } // namespace |
| |
| void HandoffPrep::Init(ktl::span<ktl::byte> buffer) { |
| // TODO(https://fxbug.dev/42164859): Use the buffer inside the data ZBI via a |
| // SingleHeapAllocator. Later allocator() will return a real(ish) allocator. |
| allocator_.allocate_function() = AllocateFunction(buffer); |
| |
| fbl::AllocChecker ac; |
| handoff_ = new (allocator(), ac) PhysHandoff; |
| ZX_ASSERT_MSG(ac.check(), "handoff buffer too small for PhysHandoff!"); |
| } |
| |
| void HandoffPrep::SetInstrumentation() { |
| auto publish_debugdata = [this](ktl::string_view sink_name, ktl::string_view vmo_name, |
| ktl::string_view vmo_name_suffix, size_t content_size) { |
| PhysVmo::Name phys_vmo_name = |
| instrumentation::DebugdataVmoName(sink_name, vmo_name, vmo_name_suffix, /*is_static=*/true); |
| return PublishVmo(VmoNameString(phys_vmo_name), content_size); |
| }; |
| for (const ElfImage* module : gSymbolize->modules()) { |
| module->PublishDebugdata(publish_debugdata); |
| } |
| } |
| |
| ktl::span<ktl::byte> HandoffPrep::PublishVmo(ktl::string_view name, size_t content_size) { |
| if (content_size == 0) { |
| return {}; |
| } |
| fbl::AllocChecker ac; |
| HandoffVmo* handoff_vmo = new (gPhysNew<memalloc::Type::kPhysScratch>, ac) HandoffVmo; |
| ZX_ASSERT_MSG(ac.check(), "cannot allocate %zu scratch bytes for HandoffVmo", |
| sizeof(*handoff_vmo)); |
| |
| handoff_vmo->vmo.set_name(name); |
| |
| ktl::span buffer = New(handoff_vmo->vmo.data, ac, content_size); |
| ZX_ASSERT_MSG(ac.check(), "cannot allocate %zu bytes for %.*s", content_size, |
| static_cast<int>(name.size()), name.data()); |
| ZX_DEBUG_ASSERT(buffer.size() == content_size); |
| |
| vmos_.push_front(handoff_vmo); |
| return buffer; |
| } |
| |
| void HandoffPrep::FinishVmos() { |
| fbl::AllocChecker ac; |
| ktl::span phys_vmos = New(handoff()->vmos, ac, vmos_.size()); |
| ZX_ASSERT_MSG(ac.check(), "cannot allocate %zu * %zu-byte PhysVmo", vmos_.size(), |
| sizeof(PhysVmo)); |
| ZX_DEBUG_ASSERT(phys_vmos.size() == vmos_.size()); |
| |
| for (PhysVmo& phys_vmo : phys_vmos) { |
| phys_vmo = ktl::move(vmos_.pop_front()->vmo); |
| } |
| } |
| |
| BootOptions& HandoffPrep::SetBootOptions(const BootOptions& boot_options) { |
| fbl::AllocChecker ac; |
| BootOptions* handoff_options = New(handoff()->boot_options, ac, *gBootOptions); |
| ZX_ASSERT_MSG(ac.check(), "cannot allocate handoff BootOptions!"); |
| |
| if (handoff_options->test_ram_reserve) { |
| FindTestRamReservation(*handoff_options->test_ram_reserve); |
| } |
| |
| return *handoff_options; |
| } |
| |
| void HandoffPrep::PublishLog(ktl::string_view name, Log&& log) { |
| if (log.empty()) { |
| return; |
| } |
| |
| const size_t content_size = log.size_bytes(); |
| Allocation buffer = ktl::move(log).TakeBuffer(); |
| ZX_ASSERT(content_size <= buffer.size_bytes()); |
| |
| ktl::span copy = PublishVmo(name, content_size); |
| ZX_ASSERT(copy.size_bytes() == content_size); |
| memcpy(copy.data(), buffer.get(), content_size); |
| } |
| |
| void HandoffPrep::UsePackageFiles(const KernelStorage::Bootfs& kernel_package) { |
| SetVersionString(kernel_package); |
| } |
| |
| void HandoffPrep::SetVersionString(KernelStorage::Bootfs kernel_package) { |
| // Fetch the version-string.txt file from the package. |
| ktl::string_view version; |
| if (auto it = kernel_package.find("version-string.txt"); it == kernel_package.end()) { |
| if (auto result = kernel_package.take_error(); result.is_error()) { |
| zbitl::PrintBootfsError(result.error_value()); |
| } |
| ZX_PANIC("no version.txt file in kernel package"); |
| } else { |
| version = {reinterpret_cast<const char*>(it->data.data()), it->data.size()}; |
| } |
| constexpr ktl::string_view kSpace = " \t\r\n"; |
| size_t skip = version.find_first_not_of(kSpace); |
| size_t trim = version.find_last_not_of(kSpace); |
| if (skip == ktl::string_view::npos || trim == ktl::string_view::npos) { |
| ZX_PANIC("version.txt of %zu chars empty after trimming whitespace", version.size()); |
| } |
| trim = version.size() - (trim + 1); |
| version.remove_prefix(skip); |
| version.remove_suffix(trim); |
| |
| fbl::AllocChecker ac; |
| ktl::string_view installed = New(handoff_->version_string, ac, version); |
| if (!ac.check()) { |
| ZX_PANIC("cannot allocate %zu chars of handoff space for version string", version.size()); |
| } |
| ZX_ASSERT(installed == version); |
| if (gBootOptions->phys_verbose) { |
| if (skip + trim == 0) { |
| printf("%s: zx_system_get_version_string (%zu chars): %.*s\n", ProgramName(), version.size(), |
| static_cast<int>(version.size()), version.data()); |
| } else { |
| printf("%s: zx_system_get_version_string (%zu chars trimmed from %zu): %.*s\n", ProgramName(), |
| version.size(), version.size() + skip + trim, static_cast<int>(version.size()), |
| version.data()); |
| } |
| } |
| } |
| |
| [[noreturn]] void HandoffPrep::DoHandoff(UartDriver& uart, ktl::span<ktl::byte> zbi, |
| const KernelStorage::Bootfs& kernel_package, |
| const ArchPatchInfo& patch_info, |
| fit::inline_function<void(PhysHandoff*)> boot) { |
| // Hand off the boot options first, which don't really change. But keep a |
| // mutable reference to update boot_options.serial later to include live |
| // driver state and not just configuration like other BootOptions members do. |
| BootOptions& handoff_options = SetBootOptions(*gBootOptions); |
| |
| // Use the updated copy from now on. |
| gBootOptions = &handoff_options; |
| |
| UsePackageFiles(kernel_package); |
| |
| SummarizeMiscZbiItems(zbi); |
| gBootTimes.SampleNow(PhysBootTimes::kZbiDone); |
| |
| ArchHandoff(patch_info); |
| |
| SetInstrumentation(); |
| |
| // This transfers the log, so logging after this is not preserved. |
| // Extracting the log buffer will automatically detach it from stdout. |
| // TODO(mcgrathr): Rename to physboot.log with some prefix. |
| PublishLog("i/llvm-profile/s/physboot.log", ktl::move(*ktl::exchange(gLog, nullptr))); |
| |
| // Finalize the published VMOs, including the log just published above. |
| FinishVmos(); |
| |
| // Now that all time samples have been collected, copy gBootTimes into the |
| // hand-off. |
| handoff()->times = gBootTimes; |
| |
| // Copy any post-Init() serial state from the live driver here in physboot |
| // into the handoff BootOptions. There should be no more printing from here |
| // on. TODO(https://fxbug.dev/42164859): Actually there is some printing in BootZbi, |
| // but no current drivers carry post-Init() state so it's harmless for now. |
| uart.Visit([&handoff_options](const auto& driver) { handoff_options.serial = driver.uart(); }); |
| |
| boot(handoff()); |
| ZX_PANIC("HandoffPrep::DoHandoff boot function returned!"); |
| } |