[gdc][sherlock] Implement GdcProcessFrame() - partial implementation

- Posts a task in a deque which is drained by a separate thread.
- When the task is run, the necessary callback is called.

TODO: This is just adding hooks to call the necessary callback function
CAM-33 #comment - in progress

Test: Added tests. Run using src/camera/TESTING.md

Change-Id: Iadc05e23b16ba80671a4182c75f8b515794153d8
diff --git a/src/camera/drivers/hw_accel/gdc/BUILD.gn b/src/camera/drivers/hw_accel/gdc/BUILD.gn
index 64ce315..ef1b453 100644
--- a/src/camera/drivers/hw_accel/gdc/BUILD.gn
+++ b/src/camera/drivers/hw_accel/gdc/BUILD.gn
@@ -20,6 +20,8 @@
     "//zircon/public/banjo/ddk.protocol.platform.device",
     "//zircon/public/banjo/ddk.protocol.sysmem",
     "//zircon/public/fidl/fuchsia-sysmem:fuchsia-sysmem_c",
+    "//zircon/public/lib/async-cpp",
+    "//zircon/public/lib/async-loop-cpp",
     "//zircon/public/lib/ddk",
     "//zircon/public/lib/ddktl",
     "//zircon/public/lib/device-protocol-pdev",
@@ -39,6 +41,7 @@
 
   configs -= [ "//build/config/fuchsia:no_cpp_standard_library" ]
   configs += [ "//build/config/fuchsia:static_cpp_standard_library" ]
