blob: 658598fac591635eeb7e8c8f15103c45e5770a99 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "boot_zbi_items.h"
#include <lib/ddk/platform-defs.h>
#include <lib/stdcompat/span.h>
#include <lib/zbi-format/board.h>
#include <lib/zbi-format/driver-config.h>
#include <lib/zbi-format/graphics.h>
#include <lib/zbi-format/memory.h>
#include <lib/zbi-format/zbi.h>
#include <lib/zbi/zbi.h>
#include <lib/zircon_boot/zbi_utils.h>
#include <lib/zx/result.h>
#include <stdio.h>
#include <stdlib.h>
#include <zircon/errors.h>
#include <zircon/limits.h>
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <efi/boot-services.h>
#include <efi/protocol/graphics-output.h>
#include <efi/system-table.h>
#include <efi/types.h>
#include <fbl/vector.h>
#include <phys/efi/main.h>
#include "acpi.h"
#include "utils.h"
namespace gigaboot {
const efi_guid kSmbiosTableGUID = SMBIOS_TABLE_GUID;
const efi_guid kSmbios3TableGUID = SMBIOS3_TABLE_GUID;
const uint8_t kSmbiosAnchor[4] = {'_', 'S', 'M', '_'};
const uint8_t kSmbios3Anchor[5] = {'_', 'S', 'M', '3', '_'};
extern "C" efi_status generate_efi_memory_attributes_table_item(
void* ramdisk, const size_t ramdisk_size, efi_system_table* sys, const void* mmap,
size_t memory_map_size, size_t dsize);
namespace {
constexpr size_t kBufferSize = (static_cast<size_t>(32) * 1024) / 2;
template <typename T>
using MemArray = std::array<T, kBufferSize / sizeof(T)>;
template <typename T>
class MemDynamicViewer {
public:
class Iterator {
public:
const T& operator*() const { return *item_; }
const T* operator->() const { return item_; }
Iterator operator++() {
item_ = reinterpret_cast<const T*>(reinterpret_cast<const uint8_t*>(item_) + item_size_);
return *this;
}
friend bool operator==(const Iterator& a, const Iterator& b) { return a.item_ == b.item_; }
friend bool operator!=(const Iterator& a, const Iterator& b) { return !(a == b); }
private:
friend MemDynamicViewer<T>;
Iterator(const T* item, size_t item_size) : item_(item), item_size_(item_size) {}
const T* item_;
size_t item_size_;
};
MemDynamicViewer(const MemArray<T>& base, size_t num_items, size_t item_size)
: base_(base.data()), num_items_(num_items), item_size_(item_size) {
// Verify that iteration won't overrun the end of the backing array.
ZX_ASSERT(num_items * item_size <= base.size() * sizeof(T));
// Verify that we aren't violating alignment requirements.
ZX_ASSERT(item_size % std::alignment_of_v<T> == 0);
}
Iterator begin() const { return Iterator(base_, item_size_); }
Iterator end() const {
// The casting and pointer arithmetic on uint8_t* is necessary
// because item_size_ may not equal sizeof(T).
// That is the whole point of this custom iterator.
return Iterator(reinterpret_cast<const T*>(reinterpret_cast<const uint8_t*>(base_) +
num_items_ * item_size_),
item_size_);
}
private:
const T* base_;
size_t num_items_;
size_t item_size_;
};
bool AppendSmbiosPtr(zbi_header_t* image, size_t capacity) {
uint64_t smbios = 0;
cpp20::span<const efi_configuration_table> entries(gEfiSystemTable->ConfigurationTable,
gEfiSystemTable->NumberOfTableEntries);
for (const efi_configuration_table& entry : entries) {
if ((entry.VendorGuid == kSmbiosTableGUID &&
!memcmp(entry.VendorTable, kSmbiosAnchor, sizeof(kSmbiosAnchor))) ||
(entry.VendorGuid == kSmbios3TableGUID &&
!memcmp(entry.VendorTable, kSmbios3Anchor, sizeof(kSmbios3Anchor)))) {
smbios = reinterpret_cast<uint64_t>(entry.VendorTable);
break;
}
}
if (smbios == 0) {
return false;
}
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_SMBIOS, 0, 0, &smbios,
sizeof(smbios)) == ZBI_RESULT_OK;
}
bool AppendAcpiRsdp(zbi_header_t* image, size_t capacity, const AcpiRsdp& rsdp) {
const auto* ptr = &rsdp;
if (zbi_result_t result = zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_ACPI_RSDP, 0, 0,
&ptr, sizeof(&ptr));
result != ZBI_RESULT_OK) {
printf("Failed to create ACPI rsdp entry, %d\n", result);
return false;
}
return true;
}
zbi_pixel_format_t PixelFormatFromBitmask(const efi_pixel_bitmask& bitmask) {
struct entry {
efi_pixel_bitmask mask;
zbi_pixel_format_t pixel_format;
};
// Ignore reserved field
constexpr entry entries[] = {
{.mask = {.RedMask = 0xFF0000, .GreenMask = 0xFF00, .BlueMask = 0xFF},
.pixel_format = ZBI_PIXEL_FORMAT_RGB_X888},
{.mask = {.RedMask = 0xE0, .GreenMask = 0x1C, .BlueMask = 0x3},
.pixel_format = ZBI_PIXEL_FORMAT_RGB_332},
{.mask = {.RedMask = 0xF800, .GreenMask = 0x7E0, .BlueMask = 0x1F},
.pixel_format = ZBI_PIXEL_FORMAT_RGB_565},
{.mask = {.RedMask = 0xC0, .GreenMask = 0x30, .BlueMask = 0xC},
.pixel_format = ZBI_PIXEL_FORMAT_RGB_2220},
};
auto equal_p = [&bitmask](const entry& e) -> bool {
// Ignore reserved
return bitmask.RedMask == e.mask.RedMask && bitmask.GreenMask == e.mask.GreenMask &&
bitmask.BlueMask == e.mask.BlueMask;
};
auto res = std::find_if(std::begin(entries), std::end(entries), equal_p);
if (res == std::end(entries)) {
printf("unsupported pixel format bitmask: r %08x / g %08x / b %08x\n", bitmask.RedMask,
bitmask.GreenMask, bitmask.BlueMask);
return ZBI_PIXEL_FORMAT_NONE;
}
return res->pixel_format;
}
uint32_t GetZbiPixelFormat(efi_graphics_output_mode_information* info) {
efi_graphics_pixel_format efi_fmt = info->PixelFormat;
switch (efi_fmt) {
case PixelBlueGreenRedReserved8BitPerColor:
return ZBI_PIXEL_FORMAT_RGB_X888;
case PixelBitMask:
return PixelFormatFromBitmask(info->PixelInformation);
default:
printf("unsupported pixel format %d!\n", efi_fmt);
return ZBI_PIXEL_FORMAT_NONE;
}
}
// If the firmware supports graphics, append
// framebuffer information to the list of zbi items.
//
// Returns true if there is no graphics support or if framebuffer information
// was successfully added, and false if appending framebuffer information
// returned an error.
bool AddFramebufferIfSupported(zbi_header_t* image, size_t capacity) {
auto graphics_protocol = gigaboot::EfiLocateProtocol<efi_graphics_output_protocol>();
if (graphics_protocol.is_error() || graphics_protocol.value() == nullptr) {
// Graphics are not strictly necessary.
printf("No valid graphics output detected\n");
return true;
}
zbi_swfb_t framebuffer = {
.base = graphics_protocol.value()->Mode->FrameBufferBase,
.width = graphics_protocol.value()->Mode->Info->HorizontalResolution,
.height = graphics_protocol.value()->Mode->Info->VerticalResolution,
.stride = graphics_protocol.value()->Mode->Info->PixelsPerScanLine,
.format = GetZbiPixelFormat(graphics_protocol.value()->Mode->Info),
};
if (zbi_result_t result = zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_FRAMEBUFFER, 0,
0, &framebuffer, sizeof(framebuffer));
result != ZBI_RESULT_OK) {
printf("Failed to add framebuffer zbi item: %d\n", result);
return false;
}
return true;
}
bool AddSystemTable(zbi_header_t* image, size_t capacity) {
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_EFI_SYSTEM_TABLE, 0, 0,
&gEfiSystemTable, sizeof(gEfiSystemTable)) == ZBI_RESULT_OK;
}
bool AddUartDriver(zbi_header_t* image, size_t capacity, const AcpiRsdp& rsdp,
ZbiContext* context) {
const auto* spcr = rsdp.LoadTable<AcpiSpcr>();
if (spcr == nullptr) {
printf("%s: no spcr\n", __func__);
return true;
}
uint32_t serial_driver_type = spcr->GetKdrv();
if (serial_driver_type == 0) {
printf("%s: no serial driver\n", __func__);
return true;
}
zbi_dcfg_simple_t uart_driver = spcr->DeriveUartDriver();
if (context) {
context->uart_mmio_phys = uart_driver.mmio_phys;
}
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_KERNEL_DRIVER, serial_driver_type,
0, &uart_driver, sizeof(uart_driver)) == ZBI_RESULT_OK;
}
bool AddMadtItems(zbi_header_t* image, size_t capacity, const AcpiRsdp& rsdp, ZbiContext* context) {
const auto* madt = rsdp.LoadTable<AcpiMadt>();
if (madt == nullptr) {
printf("%s: no madt\n", __func__);
return true;
}
std::array<zbi_topology_node_t, 1> nodes;
auto node_span = madt->GetTopology(nodes);
if (!node_span.empty() && zbi_create_entry_with_payload(
image, capacity, ZBI_TYPE_CPU_TOPOLOGY, sizeof(node_span.front()),
0, node_span.data(), node_span.size_bytes()) != ZBI_RESULT_OK) {
return false;
}
if (context) {
context->num_cpu_nodes = static_cast<uint8_t>(node_span.size());
}
std::optional<AcpiMadt::GicDescriptor> gic_cfg = madt->GetGicDriver();
if (!gic_cfg) {
printf("%s: no gic cfg\n", __func__);
return true;
}
if (context) {
context->gic_driver = gic_cfg->driver;
}
return std::visit(
[image, capacity, zbi_type = gic_cfg->zbi_type](const auto& driver) {
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_KERNEL_DRIVER,
zbi_type, 0, &driver, sizeof(driver));
},
gic_cfg->driver) == ZBI_RESULT_OK;
}
bool AddPsciDriver(zbi_header_t* image, size_t capacity, const AcpiRsdp& rsdp) {
const auto* fadt = rsdp.LoadTable<AcpiFadt>();
if (!fadt) {
printf("%s: no fadt\n", __func__);
return true;
}
std::optional<zbi_dcfg_arm_psci_driver_t> psci_driver = fadt->GetPsciDriver();
if (!psci_driver) {
printf("%s: no psci\n", __func__);
return true;
}
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_KERNEL_DRIVER,
ZBI_KERNEL_DRIVER_ARM_PSCI, 0, &(psci_driver.value()),
sizeof(*psci_driver)) == ZBI_RESULT_OK;
}
bool AddArmTimerDriver(zbi_header_t* image, size_t capacity, const AcpiRsdp& rsdp) {
const auto* gtdt = rsdp.LoadTable<AcpiGtdt>();
if (!gtdt) {
printf("%s: no gtdt\n", __func__);
return true;
}
zbi_dcfg_arm_generic_timer_driver_t timer = gtdt->GetTimer();
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_KERNEL_DRIVER,
ZBI_KERNEL_DRIVER_ARM_GENERIC_TIMER, 0, &timer,
sizeof(timer)) == ZBI_RESULT_OK;
}
bool AddPlatformId(zbi_header_t* image, size_t capacity) {
zbi_platform_id_t platform_id = {
#ifdef __x86_64__
.vid = PDEV_VID_INTEL,
.pid = PDEV_PID_X86,
#elif __aarch64__
.vid = PDEV_VID_ARM,
.pid = PDEV_PID_ACPI_BOARD,
#else
#error "Uknown platform architecture."
#endif
};
return zbi_create_entry_with_payload(image, capacity, ZBI_TYPE_PLATFORM_ID, 0, 0, &platform_id,
sizeof(platform_id)) == ZBI_RESULT_OK;
}
// Bootloader file item for ssh key provisioning
// TODO(b/239088231): Consider using dynamic allocation.
constexpr size_t kZbiFileLength = 4096;
bool zbi_file_is_initialized = false;
uint8_t zbi_files[kZbiFileLength] __attribute__((aligned(ZBI_ALIGNMENT)));
} // namespace
zbi_result_t AddBootloaderFiles(const char* name, const void* data, size_t len) {
if (!zbi_file_is_initialized) {
zbi_result_t result = zbi_init(zbi_files, kZbiFileLength);
if (result != ZBI_RESULT_OK) {
printf("Failed to initialize zbi_files: %d\n", result);
return result;
}
zbi_file_is_initialized = true;
}
return AppendZbiFile(reinterpret_cast<zbi_header_t*>(zbi_files), kZbiFileLength, name, data, len);
}
void ClearBootloaderFiles() { zbi_file_is_initialized = false; }
cpp20::span<uint8_t> GetZbiFiles() { return zbi_files; }
// Add memory related zbi items.
//
// Returns memory map key on success, which will be used for ExitBootService.
zx::result<size_t> AddMemoryItems(void* zbi, size_t capacity, const ZbiContext* context) {
static MemArray<zbi_mem_range_t> zbi_mem = {};
static MemArray<efi_memory_descriptor> efi_mem = {};
uint32_t dversion = 0;
size_t mkey = 0;
size_t dsize = 0;
size_t msize = sizeof(efi_mem);
// Note: Once memory map is grabbed, do not do anything that can change it, i.e. anything that
// involves memory allocation/de-allocation, including printf if it is printing to graphics.
efi_status status =
gEfiSystemTable->BootServices->GetMemoryMap(&msize, efi_mem.data(), &mkey, &dsize, &dversion);
if (status != EFI_SUCCESS) {
printf("boot: cannot GetMemoryMap(). %s\n", EfiStatusToString(status));
return zx::error(ZX_ERR_INTERNAL);
}
// Look for an EFI memory attributes table we can pass to the kernel.
efi_status mem_attr_res = generate_efi_memory_attributes_table_item(
zbi, capacity, gEfiSystemTable, efi_mem.data(), msize, dsize);
if (mem_attr_res != EFI_SUCCESS) {
printf("failed to generate EFI memory attributes table: %s", EfiStatusToString(mem_attr_res));
return zx::error(ZX_ERR_INTERNAL);
}
// The structures populated by GetMemoryMap may be larger than sizeof(efi_memory_descriptor),
// and we need to handle that potential difference in a dynamic manner.
MemDynamicViewer<efi_memory_descriptor> efi_mem_range(efi_mem, msize / dsize, dsize);
auto current_zbi = zbi_mem.begin();
for (const efi_memory_descriptor& efi_desc : efi_mem_range) {
if (current_zbi == zbi_mem.end()) {
break;
}
*current_zbi++ = {
.paddr = efi_desc.PhysicalStart,
.length = efi_desc.NumberOfPages * kUefiPageSize,
.type = EfiToZbiMemRangeType(efi_desc.Type),
};
}
if (context) {
if (context->uart_mmio_phys) {
if (current_zbi == zbi_mem.end()) {
printf("Insufficient memory to add memory items\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
*current_zbi++ = {
.paddr = context->uart_mmio_phys.value(),
.length = ZX_PAGE_SIZE,
.type = ZBI_MEM_TYPE_PERIPHERAL,
};
}
if (context->gic_driver) {
if (const auto* v2_driver =
std::get_if<zbi_dcfg_arm_gic_v2_driver_t>(&context->gic_driver.value())) {
// This memory range must encompass the GICC and GICD register ranges.
// Each of these generally encompass a page, but some systems like QEMU
// allocate 64K to make it easier when working with 64kb pages. Since we
// use 4K pages, we allocate 16 pages here just to be safe.
constexpr uint64_t entry_length = 16 * ZX_PAGE_SIZE;
if (current_zbi == zbi_mem.end()) {
printf("Insufficient memory to add memory items\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
*current_zbi++ = {
.paddr = v2_driver->mmio_phys,
.length = entry_length,
.type = ZBI_MEM_TYPE_PERIPHERAL,
};
if (current_zbi == zbi_mem.end()) {
printf("Insufficient memory to add memory items\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
*current_zbi++ = {
.paddr = v2_driver->mmio_phys + v2_driver->gicd_offset + v2_driver->gicc_offset,
.length = entry_length,
.type = ZBI_MEM_TYPE_PERIPHERAL,
};
if (v2_driver->use_msi) {
if (current_zbi == zbi_mem.end()) {
printf("Insufficient memory to add memory items\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
*current_zbi++ = {
.paddr = v2_driver->msi_frame_phys,
.length = entry_length,
.type = ZBI_MEM_TYPE_PERIPHERAL,
};
}
} else if (const auto* v3_driver =
std::get_if<zbi_dcfg_arm_gic_v3_driver_t>(&context->gic_driver.value())) {
// We should never have a GICv3 system with less than one core.
if (context->num_cpu_nodes < 1) {
return zx::error(ZX_ERR_INTERNAL);
}
if (current_zbi == zbi_mem.end()) {
printf("Insufficient memory to add memory items\n");
return zx::error(ZX_ERR_NO_MEMORY);
}
// This memory range must encompass the GICD and GICR register ranges.
uint64_t gic_mem_size = 0x10000; // GICD size.
gic_mem_size += v3_driver->gicr_offset + v3_driver->gicd_offset;
// Add the GICR size. Each GICR in GICv3 consists of 2 adjacent 64 KiB frames.
gic_mem_size += static_cast<uint64_t>(context->num_cpu_nodes) * 0x20000;
// Add any padding between GICRs on multi-core systems.
gic_mem_size += (context->num_cpu_nodes - 1) * v3_driver->gicr_stride;
*current_zbi++ = {
.paddr = v3_driver->mmio_phys,
.length = gic_mem_size,
.type = ZBI_MEM_TYPE_PERIPHERAL,
};
}
}
}
size_t payload_size = (current_zbi - zbi_mem.begin()) * sizeof(*current_zbi);
zbi_result_t result = zbi_create_entry_with_payload(zbi, capacity, ZBI_TYPE_MEM_CONFIG, 0, 0,
zbi_mem.data(), payload_size);
if (result != ZBI_RESULT_OK) {
printf("Failed to create memory range entry, %d\n", result);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(mkey);
}
bool AddGigabootZbiItems(zbi_header_t* image, size_t capacity, const AbrSlotIndex* slot,
ZbiContext* context) {
if (slot && AppendCurrentSlotZbiItem(image, capacity, *slot) != ZBI_RESULT_OK) {
return false;
}
const AcpiRsdp* rsdp = FindAcpiRsdp();
if (rsdp == nullptr) {
return false;
}
if (!AppendAcpiRsdp(image, capacity, *rsdp)) {
return false;
}
if (!AddUartDriver(image, capacity, *rsdp, context)) {
return false;
}
if (!AddMadtItems(image, capacity, *rsdp, context)) {
return false;
}
if (!AddPsciDriver(image, capacity, *rsdp)) {
return false;
}
if (!AddArmTimerDriver(image, capacity, *rsdp)) {
return false;
}
if (!AddFramebufferIfSupported(image, capacity)) {
return false;
}
if (!AddSystemTable(image, capacity)) {
return false;
}
if (!AppendSmbiosPtr(image, capacity)) {
return false;
}
if (!AddPlatformId(image, capacity)) {
return false;
}
if (zbi_file_is_initialized && zbi_extend(image, capacity, zbi_files) != ZBI_RESULT_OK) {
return false;
}
return true;
}
} // namespace gigaboot