18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
28c2ecf20Sopenharmony_ci/* Copyright (c) 2015-2016 Quantenna Communications. All rights reserved. */
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci#include <linux/types.h>
58c2ecf20Sopenharmony_ci#include <linux/export.h>
68c2ecf20Sopenharmony_ci#include <linux/slab.h>
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include "core.h"
98c2ecf20Sopenharmony_ci#include "commands.h"
108c2ecf20Sopenharmony_ci#include "event.h"
118c2ecf20Sopenharmony_ci#include "bus.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define QTNF_DEF_SYNC_CMD_TIMEOUT	(5 * HZ)
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ciint qtnf_trans_send_cmd_with_resp(struct qtnf_bus *bus, struct sk_buff *cmd_skb,
168c2ecf20Sopenharmony_ci				  struct sk_buff **response_skb)
178c2ecf20Sopenharmony_ci{
188c2ecf20Sopenharmony_ci	struct qtnf_cmd_ctl_node *ctl_node = &bus->trans.curr_cmd;
198c2ecf20Sopenharmony_ci	struct qlink_cmd *cmd = (void *)cmd_skb->data;
208c2ecf20Sopenharmony_ci	int ret = 0;
218c2ecf20Sopenharmony_ci	long status;
228c2ecf20Sopenharmony_ci	bool resp_not_handled = true;
238c2ecf20Sopenharmony_ci	struct sk_buff *resp_skb = NULL;
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	if (unlikely(!response_skb)) {
268c2ecf20Sopenharmony_ci		dev_kfree_skb(cmd_skb);
278c2ecf20Sopenharmony_ci		return -EFAULT;
288c2ecf20Sopenharmony_ci	}
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	spin_lock(&ctl_node->resp_lock);
318c2ecf20Sopenharmony_ci	ctl_node->seq_num++;
328c2ecf20Sopenharmony_ci	cmd->seq_num = cpu_to_le16(ctl_node->seq_num);
338c2ecf20Sopenharmony_ci	WARN(ctl_node->resp_skb, "qtnfmac: response skb not empty\n");
348c2ecf20Sopenharmony_ci	ctl_node->waiting_for_resp = true;
358c2ecf20Sopenharmony_ci	spin_unlock(&ctl_node->resp_lock);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	ret = qtnf_bus_control_tx(bus, cmd_skb);
388c2ecf20Sopenharmony_ci	dev_kfree_skb(cmd_skb);
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	if (unlikely(ret))
418c2ecf20Sopenharmony_ci		goto out;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	status = wait_for_completion_interruptible_timeout(
448c2ecf20Sopenharmony_ci						&ctl_node->cmd_resp_completion,
458c2ecf20Sopenharmony_ci						QTNF_DEF_SYNC_CMD_TIMEOUT);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	spin_lock(&ctl_node->resp_lock);
488c2ecf20Sopenharmony_ci	resp_not_handled = ctl_node->waiting_for_resp;
498c2ecf20Sopenharmony_ci	resp_skb = ctl_node->resp_skb;
508c2ecf20Sopenharmony_ci	ctl_node->resp_skb = NULL;
518c2ecf20Sopenharmony_ci	ctl_node->waiting_for_resp = false;
528c2ecf20Sopenharmony_ci	spin_unlock(&ctl_node->resp_lock);
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	if (unlikely(status <= 0)) {
558c2ecf20Sopenharmony_ci		if (status == 0) {
568c2ecf20Sopenharmony_ci			ret = -ETIMEDOUT;
578c2ecf20Sopenharmony_ci			pr_err("response timeout\n");
588c2ecf20Sopenharmony_ci		} else {
598c2ecf20Sopenharmony_ci			ret = -EINTR;
608c2ecf20Sopenharmony_ci			pr_debug("interrupted\n");
618c2ecf20Sopenharmony_ci		}
628c2ecf20Sopenharmony_ci	}
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	if (unlikely(!resp_skb || resp_not_handled)) {
658c2ecf20Sopenharmony_ci		if (!ret)
668c2ecf20Sopenharmony_ci			ret = -EFAULT;
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci		goto out;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	ret = 0;
728c2ecf20Sopenharmony_ci	*response_skb = resp_skb;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ciout:
758c2ecf20Sopenharmony_ci	if (unlikely(resp_skb && resp_not_handled))
768c2ecf20Sopenharmony_ci		dev_kfree_skb(resp_skb);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	return ret;
798c2ecf20Sopenharmony_ci}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_cistatic void qtnf_trans_signal_cmdresp(struct qtnf_bus *bus, struct sk_buff *skb)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct qtnf_cmd_ctl_node *ctl_node = &bus->trans.curr_cmd;
848c2ecf20Sopenharmony_ci	const struct qlink_resp *resp = (const struct qlink_resp *)skb->data;
858c2ecf20Sopenharmony_ci	const u16 recvd_seq_num = le16_to_cpu(resp->seq_num);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	spin_lock(&ctl_node->resp_lock);
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (unlikely(!ctl_node->waiting_for_resp)) {
908c2ecf20Sopenharmony_ci		pr_err("unexpected response\n");
918c2ecf20Sopenharmony_ci		goto out_err;
928c2ecf20Sopenharmony_ci	}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	if (unlikely(recvd_seq_num != ctl_node->seq_num)) {
958c2ecf20Sopenharmony_ci		pr_err("seq num mismatch\n");
968c2ecf20Sopenharmony_ci		goto out_err;
978c2ecf20Sopenharmony_ci	}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	ctl_node->resp_skb = skb;
1008c2ecf20Sopenharmony_ci	ctl_node->waiting_for_resp = false;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	spin_unlock(&ctl_node->resp_lock);
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	complete(&ctl_node->cmd_resp_completion);
1058c2ecf20Sopenharmony_ci	return;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ciout_err:
1088c2ecf20Sopenharmony_ci	spin_unlock(&ctl_node->resp_lock);
1098c2ecf20Sopenharmony_ci	dev_kfree_skb(skb);
1108c2ecf20Sopenharmony_ci}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic int qtnf_trans_event_enqueue(struct qtnf_bus *bus, struct sk_buff *skb)
1138c2ecf20Sopenharmony_ci{
1148c2ecf20Sopenharmony_ci	struct qtnf_qlink_transport *trans = &bus->trans;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	if (likely(skb_queue_len(&trans->event_queue) <
1178c2ecf20Sopenharmony_ci		   trans->event_queue_max_len)) {
1188c2ecf20Sopenharmony_ci		skb_queue_tail(&trans->event_queue, skb);
1198c2ecf20Sopenharmony_ci		queue_work(bus->workqueue, &bus->event_work);
1208c2ecf20Sopenharmony_ci	} else {
1218c2ecf20Sopenharmony_ci		pr_warn("event dropped due to queue overflow\n");
1228c2ecf20Sopenharmony_ci		dev_kfree_skb(skb);
1238c2ecf20Sopenharmony_ci		return -1;
1248c2ecf20Sopenharmony_ci	}
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	return 0;
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_civoid qtnf_trans_init(struct qtnf_bus *bus)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct qtnf_qlink_transport *trans = &bus->trans;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	init_completion(&trans->curr_cmd.cmd_resp_completion);
1348c2ecf20Sopenharmony_ci	spin_lock_init(&trans->curr_cmd.resp_lock);
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	spin_lock(&trans->curr_cmd.resp_lock);
1378c2ecf20Sopenharmony_ci	trans->curr_cmd.seq_num = 0;
1388c2ecf20Sopenharmony_ci	trans->curr_cmd.waiting_for_resp = false;
1398c2ecf20Sopenharmony_ci	trans->curr_cmd.resp_skb = NULL;
1408c2ecf20Sopenharmony_ci	spin_unlock(&trans->curr_cmd.resp_lock);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/* Init event handling related fields */
1438c2ecf20Sopenharmony_ci	skb_queue_head_init(&trans->event_queue);
1448c2ecf20Sopenharmony_ci	trans->event_queue_max_len = QTNF_MAX_EVENT_QUEUE_LEN;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic void qtnf_trans_free_events(struct qtnf_bus *bus)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	struct sk_buff_head *event_queue = &bus->trans.event_queue;
1508c2ecf20Sopenharmony_ci	struct sk_buff *current_event_skb = skb_dequeue(event_queue);
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	while (current_event_skb) {
1538c2ecf20Sopenharmony_ci		dev_kfree_skb_any(current_event_skb);
1548c2ecf20Sopenharmony_ci		current_event_skb = skb_dequeue(event_queue);
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_civoid qtnf_trans_free(struct qtnf_bus *bus)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	if (!bus) {
1618c2ecf20Sopenharmony_ci		pr_err("invalid bus pointer\n");
1628c2ecf20Sopenharmony_ci		return;
1638c2ecf20Sopenharmony_ci	}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	qtnf_trans_free_events(bus);
1668c2ecf20Sopenharmony_ci}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ciint qtnf_trans_handle_rx_ctl_packet(struct qtnf_bus *bus, struct sk_buff *skb)
1698c2ecf20Sopenharmony_ci{
1708c2ecf20Sopenharmony_ci	const struct qlink_msg_header *header = (void *)skb->data;
1718c2ecf20Sopenharmony_ci	int ret = -1;
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	if (unlikely(skb->len < sizeof(*header))) {
1748c2ecf20Sopenharmony_ci		pr_warn("packet is too small: %u\n", skb->len);
1758c2ecf20Sopenharmony_ci		dev_kfree_skb(skb);
1768c2ecf20Sopenharmony_ci		return -EINVAL;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	if (unlikely(skb->len != le16_to_cpu(header->len))) {
1808c2ecf20Sopenharmony_ci		pr_warn("cmd reply length mismatch: %u != %u\n",
1818c2ecf20Sopenharmony_ci			skb->len, le16_to_cpu(header->len));
1828c2ecf20Sopenharmony_ci		dev_kfree_skb(skb);
1838c2ecf20Sopenharmony_ci		return -EFAULT;
1848c2ecf20Sopenharmony_ci	}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	switch (le16_to_cpu(header->type)) {
1878c2ecf20Sopenharmony_ci	case QLINK_MSG_TYPE_CMDRSP:
1888c2ecf20Sopenharmony_ci		if (unlikely(skb->len < sizeof(struct qlink_cmd))) {
1898c2ecf20Sopenharmony_ci			pr_warn("cmd reply too short: %u\n", skb->len);
1908c2ecf20Sopenharmony_ci			dev_kfree_skb(skb);
1918c2ecf20Sopenharmony_ci			break;
1928c2ecf20Sopenharmony_ci		}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		qtnf_trans_signal_cmdresp(bus, skb);
1958c2ecf20Sopenharmony_ci		break;
1968c2ecf20Sopenharmony_ci	case QLINK_MSG_TYPE_EVENT:
1978c2ecf20Sopenharmony_ci		if (unlikely(skb->len < sizeof(struct qlink_event))) {
1988c2ecf20Sopenharmony_ci			pr_warn("event too short: %u\n", skb->len);
1998c2ecf20Sopenharmony_ci			dev_kfree_skb(skb);
2008c2ecf20Sopenharmony_ci			break;
2018c2ecf20Sopenharmony_ci		}
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci		ret = qtnf_trans_event_enqueue(bus, skb);
2048c2ecf20Sopenharmony_ci		break;
2058c2ecf20Sopenharmony_ci	default:
2068c2ecf20Sopenharmony_ci		pr_warn("unknown packet type: %x\n", le16_to_cpu(header->type));
2078c2ecf20Sopenharmony_ci		dev_kfree_skb(skb);
2088c2ecf20Sopenharmony_ci		break;
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	return ret;
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(qtnf_trans_handle_rx_ctl_packet);
214