| // Copyright 2019 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 "src/devices/board/drivers/x86/x86.h" | 
 |  | 
 | #include <fidl/fuchsia.hardware.acpi/cpp/wire.h> | 
 | #include <lib/ddk/debug.h> | 
 | #include <lib/ddk/driver.h> | 
 | #include <lib/ddk/metadata.h> | 
 | #include <lib/ddk/platform-defs.h> | 
 | #include <lib/driver-unit-test/utils.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <zircon/status.h> | 
 |  | 
 | #include <acpica/acpi.h> | 
 | #include <ddktl/fidl.h> | 
 | #include <fbl/alloc_checker.h> | 
 | #include <fbl/vector.h> | 
 |  | 
 | #include "src/devices/board/drivers/x86/include/acpi.h" | 
 | #include "src/devices/board/drivers/x86/include/smbios.h" | 
 | #include "src/devices/board/drivers/x86/include/sysmem.h" | 
 | #include "src/devices/board/drivers/x86/x64-bind.h" | 
 |  | 
 | using fuchsia_hardware_acpi::wire::kMaxAcpiTableEntries; | 
 | using fuchsia_hardware_acpi::wire::TableInfo; | 
 |  | 
 | zx_handle_t root_resource_handle; | 
 |  | 
 | static zx_status_t sys_device_suspend(void* ctx, uint8_t requested_state, bool enable_wake, | 
 |                                       uint8_t suspend_reason, uint8_t* out_state) { | 
 |   return acpi_suspend(requested_state, enable_wake, suspend_reason, out_state); | 
 | } | 
 |  | 
 | namespace x86 { | 
 |  | 
 | X86::~X86() { | 
 |   if (acpica_initialized_) { | 
 |     AcpiTerminate(); | 
 |   } | 
 | } | 
 |  | 
 | int X86::Thread() { | 
 |   zx_status_t status = SysmemInit(); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "%s: SysmemInit() failed: %d", __func__, status); | 
 |     return status; | 
 |   } | 
 |  | 
 |   status = publish_acpi_devices(acpi_manager_.get(), parent(), zxdev()); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "%s: publish_acpi_devices() failed: %d", __func__, status); | 
 |     return status; | 
 |   } | 
 |  | 
 |   status = GoldfishControlInit(); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "%s: GoldfishControlInit() failed: %d", __func__, status); | 
 |     return status; | 
 |   } | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t X86::Start() { | 
 |   int rc = thrd_create_with_name( | 
 |       &thread_, [](void* arg) -> int { return reinterpret_cast<X86*>(arg)->Thread(); }, this, | 
 |       "x86_start_thread"); | 
 |   if (rc != thrd_success) { | 
 |     return ZX_ERR_INTERNAL; | 
 |   } | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | void X86::DdkRelease() { | 
 |   int exit_code; | 
 |   thrd_join(thread_, &exit_code); | 
 |   delete this; | 
 | } | 
 |  | 
 | zx_status_t X86::Create(void* ctx, zx_device_t* parent, std::unique_ptr<X86>* out) { | 
 |   pbus_protocol_t pbus; | 
 |  | 
 |   // Please do not use get_root_resource() in new code. See fxbug.dev/31358. | 
 |   root_resource_handle = get_root_resource(); | 
 |  | 
 |   zx_status_t status = device_get_protocol(parent, ZX_PROTOCOL_PBUS, &pbus); | 
 |   if (status != ZX_OK) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   fbl::AllocChecker ac; | 
 |   *out = fbl::make_unique_checked<X86>(&ac, parent, &pbus, std::make_unique<acpi::AcpiImpl>()); | 
 |   if (!ac.check()) { | 
 |     return ZX_ERR_NO_MEMORY; | 
 |   } | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | zx_status_t X86::CreateAndBind(void* ctx, zx_device_t* parent) { | 
 |   std::unique_ptr<X86> board; | 
 |  | 
 |   zx_status_t status = Create(ctx, parent, &board); | 
 |   if (status != ZX_OK) { | 
 |     return status; | 
 |   } | 
 |  | 
 |   status = board->Bind(); | 
 |   if (status == ZX_OK) { | 
 |     // DevMgr now owns this pointer, release it to avoid destroying the | 
 |     // object when device goes out of scope. | 
 |     __UNUSED auto* ptr = board.release(); | 
 |   } | 
 |   return status; | 
 | } | 
 |  | 
 | template <size_t N> | 
 | static void SetField(const char* label, const std::string& value, char (&out)[N]) { | 
 |   if (value.empty()) { | 
 |     zxlogf(ERROR, "acpi: smbios %s could not be read", label); | 
 |   } else if (value.size() >= N) { | 
 |     zxlogf(INFO, "acpi: smbios %s too big for sysinfo: %s", label, value.data()); | 
 |   } else { | 
 |     strlcpy(out, value.data(), N); | 
 |   } | 
 | } | 
 |  | 
 | zx_status_t X86::Bind() { | 
 |   // Do early init of ACPICA etc. | 
 |   zx_status_t status = EarlyInit(); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "%s: failed to perform early initialization %d ", __func__, status); | 
 |     return status; | 
 |   } | 
 |  | 
 |   // publish the board as ACPI root under /dev/sys/platform. PCI will get created under /dev/sys | 
 |   // (to preserve compatibility). | 
 |   status = DdkAdd("acpi", DEVICE_ADD_NON_BINDABLE); | 
 |  | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "acpi: error %d in device_add(sys/platform/platform-passthrough/acpi)", status); | 
 |     return status; | 
 |   } | 
 |  | 
 |   pbus_board_info_t board_info{.board_name = "x64", .board_revision = 0}; | 
 |   pbus_bootloader_info_t bootloader_info{.vendor = "<unknown>"}; | 
 |  | 
 |   // Load SMBIOS information. | 
 |   SmbiosInfo smbios; | 
 |   status = smbios.Load(); | 
 |   if (status == ZX_OK) { | 
 |     SetField("board name", smbios.board_name(), board_info.board_name); | 
 |     SetField("vendor", smbios.vendor(), bootloader_info.vendor); | 
 |   } | 
 |  | 
 |   // Inform the platform bus of our board info. | 
 |   status = pbus_.SetBoardInfo(&board_info); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "SetBoardInfo failed: %d", status); | 
 |   } | 
 |  | 
 |   // Inform the platform bus of our bootloader info. | 
 |   status = pbus_.SetBootloaderInfo(&bootloader_info); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "SetBootloaderInfo failed: %d", status); | 
 |   } | 
 |  | 
 |   // Set the "sys" suspend op in platform-bus. | 
 |   // The devmgr coordinator code that arranges ordering in which the suspend hooks | 
 |   // are called makes sure the suspend hook attached to sys/ is called dead last, | 
 |   // (coordinator.cpp:BuildSuspendList()). If move this suspend hook elsewhere, | 
 |   // we must make sure that the coordinator code arranges for this suspend op to be | 
 |   // called last. | 
 |   pbus_sys_suspend_t suspend = {sys_device_suspend, NULL}; | 
 |   status = pbus_.RegisterSysSuspendCallback(&suspend); | 
 |   if (status != ZX_OK) { | 
 |     zxlogf(ERROR, "%s: Could not register suspend callback: %d", __func__, status); | 
 |   } | 
 |  | 
 |   // Create the ACPI manager. | 
 |   acpi_manager_ = std::make_unique<acpi::Manager>(acpi_.get(), zxdev()); | 
 |  | 
 |   // Start up our protocol helpers and platform devices. | 
 |   return Start(); | 
 | } | 
 |  | 
 | bool X86::RunUnitTests(void* ctx, zx_device_t* parent, zx_handle_t channel) { | 
 |   return driver_unit_test::RunZxTests("X86Tests", parent, channel); | 
 | } | 
 |  | 
 | zx_status_t X86::GetAcpiTableEntries(fbl::Vector<TableInfo>* entries) { | 
 |   ZX_DEBUG_ASSERT(acpica_initialized_); | 
 |  | 
 |   for (uint32_t i = 0; i < kMaxAcpiTableEntries; i++) { | 
 |     // Fetch the next table entry. | 
 |     ACPI_TABLE_HEADER* table; | 
 |     ACPI_STATUS status = AcpiGetTableByIndex(i, &table); | 
 |     if (status == AE_BAD_PARAMETER) { | 
 |       // End of iteration. | 
 |       break; | 
 |     } else if (status != AE_OK) { | 
 |       // Unexpected error. | 
 |       return ZX_ERR_INTERNAL; | 
 |     } | 
 |  | 
 |     // Create an ACPI table header entry. | 
 |     TableInfo info; | 
 |     static_assert(sizeof(info.name) == sizeof(table->Signature)); | 
 |     memcpy(info.name.data(), table->Signature, sizeof(table->Signature)); | 
 |     info.size = table->Length; | 
 |  | 
 |     // Add it to the list. | 
 |     fbl::AllocChecker ac; | 
 |     entries->push_back(info, &ac); | 
 |     if (!ac.check()) { | 
 |       return ZX_ERR_NO_MEMORY; | 
 |     } | 
 |   } | 
 |  | 
 |   return ZX_OK; | 
 | } | 
 |  | 
 | void X86::ListTableEntries(ListTableEntriesRequestView request, | 
 |                            ListTableEntriesCompleter::Sync& completer) { | 
 |   ZX_DEBUG_ASSERT(acpica_initialized_); | 
 |  | 
 |   // Fetch the entries. | 
 |   fbl::Vector<TableInfo> entries; | 
 |   zx_status_t status = GetAcpiTableEntries(&entries); | 
 |   if (status != ZX_OK) { | 
 |     completer.ReplyError(status); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Send them back to the user. | 
 |   completer.ReplySuccess(fidl::VectorView<TableInfo>::FromExternal(entries.data(), entries.size())); | 
 | } | 
 |  | 
 | void X86::ReadNamedTable(ReadNamedTableRequestView request, | 
 |                          ReadNamedTableCompleter::Sync& completer) { | 
 |   // Fetch the requested table. | 
 |   ACPI_TABLE_HEADER* table; | 
 |   if (ACPI_STATUS status = | 
 |           AcpiGetTable(reinterpret_cast<char*>(request->name.data()), request->instance, &table); | 
 |       status != AE_OK) { | 
 |     completer.ReplyError(status == AE_NOT_FOUND ? ZX_ERR_NOT_FOUND : ZX_ERR_INTERNAL); | 
 |     return; | 
 |   } | 
 |  | 
 |   // Copy it into the VMO. | 
 |   if (zx_status_t status = | 
 |           request->result.write(reinterpret_cast<uint8_t*>(table), /*offset=*/0, table->Length); | 
 |       status != ZX_OK) { | 
 |     completer.ReplyError(status); | 
 |     return; | 
 |   } | 
 |  | 
 |   completer.ReplySuccess(table->Length); | 
 | } | 
 |  | 
 | static zx_driver_ops_t x86_driver_ops = []() { | 
 |   zx_driver_ops_t ops = {}; | 
 |   ops.version = DRIVER_OPS_VERSION; | 
 |   ops.bind = X86::CreateAndBind; | 
 |   ops.run_unit_tests = X86::RunUnitTests; | 
 |   return ops; | 
 | }(); | 
 |  | 
 | }  // namespace x86 | 
 |  | 
 | ZIRCON_DRIVER(acpi_bus, x86::x86_driver_ops, "zircon", "0.1"); |