[qemu_edu] Use device interrupt in compute.

Update the qemu_edu driver to respond to the device interrupt instead
of doing a busy wait on the status flag.

Bug: 109425
Change-Id: I9467193db339bbd9353a9b477792c041d39d9893
Reviewed-on: https://fuchsia-review.googlesource.com/c/sdk-samples/drivers/+/727882
Commit-Queue: Dave Smith <smithdave@google.com>
Reviewed-by: Jocelyn Dang <jocelyndang@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
diff --git a/src/qemu_edu/drivers/BUILD.bazel b/src/qemu_edu/drivers/BUILD.bazel
index 0b49122..f7e833a 100644
--- a/src/qemu_edu/drivers/BUILD.bazel
+++ b/src/qemu_edu/drivers/BUILD.bazel
@@ -41,6 +41,7 @@
         "@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/async-cpp",
         "@fuchsia_sdk//pkg/driver2-llcpp",
         "@fuchsia_sdk//pkg/driver_runtime_cpp",
         "@fuchsia_sdk//pkg/fidl_cpp_wire",
diff --git a/src/qemu_edu/drivers/edu_device.cc b/src/qemu_edu/drivers/edu_device.cc
index 8ab28f3..4663b90 100644
--- a/src/qemu_edu/drivers/edu_device.cc
+++ b/src/qemu_edu/drivers/edu_device.cc
@@ -40,7 +40,7 @@
     mmio_ = *std::move(mmio);
   }
 
-  // Configure interrupt handling for the device
+  // Configure interrupt handling for the device using INTx
   auto result = pci_->SetInterruptMode(fuchsia_hardware_pci::wire::InterruptMode::kLegacy, 1);
   if (!result.ok()) {
     FDF_SLOG(ERROR, "failed configure interrupt mode", KV("status", result.status()));
@@ -62,39 +62,79 @@
     return zx::error(interrupt->error_value());
   }
   irq_ = std::move(interrupt->value()->interrupt);
+  // Start listening for interrupts.
+  irq_method_.set_object(irq_.get());
+  irq_method_.Begin(dispatcher_);
 
   return zx::ok();
 }
 // [END interrupt_mmio]
 
 // [START compute_factorial]
