| // 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 "mock_boot_service.h" |
| |
| #include <lib/cksum.h> |
| |
| #include <span> |
| |
| #include "efi/protocol/managed-network.h" |
| #include "efi/protocol/simple-network.h" |
| #include "efi/system-table.h" |
| #include "src/lib/utf_conversion/utf_conversion.h" |
| |
| efi_loaded_image_protocol* gEfiLoadedImage = nullptr; |
| efi_system_table* gEfiSystemTable = nullptr; |
| efi_handle gEfiImageHandle; |
| |
| constexpr uint32_t kGptHeaderRevision = 0x00010000; |
| |
| namespace gigaboot { |
| |
| namespace { |
| |
| void RecalculateGptCrcs(std::span<const gpt_entry_t> entries, gpt_header_t* header) { |
| header->entries_crc = |
| crc32(0, reinterpret_cast<uint8_t const*>(entries.data()), entries.size_bytes()); |
| |
| header->crc32 = 0; |
| header->crc32 = crc32(0, reinterpret_cast<uint8_t*>(header), sizeof(*header)); |
| } |
| |
| } // namespace |
| |
| // A helper that creates a realistic device path protocol |
| void Device::InitDevicePathProtocol(std::vector<std::string_view> path_nodes) { |
| // UEFI specification chapter 10. `efi_device_path_protocol*` is an array of |
| // variable length struct. Specifically, each element is |
| // `efi_device_path_protocol struct` + path data. |
| device_path_buffer_.clear(); |
| for (auto name : path_nodes) { |
| uint16_t node_size = static_cast<uint16_t>(name.size()) + 4; |
| device_path_buffer_.push_back(DEVICE_PATH_HARDWARE); |
| device_path_buffer_.push_back(0); |
| device_path_buffer_.push_back(node_size & 0xFF); |
| device_path_buffer_.push_back(node_size >> 8); |
| device_path_buffer_.insert(device_path_buffer_.end(), |
| reinterpret_cast<const uint8_t*>(name.data()), |
| reinterpret_cast<const uint8_t*>(name.data() + name.size())); |
| } |
| device_path_buffer_.push_back(DEVICE_PATH_END); |
| device_path_buffer_.push_back(0); |
| device_path_buffer_.push_back(4); |
| device_path_buffer_.push_back(0); |
| } |
| |
| ManagedNetworkDevice::ManagedNetworkDevice(std::vector<std::string_view> paths, |
| efi_simple_network_mode state) |
| : Device(std::move(paths)), mnp_(state) {} |
| |
| BlockDevice::BlockDevice(std::vector<std::string_view> paths, size_t blocks) |
| : Device(paths), total_blocks_(blocks) { |
| memset(&block_io_media_, 0, sizeof(block_io_media_)); |
| block_io_media_.BlockSize = kBlockSize; |
| block_io_media_.LastBlock = blocks - 1; |
| block_io_media_.MediaPresent = true; |
| block_io_protocol_ = { |
| .Revision = 0, // don't care |
| .Media = &this->block_io_media_, |
| .Reset = nullptr, // don't care |
| .ReadBlocks = nullptr, // don't care |
| .WriteBlocks = nullptr, // don't care |
| .FlushBlocks = nullptr, // don't care |
| }; |
| // Only support MediaId = 0. Allocate buffer to serve as block storage. |
| fake_disk_io_protocol_.contents(/*MediaId=*/0) = std::vector<uint8_t>(blocks * kBlockSize); |
| } |
| |
| void BlockDevice::InitializeGpt() { |
| ASSERT_GT(total_blocks_, 2 * kGptHeaderBlocks + 1); |
| // Entries start on a block |
| uint8_t* start = fake_disk_io_protocol_.contents(0).data(); |
| |
| gpt_header_t primary = { |
| .magic = GPT_MAGIC, |
| .revision = kGptHeaderRevision, |
| .size = GPT_HEADER_SIZE, |
| .crc32 = 0, |
| .reserved0 = 0, |
| .current = 1, |
| .backup = total_blocks_ - 1, |
| .first = kGptFirstUsableBlocks, |
| .last = total_blocks_ - kGptHeaderBlocks - 1, |
| .entries = 2, |
| .entries_count = kGptEntries, |
| .entries_size = GPT_ENTRY_SIZE, |
| .entries_crc = 0, |
| }; |
| |
| gpt_header_t backup(primary); |
| backup.backup = 1; |
| backup.current = total_blocks_ - 1; |
| backup.entries = total_blocks_ - kGptHeaderBlocks; |
| |
| primary.entries_crc = |
| crc32(0, start + primary.entries * kBlockSize, primary.entries_size * primary.entries_count); |
| backup.entries_crc = primary.entries_crc; |
| |
| primary.crc32 = crc32(0, reinterpret_cast<uint8_t*>(&primary), sizeof(primary)); |
| backup.crc32 = crc32(0, reinterpret_cast<uint8_t*>(&backup), sizeof(backup)); |
| |
| // Copy over the primary header. Skip mbr partition. |
| memcpy(start + kBlockSize, &primary, sizeof(primary)); |
| |
| // Copy the backup header. |
| memcpy(start + (backup.current * kBlockSize), &backup, sizeof(backup)); |
| |
| // Initialize partition entries to 0s |
| memset(start + primary.entries * kBlockSize, 0, kGptEntries * sizeof(gpt_entry_t)); |
| } |
| |
| void BlockDevice::AddGptPartition(const gpt_entry_t& new_entry) { |
| ASSERT_GE(new_entry.first, kGptFirstUsableBlocks); |
| ASSERT_LE(new_entry.last, total_blocks_ - kGptHeaderBlocks - 1); |
| |
| uint8_t* const data = fake_disk_io_protocol_.contents(0).data(); |
| |
| gpt_header_t* primary_header = reinterpret_cast<gpt_header_t*>(data + kBlockSize); |
| gpt_header_t* backup_header = |
| reinterpret_cast<gpt_header_t*>(data + (total_blocks_ - 1) * kBlockSize); |
| gpt_entry_t* primary_entries = reinterpret_cast<gpt_entry_t*>(data + 2 * kBlockSize); |
| gpt_entry_t* backup_entries = |
| reinterpret_cast<gpt_entry_t*>(data + (total_blocks_ - kGptHeaderBlocks) * kBlockSize); |
| std::span<const gpt_entry_t> entries_span(primary_entries, primary_header->entries_count); |
| |
| // Search for an empty entry |
| for (size_t i = 0; i < kGptEntries; i++) { |
| if (primary_entries[i].first == 0 && primary_entries[i].last == 0) { |
| ASSERT_EQ(backup_entries[i].first, 0UL); |
| ASSERT_EQ(backup_entries[i].last, 0UL); |
| |
| memcpy(&primary_entries[i], &new_entry, sizeof(new_entry)); |
| memcpy(&backup_entries[i], &new_entry, sizeof(new_entry)); |
| |
| RecalculateGptCrcs(entries_span, primary_header); |
| RecalculateGptCrcs(entries_span, backup_header); |
| |
| return; |
| } |
| } |
| ASSERT_TRUE(false); |
| } |
| |
| Tcg2Device::Tcg2Device() : Device({}) { |
| memset(&tcg2_protocol_, 0, sizeof(tcg2_protocol_)); |
| tcg2_protocol_.protocol_.GetCapability = Tcg2Device::GetCapability; |
| tcg2_protocol_.protocol_.SubmitCommand = Tcg2Device::SubmitCommand; |
| } |
| |
| efi_status Tcg2Device::GetCapability(struct efi_tcg2_protocol*, |
| efi_tcg2_boot_service_capability* out) { |
| *out = {}; |
| return EFI_SUCCESS; |
| } |
| |
| efi_status Tcg2Device::SubmitCommand(struct efi_tcg2_protocol* protocol, uint32_t block_size, |
| uint8_t* block_data, uint32_t output_size, |
| uint8_t* output_data) { |
| Tcg2Device::Protocol* protocol_data_ = reinterpret_cast<Tcg2Device::Protocol*>(protocol); |
| protocol_data_->last_command_ = std::vector<uint8_t>(block_data, block_data + block_size); |
| return EFI_SUCCESS; |
| } |
| |
| GraphicsOutputDevice::GraphicsOutputDevice() : Device({}) { |
| protocol_.Mode = &mode_; |
| mode_.Info = &info_; |
| } |
| |
| efi_status MockStubService::LocateProtocol(const efi_guid* protocol, void* registration, |
| void** intf) { |
| if (IsProtocol<efi_tcg2_protocol>(*protocol)) { |
| for (auto& ele : devices_) { |
| if (auto protocol = ele->GetTcg2Protocol(); protocol) { |
| *intf = protocol; |
| return EFI_SUCCESS; |
| } |
| } |
| } else if (IsProtocol<efi_graphics_output_protocol>(*protocol)) { |
| for (auto& ele : devices_) { |
| if (auto protocol = ele->GetGraphicsOutputProtocol(); protocol) { |
| *intf = protocol; |
| return EFI_SUCCESS; |
| } |
| } |
| } |
| |
| return EFI_UNSUPPORTED; |
| } |
| |
| efi_status MockStubService::LocateHandleBuffer(efi_locate_search_type search_type, |
| const efi_guid* protocol, void* search_key, |
| size_t* num_handles, efi_handle** buf) { |
| // We'll only ever use ByProtocol search type. |
| if (search_type != ByProtocol) { |
| return EFI_UNSUPPORTED; |
| } |
| |
| bool (*search_func)(Device* d) = nullptr; |
| if (IsProtocol<efi_block_io_protocol>(*protocol)) { |
| search_func = [](Device* d) { return d->GetBlockIoProtocol() != nullptr; }; |
| } else if (IsProtocol<efi_managed_network_protocol>(*protocol)) { |
| search_func = [](Device* d) { return d->GetManagedNetworkProtocol() != nullptr; }; |
| } else { |
| return EFI_UNSUPPORTED; |
| } |
| |
| std::vector<Device*> lists; |
| std::copy_if(devices_.cbegin(), devices_.cend(), std::back_inserter(lists), search_func); |
| *num_handles = lists.size(); |
| size_t size_in_bytes = lists.size() * sizeof(decltype(lists)::value_type); |
| void* buffer; |
| efi_status status = gEfiSystemTable->BootServices->AllocatePool(EfiLoaderData /*don't care*/, |
| size_in_bytes, &buffer); |
| if (status != EFI_SUCCESS) { |
| return status; |
| } |
| std::copy(lists.cbegin(), lists.cend(), reinterpret_cast<Device**>(buffer)); |
| *buf = reinterpret_cast<efi_handle*>(buffer); |
| return EFI_SUCCESS; |
| } |
| |
| efi_status MockStubService::OpenProtocol(efi_handle handle, const efi_guid* protocol, void** intf, |
| efi_handle agent_handle, efi_handle controller_handle, |
| uint32_t attributes) { |
| // The given handle must be a pointer to one of the registered devices added to `devices_`. |
| auto iter_find = std::find(devices_.begin(), devices_.end(), handle); |
| if (iter_find == devices_.end()) { |
| return EFI_NOT_FOUND; |
| } |
| |
| if (IsProtocol<efi_device_path_protocol>(*protocol)) { |
| *intf = (*iter_find)->GetDevicePathProtocol(); |
| } else if (IsProtocol<efi_block_io_protocol>(*protocol)) { |
| *intf = (*iter_find)->GetBlockIoProtocol(); |
| } else if (IsProtocol<efi_disk_io_protocol>(*protocol)) { |
| *intf = (*iter_find)->GetDiskIoProtocol(); |
| } else if (IsProtocol<efi_managed_network_protocol>(*protocol)) { |
| *intf = (*iter_find)->GetManagedNetworkProtocol(); |
| } |
| |
| return *intf ? EFI_SUCCESS : EFI_UNSUPPORTED; |
| } |
| |
| efi_status MockStubService::GetMemoryMap(size_t* memory_map_size, efi_memory_descriptor* memory_map, |
| size_t* map_key, size_t* desc_size, |
| uint32_t* desc_version) { |
| *map_key = mkey_; |
| *desc_version = 0; |
| *desc_size = sizeof(efi_memory_descriptor); |
| size_t total_size = memory_map_.size() * sizeof(efi_memory_descriptor); |
| if (*memory_map_size < total_size) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| *memory_map_size = total_size; |
| if (total_size) { |
| memcpy(memory_map, memory_map_.data(), total_size); |
| } |
| |
| return EFI_SUCCESS; |
| } |
| |
| efi_status MockStubService::CreateEvent(uint32_t type, efi_tpl notify_tpl, |
| efi_event_notify notify_fn, void* notify_ctx, |
| efi_event* event) { |
| // Only deal with timers and signals for the time being. |
| if (!(type & (EVT_TIMER | EVT_NOTIFY_SIGNAL)) || event == nullptr) { |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| *event = reinterpret_cast<efi_event>(event_counter_++); |
| return EFI_SUCCESS; |
| } |
| |
| efi_status MockStubService::SetTimer(efi_event event, efi_timer_delay type, uint64_t trigger_time) { |
| // Very quick and dirty check that this is a valid event. |
| return (reinterpret_cast<uintptr_t>(event) < event_counter_) ? EFI_SUCCESS |
| : EFI_INVALID_PARAMETER; |
| } |
| |
| efi_status MockStubService::CloseEvent(efi_event event) { |
| // Very quick and dirty check that this is a valid event. |
| return (reinterpret_cast<uintptr_t>(event) < event_counter_) ? EFI_SUCCESS |
| : EFI_INVALID_PARAMETER; |
| } |
| |
| efi_status MockStubService::CheckEvent(efi_event event) { |
| // Very quick and dirty check that this is a valid event. |
| return (reinterpret_cast<uintptr_t>(event) < event_counter_) ? timer_retval_ |
| : EFI_INVALID_PARAMETER; |
| } |
| |
| void SetGptEntryName(const char* name, gpt_entry_t& entry) { |
| size_t dst_len = sizeof(entry.name) / sizeof(uint16_t); |
| utf8_to_utf16(reinterpret_cast<const uint8_t*>(name), strlen(name), |
| reinterpret_cast<uint16_t*>(entry.name), &dst_len); |
| } |
| |
| EfiConfigTable::EfiConfigTable(uint8_t acpi_revision, SmbiosRev smbios_revision) { |
| rsdp_ = { |
| .signature = kAcpiRsdpSignature, |
| .checksum = 0, |
| .revision = acpi_revision, |
| .length = sizeof(rsdp_), |
| .extended_checksum = 0, |
| }; |
| |
| // The checksum sums all the bytes in the rsdp struct. |
| // It is valid if the sum is zero. |
| std::span<const uint8_t> bytes(reinterpret_cast<const uint8_t*>(&rsdp_), kAcpiRsdpV1Size); |
| rsdp_.checksum = static_cast<uint8_t>(0x100 - std::reduce(bytes.begin(), bytes.end(), 0)); |
| bytes = {bytes.begin(), rsdp_.length}; |
| rsdp_.extended_checksum = |
| static_cast<uint8_t>(0x100 - std::reduce(bytes.begin(), bytes.end(), 0)); |
| |
| efi_guid guid = ACPI_TABLE_GUID; |
| if (acpi_revision >= 2) { |
| guid = ACPI_20_TABLE_GUID; |
| } |
| |
| // Make the table lookups iterate at least once. |
| table_.push_back(efi_configuration_table{.VendorGuid = {}}); |
| |
| table_.push_back(efi_configuration_table{ |
| .VendorGuid = guid, |
| .VendorTable = &rsdp_, |
| }); |
| |
| switch (smbios_revision) { |
| case SmbiosRev::kV3: |
| table_.push_back(efi_configuration_table{ |
| .VendorGuid = SMBIOS3_TABLE_GUID, |
| .VendorTable = "_SM3_", |
| }); |
| break; |
| case SmbiosRev::kV1: |
| table_.push_back(efi_configuration_table{ |
| .VendorGuid = SMBIOS_TABLE_GUID, |
| .VendorTable = "_SM_", |
| }); |
| break; |
| case SmbiosRev::kNone: |
| // Deliberately omit on none |
| default: |
| break; |
| } |
| } |
| |
| const fbl::NoDestructor<EfiConfigTable> kDefaultEfiConfigTable(static_cast<uint8_t>(2), |
| EfiConfigTable::SmbiosRev::kV3); |
| |
| std::vector<zbitl::ByteView> FindItems(const void* zbi, uint32_t type) { |
| std::vector<zbitl::ByteView> ret; |
| zbitl::View<zbitl::ByteView> view{ |
| zbitl::StorageFromRawHeader(static_cast<const zbi_header_t*>(zbi))}; |
| for (auto [header, payload] : view) { |
| if (header->type == type) { |
| ret.push_back(payload); |
| } |
| } |
| |
| ZX_ASSERT(view.take_error().is_ok()); |
| return ret; |
| } |
| |
| } // namespace gigaboot |