| // 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); |