+  configs += [ "//build/config/fuchsia:enable_zircon_asserts" ]
 }
 
 package("gdc") {
@@ -63,6 +66,7 @@
   deps = [
     "//zircon/public/banjo/ddk.protocol.gdc",
     "//zircon/public/fidl/fuchsia-sysmem:fuchsia-sysmem_c",
+    "//zircon/public/lib/async-cpp",
     "//zircon/public/lib/ddk",
     "//zircon/public/lib/ddktl",
     "//zircon/public/lib/fzl",
diff --git a/src/camera/drivers/hw_accel/gdc/gdc.cc b/src/camera/drivers/hw_accel/gdc/gdc.cc
index 3f6e28f..503e7f4 100644
--- a/src/camera/drivers/hw_accel/gdc/gdc.cc
+++ b/src/camera/drivers/hw_accel/gdc/gdc.cc
@@ -8,12 +8,11 @@
 #include <ddk/debug.h>
 #include <ddk/driver.h>
 #include <fbl/alloc_checker.h>
-#include <fbl/auto_call.h>
 #include <fbl/auto_lock.h>
 #include <fbl/unique_ptr.h>
 #include <hw/reg.h>
 #include <stdint.h>
-#include <threads.h>
+#include <zircon/threads.h>
 #include <zircon/types.h>
 
 #include <memory>
@@ -82,10 +81,102 @@
   return ZX_OK;
 }
 
+void GdcDevice::ProcessTask(TaskInfo& info) {
+  auto task = info.task;
+  auto input_buffer_index = info.input_buffer_index;
+
+  // TODO(CAM-33): Add Processing of the frame implementation here.
+
+  zx_port_packet_t packet;
+  ZX_ASSERT(ZX_OK == WaitForInterrupt(&packet));
+  gdc_irq_.ack();
+
+  // Currently there is only type of event coming in at this port.
+  // We could possibly add more, like one to terminate the process.
+  if (packet.key == kPortKeyIrqMsg) {
+    // Invoke the callback function and tell about the output buffer index
+    // which is ready to be used.
+    // TODO(CAM-33): pass actual output buffer index instead of
+    // input_buffer_index.
+    task->callback()->frame_ready(task->callback()->ctx, input_buffer_index);
+  }
+}
+
+int GdcDevice::FrameProcessingThread() {
+  FX_LOGF(INFO, "", "%s: start \n", __func__);
+
+  while (running_.load()) {
+    // Waiting for the event when a task is queued.
+    sync_completion_wait(&frame_processing_signal_, ZX_TIME_INFINITE);
+    bool pending_task = false;
+    // Dequeing the entire deque till it drains.
+    do {
+      TaskInfo info;
+      {
+        fbl::AutoLock lock(&deque_lock_);
+        if (!processing_queue_.empty()) {
+          info = processing_queue_.back();
+          processing_queue_.pop_back();
+          pending_task = true;
+        } else {
+          pending_task = false;
+        }
+      }
+      if (pending_task) {
+        ProcessTask(info);
+      }
+    } while (pending_task);
+    // Now that the deque is drained we reset the signal.
+    sync_completion_reset(&frame_processing_signal_);
+  }
+  return 0;
+}
+
 zx_status_t GdcDevice::GdcProcessFrame(uint32_t task_index,
                                        uint32_t input_buffer_index) {
-  // TODO(braval): Implement this.
-  return ZX_ERR_NOT_SUPPORTED;
+  // Find the entry in hashmap.
+  auto task_entry = task_map_.find(task_index);
+  if (task_entry == task_map_.end()) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  // Validate |input_buffer_index|.
+  if (!task_entry->second->IsInputBufferIndexValid(input_buffer_index)) {
+    return ZX_ERR_INVALID_ARGS;
+  }
+
+  TaskInfo info;
+  info.task = task_entry->second.get();
+  info.input_buffer_index = input_buffer_index;
+
+  // Put the task on queue.
+  fbl::AutoLock lock(&deque_lock_);
+  processing_queue_.push_front(std::move(info));
+  sync_completion_signal(&frame_processing_signal_);
+  return ZX_OK;
+}
+
+zx_status_t GdcDevice::StartThread() {
+  running_.store(true);
+  return thrd_status_to_zx_status(thrd_create_with_name(
+      &processing_thread_,
+      [](void* arg) -> int {
+        return reinterpret_cast<GdcDevice*>(arg)->FrameProcessingThread();
+      },
+      this, "gdc-processing-thread"));
+}
+
+zx_status_t GdcDevice::StopThread() {
+  running_.store(false);
+  // Signal the thread waiting on this signal
+  sync_completion_signal(&frame_processing_signal_);
+  gdc_irq_.destroy();
+  JoinThread();
+  return ZX_OK;
+}
+
+zx_status_t GdcDevice::WaitForInterrupt(zx_port_packet_t* packet) {
+  return port_.wait(zx::time::infinite(), packet);
 }
 
 void GdcDevice::GdcRemoveTask(uint32_t task_index) {
@@ -98,7 +189,7 @@
 }
 
 void GdcDevice::GdcReleaseFrame(uint32_t task_index, uint32_t buffer_index) {
-  // TODO(braval): Implement this.
+  // TODO(CAM-33): Implement this.
 }
 
 // static
@@ -131,6 +222,19 @@
     return status;
   }
 
+  zx::port port;
+  status = zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port);
+  if (status != ZX_OK) {
+    FX_LOGF(ERROR, "%s: port create failed %d\n", __func__, status);
+    return status;
+  }
+
+  status = gdc_irq.bind(port, kPortKeyIrqMsg, 0 /*options*/);
+  if (status != ZX_OK) {
+    FX_LOGF(ERROR, "%s: interrupt bind failed %d\n", __func__, status);
+    return status;
+  }
+
   zx::bti bti;
   status = pdev.GetBti(0, &bti);
   if (status != ZX_OK) {
@@ -141,13 +245,14 @@
   fbl::AllocChecker ac;
   auto gdc_device = std::unique_ptr<GdcDevice>(
       new (&ac) GdcDevice(parent, std::move(*clk_mmio), std::move(*gdc_mmio),
-                          std::move(gdc_irq), std::move(bti)));
+                          std::move(gdc_irq), std::move(bti), std::move(port)));
   if (!ac.check()) {
     return ZX_ERR_NO_MEMORY;
   }
 
   gdc_device->InitClocks();
 
