blob: 291688f83e4ab2bb11423ae53ac59b84e236d3ed [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 "src/qemu_edu/qemu_edu.h"
#include <fidl/fuchsia.driver.compat/cpp/wire.h>
#include "src/qemu_edu/registers.h"
namespace fdf {
using namespace fuchsia_driver_framework;
} // namespace fdf
namespace qemu_edu {
namespace {
namespace regs = qemu_edu_regs;
namespace fio = fuchsia_io;
namespace fpci = fuchsia_hardware_pci;
zx::status<fidl::ClientEnd<fuchsia_driver_compat::Device>>
ConnectToParentDevice(const driver::Namespace* ns, std::string_view name) {
auto result = ns->OpenService<fuchsia_driver_compat::Service>(name);
if (result.is_error()) {
return result.take_error();
}
return result.value().connect_device();
}
} // namespace
// static
zx::status<std::unique_ptr<QemuEduDriver>> QemuEduDriver::Start(
fdf::wire::DriverStartArgs& start_args, fdf::UnownedDispatcher dispatcher,
fidl::WireSharedClient<fdf::Node> node, driver::Namespace ns,
driver::Logger logger) {
auto driver = std::make_unique<QemuEduDriver>(dispatcher->async_dispatcher(),
std::move(node), std::move(ns),
std::move(logger));
auto result = driver->Run(dispatcher->async_dispatcher(),
std::move(start_args.outgoing_dir()));
if (result.is_error()) {
return result.take_error();
}
return zx::ok(std::move(driver));
}
zx::status<> QemuEduDriver::MapInterruptAndMmio(
fidl::ClientEnd<fpci::Device> pci_client) {
auto pci = fidl::BindSyncClient(std::move(pci_client));
auto bar = pci->GetBar(0);
if (!bar.ok()) {
FDF_SLOG(ERROR, "failed to get bar", KV("status", bar.status()));
return zx::error(bar.status());
}
if (bar->result.is_err()) {
FDF_SLOG(ERROR, "failed to get bar", KV("status", bar->result.err()));
return zx::error(bar->result.err());
}
{
auto& bar_result = bar->result.response().result;
if (!bar_result.result.is_vmo()) {
FDF_SLOG(ERROR, "unexpected bar type");
return zx::error(ZX_ERR_NO_RESOURCES);
}
zx::status<fdf::MmioBuffer> mmio = fdf::MmioBuffer::Create(
0, bar_result.size, std::move(bar_result.result.vmo()),
ZX_CACHE_POLICY_UNCACHED_DEVICE);
if (mmio.is_error()) {
FDF_SLOG(ERROR, "failed to map mmio", KV("status", mmio.status_value()));
return mmio.take_error();
}
mmio_ = *std::move(mmio);
}
// Read the version information from the edu device.
auto version_reg = regs::Identification::Get().ReadFrom(&*mmio_);
// TODO(surajmalhotra): Report version via inspect.
major_version_ = version_reg.major_version();
minor_version_ = version_reg.minor_version();
// Map the interrupt.
auto result = pci->SetInterruptMode(fpci::wire::InterruptMode::kLegacy, 1);
if (!result.ok()) {
FDF_SLOG(ERROR, "failed configure interrupt mode",
KV("status", result.status()));
return zx::error(result.status());
}
if (result->result.is_err()) {
FDF_SLOG(ERROR, "failed configure interrupt mode",
KV("status", result->result.err()));
return zx::error(result->result.err());
}
auto interrupt = pci->MapInterrupt(0);
if (!interrupt.ok()) {
FDF_SLOG(ERROR, "failed to map interrupt",
KV("status", interrupt.status()));
return zx::error(interrupt.status());
}
if (interrupt->result.is_err()) {
FDF_SLOG(ERROR, "failed to map interrupt",
KV("status", interrupt->result.err()));
return zx::error(interrupt->result.err());
}
irq_ = std::move(interrupt->result.response().interrupt);
return zx::ok();
}
zx::status<> QemuEduDriver::Run(async_dispatcher* dispatcher,
fidl::ServerEnd<fio::Directory> outgoing_dir) {
// Connect to DevfsExporter.
auto exporter = ns_.Connect<fuchsia_device_fs::Exporter>();
if (exporter.is_error()) {
return exporter.take_error();
}
exporter_ = fidl::WireClient(std::move(*exporter), dispatcher);
// Connect to parent.
auto parent = ConnectToParentDevice(&ns_, "default");
if (parent.is_error()) {
FDF_SLOG(ERROR, "Failed to connect to parent",
KV("status", parent.status_string()));
return parent.take_error();
}
// Connect to pci protocol.
auto pci_endpoints = fidl::CreateEndpoints<fpci::Device>();
if (pci_endpoints.is_error()) {
return pci_endpoints.take_error();
}
auto connect_result = fidl::WireCall(*parent)->ConnectFidl(
fidl::StringView::FromExternal(
fidl::DiscoverableProtocolName<fpci::Device>),
pci_endpoints->server.TakeChannel());
if (!connect_result.ok()) {
return zx::error(connect_result.status());
}
auto pci_status = MapInterruptAndMmio(std::move(pci_endpoints->client));
if (pci_status.is_error()) {
return pci_status.take_error();
}
auto result = fidl::WireCall(*parent)->GetTopologicalPath();
if (!result.ok()) {
FDF_SLOG(ERROR, "Failed to get topological path",
KV("status", result.status_string()));
return zx::error(result.status());
}
std::string path(result->path.get());
auto status =
outgoing_.AddProtocol<fuchsia_hardware_qemuedu::Device>(this, Name());
if (status.is_error()) {
FDF_SLOG(ERROR, "Failed to add protocol",
KV("status", status.status_string()));
return status;
}
// Serve a connection to outgoing.
auto endpoints = fidl::CreateEndpoints<fuchsia_io::Directory>();
if (endpoints.is_error()) {
FDF_SLOG(ERROR, "Failed to create endpoints",
KV("status", endpoints.status_string()));
return endpoints.take_error();
}
{
auto status = outgoing_.Serve(std::move(endpoints->server));
if (status.is_error()) {
FDF_SLOG(ERROR, "Failed to serve outgoing directory",
KV("status", status.status_string()));
return status.take_error();
}
}
exporter_
->Export(
std::move(endpoints->client),
fidl::StringView::FromExternal(std::string("svc/").append(Name())),
fidl::StringView::FromExternal(path.append("/").append(Name())), 0)
.Then([this](fidl::WireUnownedResult<fuchsia_device_fs::Exporter::Export>&
result) {
if (!result.ok()) {
FDF_SLOG(ERROR, "Failed to export",
KV("status", result.status_string()));
}
});
return outgoing_.Serve(std::move(outgoing_dir));
}
void QemuEduDriver::ComputeFactorial(
ComputeFactorialRequestView request,
ComputeFactorialCompleter::Sync& completer) {
// Write a value into the factorial register.
uint32_t input = request->input;
mmio_->Write32(input, regs::kFactorialCompoutationOffset);
// Busy wait on the factorial status bit.
while (true) {
const auto status = regs::Status::Get().ReadFrom(&*mmio_);
if (!status.busy()) break;
}
// Return the result.
uint32_t factorial = mmio_->Read32(regs::kFactorialCompoutationOffset);
FDF_SLOG(INFO, "Replying with", KV("factorial", factorial));
completer.Reply(factorial);
}
void QemuEduDriver::LivenessCheck(LivenessCheckRequestView request,
LivenessCheckCompleter::Sync& completer) {
constexpr uint32_t kChallenge = 0xdeadbeef;
constexpr uint32_t kExpectedResponse = ~(kChallenge);
// Write the challenge and observe that the expected response is received.
mmio_->Write32(kChallenge, regs::kLivenessCheckOffset);
auto value = mmio_->Read32(regs::kLivenessCheckOffset);
const bool alive = value == kExpectedResponse;
FDF_SLOG(INFO, "Replying with", KV("result", alive));
completer.Reply(alive);
}
} // namespace qemu_edu
FUCHSIA_DRIVER_RECORD_CPP_V1(qemu_edu::QemuEduDriver);