[qemu_edu] Talk to hardware
Rather than implement FIDL purely in software, this patch enables
qemu_edu to map registers in the pci bar and use them to communicate
with the fake hardware.
Tested: ffx component run \
"fuchsia-pkg://fuchsiasamples.com/eductl#meta/eductl.cm"
Fixed: 98881
Change-Id: I14e6a30d8856480b298efb0c2c98e623c2b97c5e
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-samples/drivers/+/675202
Reviewed-by: Christopher Anderson <cja@google.com>
Fuchsia-Auto-Submit: Suraj Malhotra <surajmalhotra@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
Reviewed-by: Gurjant Kalsi <gkalsi@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/src/qemu_edu/BUILD.bazel b/src/qemu_edu/BUILD.bazel
index e2cebcb..62671cd 100644
--- a/src/qemu_edu/BUILD.bazel
+++ b/src/qemu_edu/BUILD.bazel
@@ -1,10 +1,10 @@
load(
"@rules_fuchsia//fuchsia:defs.bzl",
+ "fuchsia_cc_binary",
"fuchsia_component",
"fuchsia_component_manifest",
- "fuchsia_cc_binary",
- "fuchsia_driver_component",
"fuchsia_driver_bytecode_bind_rules",
+ "fuchsia_driver_component",
"fuchsia_fidl_library",
"fuchsia_fidl_llcpp_library",
"fuchsia_package",
@@ -33,7 +33,10 @@
name = "bind_bytecode",
output = "qemu_edu.bindbc",
rules = "qemu_edu.bind",
- deps = ["@fuchsia_sdk//bind/fuchsia.pci", "@fuchsia_sdk//bind/fuchsia.acpi"],
+ deps = [
+ "@fuchsia_sdk//bind/fuchsia.acpi",
+ "@fuchsia_sdk//bind/fuchsia.pci",
+ ],
)
cc_binary(
@@ -41,16 +44,20 @@
srcs = [
"qemu_edu.cc",
"qemu_edu.h",
+ "registers.h",
],
linkshared = True,
deps = [
":fuchsia.hardware.qemuedu_cc",
"@fuchsia_sdk//fidl/fuchsia.device.fs:fuchsia.device.fs_llcpp_cc",
"@fuchsia_sdk//fidl/fuchsia.driver.compat:fuchsia.driver.compat_llcpp_cc",
+ "@fuchsia_sdk//fidl/fuchsia.hardware.pci:fuchsia.hardware.pci_llcpp_cc",
"@fuchsia_sdk//fidl/zx:zx_cc",
"@fuchsia_sdk//pkg/driver2-llcpp",
"@fuchsia_sdk//pkg/driver_runtime_cpp",
"@fuchsia_sdk//pkg/fidl-llcpp-experimental-driver-only",
+ "@fuchsia_sdk//pkg/hwreg",
+ "@fuchsia_sdk//pkg/mmio",
"@fuchsia_sdk//pkg/sys_component_llcpp",
"@fuchsia_sdk//pkg/zx-experimental-driver-only",
],
@@ -63,8 +70,8 @@
fuchsia_driver_component(
name = "component",
- driver_lib = ":qemu_edu",
bind_bytecode = ":bind_bytecode",
+ driver_lib = ":qemu_edu",
manifest = ":manifest",
)
diff --git a/src/qemu_edu/eductl.cc b/src/qemu_edu/eductl.cc
index 8bc7865..031a089 100644
--- a/src/qemu_edu/eductl.cc
+++ b/src/qemu_edu/eductl.cc
@@ -16,7 +16,7 @@
#include <sys/types.h>
constexpr char kEduDevicePath[] =
- "/dev/sys/platform/platform-passthrough/PCI0/bus/00:06.0/qemu-edu";
+ "/dev/sys/platform/platform-passthrough/PCI0/bus/00:06.0_/qemu-edu";
constexpr char kEduDevicePath2[] =
"/dev/sys/platform/platform-passthrough/acpi/acpi-KBLT/qemu-edu";
diff --git a/src/qemu_edu/meta/eductl.cml b/src/qemu_edu/meta/eductl.cml
index 110560f..d1b153c 100644
--- a/src/qemu_edu/meta/eductl.cml
+++ b/src/qemu_edu/meta/eductl.cml
@@ -6,7 +6,7 @@
forward_stdout_to: "log",
args: [
'factorial',
- '15',
+ '12',
]
},
use: [
diff --git a/src/qemu_edu/qemu_edu.bind b/src/qemu_edu/qemu_edu.bind
index 14c61d6..f4a2082 100644
--- a/src/qemu_edu/qemu_edu.bind
+++ b/src/qemu_edu/qemu_edu.bind
@@ -4,7 +4,7 @@
using fuchsia.acpi;
using fuchsia.pci;
-if fuchsia.BIND_PROTOCOL == fuchsia.pci.BIND_PROTOCOL.DEVICE {
+if fuchsia.BIND_FIDL_PROTOCOL == fuchsia.pci.BIND_FIDL_PROTOCOL.DEVICE {
fuchsia.BIND_PCI_VID == 0x1234;
fuchsia.BIND_PCI_DID == 0x11e8;
} else {
diff --git a/src/qemu_edu/qemu_edu.cc b/src/qemu_edu/qemu_edu.cc
index c1efd9f..291688f 100644
--- a/src/qemu_edu/qemu_edu.cc
+++ b/src/qemu_edu/qemu_edu.cc
@@ -6,6 +6,8 @@
#include <fidl/fuchsia.driver.compat/cpp/wire.h>
+#include "src/qemu_edu/registers.h"
+
namespace fdf {
using namespace fuchsia_driver_framework;
} // namespace fdf
@@ -13,7 +15,9 @@
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) {
@@ -30,32 +34,118 @@
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()));
+ 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.status_value() != ZX_OK) {
+ 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.status_value() != ZX_OK) {
+ 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",
@@ -63,11 +153,11 @@
return zx::error(result.status());
}
- std::string path(result->path.data(), result->path.size());
+ std::string path(result->path.get());
auto status =
outgoing_.AddProtocol<fuchsia_hardware_qemuedu::Device>(this, Name());
- if (status.status_value() != ZX_OK) {
+ if (status.is_error()) {
FDF_SLOG(ERROR, "Failed to add protocol",
KV("status", status.status_string()));
return status;
@@ -106,17 +196,37 @@
void QemuEduDriver::ComputeFactorial(
ComputeFactorialRequestView request,
ComputeFactorialCompleter::Sync& completer) {
- uint32_t factorial = 1;
- for (uint32_t i = 1; i < request->input; i++) {
- factorial *= i;
+ // 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;
}
- FDF_SLOG(INFO, "Replying with", KV("factorial", std::to_string(factorial).c_str()));
+ // 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) {
- completer.Reply(true);
+ 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
diff --git a/src/qemu_edu/qemu_edu.h b/src/qemu_edu/qemu_edu.h
index 73b838a..71a61a0 100644
--- a/src/qemu_edu/qemu_edu.h
+++ b/src/qemu_edu/qemu_edu.h
@@ -7,13 +7,16 @@
#include <fidl/fuchsia.device.fs/cpp/wire.h>
#include <fidl/fuchsia.driver.framework/cpp/wire.h>
+#include <fidl/fuchsia.hardware.pci/cpp/wire.h>
#include <fidl/fuchsia.hardware.qemuedu/cpp/wire.h>
#include <lib/async/dispatcher.h>
#include <lib/driver2/namespace.h>
#include <lib/driver2/record_cpp.h>
#include <lib/driver2/structured_logger.h>
#include <lib/fdf/cpp/dispatcher.h>
+#include <lib/mmio/mmio.h>
#include <lib/sys/component/llcpp/outgoing_directory.h>
+#include <lib/zx/interrupt.h>
#include <lib/zx/status.h>
#include <memory>
@@ -48,6 +51,8 @@
LivenessCheckCompleter::Sync& completer);
private:
+ zx::status<> MapInterruptAndMmio(
+ fidl::ClientEnd<fuchsia_hardware_pci::Device> pci);
zx::status<> Run(async_dispatcher* dispatcher,
fidl::ServerEnd<fuchsia_io::Directory> outgoing_dir);
@@ -56,6 +61,12 @@
driver::Namespace ns_;
driver::Logger logger_;
fidl::WireClient<fuchsia_device_fs::Exporter> exporter_;
+
+ std::optional<fdf::MmioBuffer> mmio_;
+ zx::interrupt irq_;
+
+ uint32_t major_version_ = 0;
+ uint32_t minor_version_ = 0;
};
} // namespace qemu_edu
diff --git a/src/qemu_edu/registers.h b/src/qemu_edu/registers.h
new file mode 100644
index 0000000..57d064c
--- /dev/null
+++ b/src/qemu_edu/registers.h
@@ -0,0 +1,47 @@
+// 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.
+//
+#ifndef SRC_QEMU_EDU_REGISTERS_H_
+#define SRC_QEMU_EDU_REGISTERS_H_
+
+#include <hwreg/bitfields.h>
+
+namespace qemu_edu_regs {
+
+constexpr uint32_t kIdentificationOffset = 0x00;
+constexpr uint32_t kLivenessCheckOffset = 0x04;
+constexpr uint32_t kFactorialCompoutationOffset = 0x08;
+constexpr uint32_t kStatusRegisterOffset = 0x20;
+constexpr uint32_t kInterruptStatusRegisterOffset = 0x24;
+constexpr uint32_t kInterruptRaiseRegisterOffset = 0x60;
+constexpr uint32_t kInterruptAcknowledgeRegisterOffset = 0x64;
+constexpr uint32_t kDmaSourceAddressOffset = 0x80;
+constexpr uint32_t kDmaDestinationAddressOffset = 0x80;
+constexpr uint32_t kDmaTransferCountOffset = 0x90;
+constexpr uint32_t kDmaCommandRegisterOffset = 0x98;
+
+class Identification : public hwreg::RegisterBase<Identification, uint32_t> {
+ public:
+ DEF_FIELD(31, 24, major_version);
+ DEF_FIELD(23, 16, minor_version);
+ DEF_FIELD(15, 0, edu);
+
+ static auto Get() {
+ return hwreg::RegisterAddr<Identification>(kIdentificationOffset);
+ }
+};
+
+class Status : public hwreg::RegisterBase<Status, uint32_t> {
+ public:
+ DEF_BIT(0, busy);
+ DEF_BIT(7, irq_enable);
+
+ static auto Get() {
+ return hwreg::RegisterAddr<Status>(kStatusRegisterOffset);
+ }
+};
+
+} // namespace qemu_edu
+
+#endif // SRC_QEMU_EDU_REGISTERS_H_