+  status = gdc_device->StartThread();
   *out = std::move(gdc_device);
   return status;
 }
@@ -157,7 +262,10 @@
   DdkRemove();
 }
 
-void GdcDevice::DdkRelease() { delete this; }
+void GdcDevice::DdkRelease() {
+  StopThread();
+  delete this;
+}
 
 void GdcDevice::ShutDown() {}
 
diff --git a/src/camera/drivers/hw_accel/gdc/gdc.h b/src/camera/drivers/hw_accel/gdc/gdc.h
index c855ca1..c492242 100644
--- a/src/camera/drivers/hw_accel/gdc/gdc.h
+++ b/src/camera/drivers/hw_accel/gdc/gdc.h
@@ -2,31 +2,44 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#ifndef SRC_CAMERA_DRIVERS_HW_ACCEL_GDC_GDC_H_
+#define SRC_CAMERA_DRIVERS_HW_ACCEL_GDC_GDC_H_
+
 #include <ddk/platform-defs.h>
+#ifndef _ALL_SOURCE
+#define _ALL_SOURCE  // Enables thrd_create_with_name in <threads.h>.
+#include <threads.h>
+#endif
 #include <ddk/protocol/platform/bus.h>
 #include <ddk/protocol/platform/device.h>
 #include <ddktl/device.h>
 #include <ddktl/protocol/gdc.h>
+#include <fbl/auto_lock.h>
+#include <fbl/mutex.h>
 #include <fbl/unique_ptr.h>
 #include <hw/reg.h>
 #include <lib/device-protocol/pdev.h>
 #include <lib/device-protocol/platform-device.h>
 #include <lib/fidl-utils/bind.h>
 #include <lib/mmio/mmio.h>
+#include <lib/sync/completion.h>
+#include <lib/zx/event.h>
 #include <lib/zx/interrupt.h>
-#include <threads.h>
 #include <zircon/fidl.h>
 
+#include <atomic>
 #include <deque>
-#include <list>
 #include <unordered_map>
-#include <vector>
 
 #include "task.h"
 
 namespace gdc {
-
 // |GdcDevice| is spawned by the driver in |gdc.cc|
+namespace {
+
+constexpr uint64_t kPortKeyIrqMsg = 0x00;
+
+}  // namespace
 // This provides ZX_PROTOCOL_GDC.
 class GdcDevice;
 using GdcDeviceType = ddk::Device<GdcDevice, ddk::Unbindable>;
@@ -35,11 +48,11 @@
                   public ddk::GdcProtocol<GdcDevice, ddk::base_protocol> {
  public:
   DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(GdcDevice);
-
   explicit GdcDevice(zx_device_t* parent, ddk ::MmioBuffer clk_mmio,
                      ddk ::MmioBuffer gdc_mmio, zx::interrupt gdc_irq,
-                     zx::bti bti)
+                     zx::bti bti, zx::port port)
       : GdcDeviceType(parent),
+        port_(std::move(port)),
         clock_mmio_(std::move(clk_mmio)),
         gdc_mmio_(std::move(gdc_mmio)),
         gdc_irq_(std::move(gdc_irq)),
@@ -68,6 +81,16 @@
 
   // Used for unit tests.
   const ddk::MmioBuffer* gdc_mmio() const { return &gdc_mmio_; }
+  zx_status_t StartThread();
+  zx_status_t StopThread();
+
+ protected:
+  struct TaskInfo {
+    Task* task;
+    uint32_t input_buffer_index;
+  };
+
+  zx::port port_;
 
  private:
   friend class GdcDeviceTester;
@@ -75,6 +98,14 @@
   // All necessary clean up is done here in ShutDown().
   void ShutDown();
   void InitClocks();
+  int FrameProcessingThread();
+  int JoinThread() { return thrd_join(processing_thread_, nullptr); }
+
+  void ProcessTask(TaskInfo& info);
+  zx_status_t WaitForInterrupt(zx_port_packet_t* packet);
+
+  // Used to access the processing queue.
+  fbl::Mutex deque_lock_;
 
   // HHI register block has the clock registers
   ddk::MmioBuffer clock_mmio_;
@@ -83,6 +114,12 @@
   zx::bti bti_;
   uint32_t next_task_index_ = 0;
   std::unordered_map<uint32_t, std::unique_ptr<Task>> task_map_;
+  std::deque<TaskInfo> processing_queue_ __TA_GUARDED(deque_lock_);
+  thrd_t processing_thread_;
+  sync_completion_t frame_processing_signal_;
+  std::atomic<bool> running_;
 };
 
 }  // namespace gdc
