blob: 9dcfa96497df1cb055e1b1941467857a4b893978 [file] [log] [blame]
// 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 <fuchsia/hardware/acpi/c/banjo.h>
#include <fuchsia/hardware/pciroot/c/banjo.h>
#include <fuchsia/hardware/sysmem/c/banjo.h>
#include <inttypes.h>
#include <lib/ddk/debug.h>
#include <limits.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <zircon/process.h>
#include <zircon/syscalls.h>
#include <zircon/syscalls/iommu.h>
#include <zircon/syscalls/resource.h>
#include <zircon/threads.h>
#include <variant>
#include <vector>
#include <acpica/acpi.h>
#include <fbl/auto_lock.h>
#include "acpi-private.h"
#include "acpi.h"
#include "acpi/device.h"
#include "acpi/manager.h"
#include "acpi/status.h"
#include "dev.h"
#include "errors.h"
#include "methods.h"
#include "pci.h"
#include "power.h"
#include "src/devices/board/drivers/x86/acpi/resources.h"
#include "src/devices/lib/iommu/iommu.h"
#include "sysmem.h"
namespace {
const std::string_view hid_from_acpi_devinfo(const ACPI_DEVICE_INFO& info) {
if ((info.Valid & ACPI_VALID_HID) && (info.HardwareId.Length > 0) &&
((info.HardwareId.Length - 1) <= sizeof(uint64_t))) {
// ACPICA string lengths include the NULL terminator.
return std::string_view{info.HardwareId.String, info.HardwareId.Length - 1};
}
return std::string_view{};
}
// TODO(fxbug.dev/81684): remove these hacks once we have a proper solution for managing power of
// ACPI devices.
void acpi_apply_workarounds(acpi::Acpi* acpi, ACPI_HANDLE object, ACPI_DEVICE_INFO* info) {
// Slate workaround: Turn on the HID controller.
if (!memcmp(&info->Name, "I2C0", 4)) {
auto acpi_status = acpi->EvaluateObject(object, "H00A._PR0", std::nullopt);
if (acpi_status.is_ok()) {
acpi::UniquePtr<ACPI_OBJECT> pkg = std::move(acpi_status.value());
for (unsigned i = 0; i < pkg->Package.Count; i++) {
ACPI_OBJECT* ref = &pkg->Package.Elements[i];
if (ref->Type != ACPI_TYPE_LOCAL_REFERENCE) {
zxlogf(DEBUG, "acpi: Ignoring wrong type 0x%x", ref->Type);
} else {
zxlogf(DEBUG, "acpi: Enabling HID controller at I2C0.H00A._PR0[%u]", i);
acpi_status = acpi->EvaluateObject(ref->Reference.Handle, "_ON", std::nullopt);
if (acpi_status.is_error()) {
zxlogf(ERROR, "acpi: acpi error 0x%x in I2C0._PR0._ON", acpi_status.error_value());
}
}
}
}
// Atlas workaround: Turn on the HID controller.
acpi_status = acpi->EvaluateObject(object, "H049._PR0", std::nullopt);
if (acpi_status.is_ok()) {
acpi::UniquePtr<ACPI_OBJECT> pkg = std::move(acpi_status.value());
for (unsigned i = 0; i < pkg->Package.Count; i++) {
ACPI_OBJECT* ref = &pkg->Package.Elements[i];
if (ref->Type != ACPI_TYPE_LOCAL_REFERENCE) {
zxlogf(DEBUG, "acpi: Ignoring wrong type 0x%x", ref->Type);
} else {
zxlogf(DEBUG, "acpi: Enabling HID controller at I2C0.H049._PR0[%u]", i);
acpi_status = acpi->EvaluateObject(ref->Reference.Handle, "_ON", std::nullopt);
if (acpi_status.is_error()) {
zxlogf(ERROR, "acpi: acpi error 0x%x in I2C0._PR0._ON", acpi_status.error_value());
}
}
}
}
}
// Acer workaround: Turn on the HID controller.
else if (!memcmp(&info->Name, "I2C1", 4)) {
zxlogf(DEBUG, "acpi: Enabling HID controller at I2C1");
auto acpi_status = acpi->EvaluateObject(object, "_PS0", std::nullopt);
if (acpi_status.is_error()) {
zxlogf(ERROR, "acpi: acpi error in I2C1._PS0: 0x%x", acpi_status.error_value());
}
#ifdef ENABLE_ATLAS_CAMERA
} else if (!memcmp(&info->Name, "CAM0", 4)) {
// Atlas workaround: turn on the camera.
auto acpi_status = acpi->EvaluateObject(object, "_PR0", std::nullopt);
if (acpi_status.is_ok()) {
acpi::UniquePtr<ACPI_OBJECT> pkg = std::move(acpi_status.value());
for (unsigned i = 0; i < pkg->Package.Count; i++) {
ACPI_OBJECT* ref = &pkg->Package.Elements[i];
if (ref->Type != ACPI_TYPE_LOCAL_REFERENCE) {
zxlogf(DEBUG, "acpi: Ignoring wrong type 0x%x", ref->Type);
} else {
zxlogf(DEBUG, "acpi: Enabling camera at CAM0._PR0[%u]", i);
acpi_status = acpi->EvaluateObject(ref->Reference.Handle, "_ON", std::nullopt);
if (acpi_status.is_error()) {
zxlogf(ERROR, "acpi: acpi error 0x%x in CAM0._PR0._ON", acpi_status.error_value());
}
}
}
}
#endif
}
}
} // namespace
namespace acpi {
acpi::status<UniquePtr<ACPI_DEVICE_INFO>> GetObjectInfo(ACPI_HANDLE obj) {
ACPI_DEVICE_INFO* raw = nullptr;
ACPI_STATUS acpi_status = AcpiGetObjectInfo(obj, &raw);
UniquePtr<ACPI_DEVICE_INFO> ret{raw};
if (ACPI_SUCCESS(acpi_status)) {
return acpi::ok(std::move(ret));
}
return acpi::error(acpi_status);
}
} // namespace acpi
zx_status_t acpi_suspend(uint8_t requested_state, bool enable_wake, uint8_t suspend_reason,
uint8_t* out_state) {
switch (suspend_reason & DEVICE_MASK_SUSPEND_REASON) {
case DEVICE_SUSPEND_REASON_MEXEC: {
AcpiTerminate();
return ZX_OK;
}
case DEVICE_SUSPEND_REASON_REBOOT:
if (suspend_reason == DEVICE_SUSPEND_REASON_REBOOT_BOOTLOADER) {
reboot_bootloader();
} else if (suspend_reason == DEVICE_SUSPEND_REASON_REBOOT_RECOVERY) {
reboot_recovery();
} else {
reboot();
}
// Kill this driver so that the IPC channel gets closed; devmgr will
// perform a fallback that should shutdown or reboot the machine.
exit(0);
case DEVICE_SUSPEND_REASON_POWEROFF:
poweroff();
exit(0);
case DEVICE_SUSPEND_REASON_SUSPEND_RAM:
return suspend_to_ram();
default:
return ZX_ERR_NOT_SUPPORTED;
};
}
zx_status_t publish_acpi_devices(acpi::Manager* manager, zx_device_t* platform_bus,
zx_device_t* acpi_root) {
zx_status_t status = pwrbtn_init(acpi_root);
if (status != ZX_OK) {
zxlogf(ERROR, "acpi: failed to initialize pwrbtn device: %d", status);
}
acpi::Acpi* acpi = manager->acpi();
// TODO(fxbug.dev/81684): remove this once we have a proper solution for managing power of ACPI
// devices.
acpi::status<> acpi_status = acpi->WalkNamespace(
ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, MAX_NAMESPACE_DEPTH,
[acpi](ACPI_HANDLE object, uint32_t level, acpi::WalkDirection dir) -> acpi::status<> {
if (dir == acpi::WalkDirection::Ascending) {
return acpi::ok();
}
// We are descending. Grab our object info.
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
if (auto res = acpi::GetObjectInfo(object); res.is_error()) {
return res.take_error();
} else {
info = std::move(res.value());
}
// Apply any workarounds for quirks.
acpi_apply_workarounds(acpi, object, info.get());
return acpi::ok();
});
if (acpi_status.is_error()) {
zxlogf(WARNING, "acpi: Error (%d) during fixup and metadata pass", acpi_status.error_value());
}
auto result = manager->DiscoverDevices();
if (result.is_error()) {
zxlogf(INFO, "discover devices failed");
}
result = manager->ConfigureDiscoveredDevices();
if (result.is_error()) {
zxlogf(INFO, "configure failed");
}
result = manager->PublishDevices(platform_bus);
// Now walk the ACPI namespace looking for devices we understand, and publish
// them. For now, publish only the first PCI bus we encounter.
// TODO(fxbug.dev/78349): remove this when all drivers are removed from the x86 board driver.
acpi_status = acpi->WalkNamespace(
ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, MAX_NAMESPACE_DEPTH,
[acpi_root](ACPI_HANDLE object, uint32_t level, acpi::WalkDirection dir) -> acpi::status<> {
// We don't have anything useful to do during the ascent phase. Just
// skip it.
if (dir == acpi::WalkDirection::Ascending) {
return acpi::ok();
}
// We are descending. Grab our object info.
acpi::UniquePtr<ACPI_DEVICE_INFO> info;
if (auto res = acpi::GetObjectInfo(object); res.is_error()) {
return res.take_error();
} else {
info = std::move(res.value());
}
// Extract pointers to the hardware ID and the compatible ID if present.
// If there is no hardware ID, just skip the device.
const std::string_view hid = hid_from_acpi_devinfo(*info);
if (hid.empty()) {
return acpi::ok();
}
// Now, if we recognize the HID, go ahead and deal with publishing the
// device.
if (hid == LID_HID_STRING) {
lid_init(acpi_root, object);
} else if (hid == PWRSRC_HID_STRING) {
pwrsrc_init(acpi_root, object);
} else if (hid == EC_HID_STRING) {
ec_init(acpi_root, object);
} else if (hid == GOOGLE_TBMC_HID_STRING) {
tbmc_init(acpi_root, object);
} else if (hid == GOOGLE_CROS_EC_HID_STRING) {
cros_ec_lpc_init(acpi_root, object);
}
return acpi::ok();
});
if (acpi_status.is_error()) {
return ZX_ERR_BAD_STATE;
} else {
return ZX_OK;
}
}