summaryrefslogtreecommitdiff
path: root/drivers/net/wireless/eswin/ecrnx_cmds.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/net/wireless/eswin/ecrnx_cmds.c')
-rw-r--r--drivers/net/wireless/eswin/ecrnx_cmds.c325
1 files changed, 325 insertions, 0 deletions
diff --git a/drivers/net/wireless/eswin/ecrnx_cmds.c b/drivers/net/wireless/eswin/ecrnx_cmds.c
new file mode 100644
index 000000000000..5e4781af500a
--- /dev/null
+++ b/drivers/net/wireless/eswin/ecrnx_cmds.c
@@ -0,0 +1,325 @@
+/**
+ ******************************************************************************
+ *
+ * ecrnx_cmds.c
+ *
+ * Handles queueing (push to IPC, ack/cfm from IPC) of commands issued to
+ * LMAC FW
+ *
+ * Copyright (C) ESWIN 2015-2020
+ *
+ ******************************************************************************
+ */
+
+#include <linux/list.h>
+
+#include "ecrnx_cmds.h"
+#include "ecrnx_defs.h"
+#include "ecrnx_strs.h"
+#define CREATE_TRACE_POINTS
+#include "ecrnx_events.h"
+
+/**
+ *
+ */
+static void cmd_dump(const struct ecrnx_cmd *cmd)
+{
+#ifndef CONFIG_ECRNX_FHOST
+ ECRNX_PRINT("tkn[%d] flags:%04x result:%3d cmd:%4d-%-24s - reqcfm(%4d-%-s)\n",
+ cmd->tkn, cmd->flags, cmd->result, cmd->id, ECRNX_ID2STR(cmd->id),
+ cmd->reqid, cmd->reqid != (lmac_msg_id_t)-1 ? ECRNX_ID2STR(cmd->reqid) : "none");
+#endif
+}
+
+/**
+ *
+ */
+static void cmd_complete(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd)
+{
+ lockdep_assert_held(&cmd_mgr->lock);
+
+ list_del(&cmd->list);
+ cmd_mgr->queue_sz--;
+
+ cmd->flags |= ECRNX_CMD_FLAG_DONE;
+ if (cmd->flags & ECRNX_CMD_FLAG_NONBLOCK) {
+ kfree(cmd);
+ } else {
+ if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags)) {
+ cmd->result = 0;
+ complete(&cmd->complete);
+ }
+ }
+}
+
+/**
+ *
+ */
+static int cmd_mgr_queue(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd)
+{
+ struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr);
+ bool defer_push = false;
+
+ ECRNX_DBG(ECRNX_FN_ENTRY_STR);
+ trace_msg_send(cmd->id);
+
+ spin_lock_bh(&cmd_mgr->lock);
+
+ if (cmd_mgr->state == ECRNX_CMD_MGR_STATE_CRASHED) {
+ ECRNX_PRINT(KERN_CRIT"cmd queue crashed\n");
+ cmd->result = -EPIPE;
+ spin_unlock_bh(&cmd_mgr->lock);
+ return -EPIPE;
+ }
+
+ #ifndef CONFIG_ECRNX_FHOST
+ if (!list_empty(&cmd_mgr->cmds)) {
+ struct ecrnx_cmd *last;
+
+ if (cmd_mgr->queue_sz == cmd_mgr->max_queue_sz) {
+ ECRNX_ERR(KERN_CRIT"Too many cmds (%d) already queued\n",
+ cmd_mgr->max_queue_sz);
+ cmd->result = -ENOMEM;
+ spin_unlock_bh(&cmd_mgr->lock);
+ return -ENOMEM;
+ }
+ last = list_entry(cmd_mgr->cmds.prev, struct ecrnx_cmd, list);
+ if (last->flags & (ECRNX_CMD_FLAG_WAIT_ACK | ECRNX_CMD_FLAG_WAIT_PUSH)) {
+#if 0 // queue even NONBLOCK command.
+ if (cmd->flags & ECRNX_CMD_FLAG_NONBLOCK) {
+ printk(KERN_CRIT"cmd queue busy\n");
+ cmd->result = -EBUSY;
+ spin_unlock_bh(&cmd_mgr->lock);
+ return -EBUSY;
+ }
+#endif
+ cmd->flags |= ECRNX_CMD_FLAG_WAIT_PUSH;
+ defer_push = true;
+ }
+ }
+ #endif
+
+ cmd->flags |= ECRNX_CMD_FLAG_WAIT_ACK;
+ if (cmd->flags & ECRNX_CMD_FLAG_REQ_CFM)
+ cmd->flags |= ECRNX_CMD_FLAG_WAIT_CFM;
+
+ cmd->tkn = cmd_mgr->next_tkn++;
+ cmd->result = -EINTR;
+
+ if (!(cmd->flags & ECRNX_CMD_FLAG_NONBLOCK))
+ init_completion(&cmd->complete);
+
+ list_add_tail(&cmd->list, &cmd_mgr->cmds);
+ cmd_mgr->queue_sz++;
+ spin_unlock_bh(&cmd_mgr->lock);
+
+ if (!defer_push) {
+ ecrnx_ipc_msg_push(ecrnx_hw, cmd, ECRNX_CMD_A2EMSG_LEN(cmd->a2e_msg));
+ kfree(cmd->a2e_msg);
+ }
+
+ if (!(cmd->flags & ECRNX_CMD_FLAG_NONBLOCK)) {
+ #ifdef CONFIG_ECRNX_FHOST
+ if (wait_for_completion_killable(&cmd->complete)) {
+ if (cmd->flags & ECRNX_CMD_FLAG_WAIT_ACK)
+ up(&ecrnx_hw->term.fw_cmd);
+ cmd->result = -EINTR;
+ spin_lock_bh(&cmd_mgr->lock);
+ cmd_complete(cmd_mgr, cmd);
+ spin_unlock_bh(&cmd_mgr->lock);
+ /* TODO: kill the cmd at fw level */
+ } else {
+ if (cmd->flags & ECRNX_CMD_FLAG_WAIT_ACK)
+ up(&ecrnx_hw->term.fw_cmd);
+ }
+ #else
+ unsigned long tout = msecs_to_jiffies(ECRNX_80211_CMD_TIMEOUT_MS * cmd_mgr->queue_sz);
+ if (!wait_for_completion_killable_timeout(&cmd->complete, tout)) {
+ ECRNX_PRINT("cmd timed-out queue_sz:%d\n",cmd_mgr->queue_sz);
+ cmd_dump(cmd);
+ spin_lock_bh(&cmd_mgr->lock);
+ //cmd_mgr->state = ECRNX_CMD_MGR_STATE_CRASHED;
+ if (!(cmd->flags & ECRNX_CMD_FLAG_DONE)) {
+ cmd->result = -ETIMEDOUT;
+ cmd_complete(cmd_mgr, cmd);
+ }
+ spin_unlock_bh(&cmd_mgr->lock);
+ }
+ #endif
+ } else {
+ cmd->result = 0;
+ }
+
+ return 0;
+}
+
+/**
+ *
+ */
+static int cmd_mgr_llind(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd *cmd)
+{
+ struct ecrnx_cmd *cur, *acked = NULL, *next = NULL;
+
+ ECRNX_DBG(ECRNX_FN_ENTRY_STR);
+
+ spin_lock(&cmd_mgr->lock);
+ list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+ if (!acked) {
+ if (cur->tkn == cmd->tkn) {
+ if (WARN_ON_ONCE(cur != cmd)) {
+ cmd_dump(cmd);
+ }
+ acked = cur;
+ continue;
+ }
+ }
+ if (cur->flags & ECRNX_CMD_FLAG_WAIT_PUSH) {
+ next = cur;
+ break;
+ }
+ }
+ if (!acked) {
+ ECRNX_PRINT(KERN_CRIT "Error: acked cmd not found\n");
+ } else {
+ cmd->flags &= ~ECRNX_CMD_FLAG_WAIT_ACK;
+ if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags))
+ cmd_complete(cmd_mgr, cmd);
+ }
+ if (next) {
+ struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr);
+ next->flags &= ~ECRNX_CMD_FLAG_WAIT_PUSH;
+ ecrnx_ipc_msg_push(ecrnx_hw, next, ECRNX_CMD_A2EMSG_LEN(next->a2e_msg));
+ kfree(next->a2e_msg);
+ }
+ spin_unlock(&cmd_mgr->lock);
+
+ return 0;
+}
+
+
+
+static int cmd_mgr_run_callback(struct ecrnx_hw *ecrnx_hw, struct ecrnx_cmd *cmd,
+ struct ecrnx_cmd_e2amsg *msg, msg_cb_fct cb)
+{
+ int res;
+
+ if (! cb)
+ return 0;
+
+ spin_lock(&ecrnx_hw->cb_lock);
+ res = cb(ecrnx_hw, cmd, msg);
+ spin_unlock(&ecrnx_hw->cb_lock);
+
+ return res;
+}
+
+/**
+ *
+
+ */
+static int cmd_mgr_msgind(struct ecrnx_cmd_mgr *cmd_mgr, struct ecrnx_cmd_e2amsg *msg,
+ msg_cb_fct cb)
+{
+ struct ecrnx_hw *ecrnx_hw = container_of(cmd_mgr, struct ecrnx_hw, cmd_mgr);
+ struct ecrnx_cmd *cmd;
+ bool found = false;
+
+ ECRNX_DBG(ECRNX_FN_ENTRY_STR);
+ trace_msg_recv(msg->id);
+
+ spin_lock(&cmd_mgr->lock);
+ list_for_each_entry(cmd, &cmd_mgr->cmds, list) {
+ if (cmd->reqid == msg->id &&
+ (cmd->flags & ECRNX_CMD_FLAG_WAIT_CFM)) {
+
+ if (!cmd_mgr_run_callback(ecrnx_hw, cmd, msg, cb)) {
+ found = true;
+ cmd->flags &= ~ECRNX_CMD_FLAG_WAIT_CFM;
+
+ if (WARN((msg->param_len > ECRNX_CMD_E2AMSG_LEN_MAX),
+ "Unexpect E2A msg len %d > %d\n", msg->param_len,
+ ECRNX_CMD_E2AMSG_LEN_MAX)) {
+ msg->param_len = ECRNX_CMD_E2AMSG_LEN_MAX;
+ }
+
+ if (cmd->e2a_msg && msg->param_len)
+ memcpy(cmd->e2a_msg, &msg->param, msg->param_len);
+
+ if (ECRNX_CMD_WAIT_COMPLETE(cmd->flags))
+ cmd_complete(cmd_mgr, cmd);
+
+ break;
+ }
+ }
+ }
+ spin_unlock(&cmd_mgr->lock);
+
+ if (!found)
+ cmd_mgr_run_callback(ecrnx_hw, NULL, msg, cb);
+
+ ECRNX_DBG("%s exit!! \n", __func__);
+ return 0;
+}
+
+/**
+ *
+ */
+static void cmd_mgr_print(struct ecrnx_cmd_mgr *cmd_mgr)
+{
+ struct ecrnx_cmd *cur;
+
+ spin_lock_bh(&cmd_mgr->lock);
+ ECRNX_PRINT("q_sz/max: %2d / %2d - next tkn: %d\n",
+ cmd_mgr->queue_sz, cmd_mgr->max_queue_sz,
+ cmd_mgr->next_tkn);
+ list_for_each_entry(cur, &cmd_mgr->cmds, list) {
+ cmd_dump(cur);
+ }
+ spin_unlock_bh(&cmd_mgr->lock);
+}
+
+/**
+ *
+ */
+static void cmd_mgr_drain(struct ecrnx_cmd_mgr *cmd_mgr)
+{
+ struct ecrnx_cmd *cur, *nxt;
+
+ ECRNX_DBG(ECRNX_FN_ENTRY_STR);
+
+ spin_lock_bh(&cmd_mgr->lock);
+ list_for_each_entry_safe(cur, nxt, &cmd_mgr->cmds, list) {
+ list_del(&cur->list);
+ cmd_mgr->queue_sz--;
+ if (!(cur->flags & ECRNX_CMD_FLAG_NONBLOCK))
+ complete(&cur->complete);
+ }
+ spin_unlock_bh(&cmd_mgr->lock);
+}
+
+/**
+ *
+ */
+void ecrnx_cmd_mgr_init(struct ecrnx_cmd_mgr *cmd_mgr)
+{
+ ECRNX_DBG(ECRNX_FN_ENTRY_STR);
+
+ INIT_LIST_HEAD(&cmd_mgr->cmds);
+ spin_lock_init(&cmd_mgr->lock);
+ cmd_mgr->max_queue_sz = ECRNX_CMD_MAX_QUEUED;
+ cmd_mgr->queue = &cmd_mgr_queue;
+ cmd_mgr->print = &cmd_mgr_print;
+ cmd_mgr->drain = &cmd_mgr_drain;
+ cmd_mgr->llind = &cmd_mgr_llind;
+ cmd_mgr->msgind = &cmd_mgr_msgind;
+}
+
+/**
+ *
+ */
+void ecrnx_cmd_mgr_deinit(struct ecrnx_cmd_mgr *cmd_mgr)
+{
+ cmd_mgr->print(cmd_mgr);
+ cmd_mgr->drain(cmd_mgr);
+ memset(cmd_mgr, 0, sizeof(*cmd_mgr));
+}