+
+#endif  // SRC_CAMERA_DRIVERS_HW_ACCEL_GDC_GDC_H_
diff --git a/src/camera/drivers/hw_accel/gdc/task.cc b/src/camera/drivers/hw_accel/gdc/task.cc
index 2bda3c7..85eb3d6 100644
--- a/src/camera/drivers/hw_accel/gdc/task.cc
+++ b/src/camera/drivers/hw_accel/gdc/task.cc
@@ -8,7 +8,6 @@
 #include <fbl/alloc_checker.h>
 #include <lib/syslog/global.h>
 #include <stdint.h>
-#include <threads.h>
 #include <zircon/types.h>
 
 #include <memory>
@@ -138,6 +137,8 @@
     FX_LOG(ERROR, "%s: InitBuffers Failed\n", __func__);
   }
 
+  task->callback_ = callback;
+
   *out = std::move(task);
   return status;
 }
diff --git a/src/camera/drivers/hw_accel/gdc/task.h b/src/camera/drivers/hw_accel/gdc/task.h
index 6b43fae..b58abb1 100644
--- a/src/camera/drivers/hw_accel/gdc/task.h
+++ b/src/camera/drivers/hw_accel/gdc/task.h
@@ -34,14 +34,21 @@
   zx_status_t GetInputBufferPhysAddr(uint32_t input_buffer_index,
                                      zx_paddr_t* out) const;
 
+  // Validates input buffer index.
+  bool IsInputBufferIndexValid(uint32_t input_buffer_index) const {
+    return input_buffer_index < input_buffers_.size();
+  }
+
   // Returns a |Buffer| object which is free for use as output buffer.
   std::optional<fzl::VmoPool::Buffer> GetOutputBuffer() {
     return output_buffers_.LockBufferForWrite();
   }
 
+  const gdc_callback_t* callback() { return callback_; }
+
   // Static function to create a task object.
   // |input_buffer_collection|              : Input buffer collection.
-  // |output_buffer_collection|             : Onput buffer collection.
+  // |output_buffer_collection|             : Output buffer collection.
   // |config_vmo|                           : Configuration is stored in this
   // VMO. |callback|                             : Callback function to call for
   // this task. |out|                                  : Pointer to a task
@@ -66,6 +73,7 @@
   fzl::PinnedVmo config_vmo_pinned_;
   fzl::VmoPool output_buffers_;
   fbl::Array<fzl::PinnedVmo> input_buffers_;
+  const gdc_callback_t* callback_;
 };
 }  // namespace gdc
 
diff --git a/src/camera/drivers/hw_accel/gdc/test/BUILD.gn b/src/camera/drivers/hw_accel/gdc/test/BUILD.gn
index 3eff58d..fc1a325 100644
--- a/src/camera/drivers/hw_accel/gdc/test/BUILD.gn
+++ b/src/camera/drivers/hw_accel/gdc/test/BUILD.gn
@@ -19,9 +19,11 @@
     "//zircon/public/banjo/ddk.protocol.platform.device",
     "//zircon/public/banjo/ddk.protocol.sysmem",
     "//zircon/public/fidl/fuchsia-sysmem:fuchsia-sysmem_c",
