blob: 161aba15a8f3151bca3410190011a99a253c5aeb [file] [log] [blame]
// 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 "acpi_controller.h"
#include <fidl/fuchsia.component.decl/cpp/wire_types.h>
#include <fidl/fuchsia.driver.framework/cpp/wire.h>
#include <fidl/fuchsia.hardware.acpi/cpp/wire_types.h>
#include <fidl/fuchsia.mem/cpp/wire_types.h>
#include <lib/async/cpp/task.h>
#include <lib/driver2/record_cpp.h>
#include <lib/driver2/structured_logger.h>
#include <lib/fidl/cpp/wire/connect_service.h>
#include <lib/zx/clock.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include "registers.h"
namespace acpi_multiply {
zx::status<std::unique_ptr<AcpiMultiplyController>> AcpiMultiplyController::Start(
fuchsia_driver_framework::wire::DriverStartArgs &start_args, fdf::UnownedDispatcher dispatcher,
fidl::WireSharedClient<fuchsia_driver_framework::Node> node, driver::Namespace ns,
driver::Logger logger) {
auto driver = std::make_unique<AcpiMultiplyController>(
dispatcher->async_dispatcher(), std::move(node), std::move(ns), std::move(logger));
auto result = driver->Run(std::move(start_args.outgoing_dir()));
if (result.is_error()) {
return result.take_error();
}
return zx::ok(std::move(driver));
}
zx::status<> AcpiMultiplyController::Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir) {
// Allocate an MMIO buffer representing the ACPI multiply device
zx::vmo vmo;
zx_status_t raw_status = zx::vmo::create(zx_system_get_page_size(), 0, &vmo);
if (raw_status != ZX_OK) {
return zx::error(raw_status);
}
auto buffer = fdf::MmioBuffer::Create(0, zx_system_get_page_size(), std::move(vmo),
ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (buffer.is_error()) {
return buffer.take_error();
}
buffer_ = std::move(buffer.value());
// Create an IRQ resource for the ACPI multiply device
raw_status = zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq_);
if (raw_status != ZX_OK) {
return zx::error(raw_status);
}
// Serve `fuchsia.hardware.acpi/Device` to child device nodes
auto service = [this](fidl::ServerEnd<fuchsia_hardware_acpi::Device> server_end) {
fidl::BindServer(dispatcher_, std::move(server_end), this);
};
auto status = outgoing_.AddProtocol<fuchsia_hardware_acpi::Device>(std::move(service));
if (status.is_error()) {
FDF_SLOG(ERROR, "Failed to add protocol", KV("status", status.status_string()));
return status;
}
status = AddChild();
if (status.is_error()) {
return status;
}
return outgoing_.Serve(std::move(outgoing_dir));
}
zx::status<> AcpiMultiplyController::AddChild() {
fidl::Arena arena;
// Offer fuchsia.hardware.acpi.Device to the driver that binds to the node.
auto protocol_offer =
fuchsia_component_decl::wire::OfferProtocol::Builder(arena)
.source_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_acpi::Device>)
.target_name(arena, fidl::DiscoverableProtocolName<fuchsia_hardware_acpi::Device>)
.dependency_type(fuchsia_component_decl::wire::DependencyType::kStrong)
.Build();
fuchsia_component_decl::wire::Offer offer =
fuchsia_component_decl::wire::Offer::WithProtocol(arena, protocol_offer);
// Set the properties of the node that a driver binds to.
auto properties = fidl::VectorView<fuchsia_driver_framework::wire::NodeProperty>(arena, 2);
properties[0] =
fuchsia_driver_framework::wire::NodeProperty::Builder(arena)
.key(fuchsia_driver_framework::wire::NodePropertyKey::WithIntValue(1 /* BIND_PROTOCOL */))
.value(fuchsia_driver_framework::wire::NodePropertyValue::WithIntValue(
30 /* ZX_PROTOCOL_ACPI */))
.Build();
properties[1] = fuchsia_driver_framework::wire::NodeProperty::Builder(arena)
.key(fuchsia_driver_framework::wire::NodePropertyKey::WithStringValue(
arena, fidl::StringView::FromExternal("fuchsia.acpi.hid")))
.value(fuchsia_driver_framework::wire::NodePropertyValue::WithStringValue(
arena, fidl::StringView::FromExternal("FDFS0001")))
.Build();
auto args =
fuchsia_driver_framework::wire::NodeAddArgs::Builder(arena)
.name(arena, "acpi-child")
.offers(fidl::VectorView<fuchsia_component_decl::wire::Offer>::FromExternal(&offer, 1))
.properties(properties)
.Build();
// Create endpoints of the `NodeController` for the node.
auto endpoints = fidl::CreateEndpoints<fuchsia_driver_framework::NodeController>();
if (endpoints.is_error()) {
FDF_SLOG(ERROR, "Failed to create endpoint", KV("status", endpoints.status_string()));
return zx::error(endpoints.status_value());
}
auto result = node_.sync()->AddChild(args, std::move(endpoints->server), {});
if (!result.ok()) {
FDF_SLOG(ERROR, "Failed to add child", KV("status", result.status_string()));
return zx::error(result.status());
}
return zx::ok();
}
// Protocol method for `fuchsia.hardware.acpi/Device` to return a requested MMIO region.
void AcpiMultiplyController::GetMmio(GetMmioRequestView request,
GetMmioCompleter::Sync &completer) {
if (request->index != 0) {
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
zx::vmo clone;
zx_status_t status = buffer_->get_vmo()->duplicate(ZX_RIGHT_SAME_RIGHTS, &clone);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(fuchsia_mem::wire::Range{
.vmo = std::move(clone),
.offset = 0,
.size = zx_system_get_page_size(),
});
}
// Protocol method for `fuchsia.hardware.acpi/Device` to return the requested IRQ.
void AcpiMultiplyController::MapInterrupt(MapInterruptRequestView request,
MapInterruptCompleter::Sync &completer) {
if (request->index != 0) {
completer.ReplyError(ZX_ERR_OUT_OF_RANGE);
return;
}
zx::interrupt clone;
zx_status_t status = irq_.duplicate(ZX_RIGHT_SAME_RIGHTS, &clone);
if (status != ZX_OK) {
completer.ReplyError(status);
return;
}
completer.ReplySuccess(std::move(clone));
}
// Protocol method for `fuchsia.hardware.acpi/Device` to interpret an invoke the control method
// associated with the provided object path.
void AcpiMultiplyController::EvaluateObject(EvaluateObjectRequestView request,
EvaluateObjectCompleter::Sync &completer) {
if (request->mode != fuchsia_hardware_acpi::wire::EvaluateObjectMode::kPlainObject) {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kNotSupported);
return;
}
if (request->path.get() != "_MUL") {
completer.ReplyError(fuchsia_hardware_acpi::wire::Status::kNotFound);
return;
}
fidl::Arena arena;
completer.ReplySuccess(fuchsia_hardware_acpi::wire::EncodedObject());
// To simulate asynchronous hardware, we post a delayed task and trigger an interrupt when the
// task is complete.
async::PostDelayedTask(
dispatcher_,
[this]() mutable {
auto value1 = acpi_multiply::MultiplyArgumentReg::Get(true).ReadFrom(&buffer_.value());
auto value2 = acpi_multiply::MultiplyArgumentReg::Get(false).ReadFrom(&buffer_.value());
auto status_reg = acpi_multiply::MultiplyStatusReg::Get().FromValue(0);
auto result_reg = acpi_multiply::MultiplyResultReg::Get().FromValue(0);
uint32_t result = value1.operand() * value2.operand();
// Check for overflow
if (value1.operand() != 0 && result / value1.operand() != value2.operand()) {
status_reg.set_overflow(true);
}
result_reg.set_result(result);
result_reg.WriteTo(&buffer_.value());
status_reg.set_finished(true).WriteTo(&buffer_.value());
zx_status_t status = irq_.trigger(0, zx::clock::get_monotonic());
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to trigger interrupt",
KV("status", zx_status_get_string(status)));
}
},
zx::msec(10));
}
} // namespace acpi_multiply
FUCHSIA_DRIVER_RECORD_CPP_V1(acpi_multiply::AcpiMultiplyController);