-// Write data into the factorial register and return the result.
-uint32_t QemuEduDevice::ComputeFactorial(uint32_t input) {
-  // Write a value into the factorial register.
-  mmio_->Write32(input, kFactorialComputationOffset);
-
-  // Busy wait on the factorial status bit.
-  while (true) {
-    const auto status = StatusRegister();
-    if (!status.busy())
-      break;
+// Write data into the factorial register wait for an interrupt.
+void QemuEduDevice::ComputeFactorial(uint32_t input,
+                                     fit::callback<void(zx::status<uint32_t>)> callback) {
+  if (pending_callback_.has_value()) {
+    callback(zx::error(ZX_ERR_SHOULD_WAIT));
   }
 
-  // Return the result.
+  // Tell the device to raise an interrupt after computation.
+  auto status = StatusRegister();
+  status.set_irq_enable(true);
+  status.WriteTo(&*mmio_);
+
+  // Write the value into the factorial register to start computation.
+  mmio_->Write32(input, kFactorialComputationOffset);
+
+  // We will receive an interrupt when the computation completes.
+  pending_callback_ = std::move(callback);
+}
+
+// Respond to INTx interrupts triggered by the device, and return the compute result.
+void QemuEduDevice::HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq,
+                              zx_status_t status, const zx_packet_interrupt_t* interrupt) {
+  irq_.ack();
+  if (!pending_callback_.has_value()) {
+    FDF_LOG(ERROR, "Received unexpected interrupt!");
+    return;
+  }
+  auto callback = std::move(*pending_callback_);
+  pending_callback_ = std::nullopt;
+  if (status != ZX_OK) {
+    FDF_SLOG(ERROR, "Failed to wait for interrupt", KV("status", zx_status_get_string(status)));
+    callback(zx::error(status));
+    return;
+  }
+
+  // Acknowledge the interrupt with the edu device.
+  auto int_status = mmio_->Read32(kInterruptStatusRegisterOffset);
+  mmio_->Write32(int_status, kInterruptAcknowledgeRegisterOffset);
+
+  // Deassert the legacy INTx interrupt on the PCI bus.
+  auto irq_result = pci_->AckInterrupt();
+  if (!irq_result.ok() || irq_result->is_error()) {
+    FDF_SLOG(ERROR, "Failed to ack PCI interrupt",
+             KV("status", irq_result.ok() ? irq_result->error_value() : irq_result.status()));
+    callback(zx::error(ZX_ERR_IO));
+    return;
+  }
+
+  // Reply with the result.
   uint32_t factorial = mmio_->Read32(kFactorialComputationOffset);
-  return factorial;
+  FDF_SLOG(INFO, "Replying with", KV("factorial", factorial));
+  callback(zx::ok(factorial));
 }
 // [END compute_factorial]
 
 // [START liveness_check]
 // Write a challenge value to the liveness check register and return the result.
-uint32_t QemuEduDevice::LivenessCheck(uint32_t challenge) {
+zx::status<uint32_t> QemuEduDevice::LivenessCheck(uint32_t challenge) {
   // Write the challenge value to the liveness check register.
   mmio_->Write32(challenge, kLivenessCheckOffset);
 
   // Return the result.
   auto value = mmio_->Read32(kLivenessCheckOffset);
-  return value;
+  return zx::ok(value);
 }
 // [END liveness_check]
 
diff --git a/src/qemu_edu/drivers/edu_device.h b/src/qemu_edu/drivers/edu_device.h
index 11636b8..8aa9130 100644
--- a/src/qemu_edu/drivers/edu_device.h
+++ b/src/qemu_edu/drivers/edu_device.h
@@ -11,6 +11,7 @@
 
 // [START hw_imports]
 #include <fidl/fuchsia.hardware.pci/cpp/wire.h>
+#include <lib/async/cpp/irq.h>
 #include <lib/driver2/namespace.h>
 #include <lib/driver2/structured_logger.h>
 #include <lib/mmio/mmio.h>
@@ -61,30 +62,38 @@
  public:
   explicit QemuEduDevice(driver::Namespace& ns, async_dispatcher_t* dispatcher,
                          fidl::WireSyncClient<fuchsia_hardware_pci::Device> pci)
-      : pci_(std::move(pci)) {
+      : dispatcher_(dispatcher), pci_(std::move(pci)) {
     auto logger_result = driver::Logger::Create(ns, dispatcher, "qemu-edu");
     ZX_ASSERT(logger_result.is_ok());
     logger_ = std::move(*logger_result);
   }
 
   zx::status<> MapInterruptAndMmio();
-
-  uint32_t ComputeFactorial(uint32_t input);
-  uint32_t LivenessCheck(uint32_t challenge);
   // [END public_main]
 
   // [START public_registers]
+  void ComputeFactorial(uint32_t input, fit::callback<void(zx::status<uint32_t>)> callback);
+  zx::status<uint32_t> LivenessCheck(uint32_t challenge);
+
   Identification IdentificationRegister() { return Identification::Get().ReadFrom(&*mmio_); }
   Status StatusRegister() { return Status::Get().ReadFrom(&*mmio_); }
   // [END public_registers]
 
   // [START private_main]
  private:
+  void HandleIrq(async_dispatcher_t* dispatcher, async::IrqBase* irq, zx_status_t status,
+                 const zx_packet_interrupt_t* interrupt);
+
+  async_dispatcher_t* const dispatcher_;
+
   driver::Logger logger_;
+  fidl::WireSyncClient<fuchsia_hardware_pci::Device> pci_;
   std::optional<fdf::MmioBuffer> mmio_;
   zx::interrupt irq_;
-  fidl::WireSyncClient<fuchsia_hardware_pci::Device> pci_;
+  async::IrqMethod<QemuEduDevice, &QemuEduDevice::HandleIrq> irq_method_{this};
+  std::optional<fit::callback<void(zx::status<uint32_t>)>> pending_callback_;
   // [END private_main]
+
   // [START class_footer]
 };
 // [END class_footer]
diff --git a/src/qemu_edu/drivers/qemu_edu.cc b/src/qemu_edu/drivers/qemu_edu.cc
index fdaae86..34a2b22 100644
--- a/src/qemu_edu/drivers/qemu_edu.cc
+++ b/src/qemu_edu/drivers/qemu_edu.cc
@@ -144,16 +144,22 @@
                                      ComputeFactorialCompleter::Sync& completer) {
   auto edu_device = device_.lock();
   if (!edu_device) {
-    FDF_LOG(ERROR, "Unable to access device resources");
+    FDF_LOG(ERROR, "Unable to access device resources.");
+    completer.ReplyError(ZX_ERR_BAD_STATE);
     return;
   }
 
   uint32_t input = request->input;
 
-  uint32_t factorial = edu_device->ComputeFactorial(input);
-
-  FDF_SLOG(INFO, "Replying with", KV("factorial", factorial));
-  completer.Reply(factorial);
+  edu_device->ComputeFactorial(
+      input, [completer = completer.ToAsync()](zx::status<uint32_t> result_status) mutable {
+        if (result_status.is_error()) {
+          completer.ReplyError(result_status.error_value());
+          return;
+        }
+        uint32_t factorial = result_status.value();
+        completer.ReplySuccess(factorial);
+      });
 }
 // [END compute_factorial]
 
@@ -163,18 +169,24 @@
                                   LivenessCheckCompleter::Sync& completer) {
   auto edu_device = device_.lock();
   if (!edu_device) {
-    FDF_LOG(ERROR, "Unable to access device resources");
+    FDF_LOG(ERROR, "Unable to access device resources.");
+    completer.ReplyError(ZX_ERR_BAD_STATE);
     return;
   }
 
   constexpr uint32_t kChallenge = 0xdeadbeef;
   constexpr uint32_t kExpectedResponse = ~(kChallenge);
 
-  uint32_t value = edu_device->LivenessCheck(kChallenge);
-  const bool alive = (value == kExpectedResponse);
+  auto status = edu_device->LivenessCheck(kChallenge);
+  if (status.is_error()) {
+    FDF_LOG(ERROR, "Unable to send liveness check request.");
+    completer.ReplyError(status.error_value());
+    return;
+  }
+  const bool alive = (status.value() == kExpectedResponse);
 
   FDF_SLOG(INFO, "Replying with", KV("result", alive));
-  completer.Reply(alive);
+  completer.ReplySuccess(alive);
 }
 // [END liveness_check]
 
