blob: 5251de55bc24489c9c3dd6f622e4075e61ab283a [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 <lib/fit/result.h>
#include <lib/zbi-format/memory.h>
#include <lib/zircon-internal/e820.h>
#include <stdio.h>
#include <ktl/algorithm.h>
#include <ktl/byte.h>
#include <ktl/span.h>
#include <phys/address-space.h>
#include <phys/main.h>
#include <phys/symbolize.h>
#include "legacy-boot.h"
#include "linuxboot.h"
#include <ktl/enforce.h>
namespace {
zbi_mem_range_t FromE820(const E820Entry& in) {
zbi_mem_range_t out = {.paddr = in.addr, .length = in.size};
switch (in.type) {
case E820Type::kRam:
out.type = ZBI_MEM_TYPE_RAM;
break;
case E820Type::kReserved:
default:
// There are other E820Type values but none indicates usable RAM and
// none corresponds to ZBI_MEM_TYPE_PERIPHERAL.
out.type = ZBI_MEM_TYPE_RESERVED;
break;
}
return out;
}
// The E820 table corresponds directly to the zbi_mem_range_t table
// semantically (and nearly in format), except that E820 entries are only 20
// bytes long while zbi_mem_range_t entries are aligned properly for 64-bit
// use at 24 bytes long. So there isn't space to rewrite the data in place.
// However, the boot_params format has a fixed table size anyway, so a table
// in the shim's own bss can be used to store the normalized entries.
static_assert(sizeof(zbi_mem_range_t) > sizeof(E820Entry),
"could rewrite in place if entry sizes matched");
zbi_mem_range_t gMemRangesBuffer[linuxboot::kMaxE820TableEntries];
void PopulateMemRages(const linuxboot::boot_params& bp) {
if (bp.e820_entries > ktl::size(bp.e820_table)) {
printf("%s: e820_entries %zu exceeds format maximum %zu\n", ProgramName(),
static_cast<size_t>(bp.e820_entries), ktl::size(bp.e820_table));
}
ktl::span e820{
bp.e820_table,
ktl::min(static_cast<size_t>(bp.e820_entries), ktl::size(bp.e820_table)),
};
// Translate the entries directly.
size_t count = 0;
for (const E820Entry& in : e820) {
if (in.size > 0) {
gMemRangesBuffer[count++] = FromE820(in);
}
}
gLegacyBoot.mem_config = ktl::span(gMemRangesBuffer).subspan(0, count);
}
ktl::string_view GetBootloaderName(const linuxboot::boot_params& bp) {
static char bootloader_name[] = "Linux/x86 bzImage XXXX";
uint8_t loader = bp.hdr.type_of_loader & 0xf0u;
if (loader == 0xe0u) {
loader = bp.hdr.ext_loader_type + 0x10u;
}
uint8_t version =
(bp.hdr.type_of_loader & 0x0fu) + static_cast<uint8_t>(bp.hdr.ext_loader_ver << 4);
snprintf(&bootloader_name[sizeof(bootloader_name) - 5], 5, "%02hhx%02hhx", loader, version);
return {bootloader_name, sizeof(bootloader_name) - 1};
}
// This combines a 64-bit address stored in two separate 32-bit fields.
// If the actual address doesn't fit in uintptr_t, it returns ktl::nullopt.
// Note that a "successful" return might return nullptr.
template <typename T>
fit::result<uint64_t, T*> GetExtendedPtr(uint32_t low, uint32_t high) {
const uint64_t addr = (static_cast<uint64_t>(high) << 32) | low;
if (const uintptr_t ptr = static_cast<uintptr_t>(addr); ptr == addr) {
return fit::ok(reinterpret_cast<T*>(ptr));
}
return fit::error{addr};
}
fit::result<uint64_t, size_t> GetExtendedSize(uint32_t low, uint32_t high) {
const uint64_t value = (static_cast<uint64_t>(high) << 32) | low;
if (const size_t size = static_cast<size_t>(value); size == value) {
return fit::ok(size);
}
return fit::error{value};
}
} // namespace
LegacyBoot gLegacyBoot;
// This populates the allocator and also collects other information.
void InitMemory(const void* bootloader_data, ktl::optional<EarlyBootZbi> zbi,
AddressSpace* aspace) {
// A linuxboot shim should not have a ZBI encoding boot state at this point.
ZX_DEBUG_ASSERT(!zbi);
auto& bp = *static_cast<const linuxboot::boot_params*>(bootloader_data);
// Synthesize a boot loader name from the few bits we get.
gLegacyBoot.bootloader = GetBootloaderName(bp);
auto cmdline_ptr = GetExtendedPtr<const char>(bp.hdr.cmd_line_ptr, bp.ext_cmd_line_ptr);
if (cmdline_ptr.is_ok() && *cmdline_ptr) {
// The command line is NUL-terminated.
gLegacyBoot.cmdline = *cmdline_ptr;
}
auto ramdisk_image = GetExtendedPtr<ktl::byte>(bp.hdr.ramdisk_image, bp.ext_ramdisk_image);
auto ramdisk_size = GetExtendedSize(bp.hdr.ramdisk_size, bp.ext_ramdisk_size);
if (ramdisk_image.is_ok() && ramdisk_size.is_ok()) {
gLegacyBoot.ramdisk = {*ramdisk_image, *ramdisk_size};
}
gLegacyBoot.acpi_rsdp = bp.acpi_rsdp_addr;
// First translate the data into ZBI item format in gLegacyBoot.mem_config.
PopulateMemRages(bp);
// Now prime the allocator from that information and then set up paging.
LegacyBootInitMemory(aspace);
// That may have set up the console, so these messages might be seen.
if (cmdline_ptr.is_error()) {
printf("%s: WARNING: Linux command-line address %#" PRIx64
" too high for 32-bit code, ignored.",
ProgramName(), cmdline_ptr.error_value());
}
if (ramdisk_image.is_error()) {
printf("%s: WARNING: Linux initrd address %#" PRIx64 " too high for 32-bit code, ignored.",
ProgramName(), ramdisk_image.error_value());
}
if (ramdisk_size.is_error()) {
printf("%s: WARNING: Linux initrd size %#" PRIx64 " too big for 32-bit code, ignored.",
ProgramName(), ramdisk_size.error_value());
}
// Note this doesn't remove the memory covering the boot_params (zero page)
// just examined. We assume those have already been consumed as needed
// before allocation starts.
}