blob: cc80ad850a65287085bd2820d4ac394659da5ca9 [file] [log] [blame]
// 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 <lib/boot-options/boot-options.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 <stdio.h>
#include <string-file.h>
#include <zircon/assert.h>
#include <ktl/algorithm.h>
#include <phys/allocation.h>
#include <phys/handoff.h>
#include <phys/symbolize.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);
}
} // namespace
void HandoffPrep::Init(ktl::span<ktl::byte> buffer) {
// TODO(fxbug.dev/84107): 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() {
ZX_DEBUG_ASSERT(gSymbolize);
// Publish llvm-profdata if present.
LlvmProfdata profdata;
profdata.Init(gSymbolize->BuildId());
if (profdata.size_bytes() != 0) {
fbl::AllocChecker ac;
ktl::span buffer = New(handoff()->instrumentation.llvm_profdata, ac, profdata.size_bytes());
ZX_ASSERT_MSG(ac.check(), "cannot allocate %zu bytes for llvm-profdata", profdata.size_bytes());
ZX_DEBUG_ASSERT(buffer.size() == profdata.size_bytes());
// Copy the fixed data and initial counter values and then start updating
// the handoff data in place.
ktl::span counters = profdata.WriteFixedData(buffer);
profdata.CopyCounters(counters);
LlvmProfdata::UseCounters(counters);
}
// Collect the symbolizer logging, including logs for each nonempty dump.
SetSymbolizerLog({
{
.announce = LlvmProfdata::kAnnounce,
.sink_name = LlvmProfdata::kDataSinkName,
.vmo_name = "physboot.profraw",
.size_bytes = profdata.size_bytes(),
},
});
}
void HandoffPrep::SetSymbolizerLog(ktl::initializer_list<Debugdata> dumps) {
auto log_to = [dumps](FILE& file) {
Symbolize symbolize(ProgramName(), &file);
symbolize.Context();
for (const Debugdata& dump : dumps) {
if (dump.size_bytes != 0) {
symbolize.DumpFile(dump.sink_name, dump.vmo_name, dump.announce, dump.size_bytes);
}
}
};
// First generate the symbolzer log text just to count its size.
struct CountFile {
public:
size_t size() const { return size_; }
int Write(ktl::string_view str) {
size_ += str.size();
return static_cast<int>(str.size());
}
private:
size_t size_ = 0;
};
CountFile counter;
FILE count_file(&counter);
log_to(count_file);
// Now we can allocate the handoff buffer for that data.
fbl::AllocChecker ac;
ktl::span buffer = New(handoff()->instrumentation.symbolizer_log, ac, counter.size() + 1);
ZX_ASSERT_MSG(ac.check(), "cannot allocate %zu bytes for symbolizer log", counter.size());
// Finally, generate the same text again to fill the buffer.
StringFile buffer_file(buffer);
log_to(buffer_file);
// We had to add an extra char to the buffer since StringFile wants to
// NUL-terminate it. But we don't want the NUL, so make it whitespace.
ktl::move(buffer_file).take().back() = '\n';
}
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;
}