| // Copyright 2020 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 "zircon/system/utest/device-enumeration/aemu.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <fidl/fuchsia.hardware.acpi/cpp/wire.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/zx/channel.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/status.h> |
| #include <zircon/types.h> |
| |
| #include <cstdlib> |
| #include <cstring> |
| #include <string_view> |
| |
| #include <fbl/array.h> |
| #include <fbl/unique_fd.h> |
| |
| namespace device_enumeration { |
| |
| namespace { |
| |
| using fuchsia_hardware_acpi::Acpi; |
| using fuchsia_hardware_acpi::wire::TableInfo; |
| |
| const char* kAcpiDevicePath = "/dev/sys/platform/platform-passthrough/acpi"; |
| const char* kAcpiDsdtTableName = "DSDT"; |
| |
| template <typename T> |
| bool FindPattern(const fbl::Array<T>& haystack, const fbl::Array<T>& needle) { |
| if (needle.size() > haystack.size()) { |
| return false; |
| } |
| for (size_t start = 0; start + needle.size() <= haystack.size(); ++start) { |
| if (memcmp(needle.data(), haystack.data() + start, needle.size()) == 0) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Fetch raw data for a table. |
| zx_status_t FetchTable(const zx::channel& channel, const TableInfo& table, |
| fbl::Array<uint8_t>* data) { |
| // Allocate a VMO for the read. |
| zx::vmo vmo; |
| if (zx_status_t status = zx::vmo::create(table.size, /*options=*/0, &vmo); status != ZX_OK) { |
| return status; |
| } |
| |
| // Make a copy to send to the driver. |
| zx::vmo vmo_copy; |
| if (zx_status_t status = vmo.duplicate(ZX_RIGHT_SAME_RIGHTS, &vmo_copy); status != ZX_OK) { |
| return status; |
| } |
| |
| // Fetch the data. |
| fidl::WireResult<Acpi::ReadNamedTable> result = |
| fidl::WireCall<Acpi>(channel.borrow())->ReadNamedTable(table.name, 0, std::move(vmo_copy)); |
| if (!result.ok()) { |
| return result.status(); |
| } |
| |
| // Copy the data into memory. |
| uint32_t size = result->value()->size; |
| auto table_data = fbl::Array<uint8_t>(new uint8_t[size], size); |
| if (zx_status_t status = vmo.read(table_data.data(), 0, size); status != ZX_OK) { |
| return status; |
| } |
| |
| *data = std::move(table_data); |
| return ZX_OK; |
| } |
| |
| // Find a certain byte sequence |keyword| in ACPI table |table_name|. |
| // |
| // Returns false if it cannot access the ACPI data, or none of the ACPI |
| // tables with name |table_name| has the keyword. |
| bool AcpiTableHasKeyword(const zx::channel& acpi_channel, std::string_view table_name, |
| const fbl::Array<uint8_t>& keyword) { |
| // List ACPI entries. |
| fidl::WireResult<Acpi::ListTableEntries> result = |
| fidl::WireCall<Acpi>(zx::unowned_channel(acpi_channel))->ListTableEntries(); |
| if (!result.ok()) { |
| fprintf(stderr, "Could not list ACPI table entries: %s.\n", |
| zx_status_get_string(result.status())); |
| return false; |
| } |
| if (result.value().is_error()) { |
| fprintf(stderr, "Call to list ACPI table entries failed: %s.\n", |
| zx_status_get_string(result.value().error_value())); |
| return false; |
| } |
| |
| auto& entries = result->value()->entries; |
| for (auto table : entries) { |
| if (std::string_view(reinterpret_cast<const char*>(table.name.begin()), table.name.size()) != |
| table_name) { |
| continue; |
| } |
| |
| // Fetch table contents. |
| fbl::Array<uint8_t> table_data; |
| zx_status_t status = FetchTable(acpi_channel, table, &table_data); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Call to FetchTable failed: %s.\n", zx_status_get_string(status)); |
| continue; |
| } |
| |
| // There can be multiple tables with the same name. So we continue |
| // searching for the keyword if it is missing in the current table. |
| if (FindPattern(table_data, keyword)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // AEMU and QEMU boards have the same board name, but AEMU boards also have |
| // some AEMU-specific ACPI devices which can be used for AEMU board detection. |
| // |
| // In this function we find Goldfish pipe device, with ACPI HID |GFSH0002|, |
| // in the ACPI DSDT (Differentiated System Description Table) table. This |
| // device can be found if and only if it's an AEMU board. |
| bool IsAemuBoard() { |
| // Open up channel to ACPI device. |
| fbl::unique_fd fd(open(kAcpiDevicePath, O_RDWR)); |
| if (!fd.is_valid()) { |
| return false; |
| } |
| |
| zx::channel channel; |
| if (fdio_get_service_handle(fd.release(), channel.reset_and_get_address()) != ZX_OK) { |
| return false; |
| } |
| |
| // Look for |GFSH0002| in ACPI table. |
| constexpr size_t kAemuAcpiKeywordLen = 8; |
| constexpr uint8_t kAemuAcpiKeyword[] = "GFSH0002"; |
| fbl::Array<uint8_t> aemu_acpi_keyword(new uint8_t[kAemuAcpiKeywordLen], kAemuAcpiKeywordLen); |
| memcpy(aemu_acpi_keyword.data(), kAemuAcpiKeyword, kAemuAcpiKeywordLen); |
| |
| return AcpiTableHasKeyword(channel, kAcpiDsdtTableName, aemu_acpi_keyword); |
| } |
| |
| } // namespace device_enumeration |