blob: 74f53b0ace824e25d42e16e06c86f40ff7e6b488 [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_multiply.h"
#include <fidl/fuchsia.hardware.acpi/cpp/wire.h>
#include <fidl/fuchsia.hardware.acpi/cpp/wire_types.h>
#include <fidl/sample.acpi.multiply/cpp/wire_types.h>
#include <lib/async/cpp/irq.h>
#include <lib/async/cpp/task.h>
#include <lib/driver2/record_cpp.h>
#include <lib/driver2/structured_logger.h>
#include <lib/mmio/mmio-buffer.h>
#include <zircon/errors.h>
#include <zircon/types.h>
#include "registers.h"
namespace acpi_multiply {
zx::status<std::unique_ptr<AcpiMultiplyDriver>> AcpiMultiplyDriver::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<AcpiMultiplyDriver>(
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<> AcpiMultiplyDriver::Run(fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir) {
auto status = outgoing_.AddProtocol<sample_acpi_multiply::Device>(this, Name());
if (status.is_error()) {
FDF_SLOG(ERROR, "Failed to add protocol", KV("status", status.status_string()));
return status.take_error();
}
// Connect to the ACPI protocol exposed by the parent driver.
auto client = ns_.Connect<fuchsia_hardware_acpi::Device>();
if (client.is_error()) {
FDF_SLOG(ERROR, "Failed to connect to ACPI", KV("status", client.status_string()));
return client.take_error();
}
acpi_.Bind(std::move(client.value()));
// Get interrupt.
auto irq_result = acpi_->MapInterrupt(0);
if (!irq_result.ok() || irq_result->is_error()) {
return irq_result.ok() ? irq_result->take_error() : zx::error(irq_result.error().status());
}
irq_ = std::move(irq_result->value()->irq);
// Start listening for interrupts.
irq_method_.set_object(irq_.get());
irq_method_.Begin(dispatcher_);
// Get MMIO region.
auto mmio_result = acpi_->GetMmio(0);
if (!mmio_result.ok() || mmio_result->is_error()) {
return mmio_result.ok() ? mmio_result->take_error() : zx::error(mmio_result.error().status());
}
auto& mmio = mmio_result->value()->mmio;
auto buf_result = fdf::MmioBuffer::Create(mmio.offset, mmio.size, std::move(mmio.vmo),
ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (buf_result.is_error()) {
return buf_result.take_error();
}
mmio_ = std::move(buf_result.value());
// This is not strictly necessary, but it does a multiplication to prove that the driver
// behaves as expected.
async::PostTask(dispatcher_, [this]() {
auto endpoints = fidl::CreateEndpoints<sample_acpi_multiply::Device>();
if (endpoints.is_error()) {
FDF_LOG(ERROR, "Failed to create endpoints");
return;
}
fidl::BindServer(dispatcher_, std::move(endpoints->server), this);
client_.Bind(std::move(endpoints->client), dispatcher_);
// Do the multiply
client_->Multiply(UINT32_MAX, 9)
.Then([this](fidl::WireUnownedResult<sample_acpi_multiply::Device::Multiply>& response) {
if (response.ok() && response->is_ok()) {
FDF_SLOG(INFO, "UINT32_MAX*9: Got result",
KV("result", response.value().value()->result),
KV("overflowed", response.value().value()->overflowed));
} else {
FDF_SLOG(ERROR, "Multiply failed",
KV("fidl status", (const char*)response.FormatDescription().data()),
KV("device status",
(const char*)(response.ok() ? response.status_string() : "N/A")));
}
});
client_->Multiply(2, 9).Then(
[this](fidl::WireUnownedResult<sample_acpi_multiply::Device::Multiply>& response) {
if (response.ok() && response->is_ok()) {
FDF_SLOG(INFO, "2*9: Got result", KV("result", response.value().value()->result),
KV("overflowed", response.value().value()->overflowed));
} else {
FDF_SLOG(ERROR, "Multiply failed",
KV("fidl status", (const char*)response.FormatDescription().data()),
KV("device status",
(const char*)(response.ok() ? response.status_string() : "N/A")));
}
});
});
return zx::ok();
}
// Protocol method in `sample.acpi.multiply` to execute a multiply operation
void AcpiMultiplyDriver::Multiply(MultiplyRequestView request, MultiplyCompleter::Sync& completer) {
std::scoped_lock lock(completer_lock_);
// Queue operations if there is already one in-flight.
if (cur_completer_.has_value()) {
operation_queue_.emplace_back(Operation{
.a = request->a,
.b = request->b,
.completer = completer.ToAsync(),
});
return;
}
DoMultiply(Operation{
.a = request->a,
.b = request->b,
.completer = completer.ToAsync(),
});
}
// Call the underlying hardware resources (MMIO, Interrupt) to perform a multiply operation.
void AcpiMultiplyDriver::DoMultiply(Operation operation) {
// Write the operands to the two MMIO registers.
MultiplyArgumentReg::Get(true).FromValue(0).set_operand(operation.a).WriteTo(&mmio_.value());
MultiplyArgumentReg::Get(false).FromValue(0).set_operand(operation.b).WriteTo(&mmio_.value());
cur_completer_ = std::move(operation.completer);
fidl::Arena arena;
// Call the ACPI method that will trigger the multiply operation.
auto result =
acpi_->EvaluateObject(fidl::StringView::FromExternal("_MUL"),
fuchsia_hardware_acpi::wire::EvaluateObjectMode::kPlainObject,
fidl::VectorView<fuchsia_hardware_acpi::wire::Object>(arena, 0));
if (!result.ok() || result->is_error()) {
FDF_SLOG(ERROR, "Failed to send EvaluateObject",
KV("error", (const char*)(result.ok() ? std::to_string(result->error_value()).data()
: result.FormatDescription().data())));
cur_completer_->ReplyError(ZX_ERR_INTERNAL);
cur_completer_ = std::nullopt;
return;
}
// We will receive an interrupt when the operation is done.
}
// Callback when the ACPI device raises an interrupt, signaling the multiply operation is complete.
void AcpiMultiplyDriver::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq,
zx_status_t status, const zx_packet_interrupt_t* interrupt) {
irq_.ack();
std::scoped_lock lock(completer_lock_);
if (!cur_completer_.has_value()) {
FDF_LOG(ERROR, "Spurious interrupt!");
return;
}
auto completer = std::move(cur_completer_.value());
cur_completer_ = std::nullopt;
if (status != ZX_OK) {
FDF_SLOG(ERROR, "Failed to wait for interrupt", KV("status", zx_status_get_string(status)));
completer.ReplyError(status);
return;
}
auto status_reg = MultiplyStatusReg::Get().ReadFrom(&mmio_.value());
if (!status_reg.finished()) {
FDF_LOG(ERROR, "Interrupt came too soon!");
completer.ReplyError(ZX_ERR_BAD_STATE);
return;
}
auto result = MultiplyResultReg::Get().ReadFrom(&mmio_.value());
completer.ReplySuccess(result.result(), status_reg.overflow());
// Start the next operation if there is one.
if (!operation_queue_.empty()) {
DoMultiply(std::move(operation_queue_.front()));
operation_queue_.pop_front();
}
}
} // namespace acpi_multiply
FUCHSIA_DRIVER_RECORD_CPP_V1(acpi_multiply::AcpiMultiplyDriver);