summaryrefslogtreecommitdiff
path: root/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
diff options
context:
space:
mode:
Diffstat (limited to 'gcip-kernel-driver/drivers/gcip/gcip-mailbox.c')
-rw-r--r--gcip-kernel-driver/drivers/gcip/gcip-mailbox.c680
1 files changed, 680 insertions, 0 deletions
diff --git a/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c b/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
new file mode 100644
index 0000000..cbb3c80
--- /dev/null
+++ b/gcip-kernel-driver/drivers/gcip/gcip-mailbox.c
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GCIP Mailbox Interface.
+ *
+ * Copyright (C) 2022 Google LLC
+ */
+
+#include <linux/device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/string.h> /* memcpy */
+#include <linux/wait.h>
+
+#include <gcip/gcip-mailbox.h>
+
+#if IS_ENABLED(CONFIG_GCIP_TEST)
+#include "unittests/helper/gcip-mailbox-controller.h"
+
+#define TEST_TRIGGER_TIMEOUT_RACE(awaiter) gcip_mailbox_controller_trigger_timeout_race(awaiter)
+#else
+#define TEST_TRIGGER_TIMEOUT_RACE(...)
+#endif
+
+#define GET_CMD_QUEUE_HEAD() mailbox->ops->get_cmd_queue_head(mailbox)
+#define GET_CMD_QUEUE_TAIL() mailbox->ops->get_cmd_queue_tail(mailbox)
+#define INC_CMD_QUEUE_TAIL(inc) mailbox->ops->inc_cmd_queue_tail(mailbox, inc)
+#define ACQUIRE_CMD_QUEUE_LOCK(try) mailbox->ops->acquire_cmd_queue_lock(mailbox, try)
+#define RELEASE_CMD_QUEUE_LOCK() mailbox->ops->release_cmd_queue_lock(mailbox)
+
+#define GET_CMD_ELEM_SEQ(cmd) mailbox->ops->get_cmd_elem_seq(mailbox, cmd)
+#define SET_CMD_ELEM_SEQ(cmd, seq) mailbox->ops->set_cmd_elem_seq(mailbox, cmd, seq)
+#define GET_CMD_ELEM_CODE(cmd) mailbox->ops->get_cmd_elem_code(mailbox, cmd)
+
+#define GET_RESP_QUEUE_SIZE() mailbox->ops->get_resp_queue_size(mailbox)
+#define GET_RESP_QUEUE_HEAD() mailbox->ops->get_resp_queue_head(mailbox)
+#define INC_RESP_QUEUE_HEAD(inc) mailbox->ops->inc_resp_queue_head(mailbox, inc)
+#define GET_RESP_QUEUE_TAIL() mailbox->ops->get_resp_queue_tail(mailbox)
+#define ACQUIRE_RESP_QUEUE_LOCK(try) mailbox->ops->acquire_resp_queue_lock(mailbox, try)
+#define RELEASE_RESP_QUEUE_LOCK() mailbox->ops->release_resp_queue_lock(mailbox)
+
+#define GET_RESP_ELEM_SEQ(resp) mailbox->ops->get_resp_elem_seq(mailbox, resp)
+#define SET_RESP_ELEM_SEQ(resp, seq) mailbox->ops->set_resp_elem_seq(mailbox, resp, seq)
+#define GET_RESP_ELEM_STATUS(resp) mailbox->ops->get_resp_elem_status(mailbox, resp)
+#define SET_RESP_ELEM_STATUS(resp, status) mailbox->ops->set_resp_elem_status(mailbox, resp, status)
+
+#define ACQUIRE_WAIT_LIST_LOCK(irqsave, flags) \
+ mailbox->ops->acquire_wait_list_lock(mailbox, irqsave, flags)
+#define RELEASE_WAIT_LIST_LOCK(irqrestore, flags) \
+ mailbox->ops->release_wait_list_lock(mailbox, irqrestore, flags)
+
+struct gcip_mailbox_wait_list_elem {
+ struct list_head list;
+ void *resp;
+ struct gcip_mailbox_resp_awaiter *awaiter;
+};
+
+static void gcip_mailbox_awaiter_release(struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ if (awaiter->release_data)
+ awaiter->release_data(awaiter->data);
+ kfree(awaiter);
+}
+
+static void gcip_mailbox_awaiter_dec_refs(struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ if (refcount_dec_and_test(&awaiter->refs))
+ gcip_mailbox_awaiter_release(awaiter);
+}
+
+/*
+ * Removes the response previously pushed with gcip_mailbox_push_wait_resp().
+ *
+ * This is used when the kernel gives up waiting for the response.
+ */
+static void gcip_mailbox_del_wait_resp(struct gcip_mailbox *mailbox, void *resp)
+{
+ struct gcip_mailbox_wait_list_elem *cur;
+ unsigned long flags;
+ u64 cur_seq, seq = GET_RESP_ELEM_SEQ(resp);
+
+ ACQUIRE_WAIT_LIST_LOCK(true, &flags);
+
+ list_for_each_entry (cur, &mailbox->wait_list, list) {
+ cur_seq = GET_RESP_ELEM_SEQ(cur->resp);
+ if (cur_seq > seq)
+ break;
+ if (cur_seq == seq) {
+ list_del(&cur->list);
+ if (cur->awaiter) {
+ /* Remove the reference of the arrived handler. */
+ gcip_mailbox_awaiter_dec_refs(cur->awaiter);
+ }
+ kfree(cur);
+ break;
+ }
+ }
+
+ RELEASE_WAIT_LIST_LOCK(true, flags);
+}
+
+/*
+ * Adds @resp to @mailbox->wait_list. If @awaiter is not NULL, the @resp is asynchronous.
+ * Otherwise, the @resp is synchronous.
+ *
+ * wait_list is a FIFO queue, with sequence number in increasing order.
+ *
+ * Returns 0 on success, or -ENOMEM if failed on allocation.
+ */
+static int gcip_mailbox_push_wait_resp(struct gcip_mailbox *mailbox, void *resp,
+ struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ struct gcip_mailbox_wait_list_elem *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ unsigned long flags;
+
+ if (!entry)
+ return -ENOMEM;
+
+ /* Increase a reference of arrived handler. */
+ if (awaiter)
+ refcount_inc(&awaiter->refs);
+
+ entry->resp = resp;
+ entry->awaiter = awaiter;
+ ACQUIRE_WAIT_LIST_LOCK(true, &flags);
+ list_add_tail(&entry->list, &mailbox->wait_list);
+ RELEASE_WAIT_LIST_LOCK(true, flags);
+
+ return 0;
+}
+
+/*
+ * Pushes @cmd to the command queue of mailbox and returns. @resp should be passed if the request
+ * is synchronous and want to get the response. If @resp is NULL even though the request is
+ * synchronous, the @cmd will be put into the queue, but the caller may not wait the response and
+ * ignore it. If the request is async, @awaiter should be passed too.
+ */
+static int gcip_mailbox_enqueue_cmd(struct gcip_mailbox *mailbox, void *cmd, void *resp,
+ struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ int ret = 0;
+ u32 tail;
+
+ ACQUIRE_CMD_QUEUE_LOCK(false);
+
+ SET_CMD_ELEM_SEQ(cmd, mailbox->cur_seq);
+ /*
+ * The lock ensures mailbox cmd_queue_tail cannot be changed by other processes (this
+ * method should be the only one to modify the value of tail), therefore we can remember
+ * its value here and use it in the condition of wait_event() call.
+ */
+ tail = GET_CMD_QUEUE_TAIL();
+
+ if (mailbox->ops->wait_for_cmd_queue_not_full) {
+ /* Wait until the cmd queue has a space for putting cmd. */
+ ret = mailbox->ops->wait_for_cmd_queue_not_full(mailbox);
+ if (ret)
+ goto out;
+ } else if (GET_CMD_QUEUE_HEAD() == (tail ^ mailbox->queue_wrap_bit)) {
+ /*
+ * Default logic of checking the fullness of cmd_queue. If the cmd_queue is full,
+ * it's up to the caller to retry.
+ */
+ ret = -EAGAIN;
+ goto out;
+ }
+
+ if (resp) {
+ /* Adds @resp to the wait_list only if the cmd can be pushed successfully. */
+ SET_RESP_ELEM_SEQ(resp, GET_CMD_ELEM_SEQ(cmd));
+ SET_RESP_ELEM_STATUS(resp, GCIP_MAILBOX_STATUS_WAITING_RESPONSE);
+ ret = gcip_mailbox_push_wait_resp(mailbox, resp, awaiter);
+ if (ret)
+ goto out;
+ }
+ /* Size of cmd_queue is a multiple of mailbox->cmd_elem_size. */
+ memcpy(mailbox->cmd_queue + mailbox->cmd_elem_size *
+ CIRC_QUEUE_REAL_INDEX(tail, mailbox->queue_wrap_bit),
+ cmd, mailbox->cmd_elem_size);
+ INC_CMD_QUEUE_TAIL(1);
+ if (mailbox->ops->after_enqueue_cmd) {
+ ret = mailbox->ops->after_enqueue_cmd(mailbox, cmd);
+ if (ret < 0) {
+ /*
+ * Currently, as both DSP and EdgeTPU never return errors, do nothing
+ * here. We can decide later how to rollback the status such as
+ * `cmd_queue_tail` when the possibility of returning an error is raised.
+ */
+ dev_warn(mailbox->dev,
+ "after_enqueue_cmd returned an error, but not handled: ret=%d\n",
+ ret);
+ goto out;
+ }
+ mailbox->cur_seq += ret;
+ ret = 0;
+ } else
+ mailbox->cur_seq += 1;
+
+out:
+ RELEASE_CMD_QUEUE_LOCK();
+ if (ret)
+ dev_dbg(mailbox->dev, "%s: ret=%d", __func__, ret);
+
+ return ret;
+}
+
+/*
+ * Handler of a response.
+ * Pops the wait_list until the sequence number of @resp is found, and copies @resp to the found
+ * entry.
+ *
+ * Both entry in wait_list and response handling should have sequence number in increasing order.
+ * Comparing the #seq of head of wait_list with @resp->seq, we have three cases:
+ * 1. #seq > @resp->seq:
+ * - Nothing to do, @resp is not needed and we're done.
+ * 2. #seq == @resp->seq:
+ * - Copy @resp, pop the head and we're done.
+ * 3. #seq < @resp->seq:
+ * - If @mailbox->ignore_seq_order is specified, this is a normal case and the entry is skipped.
+ * - Otherwise, it *should* not happen, this implies the sequence number of either entries in
+ * wait_list or responses are out-of-order, or remote didn't respond to a command. In this
+ * case, the status of response will be set to GCIP_MAILBOX_STATUS_NO_RESPONSE. Then pop until
+ * case 1. or 2.
+ */
+static void gcip_mailbox_handle_response(struct gcip_mailbox *mailbox, void *resp)
+{
+ struct gcip_mailbox_wait_list_elem *cur, *nxt;
+ struct gcip_mailbox_resp_awaiter *awaiter;
+ unsigned long flags;
+ u64 cur_seq, seq = GET_RESP_ELEM_SEQ(resp);
+
+ /* If before_handle_resp is defined and it returns false, don't handle the response */
+ if (mailbox->ops->before_handle_resp && !mailbox->ops->before_handle_resp(mailbox, resp))
+ return;
+
+ SET_RESP_ELEM_STATUS(resp, GCIP_MAILBOX_STATUS_OK);
+ ACQUIRE_WAIT_LIST_LOCK(true, &flags);
+
+ list_for_each_entry_safe (cur, nxt, &mailbox->wait_list, list) {
+ cur_seq = GET_RESP_ELEM_SEQ(cur->resp);
+ if (cur_seq > seq) {
+ /*
+ * This response has already timed out and been removed
+ * from the wait list (or this is an invalid response).
+ * Drop it.
+ */
+ break;
+ }
+ if (cur_seq == seq) {
+ memcpy(cur->resp, resp, mailbox->resp_elem_size);
+ list_del(&cur->list);
+ if (cur->awaiter) {
+ awaiter = cur->awaiter;
+
+ /*
+ * The timedout handler will be fired, but pended by waiting for
+ * acquiring the wait_list_lock.
+ */
+ TEST_TRIGGER_TIMEOUT_RACE(awaiter);
+
+ /*
+ * If canceling timeout_work succeeded, we have to decrease the
+ * reference count here because the timeout handler will not be
+ * called. Otherwise, the timeout handler is already canceled or
+ * pending by race. If it is canceled, the count must be decreased
+ * already, and if it is pending, the timeout handler will decrease
+ * the awaiter reference.
+ */
+ if (cancel_delayed_work(&awaiter->timeout_work))
+ gcip_mailbox_awaiter_dec_refs(awaiter);
+ /*
+ * If `handle_awaiter_arrived` callback is defined, @awaiter
+ * will be released from the implementation side. Otherwise, it
+ * should be freed from here.
+ */
+ if (mailbox->ops->handle_awaiter_arrived)
+ mailbox->ops->handle_awaiter_arrived(mailbox, awaiter);
+ gcip_mailbox_awaiter_dec_refs(awaiter);
+ }
+ kfree(cur);
+ break;
+ }
+ if (!mailbox->ignore_seq_order && cur_seq < seq) {
+ SET_RESP_ELEM_STATUS(cur->resp, GCIP_MAILBOX_STATUS_NO_RESPONSE);
+ list_del(&cur->list);
+ if (cur->awaiter) {
+ /* Remove the reference of the arrived handler. */
+ gcip_mailbox_awaiter_dec_refs(cur->awaiter);
+ }
+ kfree(cur);
+ }
+ }
+
+ RELEASE_WAIT_LIST_LOCK(true, flags);
+}
+
+/*
+ * Fetches elements in the response queue.
+ *
+ * Returns the pointer of fetched response elements.
+ * @total_ptr will be the number of elements fetched.
+ *
+ * Returns -ENOMEM if failed on memory allocation.
+ * Returns NULL if the response queue is empty or there is another worker fetching responses.
+ */
+static void *gcip_mailbox_fetch_responses(struct gcip_mailbox *mailbox, u32 *total_ptr)
+{
+ u32 head;
+ u32 tail;
+ u32 count;
+ u32 i;
+ u32 j;
+ u32 total = 0;
+ const u32 wrap_bit = mailbox->queue_wrap_bit;
+ const u32 size = GET_RESP_QUEUE_SIZE();
+ const u32 elem_size = mailbox->resp_elem_size;
+ void *ret = NULL; /* Array of responses. */
+ void *prev_ptr = NULL; /* Temporary pointer to realloc ret. */
+
+ /* Someone is working on consuming - we can leave early. */
+ if (!ACQUIRE_RESP_QUEUE_LOCK(true))
+ goto out;
+
+ head = GET_RESP_QUEUE_HEAD();
+ /* Loops until our head equals to CSR tail. */
+ while (1) {
+ tail = GET_RESP_QUEUE_TAIL();
+ /*
+ * Make sure the CSR is read and reported properly by checking if any bit higher
+ * than wrap_bit is set and if the tail exceeds resp_queue size.
+ */
+ if (unlikely(tail & ~CIRC_QUEUE_VALID_MASK(wrap_bit) ||
+ CIRC_QUEUE_REAL_INDEX(tail, wrap_bit) >= size)) {
+ dev_err_ratelimited(mailbox->dev, "Invalid response queue tail: %#x\n",
+ tail);
+ break;
+ }
+
+ count = gcip_circ_queue_cnt(head, tail, size, wrap_bit);
+ if (count == 0)
+ break;
+
+ prev_ptr = ret;
+ ret = krealloc(prev_ptr, (total + count) * elem_size, GFP_KERNEL);
+ /*
+ * Out-of-memory, we can return the previously fetched responses if any, or ENOMEM
+ * otherwise.
+ */
+ if (!ret) {
+ if (!prev_ptr)
+ ret = ERR_PTR(-ENOMEM);
+ else
+ ret = prev_ptr;
+ break;
+ }
+ /* Copies responses. */
+ j = CIRC_QUEUE_REAL_INDEX(head, wrap_bit);
+ for (i = 0; i < count; i++) {
+ memcpy(ret + elem_size * total, mailbox->resp_queue + elem_size * j,
+ elem_size);
+ j = (j + 1) % size;
+ total++;
+ }
+ head = gcip_circ_queue_inc(head, count, size, wrap_bit);
+ }
+ INC_RESP_QUEUE_HEAD(total);
+
+ RELEASE_RESP_QUEUE_LOCK();
+
+ if (mailbox->ops->after_fetch_resps)
+ mailbox->ops->after_fetch_resps(mailbox, total);
+out:
+ *total_ptr = total;
+ return ret;
+}
+
+/* Fetches one response from the response queue. */
+static int gcip_mailbox_fetch_one_response(struct gcip_mailbox *mailbox, void *resp)
+{
+ u32 head;
+ u32 tail;
+
+ if (!ACQUIRE_RESP_QUEUE_LOCK(true))
+ return 0;
+
+ head = GET_RESP_QUEUE_HEAD();
+ tail = GET_RESP_QUEUE_TAIL();
+ /* Queue empty. */
+ if (head == tail) {
+ RELEASE_RESP_QUEUE_LOCK();
+ return 0;
+ }
+
+ memcpy(resp,
+ mailbox->resp_queue + CIRC_QUEUE_REAL_INDEX(head, mailbox->queue_wrap_bit) *
+ mailbox->resp_elem_size,
+ mailbox->resp_elem_size);
+ INC_RESP_QUEUE_HEAD(1);
+
+ RELEASE_RESP_QUEUE_LOCK();
+
+ if (mailbox->ops->after_fetch_resps)
+ mailbox->ops->after_fetch_resps(mailbox, 1);
+
+ return 1;
+}
+
+/* Handles the timed out asynchronous commands. */
+static void gcip_mailbox_async_cmd_timeout_work(struct work_struct *work)
+{
+ struct gcip_mailbox_resp_awaiter *awaiter =
+ container_of(work, struct gcip_mailbox_resp_awaiter, timeout_work.work);
+ struct gcip_mailbox *mailbox = awaiter->mailbox;
+
+ /*
+ * This function will acquire the mailbox wait_list_lock. This means if
+ * response processing is in progress, it will complete before this
+ * response can be removed from the wait list.
+ *
+ * Once this function has the wait_list_lock, no future response
+ * processing will begin until this response has been removed.
+ */
+ gcip_mailbox_del_wait_resp(mailbox, awaiter->resp);
+
+ /*
+ * Handle timed out awaiter. If `handle_awaiter_timedout` is defined, @awaiter
+ * will be released from the implementation side. Otherwise, it should be freed from here.
+ */
+ if (mailbox->ops->handle_awaiter_timedout)
+ mailbox->ops->handle_awaiter_timedout(mailbox, awaiter);
+
+ /* Remove the reference of the timedout handler. */
+ gcip_mailbox_awaiter_dec_refs(awaiter);
+}
+
+/* Cleans up all the asynchronous responses which are not responded yet. */
+static void gcip_mailbox_flush_awaiter(struct gcip_mailbox *mailbox)
+{
+ struct gcip_mailbox_wait_list_elem *cur, *nxt;
+ struct gcip_mailbox_resp_awaiter *awaiter;
+ struct list_head resps_to_flush;
+
+ /* If mailbox->ops is NULL, the mailbox is already released. */
+ if (!mailbox->ops)
+ return;
+
+ /*
+ * At this point only async responses should be pending. Flush them all
+ * from the `wait_list` at once so any remaining timeout workers
+ * waiting on `wait_list_lock` will know their responses have been
+ * handled already.
+ */
+ INIT_LIST_HEAD(&resps_to_flush);
+ ACQUIRE_WAIT_LIST_LOCK(false, NULL);
+ list_for_each_entry_safe (cur, nxt, &mailbox->wait_list, list) {
+ list_del(&cur->list);
+ if (cur->awaiter) {
+ list_add_tail(&cur->list, &resps_to_flush);
+ /*
+ * Clear the response's destination queue so that if the
+ * timeout worker is running, it won't try to process
+ * this response after `wait_list_lock` is released.
+ */
+ awaiter = cur->awaiter;
+ if (mailbox->ops->flush_awaiter)
+ mailbox->ops->flush_awaiter(mailbox, awaiter);
+ /* Remove the reference of the arrived handler. */
+ gcip_mailbox_awaiter_dec_refs(cur->awaiter);
+ } else {
+ dev_warn(mailbox->dev,
+ "Unexpected synchronous command pending on mailbox release\n");
+ kfree(cur);
+ }
+ }
+ RELEASE_WAIT_LIST_LOCK(false, 0);
+
+ /*
+ * Cancel the timeout timer of and free any responses that were still in
+ * the `wait_list` above.
+ */
+ list_for_each_entry_safe (cur, nxt, &resps_to_flush, list) {
+ list_del(&cur->list);
+ awaiter = cur->awaiter;
+ /* Cancel the timeout work and remove the reference of the timedout handler. */
+ gcip_mailbox_cancel_awaiter_timeout(awaiter);
+ /* Remove the reference of the caller. */
+ gcip_mailbox_awaiter_dec_refs(cur->awaiter);
+ kfree(cur);
+ }
+}
+
+/* Verifies and sets the mailbox operators. */
+static int gcip_mailbox_set_ops(struct gcip_mailbox *mailbox, const struct gcip_mailbox_ops *ops)
+{
+ if (!ops) {
+ mailbox->ops = NULL;
+ return 0;
+ }
+
+ if (!ops->get_cmd_queue_head || !ops->get_cmd_queue_tail || !ops->inc_cmd_queue_tail ||
+ !ops->acquire_cmd_queue_lock || !ops->release_cmd_queue_lock ||
+ !ops->get_cmd_elem_seq || !ops->set_cmd_elem_seq || !ops->get_cmd_elem_code) {
+ dev_err(mailbox->dev, "Incomplete mailbox CMD queue ops.\n");
+ return -EINVAL;
+ }
+
+ if (!ops->get_resp_queue_size || !ops->get_resp_queue_head || !ops->get_resp_queue_tail ||
+ !ops->inc_resp_queue_head || !ops->acquire_resp_queue_lock ||
+ !ops->release_resp_queue_lock || !ops->get_resp_elem_seq || !ops->set_resp_elem_seq ||
+ !ops->get_resp_elem_status || !ops->set_resp_elem_status) {
+ dev_err(mailbox->dev, "Incomplete mailbox RESP queue ops.\n");
+ return -EINVAL;
+ }
+
+ if (!ops->acquire_wait_list_lock || !ops->release_wait_list_lock) {
+ dev_err(mailbox->dev, "Incomplete mailbox wait_list ops.\n");
+ return -EINVAL;
+ }
+
+ mailbox->ops = ops;
+
+ return 0;
+}
+
+/* Sets the mailbox private data. */
+static inline void gcip_mailbox_set_data(struct gcip_mailbox *mailbox, void *data)
+{
+ mailbox->data = data;
+}
+
+int gcip_mailbox_init(struct gcip_mailbox *mailbox, const struct gcip_mailbox_args *args)
+{
+ int ret;
+
+ mailbox->dev = args->dev;
+ mailbox->queue_wrap_bit = args->queue_wrap_bit;
+ mailbox->cmd_queue = args->cmd_queue;
+ mailbox->cmd_elem_size = args->cmd_elem_size;
+ mailbox->resp_queue = args->resp_queue;
+ mailbox->resp_elem_size = args->resp_elem_size;
+ mailbox->timeout = args->timeout;
+ mailbox->cur_seq = 0;
+ mailbox->ignore_seq_order = args->ignore_seq_order;
+ gcip_mailbox_set_data(mailbox, args->data);
+
+ ret = gcip_mailbox_set_ops(mailbox, args->ops);
+ if (ret)
+ goto err_unset_data;
+
+ INIT_LIST_HEAD(&mailbox->wait_list);
+ init_waitqueue_head(&mailbox->wait_list_waitq);
+
+ return 0;
+
+err_unset_data:
+ gcip_mailbox_set_data(mailbox, NULL);
+
+ return ret;
+}
+
+void gcip_mailbox_release(struct gcip_mailbox *mailbox)
+{
+ gcip_mailbox_flush_awaiter(mailbox);
+ gcip_mailbox_set_ops(mailbox, NULL);
+ gcip_mailbox_set_data(mailbox, NULL);
+}
+
+void gcip_mailbox_consume_responses_work(struct gcip_mailbox *mailbox)
+{
+ void *responses;
+ u32 i;
+ u32 count = 0;
+
+ /* Fetches responses and bumps resp_queue head. */
+ responses = gcip_mailbox_fetch_responses(mailbox, &count);
+ if (count == 0)
+ return;
+ if (IS_ERR(responses)) {
+ dev_err(mailbox->dev, "GCIP mailbox failed on fetching responses: %ld",
+ PTR_ERR(responses));
+ return;
+ }
+
+ for (i = 0; i < count; i++)
+ gcip_mailbox_handle_response(mailbox, responses + mailbox->resp_elem_size * i);
+ /* Responses handled, wake up threads that are waiting for a response. */
+ wake_up(&mailbox->wait_list_waitq);
+ kfree(responses);
+}
+
+int gcip_mailbox_send_cmd(struct gcip_mailbox *mailbox, void *cmd, void *resp)
+{
+ int ret;
+
+ ret = gcip_mailbox_enqueue_cmd(mailbox, cmd, resp, NULL);
+ if (ret)
+ return ret;
+
+ if (!resp)
+ return 0;
+
+ ret = wait_event_timeout(mailbox->wait_list_waitq,
+ GET_RESP_ELEM_STATUS(resp) != GCIP_MAILBOX_STATUS_WAITING_RESPONSE,
+ msecs_to_jiffies(mailbox->timeout));
+ if (!ret) {
+ dev_dbg(mailbox->dev, "event wait timeout");
+ gcip_mailbox_del_wait_resp(mailbox, resp);
+ return -ETIMEDOUT;
+ }
+ if (GET_RESP_ELEM_STATUS(resp) != GCIP_MAILBOX_STATUS_OK) {
+ dev_err(mailbox->dev, "Mailbox cmd %u response status %u", GET_CMD_ELEM_CODE(cmd),
+ GET_RESP_ELEM_STATUS(resp));
+ return -ENOMSG;
+ }
+
+ return 0;
+}
+
+struct gcip_mailbox_resp_awaiter *gcip_mailbox_put_cmd(struct gcip_mailbox *mailbox, void *cmd,
+ void *resp, void *data)
+{
+ struct gcip_mailbox_resp_awaiter *awaiter;
+ int ret;
+
+ awaiter = kzalloc(sizeof(*awaiter), GFP_KERNEL);
+ if (!awaiter)
+ return ERR_PTR(-ENOMEM);
+
+ awaiter->resp = resp;
+ awaiter->mailbox = mailbox;
+ awaiter->data = data;
+ awaiter->release_data = mailbox->ops->release_awaiter_data;
+ /* 2 refs: caller (vd) and timedout handler. */
+ refcount_set(&awaiter->refs, 2);
+
+ INIT_DELAYED_WORK(&awaiter->timeout_work, gcip_mailbox_async_cmd_timeout_work);
+ schedule_delayed_work(&awaiter->timeout_work, msecs_to_jiffies(mailbox->timeout));
+
+ ret = gcip_mailbox_enqueue_cmd(mailbox, cmd, awaiter->resp, awaiter);
+ if (ret)
+ goto err_free_resp;
+
+ return awaiter;
+
+err_free_resp:
+ gcip_mailbox_cancel_awaiter_timeout(awaiter);
+ kfree(awaiter);
+ return ERR_PTR(ret);
+}
+
+void gcip_mailbox_cancel_awaiter(struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ gcip_mailbox_del_wait_resp(awaiter->mailbox, awaiter->resp);
+ gcip_mailbox_cancel_awaiter_timeout(awaiter);
+}
+
+void gcip_mailbox_cancel_awaiter_timeout(struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ if (cancel_delayed_work_sync(&awaiter->timeout_work))
+ gcip_mailbox_awaiter_dec_refs(awaiter);
+}
+
+void gcip_mailbox_release_awaiter(struct gcip_mailbox_resp_awaiter *awaiter)
+{
+ gcip_mailbox_awaiter_dec_refs(awaiter);
+}
+
+void gcip_mailbox_consume_one_response(struct gcip_mailbox *mailbox, void *resp)
+{
+ int ret;
+
+ /* Fetches (at most) one response. */
+ ret = gcip_mailbox_fetch_one_response(mailbox, resp);
+ if (!ret)
+ return;
+
+ gcip_mailbox_handle_response(mailbox, resp);
+
+ /* Responses handled, wakes up threads that are waiting for a response. */
+ wake_up(&mailbox->wait_list_waitq);
+}