[dev][intel-hda][dsp] introduce ipc workqueue

Change-Id: Ia1aeefd92fae121341a0974b005b0167d91da154
diff --git a/system/dev/audio/intel-hda/dsp/intel-audio-dsp.cpp b/system/dev/audio/intel-hda/dsp/intel-audio-dsp.cpp
index 0c26213..3add636 100644
--- a/system/dev/audio/intel-hda/dsp/intel-audio-dsp.cpp
+++ b/system/dev/audio/intel-hda/dsp/intel-audio-dsp.cpp
@@ -281,6 +281,9 @@
     ResetCore(ADSP_REG_ADSPCS_CORE0_MASK);
     PowerDownCore(ADSP_REG_ADSPCS_CORE0_MASK);
 
+    // Fail all pending IPCs
+    ipc_.Shutdown();
+
     state_ = State::SHUT_DOWN;
 }
 
diff --git a/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.cpp b/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.cpp
index d05b7c4..e4cb370 100644
--- a/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.cpp
+++ b/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.cpp
@@ -15,6 +15,11 @@
 namespace audio {
 namespace intel_hda {
 
+namespace {
+constexpr zx_signals_t IPC_RECEIVED = ZX_EVENT_SIGNALED;
+constexpr zx_signals_t IPC_SHUTDOWN = ZX_USER_SIGNAL_0;
+}  // anon namespace
+
 IntelDspIpc::IntelDspIpc(IntelAudioDsp& dsp) : dsp_(dsp) {
     snprintf(log_prefix_, sizeof(log_prefix_), "IHDA DSP IPC (unknown BDF)");
 }
@@ -23,32 +28,34 @@
     snprintf(log_prefix_, sizeof(log_prefix_), "%s IPC", new_prefix);
 }
 
-zx_status_t IntelDspIpc::SendIpc(Txn* txn) {
-    {
-        fbl::AutoLock ipc_lock(&ipc_lock_);
-        // 1 at a time
-        ZX_DEBUG_ASSERT(pending_txn_ == nullptr);
-        if (pending_txn_ != nullptr) {
-            return ZX_ERR_BAD_STATE;
-        }
-        pending_txn_ = txn;
+void IntelDspIpc::Shutdown() {
+    fbl::AutoLock ipc_lock(&ipc_lock_);
+    // Fail all pending IPCs
+    for (auto iter = ipc_queue_.begin(); iter != ipc_queue_.end(); iter++) {
+        completion_signal(&iter->completion);
     }
+}
 
+void IntelDspIpc::SendIpc(const Txn& txn) {
     // Copy tx data to outbox
-    if (txn->tx_size > 0) {
-        dsp_.IpcMailboxWrite(txn->tx_data, txn->tx_size);
+    if (txn.tx_size > 0) {
+        dsp_.IpcMailboxWrite(txn.tx_data, txn.tx_size);
     }
-    dsp_.SendIpcMessage(txn->request);
-    return ZX_OK;
+    dsp_.SendIpcMessage(txn.request);
 }
 
 zx_status_t IntelDspIpc::SendIpcWait(Txn* txn) {
-    zx_status_t res = SendIpc(txn);
-    if (res != ZX_OK) {
-        return res;
+    {
+        // Add to the pending queue and start the ipc if necessary
+        fbl::AutoLock ipc_lock(&ipc_lock_);
+        bool needs_start = ipc_queue_.is_empty();
+        ipc_queue_.push_back(txn);
+        if (needs_start) {
+            SendIpc(ipc_queue_.front());
+        }
     }
     // Wait for completion
-    res = completion_wait(&txn->completion, ZX_MSEC(300));
+    zx_status_t res = completion_wait(&txn->completion, ZX_MSEC(300));
     if (res != ZX_OK) {
         dsp_.DeviceShutdown();
     }
@@ -212,37 +219,45 @@
 
 void IntelDspIpc::ProcessIpcReply(const IpcMessage& reply) {
     fbl::AutoLock ipc_lock(&ipc_lock_);
-    if (pending_txn_ == nullptr) {
+    if (ipc_queue_.is_empty()) {
         LOG(INFO, "got spurious reply message\n");
         return;
     }
+    Txn& pending = ipc_queue_.front();
 
     // Check if the reply matches the pending request.
-    IpcMessage* pending = &pending_txn_->request;
-    if ((pending->msg_tgt() != reply.msg_tgt()) || (pending->type() != reply.type())) {
+    IpcMessage* req = &pending.request;
+    if ((req->msg_tgt() != reply.msg_tgt()) || (req->type() != reply.type())) {
         LOG(INFO, "reply msg mismatch, got pri 0x%08x ext 0x%08x, expect pri 0x%08x ext 0x%08x\n",
-            reply.primary, reply.extension, pending->primary, pending->extension);
+            reply.primary, reply.extension, req->primary, req->extension);
         return;
     }
 
+    // The pending txn is done
+    ipc_queue_.pop_front();
+    pending.reply = reply;
+    pending.done = true;
+
     LOG(INFO, "got reply (status %u) for pending msg, pri 0x%08x ext 0x%08x\n",
               to_underlying(reply.status()), reply.primary, reply.extension);
 
-    pending_txn_->reply = reply;
-
     if (reply.msg_tgt() == MsgTarget::MODULE_MSG) {
         ModuleMsgType type = static_cast<ModuleMsgType>(reply.type());
         switch (type) {
         case ModuleMsgType::LARGE_CONFIG_GET:
-            ProcessLargeConfigGetReply(pending_txn_);
+            ProcessLargeConfigGetReply(&pending);
             break;
         default:
             break;
         }
     }
 
-    completion_signal(&pending_txn_->completion);
-    pending_txn_ = nullptr;
+    completion_signal(&pending.completion);
+
+    // Send the next ipc in the queue
+    if (!ipc_queue_.is_empty()) {
+        SendIpc(ipc_queue_.front());
+    }
 }
 
 void IntelDspIpc::ProcessLargeConfigGetReply(Txn* txn) {
diff --git a/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.h b/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.h
index bd122fa..1b23f1d 100644
--- a/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.h
+++ b/system/dev/audio/intel-hda/dsp/intel-dsp-ipc.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <fbl/intrusive_double_list.h>
 #include <sync/completion.h>
 #include <zircon/thread_annotations.h>
 
@@ -22,19 +23,24 @@
 
     const char*  log_prefix() const { return log_prefix_; }
 
-    struct Txn {
+    class Txn : public fbl::DoublyLinkedListable<Txn*> {
+    public:
         Txn(const void* tx, size_t txs, void* rx, size_t rxs)
             : tx_data(tx), tx_size(txs), rx_data(rx), rx_size(rxs) { }
         Txn(uint32_t pri, uint32_t ext, const void* tx, size_t txs, void* rx, size_t rxs)
             : request(pri, ext), tx_data(tx), tx_size(txs), rx_data(rx), rx_size(rxs) { }
 
+        DISALLOW_NEW;
+
         bool success() {
-            return reply.status() == MsgStatus::IPC_SUCCESS;
+            return done && reply.status() == MsgStatus::IPC_SUCCESS;
         }
 
         IpcMessage request;
         IpcMessage reply;
 
+        bool done = false;
+
         const void* tx_data = nullptr;
         size_t      tx_size = 0;
         void*       rx_data = nullptr;
@@ -46,6 +52,8 @@
 
     void SetLogPrefix(const char* new_prefix);
 
+    void Shutdown();
+
     // Library & Module Management IPC
     zx_status_t InitInstance(uint16_t module_id, uint8_t instance_id, ProcDomain proc_domain,
                              uint8_t core_id, uint8_t ppl_instance_id, uint16_t param_block_size,
@@ -67,12 +75,11 @@
     void ProcessLargeConfigGetReply(Txn* txn);
 
 private:
-    // Send an IPC message
-    zx_status_t SendIpc(Txn* txn);
-
     // Send an IPC message and wait for response
     zx_status_t SendIpcWait(Txn* txn);
 
+    void SendIpc(const Txn& txn);
+
     zx_status_t dsp_to_zx_status(MsgStatus status) {
         return (status == MsgStatus::IPC_SUCCESS) ? ZX_OK : ZX_ERR_INTERNAL;
     }
@@ -82,7 +89,11 @@
 
     // Pending IPC
     fbl::Mutex ipc_lock_;
-    Txn* pending_txn_ TA_GUARDED(ipc_lock_) = nullptr;
+    fbl::DoublyLinkedList<Txn*> ipc_queue_ TA_GUARDED(ipc_lock_);
+
+    // Work queue
+    thrd_t work_thread_;
+    zx::event work_event_;
 
     // A reference to the owning DSP
     IntelAudioDsp& dsp_;