| // 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 |