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