+    "//zircon/public/lib/async-loop-cpp",
     "//zircon/public/lib/ddk",
     "//zircon/public/lib/ddktl",
     "//zircon/public/lib/device-protocol-pdev",
+    "//zircon/public/lib/device-protocol-pdev",
     "//zircon/public/lib/device-protocol-platform-device",
     "//zircon/public/lib/driver",
     "//zircon/public/lib/fake-bti",
diff --git a/src/camera/drivers/hw_accel/gdc/test/task-test.cc b/src/camera/drivers/hw_accel/gdc/test/task-test.cc
index 6c89426..9e7df4a 100644
--- a/src/camera/drivers/hw_accel/gdc/test/task-test.cc
+++ b/src/camera/drivers/hw_accel/gdc/test/task-test.cc
@@ -33,6 +33,11 @@
 
 // Integration test for the driver defined in zircon/system/dev/camera/arm-isp.
 class TaskTest : public zxtest::Test {
+ public:
+  void ProcessFrameCallback(uint32_t output_buffer_id) {
+    callback_check_.push_back(output_buffer_id);
+  }
+
  protected:
   void SetUpBufferCollections(uint32_t buffer_collection_count) {
     ASSERT_OK(fake_bti_create(bti_handle_.reset_and_get_address()));
@@ -52,12 +57,46 @@
     ASSERT_OK(status);
   }
 
+  void SetupForFrameProcessing() {
+    SetUpBufferCollections(kNumberOfBuffers);
+    ddk_mock::MockMmioReg fake_reg_array[kNumberOfBuffers];
+    ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
+                                          kNumberOfBuffers);
+    zx::port port;
+    ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port));
+    callback_.frame_ready = [](void* ctx, uint32_t idx) {
+      return static_cast<TaskTest*>(ctx)->ProcessFrameCallback(idx);
+    };
+    callback_.ctx = this;
+
+    ASSERT_OK(
+        zx::interrupt::create(zx::resource(), 0, ZX_INTERRUPT_VIRTUAL, &irq_));
+    ASSERT_OK(port.duplicate(ZX_RIGHT_SAME_RIGHTS, &port_));
+
+    gdc_device_ = std::make_unique<GdcDevice>(
+        nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
+        ddk::MmioBuffer(fake_regs.GetMmioBuffer()), std::move(irq_),
+        std::move(bti_handle_), std::move(port));
+
+    // Start the thread.
+    EXPECT_OK(gdc_device_->StartThread());
+  }
+
+  void TearDown() override {
+    if (bti_handle_ != ZX_HANDLE_INVALID) {
+      fake_bti_destroy(bti_handle_.get());
+    }
+  }
+
   zx::vmo config_vmo_;
   zx::bti bti_handle_;
+  zx::port port_;
+  zx::interrupt irq_;
   gdc_callback_t callback_;
   buffer_collection_info_t input_buffer_collection_;
   buffer_collection_info_t output_buffer_collection_;
   std::unique_ptr<GdcDevice> gdc_device_;
+  std::vector<uint32_t> callback_check_;
 };
 
 TEST_F(TaskTest, BasicCreationTest) {
@@ -113,6 +152,168 @@
   EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
 }
 
