blob: 0bce1a57c1242d4f2b8a72ef00f86a38b36b5e36 [file]
// Copyright 2021 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/lib/acpi/device.h"
#include <lib/async/cpp/executor.h>
#include <lib/component/outgoing/cpp/handlers.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/metadata.h>
#include <lib/fit/defer.h>
#include <lib/fpromise/promise.h>
#include <zircon/errors.h>
#include <zircon/syscalls/resource.h>
#include <zircon/types.h>
#include <atomic>
#include <cstdint>
#include <optional>
#include <string>
#include <fbl/auto_lock.h>
#include <fbl/string_printf.h>
#include "lib/ddk/device.h"
#include "lib/zx/result.h"
#include "src/devices/board/lib/acpi/event.h"
#include "src/devices/board/lib/acpi/fidl.h"
#include "src/devices/board/lib/acpi/global-lock.h"
#include "src/devices/board/lib/acpi/manager.h"
#include "src/devices/board/lib/acpi/power-resource.h"
#include "src/devices/lib/iommu/iommu.h"
#include "third_party/acpica-migrating/src/source/include/actypes.h"
namespace acpi {
namespace {
// Maximum number of pending Device Object Notifications before we stop sending them to a device.
constexpr size_t kMaxPendingNotifications = 1000;
} // namespace
ACPI_STATUS Device::AddResource(ACPI_RESOURCE* res) {
if (resource_is_memory(res)) {
resource_memory_t mem;
zx_status_t st = resource_parse_memory(res, &mem);
// only expect fixed memory resource. resource_parse_memory sets minimum == maximum
// for this memory resource type.
if ((st != ZX_OK) || (mem.minimum != mem.maximum)) {
return AE_ERROR;
}
mmio_resources_.emplace_back(mem);
} else if (resource_is_address(res)) {
resource_address_t addr;
zx_status_t st = resource_parse_address(res, &addr);
if (st != ZX_OK) {
return AE_ERROR;
}
if ((addr.resource_type == RESOURCE_ADDRESS_MEMORY) && addr.min_address_fixed &&
addr.max_address_fixed && (addr.maximum < addr.minimum)) {
mmio_resources_.emplace_back(/* writeable= */ true, addr.min_address_fixed,
/* alignment= */ 0, static_cast<uint32_t>(addr.address_length));
}
} else if (resource_is_io(res)) {
resource_io_t io;
zx_status_t st = resource_parse_io(res, &io);
if (st != ZX_OK) {
return AE_ERROR;
}
pio_resources_.emplace_back(io);
} else if (resource_is_irq(res)) {
resource_irq_t irq;
zx_status_t st = resource_parse_irq(res, &irq);
if (st != ZX_OK) {
return AE_ERROR;
}
for (auto i = 0; i < irq.pin_count; i++) {
irqs_.emplace_back(irq, i);
}
}
return AE_OK;
}
zx_status_t Device::ReportCurrentResources() {
if (got_resources_) {
return ZX_OK;
}
// Check device state.
auto state = acpi_->EvaluateObject(acpi_handle_, "_STA", std::nullopt);
uint64_t sta;
if (state.is_error() || state->Type != ACPI_TYPE_INTEGER) {
sta = 0xf;
} else {
sta = state->Integer.Value;
}
if ((sta & ACPI_STA_DEVICE_ENABLED) == 0) {
// We're not allowed to enumerate resources if the device is not enabled.
// see ACPI 6.4 section 6.3.7.
return ZX_OK;
}
// call _CRS to fill in resources
ACPI_STATUS acpi_status = AcpiWalkResources(
acpi_handle_, const_cast<char*>("_CRS"),
[](ACPI_RESOURCE* res, void* ctx) __TA_REQUIRES(reinterpret_cast<Device*>(ctx)->lock_) {
return reinterpret_cast<Device*>(ctx)->AddResource(res);
},
this);
if ((acpi_status != AE_NOT_FOUND) && (acpi_status != AE_OK)) {
return acpi_to_zx_status(acpi_status);
}
zxlogf(DEBUG, "acpi-bus: found %zd port resources %zd memory resources %zx irqs",
pio_resources_.size(), mmio_resources_.size(), irqs_.size());
if (zxlog_level_enabled(TRACE)) {
zxlogf(TRACE, "port resources:");
for (size_t i = 0; i < pio_resources_.size(); i++) {
zxlogf(TRACE, " %02zd: addr=0x%x length=0x%x align=0x%x", i, pio_resources_[i].base_address,
pio_resources_[i].address_length, pio_resources_[i].alignment);
}
zxlogf(TRACE, "memory resources:");
for (size_t i = 0; i < mmio_resources_.size(); i++) {
zxlogf(TRACE, " %02zd: addr=0x%x length=0x%x align=0x%x writeable=%d", i,
mmio_resources_[i].base_address, mmio_resources_[i].address_length,
mmio_resources_[i].alignment, mmio_resources_[i].writeable);
}
zxlogf(TRACE, "irqs:");
for (size_t i = 0; i < irqs_.size(); i++) {
const char* trigger;
switch (irqs_[i].trigger) {
case ACPI_IRQ_TRIGGER_EDGE:
trigger = "edge";
break;
case ACPI_IRQ_TRIGGER_LEVEL:
trigger = "level";
break;
default:
trigger = "bad_trigger";
break;
}
const char* polarity;
switch (irqs_[i].polarity) {
case ACPI_IRQ_ACTIVE_BOTH:
polarity = "both";
break;
case ACPI_IRQ_ACTIVE_LOW:
polarity = "low";
break;
case ACPI_IRQ_ACTIVE_HIGH:
polarity = "high";
break;
default:
polarity = "bad_polarity";
break;
}
zxlogf(TRACE, " %02zd: pin=%u %s %s %s %s", i, irqs_[i].pin, trigger, polarity,
(irqs_[i].sharable == ACPI_IRQ_SHARED) ? "shared" : "exclusive",
irqs_[i].wake_capable ? "wake" : "nowake");
}
}
got_resources_ = true;
return ZX_OK;
}
void Device::DdkInit(ddk::InitTxn txn) {
auto use_global_lock = acpi_->EvaluateObject(acpi_handle_, "_GLK", std::nullopt);
if (use_global_lock.is_ok()) {
if (use_global_lock->Type == ACPI_TYPE_INTEGER && use_global_lock->Integer.Value == 1) {
can_use_global_lock_ = true;
}
}
zx_status_t result = InitializePowerManagement();
if (result != ZX_OK) {
zxlogf(ERROR, "Error initializing power management for ACPI device: %s",
zx_status_get_string(result));
txn.Reply(result);
return;
}
#ifdef ENABLE_ATLAS_CAMERA
bool atlas_camera_enabled = true;
#else
bool atlas_camera_enabled = false;
#endif
// Initial transition to D state 0.
// Skip turning on Atlas camera unless enabled.
if ((name_ != "CAM0" && name_ != "NVM0") || atlas_camera_enabled) {
if (GetPowerStateInfo(DEV_POWER_STATE_D0)) {
PowerStateTransitionResponse result = TransitionToPowerState(DEV_POWER_STATE_D0);
if (result.status != ZX_OK) {
zxlogf(ERROR, "Error transitioning ACPI device to D0 in Init: %s",
zx_status_get_string(result.status));
txn.Reply(result.status);
return;
}
}
}
txn.Reply(ZX_OK);
}
void Device::DdkUnbind(ddk::UnbindTxn txn) {
if (notify_handler_.has_value()) {
RemoveNotifyHandler();
}
std::optional<fpromise::promise<void>> address_handler_finished;
{
std::scoped_lock lock(address_handler_lock_);
for (auto& entry : address_handlers_) {
entry.second.AsyncTeardown();
}
address_handler_finished.emplace(
fpromise::join_promise_vector(std::move(address_handler_teardown_finished_))
.discard_result());
}
std::optional<fpromise::promise<void>> teardown_finished;
notify_teardown_finished_.swap(teardown_finished);
auto promise = fpromise::join_promises(
std::move(teardown_finished).value_or(fpromise::make_ok_promise()),
std::move(address_handler_finished).value_or(fpromise::make_ok_promise()))
.discard_result()
.and_then([txn = std::move(txn)]() mutable { txn.Reply(); });
executor_.schedule_task(std::move(promise));
}
void Device::GetMmio(GetMmioRequestView request, GetMmioCompleter::Sync& completer) {
std::scoped_lock guard{lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
zxlogf(ERROR, "Internal error evaluating resources: %s", zx_status_get_string(st));
completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
if (request->index >= mmio_resources_.size()) {
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
const DeviceMmioResource& res = mmio_resources_[request->index];
// TODO(https://fxbug.dev/42146863): This check becomes overly pessimistic at larger page sizes.
if (((res.base_address & (zx_system_get_page_size() - 1)) != 0) ||
((res.address_length & (zx_system_get_page_size() - 1)) != 0)) {
zxlogf(ERROR, "acpi-bus: memory id=%d addr=0x%08x len=0x%x is not page aligned", request->index,
res.base_address, res.address_length);
completer.ReplyError(ZX_ERR_INVALID_ARGS);
return;
}
zx_handle_t vmo;
size_t size{res.address_length};
st = zx_vmo_create_physical(get_mmio_resource(parent()), res.base_address, size, &vmo);
if (st != ZX_OK) {
zxlogf(ERROR, "Internal error creating VMO: %s", zx_status_get_string(st));
completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
completer.ReplySuccess(fuchsia_mem::wire::Range{
.vmo = zx::vmo(vmo),
.offset = 0,
.size = size,
});
}
void Device::GetBti(GetBtiRequestView request, GetBtiCompleter::Sync& completer) {
// We only support getting BTIs for devices with no bus.
if (bus_type_ != BusType::kUnknown) {
completer.ReplyError(ZX_ERR_NOT_SUPPORTED);
return;
}
if (request->index != 0) {
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
// For dummy IOMMUs, the bti_id just needs to be unique.
// We assume that the device will never get an actual BTI
// because it is a pure ACPI device.
//
// TODO(https://fxbug.dev/42173782): check the DMAR for ACPI entries.
auto path = acpi_->GetPath(acpi_handle_);
if (path.is_error()) {
completer.ReplyError(path.zx_status_value());
return;
}
auto iommu_handle = manager_->iommu_manager()->IommuForAcpiDevice(path.value());
zx::bti bti;
zx::bti::create(*iommu_handle, 0, bti_id_, &bti);
completer.ReplySuccess(std::move(bti));
}
zx::result<zx::channel> Device::PrepareOutgoing() {
auto result = outgoing_.AddService<fuchsia_hardware_acpi::Service>(
fuchsia_hardware_acpi::Service::InstanceHandler({.device = bind_handler(dispatcher_)}));
if (result.is_error()) {
return result.take_error();
}
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
result = outgoing_.Serve(std::move(endpoints->server));
if (result.is_error()) {
zxlogf(ERROR, "Failed to serve the outgoing directory: %s", result.status_string());
return result.take_error();
}
return zx::ok(endpoints->client.TakeChannel());
}
zx_status_t Device::CallPsxMethod(const PowerStateInfo& state) {
if (!state.defines_psx_method) {
return ZX_OK;
}
std::string method_name = "_PS" + std::to_string(state.d_state);
auto psx = acpi_->EvaluateObject(acpi_handle_, method_name.c_str(), std::nullopt);
return psx.zx_status_value();
}
zx::result<Device::PowerStateInfo> Device::GetInfoForState(uint8_t d_state) {
PowerStateInfo power_state_info{.d_state = d_state};
std::vector<const PowerResource*> power_resources;
// Gather information about what power resources are needed in this D state.
std::string method_name = "_PR" + std::to_string(d_state);
auto prx = acpi_->EvaluateObject(acpi_handle_, method_name.c_str(), std::nullopt);
if (prx.is_ok()) {
// Whether the status of power resources implies that the device is in this state.
bool all_resources_on = true;
for (size_t i = 0; i < prx->Package.Count; i++) {
ACPI_OBJECT power_resource_reference = prx->Package.Elements[i];
const PowerResource* power_resource =
manager_->AddPowerResource(power_resource_reference.Reference.Handle);
if (power_resource == nullptr) {
zxlogf(ERROR, "Failed to add power resource");
return zx::error(ZX_ERR_INTERNAL);
}
if (power_resource) {
power_resources.push_back(power_resource);
if (!power_resource->is_on()) {
all_resources_on = false;
}
}
}
// Save the shallowest power state that power resources imply to be on.
if (all_resources_on && current_power_state_ > d_state) {
current_power_state_ = d_state;
}
}
// Map from D states to supported S states based on power resource system_levels.
uint8_t shallowest_system_level = 4;
for (const PowerResource* power_resource : power_resources) {
shallowest_system_level = std::min(shallowest_system_level, power_resource->system_level());
}
for (uint8_t s_state = 0; s_state <= shallowest_system_level; ++s_state) {
power_state_info.supported_s_states.insert(s_state);
}
// Sort power resources by ascending resource_order.
std::sort(power_resources.begin(), power_resources.end(),
[](const PowerResource* lhs, const PowerResource* rhs) {
return lhs->resource_order() < rhs->resource_order();
});
for (auto power_resource : power_resources) {
power_state_info.power_resources.push_back(power_resource->handle());
}
// Check whether this D state has a _PSx method defined.
method_name = "_PS" + std::to_string(d_state);
auto psx = acpi_->GetHandle(acpi_handle_, method_name.c_str());
if (psx.is_ok()) {
power_state_info.defines_psx_method = true;
}
return zx::ok(power_state_info);
}
zx_status_t Device::ConfigureInitialPowerState() {
if (supported_power_states_.empty()) {
return ZX_OK;
}
auto psc = acpi_->EvaluateObject(acpi_handle_, "_PSC", std::nullopt);
if (psc.is_ok()) {
// This overrides any power state earlier implied by power resource status.
current_power_state_ = static_cast<uint8_t>(psc->Integer.Value);
}
if (current_power_state_ == DEV_POWER_STATE_D3COLD &&
!GetPowerStateInfo(DEV_POWER_STATE_D3COLD)) {
current_power_state_ = DEV_POWER_STATE_D3HOT;
}
PowerStateInfo* current_power_state_info = GetPowerStateInfo(current_power_state_);
ZX_ASSERT_MSG(current_power_state_info, "ACPI device initial state is not a supported state");
zx_status_t result = manager_->ReferencePowerResources(current_power_state_info->power_resources);
if (result != ZX_OK) {
zxlogf(ERROR, "Failed to reference initial power resources for ACPI device: %s",
zx_status_get_string(result));
return result;
}
if (psc.is_error() && current_power_state_ == DEV_POWER_STATE_D0) {
// We inferred the power state to be D0 from power resources so we may still need to call _PS0.
result = CallPsxMethod(*current_power_state_info);
if (result != ZX_OK) {
zxlogf(ERROR, "Failed initial call to _PS0 for ACPI device: %s",
zx_status_get_string(result));
return result;
}
}
return ZX_OK;
}
zx_status_t Device::InitializePowerManagement() {
for (uint8_t d_state = DEV_POWER_STATE_D0; d_state <= DEV_POWER_STATE_D3HOT; ++d_state) {
zx::result<PowerStateInfo> power_state_info = GetInfoForState(d_state);
if (power_state_info.is_error()) {
zxlogf(ERROR, "Failed to get info for D state %d", d_state);
return power_state_info.error_value();
}
if (!power_state_info->power_resources.empty() || power_state_info->defines_psx_method) {
supported_power_states_.insert({d_state, *power_state_info});
}
}
// If power resources are provided for D3hot, D3cold is supported.
if (PowerStateInfo* d3hot_state = GetPowerStateInfo(DEV_POWER_STATE_D3HOT)) {
if (!d3hot_state->power_resources.empty()) {
PowerStateInfo d3cold_state{.d_state = DEV_POWER_STATE_D3COLD,
.supported_s_states{0, 1, 2, 3, 4}};
supported_power_states_.insert({DEV_POWER_STATE_D3COLD, d3cold_state});
}
}
// If D0 is supported, D3hot must be supported.
if (GetPowerStateInfo(DEV_POWER_STATE_D0) && !GetPowerStateInfo(DEV_POWER_STATE_D3HOT)) {
PowerStateInfo d3hot_state{.d_state = DEV_POWER_STATE_D3HOT,
.supported_s_states{0, 1, 2, 3, 4}};
supported_power_states_.insert({DEV_POWER_STATE_D3HOT, d3hot_state});
}
// Call _SxD methods to figure out valid D state to S state mapping.
// This removes any mappings which were valid according to power resource system_levels but are
// invalid according to the _SxD methods.
for (uint8_t s_state = 1; s_state <= 4; ++s_state) {
fbl::String method_name = fbl::StringPrintf("_S%dD", s_state);
auto sxd = acpi_->EvaluateObject(acpi_handle_, method_name.c_str(), std::nullopt);
if (sxd.is_ok()) {
for (uint8_t d_state = DEV_POWER_STATE_D0; d_state < static_cast<uint8_t>(sxd->Integer.Value);
++d_state) {
if (PowerStateInfo* power_state = GetPowerStateInfo(d_state)) {
power_state->supported_s_states.erase(s_state);
}
}
}
}
zx_status_t result = ConfigureInitialPowerState();
if (result != ZX_OK) {
return result;
}
return ZX_OK;
}
std::unordered_map<uint8_t, DevicePowerState> Device::GetSupportedPowerStates() {
std::unordered_map<uint8_t, DevicePowerState> states;
for (const auto& power_state : supported_power_states_) {
states.insert({power_state.first,
DevicePowerState(power_state.first, power_state.second.supported_s_states)});
}
return states;
}
zx_status_t Device::Resume(const PowerStateInfo& requested_state_info) {
PowerStateInfo* current_state_info = GetPowerStateInfo(current_power_state_);
zx_status_t status = manager_->ReferencePowerResources(requested_state_info.power_resources);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to reference power resources for ACPI device: %s",
zx_status_get_string(status));
return status;
}
status = manager_->DereferencePowerResources(current_state_info->power_resources);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to dereference power resources for ACPI device: %s",
zx_status_get_string(status));
goto undo2;
}
status = CallPsxMethod(requested_state_info);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to call PSx method for ACPI device: %s", zx_status_get_string(status));
goto undo1;
}
return ZX_OK;
undo1:
manager_->ReferencePowerResources(current_state_info->power_resources);
undo2:
manager_->DereferencePowerResources(requested_state_info.power_resources);
return status;
}
zx_status_t Device::Suspend(const PowerStateInfo& requested_state_info) {
PowerStateInfo* current_state_info = GetPowerStateInfo(current_power_state_);
zx_status_t status;
bool called_psx_method = false;
// When transitioning from D3hot to D3cold, we've already called _PS3 so skip it.
if (current_power_state_ != DEV_POWER_STATE_D3HOT ||
requested_state_info.d_state != DEV_POWER_STATE_D3COLD) {
called_psx_method = true;
// When transitioning from D0 to D3cold, we need to call _PS3.
if (current_power_state_ == DEV_POWER_STATE_D0 &&
requested_state_info.d_state == DEV_POWER_STATE_D3COLD) {
status = CallPsxMethod(*GetPowerStateInfo(DEV_POWER_STATE_D3HOT));
} else {
status = CallPsxMethod(requested_state_info);
}
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to call PSx method for ACPI device: %s", zx_status_get_string(status));
return status;
}
}
status = manager_->ReferencePowerResources(requested_state_info.power_resources);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to reference power resources for ACPI device: %s",
zx_status_get_string(status));
goto undo2;
}
status = manager_->DereferencePowerResources(current_state_info->power_resources);
if (status != ZX_OK) {
zxlogf(ERROR, "Failed to dereference power resources for ACPI device: %s",
zx_status_get_string(status));
goto undo1;
}
return ZX_OK;
undo1:
manager_->DereferencePowerResources(requested_state_info.power_resources);
undo2:
if (called_psx_method) {
CallPsxMethod(*current_state_info);
}
return status;
}
PowerStateTransitionResponse Device::TransitionToPowerState(uint8_t requested_state) {
if (current_power_state_ == requested_state) {
return PowerStateTransitionResponse(ZX_OK, current_power_state_);
}
PowerStateInfo* requested_state_info = GetPowerStateInfo(requested_state);
if (requested_state_info == nullptr) {
zxlogf(ERROR, "Tried to transition an ACPI device to an unsupported power state.");
return PowerStateTransitionResponse(ZX_ERR_NOT_SUPPORTED, current_power_state_);
}
// Cannot transition between non-D0 states.
if (current_power_state_ != DEV_POWER_STATE_D0 && requested_state != DEV_POWER_STATE_D0) {
// Unless transitioning from D3hot to D3cold.
if (current_power_state_ != DEV_POWER_STATE_D3HOT ||
requested_state != DEV_POWER_STATE_D3COLD) {
zxlogf(ERROR, "Cannot transition an ACPI device from state %d to %d.", current_power_state_,
requested_state);
return PowerStateTransitionResponse(ZX_ERR_NOT_SUPPORTED, current_power_state_);
}
}
zx_status_t status;
if (requested_state == DEV_POWER_STATE_D0) {
status = Resume(*requested_state_info);
} else {
status = Suspend(*requested_state_info);
}
if (status != ZX_OK) {
return PowerStateTransitionResponse(status, current_power_state_);
}
current_power_state_ = requested_state;
return PowerStateTransitionResponse(ZX_OK, current_power_state_);
}
zx::result<> Device::AddDevice(const char* name, cpp20::span<zx_device_str_prop_t> str_props,
uint32_t flags) {
auto outgoing = PrepareOutgoing();
if (outgoing.is_error()) {
zxlogf(ERROR, "failed to add acpi device '%s' - while setting up outgoing: %s", name,
outgoing.status_string());
return outgoing.take_error();
}
// A node can either have children manually added to it, or have drivers bound to it. To make this
// work and preserve the tree topology of ACPI we create a passthrough node called
// 'passthrough-device' which is what drivers bind to.
bool needs_passthrough = false;
if (!(flags & DEVICE_ADD_NON_BINDABLE)) {
needs_passthrough = true;
}
std::array offers = {
ddk::MetadataServer<fuchsia_hardware_i2c_businfo::I2CBusMetadata>::kFidlServiceName,
ddk::MetadataServer<fuchsia_hardware_spi_businfo::SpiBusMetadata>::kFidlServiceName,
};
auto path_result = acpi_->GetPath(acpi_handle_);
std::string address_str = name;
if (path_result.is_ok()) {
address_str = path_result.value();
}
auto bus_info =
std::make_unique<fuchsia_driver_framework::BusInfo>(fuchsia_driver_framework::BusInfo{{
.bus = fuchsia_driver_framework::BusType::kAcpi,
.address = fuchsia_driver_framework::DeviceAddress::WithStringValue(address_str),
.address_stability =
fuchsia_driver_framework::DeviceAddressStability::kUnstableBetweenSoftwareUpdate,
}});
zx_status_t status = DdkAdd(ddk::DeviceAddArgs(name)
.set_flags(DEVICE_ADD_NON_BINDABLE)
.set_fidl_service_offers(offers)
.set_bus_info(std::move(bus_info)));
if (status != ZX_OK) {
return zx::error(status);
}
if (!needs_passthrough) {
return zx::ok();
}
static const zx_protocol_device_t passthrough_proto = {
.version = DEVICE_OPS_VERSION,
.init =
[](void* ctx) {
Device* dev = static_cast<Device*>(ctx);
zx_status_t result = ZX_OK;
switch (dev->bus_type_) {
case BusType::kSpi: {
const auto& metadata =
std::get<fuchsia_hardware_spi_businfo::SpiBusMetadata>(dev->metadata_);
auto& bus_metadata_server = dev->bus_metadata_server_.emplace<
ddk::MetadataServer<fuchsia_hardware_spi_businfo::SpiBusMetadata>>();
if (zx_status_t status = bus_metadata_server.SetMetadata(metadata);
status != ZX_OK) {
zxlogf(ERROR, "Failed to set metadata for bus metadata server: %s",
zx_status_get_string(status));
result = status;
break;
}
if (zx_status_t status =
bus_metadata_server.Serve(dev->outgoing_, dev->dispatcher_);
status != ZX_OK) {
zxlogf(ERROR, "Failed serve bus metadata: %s", zx_status_get_string(status));
result = status;
break;
}
break;
}
case BusType::kI2c: {
const auto& metadata =
std::get<fuchsia_hardware_i2c_businfo::I2CBusMetadata>(dev->metadata_);
auto& bus_metadata_server = dev->bus_metadata_server_.emplace<
ddk::MetadataServer<fuchsia_hardware_i2c_businfo::I2CBusMetadata>>();
if (zx_status_t status = bus_metadata_server.SetMetadata(metadata);
status != ZX_OK) {
zxlogf(ERROR, "Failed to set metadata for bus metadata server: %s",
zx_status_get_string(status));
result = status;
break;
}
if (zx_status_t status =
bus_metadata_server.Serve(dev->outgoing_, dev->dispatcher_);
status != ZX_OK) {
zxlogf(ERROR, "Failed serve bus metadata: %s", zx_status_get_string(status));
result = status;
break;
}
break;
}
default:
break;
}
device_init_reply_args_t args{};
device_init_reply(dev->passthrough_dev_, result, &args);
},
.release = [](void* dev) {},
};
std::array pt_offers = {
fuchsia_hardware_acpi::Service::Name,
};
device_add_args_t passthrough_args{
.version = DEVICE_ADD_ARGS_VERSION,
.name = "pt",
.ctx = this,
.ops = &passthrough_proto,
.str_props = str_props.data(),
.str_prop_count = static_cast<uint32_t>(str_props.size()),
.proto_id = ZX_PROTOCOL_ACPI,
.fidl_service_offers = pt_offers.data(),
.fidl_service_offer_count = pt_offers.size(),
.flags = flags | DEVICE_ADD_MUST_ISOLATE | DEVICE_ADD_ALLOW_MULTI_COMPOSITE,
.outgoing_dir_channel = outgoing->release(),
};
status = device_add(zxdev(), &passthrough_args, &passthrough_dev_);
if (status != ZX_OK) {
zxlogf(WARNING, "Failed to add passthrough device for '%s': %s", name,
zx_status_get_string(status));
// Do not fail here so that child devices can still get added.
}
return zx::ok();
}
void Device::GetBusId(GetBusIdCompleter::Sync& completer) {
if (bus_id_ == UINT32_MAX) {
completer.ReplyError(ZX_ERR_BAD_STATE);
} else {
completer.ReplySuccess(bus_id_);
}
}
void Device::EvaluateObject(EvaluateObjectRequestView request,
EvaluateObjectCompleter::Sync& completer) {
auto helper = EvaluateObjectFidlHelper::FromRequest(get_mmio_resource(parent()), acpi_,
acpi_handle_, request);
fidl::Arena<> alloc;
auto result = helper.Evaluate(alloc);
if (result.is_error()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status(result.error_value()));
} else {
completer.ReplySuccess(std::move(result.value().response().result));
}
}
zx::result<zx::interrupt> Device::GetInterrupt(size_t index) {
std::scoped_lock guard{lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
zxlogf(ERROR, "Internal error evaluating resources: %s", zx_status_get_string(st));
return zx::error(ZX_ERR_INTERNAL);
}
if (index >= irqs_.size()) {
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
const DeviceIrqResource& irq = irqs_[index];
uint32_t mode;
mode = ZX_INTERRUPT_MODE_DEFAULT;
st = ZX_OK;
switch (irq.trigger) {
case ACPI_IRQ_TRIGGER_EDGE:
switch (irq.polarity) {
case ACPI_IRQ_ACTIVE_BOTH:
mode = ZX_INTERRUPT_MODE_EDGE_BOTH;
break;
case ACPI_IRQ_ACTIVE_LOW:
mode = ZX_INTERRUPT_MODE_EDGE_LOW;
break;
case ACPI_IRQ_ACTIVE_HIGH:
mode = ZX_INTERRUPT_MODE_EDGE_HIGH;
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
break;
case ACPI_IRQ_TRIGGER_LEVEL:
switch (irq.polarity) {
case ACPI_IRQ_ACTIVE_LOW:
mode = ZX_INTERRUPT_MODE_LEVEL_LOW;
break;
case ACPI_IRQ_ACTIVE_HIGH:
mode = ZX_INTERRUPT_MODE_LEVEL_HIGH;
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
break;
default:
st = ZX_ERR_INVALID_ARGS;
break;
}
if (st != ZX_OK) {
return zx::error(st);
}
zx::interrupt out_irq;
st = zx::interrupt::create(*zx::unowned_resource{get_irq_resource(parent())}, irq.pin,
ZX_INTERRUPT_REMAP_IRQ | mode, &out_irq);
if (st != ZX_OK) {
zxlogf(ERROR, "Internal error creating interrupt: %s", zx_status_get_string(st));
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(std::move(out_irq));
}
void Device::MapInterrupt(MapInterruptRequestView request, MapInterruptCompleter::Sync& completer) {
auto result = GetInterrupt(request->index);
if (result.is_error()) {
completer.ReplyError(result.error_value());
} else {
completer.ReplySuccess(std::move(*result));
}
}
void Device::GetPio(GetPioRequestView request, GetPioCompleter::Sync& completer) {
std::scoped_lock guard{lock_};
zx_status_t st = ReportCurrentResources();
if (st != ZX_OK) {
zxlogf(ERROR, "Internal error evaluating resources: %s", zx_status_get_string(st));
completer.ReplyError(ZX_ERR_INTERNAL);
return;
}
if (request->index >= pio_resources_.size()) {
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
const DevicePioResource& res = pio_resources_[request->index];
char name[ZX_MAX_NAME_LEN];
snprintf(name, ZX_MAX_NAME_LEN, "ioport-%u", request->index);
zx::resource out_pio;
zx_status_t status = zx::resource::create(*zx::unowned_resource{get_ioport_resource(parent())},
ZX_RSRC_KIND_IOPORT, res.base_address,
res.address_length, name, 0, &out_pio);
if (status != ZX_OK) {
zxlogf(ERROR, "Internal error creating resource: %s", zx_status_get_string(status));
completer.ReplyError(ZX_ERR_INTERNAL);
} else {
completer.ReplySuccess(std::move(out_pio));
}
}
void Device::InstallNotifyHandler(InstallNotifyHandlerRequestView request,
InstallNotifyHandlerCompleter::Sync& completer) {
// Try and take the notification handler.
// Will set is_active to true if is_active is already true.
bool is_active = false;
notify_handler_active_.compare_exchange_strong(is_active, true, std::memory_order_acq_rel,
std::memory_order_acquire);
if (notify_handler_ && notify_handler_->is_valid() && is_active) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kAlreadyExists);
return;
}
notify_handler_type_ = static_cast<uint32_t>(request->mode);
if (!request->handler.is_valid()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kBadParameter);
return;
}
if (request->mode.has_unknown_bits()) {
zxlogf(WARNING, "Unknown mode bits for notify handler ignored: 0x%x",
uint32_t(request->mode.unknown_bits()));
}
uint32_t mode(request->mode & fuchsia_hardware_acpi::wire::NotificationMode::kMask);
auto async_completer = completer.ToAsync();
std::optional<fpromise::promise<void>> teardown_finished;
notify_teardown_finished_.swap(teardown_finished);
auto promise =
std::move(teardown_finished)
.value_or(fpromise::make_ok_promise())
.and_then([this, mode, async_completer = std::move(async_completer),
handler = std::move(request->handler)]() mutable {
pending_notify_count_.store(0, std::memory_order_release);
// Reset the "teardown finished" promise.
fpromise::bridge<void> bridge;
notify_teardown_finished_ = bridge.consumer.promise();
auto notify_event_handler =
std::make_unique<NotifyEventHandler>(this, std::move(bridge.completer));
fidl::WireSharedClient<fuchsia_hardware_acpi::NotifyHandler> client(
std::move(handler), dispatcher_, std::move(notify_event_handler));
notify_handler_ = std::move(client);
auto status = acpi_->InstallNotifyHandler(
acpi_handle_, mode, Device::DeviceObjectNotificationHandler, this);
if (status.is_error()) {
notify_handler_.reset();
async_completer.ReplyError(fuchsia_hardware_acpi::wire::Status(status.error_value()));
return;
}
async_completer.ReplySuccess();
})
.box();
executor_.schedule_task(std::move(promise));
}
void Device::DeviceObjectNotificationHandler(ACPI_HANDLE object, uint32_t value, void* context) {
Device* device = static_cast<Device*>(context);
if (device->pending_notify_count_.load(std::memory_order_acquire) >= kMaxPendingNotifications) {
if (!device->notify_count_warned_) {
zxlogf(ERROR, "%s: too many un-handled pending notifications. Will drop notifications.",
device->name());
device->notify_count_warned_ = true;
}
return;
}
device->pending_notify_count_.fetch_add(1, std::memory_order_acq_rel);
if (device->notify_handler_ && device->notify_handler_->is_valid()) {
device->notify_handler_.value()->Handle(value).ThenExactlyOnce(
[device](fidl::WireUnownedResult<fuchsia_hardware_acpi::NotifyHandler::Handle>& result) {
if (!result.ok()) {
return;
}
device->pending_notify_count_.fetch_sub(1, std::memory_order_acq_rel);
});
}
}
void Device::RemoveNotifyHandler(RemoveNotifyHandlerCompleter::Sync& completer) {
auto status = RemoveNotifyHandler();
if (status != AE_OK) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status(status));
return;
}
completer.ReplySuccess();
}
ACPI_STATUS Device::RemoveNotifyHandler() {
// Try and mark the notify handler as inactive. If this fails, then someone else marked it as
// inactive.
// If this succeeds, then we're going to tear down the notify handler.
bool is_active = true;
notify_handler_active_.compare_exchange_strong(is_active, false, std::memory_order_acq_rel,
std::memory_order_acquire);
if (!is_active) {
return AE_OK;
}
auto status = acpi_->RemoveNotifyHandler(acpi_handle_, notify_handler_type_,
Device::DeviceObjectNotificationHandler);
if (status.is_error()) {
zxlogf(ERROR, "Failed to remove notification handler from '%s': %d", name(),
status.error_value());
return status.error_value();
}
notify_handler_->AsyncTeardown();
return AE_OK;
}
void Device::AcquireGlobalLock(AcquireGlobalLockCompleter::Sync& completer) {
if (!can_use_global_lock_) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kAccess);
return;
}
GlobalLockHandle::Create(acpi_, dispatcher_, completer.ToAsync());
}
ACPI_STATUS Device::AddressSpaceHandler(uint32_t function, ACPI_PHYSICAL_ADDRESS physical_address,
uint32_t bit_width, UINT64* value, void* handler_ctx,
void* region_ctx) {
HandlerCtx* ctx = static_cast<HandlerCtx*>(handler_ctx);
std::scoped_lock lock(ctx->device->address_handler_lock_);
auto client = ctx->device->address_handlers_.find(ctx->space_type);
if (client == ctx->device->address_handlers_.end()) {
zxlogf(ERROR, "No handler found for space %u", ctx->space_type);
return AE_NOT_FOUND;
}
switch (function) {
case ACPI_READ: {
auto result = client->second.sync()->Read(physical_address, bit_width);
if (!result.ok()) {
zxlogf(ERROR, "FIDL Read failed: %s", result.FormatDescription().data());
return AE_ERROR;
}
if (result->is_error()) {
return static_cast<ACPI_STATUS>(result->error_value());
}
*value = result->value()->value;
break;
}
case ACPI_WRITE: {
auto result = client->second.sync()->Write(physical_address, bit_width, *value);
if (!result.ok()) {
zxlogf(ERROR, "FIDL Write failed: %s", result.FormatDescription().data());
return AE_ERROR;
}
if (result->is_error()) {
return static_cast<ACPI_STATUS>(result->error_value());
}
break;
}
}
return AE_OK;
}
void Device::InstallAddressSpaceHandler(InstallAddressSpaceHandlerRequestView request,
InstallAddressSpaceHandlerCompleter::Sync& completer) {
if (request->space.IsUnknown()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kNotSupported);
return;
}
std::scoped_lock lock(address_handler_lock_);
uint32_t space(request->space);
if (address_handlers_.find(space) != address_handlers_.end()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kAlreadyExists);
return;
}
// Allocated using new, and then destroyed by the FIDL teardown handler.
auto ctx = std::make_unique<HandlerCtx>();
ctx->device = this;
ctx->space_type = space;
// It's safe to do this now, because any address space requests will try and acquire the
// address_handler_lock_. As a result, nothing will happen until we've finished setting up the
// FIDL client and our bookkeeping below.
auto status = acpi_->InstallAddressSpaceHandler(acpi_handle_, static_cast<uint8_t>(space),
AddressSpaceHandler, nullptr, ctx.get());
if (status.is_error()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status(status.error_value()));
return;
}
fpromise::bridge<void> bridge;
fidl::WireSharedClient<fuchsia_hardware_acpi::AddressSpaceHandler> client(
std::move(request->handler), dispatcher_,
fidl::AnyTeardownObserver::ByCallback(
[this, ctx = std::move(ctx), space, completer = std::move(bridge.completer)]() mutable {
std::scoped_lock lock(address_handler_lock_);
// Remove the address space handler from ACPICA.
auto result = acpi_->RemoveAddressSpaceHandler(
acpi_handle_, static_cast<uint8_t>(space), AddressSpaceHandler);
if (result.is_error()) {
zxlogf(ERROR, "Failed to remove address space handler: %d", result.status_value());
// We're in a strange state now. Claim that we've torn down, but avoid freeing
// things to minimise the chance of a UAF in the address space handler.
ZX_DEBUG_ASSERT_MSG(false, "Failed to remove address space handler: %d",
result.status_value());
completer.complete_ok();
return;
}
// Clean up other things.
address_handlers_.erase(space);
completer.complete_ok();
}));
// Everything worked, so insert our book-keeping.
address_handler_teardown_finished_.emplace_back(bridge.consumer.promise());
address_handlers_.emplace(space, std::move(client));
completer.ReplySuccess();
}
void Device::SetWakeDevice(SetWakeDeviceRequestView request,
SetWakeDeviceCompleter::Sync& completer) {
// Get the GPE device and GPE number associated with the device's Power Resource for Wake
auto prw_result = acpi_->EvaluateObject(acpi_handle_, "_PRW", std::nullopt);
if (prw_result.is_error()) {
zxlogf(ERROR, "EvaluateObject failed: %d", int(prw_result.error_value()));
completer.ReplyError(fuchsia_hardware_acpi::wire::Status(prw_result.error_value()));
return;
}
if (prw_result->Type != ACPI_TYPE_PACKAGE || prw_result->Package.Count < 2) {
zxlogf(ERROR, "Unexpected response from EvaluateObject");
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kBadData);
return;
}
if (request->requested_state > prw_result->Package.Elements[1].Integer.Value) {
zxlogf(ERROR,
"Requested sleep state (%u) is deeper than the deepest sleep state that the device can "
"wake the system from (%llu)",
request->requested_state, prw_result->Package.Elements[1].Integer.Value);
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kNotSupported);
return;
}
ACPI_HANDLE gpe_dev = nullptr;
uint32_t gpe_num;
// See ACPI v6.3 Section 7.3.13
// The first object within the _PRW object is the information about the _GPE object
// associated with the device. This evaluates to either an integer or a package.
// The integer specifies the bit in the FADT GPEx_STS blocks to use.
// The package contains the reference to the device and the index in that device where the
// event is.
auto& gpe_info = prw_result->Package.Elements[0];
if (gpe_info.Type == ACPI_TYPE_INTEGER) {
gpe_num = static_cast<uint32_t>(gpe_info.Integer.Value);
} else if (gpe_info.Type == ACPI_TYPE_PACKAGE) {
if (gpe_info.Package.Count != 2 ||
gpe_info.Package.Elements[0].Type != ACPI_TYPE_LOCAL_REFERENCE ||
gpe_info.Package.Elements[1].Type != ACPI_TYPE_INTEGER) {
zxlogf(ERROR, "Unexpected response from EvaluateObject");
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kBadData);
return;
}
gpe_dev = gpe_info.Package.Elements[0].Reference.Handle;
gpe_num = static_cast<uint32_t>(gpe_info.Package.Elements[1].Integer.Value);
} else {
zxlogf(ERROR, "Unexpected response from EvaluateObject");
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kBadData);
return;
}
auto status = acpi_->SetGpeWakeMask(gpe_dev, gpe_num, true);
if (status.is_error()) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status(status.error_value()));
return;
}
zxlogf(INFO, "Deepest sleep state that device can wake system from: %llu",
prw_result->Package.Elements[1].Integer.Value);
// Get the power resources associated with the _PRW object and turn them all on.
std::vector<ACPI_HANDLE> power_resources;
// The first two elements of the _PRW object are the event info, and the lowest sleep state the
// device can wake from. The rest of the elements are power resources.
uint64_t pwr_res_count = prw_result->Package.Count - 2;
for (uint64_t i = 0; i < pwr_res_count; i++) {
ACPI_OBJECT power_resource_reference = prw_result->Package.Elements[i + 2];
const PowerResource* power_resource =
manager_->AddPowerResource(power_resource_reference.Reference.Handle);
if (power_resource == nullptr) {
zxlogf(ERROR, "Failed to add power resource");
}
if (power_resource && !power_resource->is_on()) {
power_resources.push_back(power_resource->handle());
}
}
zx_status_t zx_status = manager_->ReferencePowerResources(power_resources);
if (zx_status != ZX_OK) {
zxlogf(ERROR, "Failed to reference power resources for ACPI device: %s",
zx_status_get_string(zx_status));
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kError);
}
completer.ReplySuccess();
}
} // namespace acpi