diff --git a/src/qemu_edu/drivers/qemu_edu.h b/src/qemu_edu/drivers/qemu_edu.h
index 73d5bca..0070f5c 100644
--- a/src/qemu_edu/drivers/qemu_edu.h
+++ b/src/qemu_edu/drivers/qemu_edu.h
@@ -47,7 +47,9 @@
   // This method is called when a server connection is torn down.
   void OnUnbound(fidl::UnbindInfo info,
                  fidl::ServerEnd<fuchsia_examples_qemuedu::Device> server_end) {
-    if (!info.is_user_initiated()) {
+    if (info.is_peer_closed()) {
+      FDF_LOG(DEBUG, "Client disconnected");
+    } else if (!info.is_user_initiated()) {
       FDF_LOG(ERROR, "Client connection unbound: %s", info.status_string());
     }
   }
diff --git a/src/qemu_edu/fidl/BUILD.bazel b/src/qemu_edu/fidl/BUILD.bazel
index 17f5df6..98aa50f 100644
--- a/src/qemu_edu/fidl/BUILD.bazel
+++ b/src/qemu_edu/fidl/BUILD.bazel
@@ -18,6 +18,9 @@
     ],
     library = "fuchsia.examples.qemuedu",
     visibility = ["//visibility:public"],
+    deps = [
+        "@fuchsia_sdk//fidl/zx",
+    ],
 )
 
 fuchsia_fidl_llcpp_library(
diff --git a/src/qemu_edu/fidl/qemu_edu.fidl b/src/qemu_edu/fidl/qemu_edu.fidl
index 045e9a3..72f95cb 100644
--- a/src/qemu_edu/fidl/qemu_edu.fidl
+++ b/src/qemu_edu/fidl/qemu_edu.fidl
@@ -5,6 +5,8 @@
 // [START example_snippet]
 library fuchsia.examples.qemuedu;
 
+using zx;
+
 /// The Device protocol provides access to the functions of the QEMU edu hardware.
 protocol Device {
     /// Computes the factorial of 'input' using the edu device and returns the
@@ -13,12 +15,12 @@
         input uint32;
     }) -> (struct {
         output uint32;
-    });
+    }) error zx.status;
 
     /// Performs a liveness check and return true if the device passes.
     LivenessCheck() -> (struct {
         result bool;
-    });
+    }) error zx.status;
 };
 
 /// The Service enables the driver framework to offer the Device protocol to
diff --git a/src/qemu_edu/tests/qemu_edu_system_test.cc b/src/qemu_edu/tests/qemu_edu_system_test.cc
index 22dec21..07d028e 100644
--- a/src/qemu_edu/tests/qemu_edu_system_test.cc
+++ b/src/qemu_edu/tests/qemu_edu_system_test.cc
@@ -39,7 +39,7 @@
 TEST_F(QemuEduSystemTest, LivenessCheck) {
   fidl::WireResult result = device()->LivenessCheck();
   ASSERT_EQ(result.status(), ZX_OK);
-  ASSERT_TRUE(result->result);
+  ASSERT_TRUE(result->value()->result);
 }
 
 TEST_F(QemuEduSystemTest, ComputeFactorial) {
@@ -49,7 +49,7 @@
   for (int i = 0; i < kExpected.size(); i++) {
     fidl::WireResult result = device()->ComputeFactorial(i);
     ASSERT_EQ(result.status(), ZX_OK);
-    EXPECT_EQ(result->output, kExpected[i]);
+    EXPECT_EQ(result->value()->output, kExpected[i]);
   }
 }
 
diff --git a/src/qemu_edu/tools/eductl.cc b/src/qemu_edu/tools/eductl.cc
index 971bb36..3123029 100644
--- a/src/qemu_edu/tools/eductl.cc
+++ b/src/qemu_edu/tools/eductl.cc
@@ -94,7 +94,7 @@
     return -1;
   }
 
-  if (liveness_check_result->result) {
+  if (liveness_check_result->value()->result) {
     printf("Liveness check passed!\n");
     return 0;
   } else {
@@ -126,7 +126,7 @@
     return -1;
   }
 
-  printf("Factorial(%u) = %u\n", input, compute_factorial_result->output);
+  printf("Factorial(%u) = %u\n", input, compute_factorial_result->value()->output);
 
   return 0;
 }