+TEST_F(TaskTest, InitTaskTest) {
+  SetUpBufferCollections(kNumberOfBuffers);
+  ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
+  ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
+                                        kNumberOfMmios);
+  ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_));
+
+  GdcDevice gdc_device(nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
+                       ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
+                       zx::interrupt(), std::move(bti_handle_),
+                       std::move(port_));
+
+  std::vector<uint32_t> received_ids;
+  for (uint32_t i = 0; i < kMaxTasks; i++) {
+    zx::vmo config_vmo;
+    ASSERT_OK(config_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &config_vmo));
+
+    uint32_t task_id;
+    zx_status_t status = gdc_device.GdcInitTask(
+        &input_buffer_collection_, &output_buffer_collection_,
+        std::move(config_vmo), &callback_, &task_id);
+    EXPECT_OK(status);
+    // Checking to see if we are getting unique task ids.
+    auto entry = find(received_ids.begin(), received_ids.end(), task_id);
+    EXPECT_EQ(received_ids.end(), entry);
+    received_ids.push_back(task_id);
+  }
+}
+
+TEST_F(TaskTest, RemoveTaskTest) {
+  SetUpBufferCollections(kNumberOfBuffers);
+  ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
+  ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
+                                        kNumberOfMmios);
+
+  ASSERT_OK(zx::port::create(ZX_PORT_BIND_TO_INTERRUPT, &port_));
+
+  gdc_device_ = std::make_unique<GdcDevice>(
+      nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
+      ddk::MmioBuffer(fake_regs.GetMmioBuffer()), zx::interrupt(),
+      std::move(bti_handle_), std::move(port_));
+
+  uint32_t task_id;
+  zx_status_t status = gdc_device_->GdcInitTask(
+      &input_buffer_collection_, &output_buffer_collection_,
+      std::move(config_vmo_), &callback_, &task_id);
+  EXPECT_OK(status);
+
+  // Valid id.
+  ASSERT_NO_DEATH(([this, task_id]() { gdc_device_->GdcRemoveTask(task_id); }));
+
+  // Invalid id.
+  ASSERT_DEATH(
+      ([this, task_id]() { gdc_device_->GdcRemoveTask(task_id + 1); }));
+}
+
+TEST_F(TaskTest, ProcessInvalidFrameTest) {
+  SetupForFrameProcessing();
+
+  // Invalid task id.
+  zx_status_t status = gdc_device_->GdcProcessFrame(0xFF, 0);
+  EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
+
+  ASSERT_OK(gdc_device_->StopThread());
+}
+
+TEST_F(TaskTest, InvalidBufferProcessFrameTest) {
+  SetupForFrameProcessing();
+
+  uint32_t task_id;
+  zx_status_t status = gdc_device_->GdcInitTask(
+      &input_buffer_collection_, &output_buffer_collection_,
+      std::move(config_vmo_), &callback_, &task_id);
+  EXPECT_OK(status);
+
+  // Invalid buffer id.
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers);
+  EXPECT_EQ(ZX_ERR_INVALID_ARGS, status);
+
+  ASSERT_OK(gdc_device_->StopThread());
+}
+
+TEST_F(TaskTest, ProcessFrameTest) {
+  SetupForFrameProcessing();
+
+  uint32_t task_id;
+  zx_status_t status = gdc_device_->GdcInitTask(
+      &input_buffer_collection_, &output_buffer_collection_,
+      std::move(config_vmo_), &callback_, &task_id);
+  EXPECT_OK(status);
+
+  // Valid buffer & task id.
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 1);
+  EXPECT_OK(status);
+
+  // Trigger the interrupt manually.
+  zx_port_packet packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
+  EXPECT_OK(port_.queue(&packet));
+
+  // Check if the callback was called.
+  zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+  EXPECT_EQ(1, callback_check_.size());
+
+  ASSERT_OK(gdc_device_->StopThread());
+}
+
+TEST_F(TaskTest, MultipleProcessFrameTest) {
+  SetupForFrameProcessing();
+
+  uint32_t task_id;
+  zx_status_t status = gdc_device_->GdcInitTask(
+      &input_buffer_collection_, &output_buffer_collection_,
+      std::move(config_vmo_), &callback_, &task_id);
+
+  // Process few frames, putting them in a queue
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 1);
+  EXPECT_OK(status);
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 2);
+  EXPECT_OK(status);
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 3);
+  EXPECT_OK(status);
+
+  // Trigger the interrupt manually.
+  zx_port_packet packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
+  EXPECT_OK(port_.queue(&packet));
+
+  // Check if the callback was called once.
+  zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+  EXPECT_EQ(1, callback_check_.size());
+
+  // Trigger the interrupt manually.
+  packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
+  EXPECT_OK(port_.queue(&packet));
+
+  // Check if the callback was called one more time.
+  zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+  EXPECT_EQ(2, callback_check_.size());
+
+  // This time adding another frame to process while its
+  // waiting for an interrupt.
+  status = gdc_device_->GdcProcessFrame(task_id, kNumberOfBuffers - 3);
+  EXPECT_OK(status);
+
+  // Trigger the interrupt manually.
+  packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
+  EXPECT_OK(port_.queue(&packet));
+
+  // Check if the callback was called one more time.
+  zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+  EXPECT_EQ(3, callback_check_.size());
+
+  // Trigger the interrupt manually.
+  packet = {kPortKeyIrqMsg, ZX_PKT_TYPE_USER, ZX_OK, {}};
+  EXPECT_OK(port_.queue(&packet));
+
+  // Check if the callback was called one more time.
+  zx_nanosleep(zx_deadline_after(ZX_MSEC(100)));
+  EXPECT_EQ(4, callback_check_.size());
+
+  ASSERT_OK(gdc_device_->StopThread());
+}
+
 TEST(TaskTest, NonContigVmoTest) {
   zx_handle_t bti_handle = ZX_HANDLE_INVALID;
   gdc_callback_t callback;
@@ -157,56 +358,5 @@
   EXPECT_NE(ZX_OK, status);
 }
 
-TEST_F(TaskTest, InitTaskTest) {
-  SetUpBufferCollections(kNumberOfBuffers);
-  ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
-  ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
-                                        kNumberOfMmios);
-  GdcDevice gdc_device(nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
-                       ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
-                       zx::interrupt(), std::move(bti_handle_));
-
-  std::vector<uint32_t> received_ids;
-  for (uint32_t i = 0; i < kMaxTasks; i++) {
-    zx::vmo config_vmo;
-    ASSERT_OK(config_vmo_.duplicate(ZX_RIGHT_SAME_RIGHTS, &config_vmo));
-
-    uint32_t task_id;
-    zx_status_t status = gdc_device.GdcInitTask(
-        &input_buffer_collection_, &output_buffer_collection_,
-        std::move(config_vmo), &callback_, &task_id);
-    EXPECT_OK(status);
-    // Checking to see if we are getting unique task ids.
-    auto entry = find(received_ids.begin(), received_ids.end(), task_id);
-    EXPECT_EQ(received_ids.end(), entry);
-    received_ids.push_back(task_id);
-  }
-}
-
-TEST_F(TaskTest, RemoveTaskTest) {
-  SetUpBufferCollections(kNumberOfBuffers);
-  ddk_mock::MockMmioReg fake_reg_array[kNumberOfMmios];
-  ddk_mock::MockMmioRegRegion fake_regs(fake_reg_array, sizeof(uint32_t),
-                                        kNumberOfMmios);
-
-  fbl::AllocChecker ac;
-  gdc_device_ = std::make_unique<GdcDevice>(
-      nullptr, ddk::MmioBuffer(fake_regs.GetMmioBuffer()),
-      ddk::MmioBuffer(fake_regs.GetMmioBuffer()), zx::interrupt(),
-      std::move(bti_handle_));
-
-  uint32_t task_id;
-  zx_status_t status = gdc_device_->GdcInitTask(
-      &input_buffer_collection_, &output_buffer_collection_,
-      std::move(config_vmo_), &callback_, &task_id);
-  EXPECT_OK(status);
-
-  // Valid id.
-  ASSERT_NO_DEATH(([this, task_id]() { gdc_device_->GdcRemoveTask(task_id); }));
-
-  // Invalid id.
-  ASSERT_DEATH(
-      ([this, task_id]() { gdc_device_->GdcRemoveTask(task_id + 1); }));
-}
 }  // namespace
 }  // namespace gdc