/*
 * Copyright (c) 2017 QLogic Corporation.
 * All rights reserved.
 * www.qlogic.com
 *
 * See LICENSE.qede_pmd for copyright and licensing details.
 */

#include <rte_udp.h>
#include <rte_tcp.h>
#include <rte_sctp.h>
#include <rte_errno.h>

#include "qede_ethdev.h"

#define IP_VERSION				(0x40)
#define IP_HDRLEN				(0x5)
#define QEDE_FDIR_IP_DEFAULT_VERSION_IHL	(IP_VERSION | IP_HDRLEN)
#define QEDE_FDIR_TCP_DEFAULT_DATAOFF		(0x50)
#define QEDE_FDIR_IPV4_DEF_TTL			(64)

/* Sum of length of header types of L2, L3, L4.
 * L2 : ether_hdr + vlan_hdr + vxlan_hdr
 * L3 : ipv6_hdr
 * L4 : tcp_hdr
 */
#define QEDE_MAX_FDIR_PKT_LEN			(86)

#ifndef IPV6_ADDR_LEN
#define IPV6_ADDR_LEN				(16)
#endif

#define QEDE_VALID_FLOW(flow_type) \
	((flow_type) == RTE_ETH_FLOW_NONFRAG_IPV4_TCP	|| \
	(flow_type) == RTE_ETH_FLOW_NONFRAG_IPV4_UDP	|| \
	(flow_type) == RTE_ETH_FLOW_NONFRAG_IPV6_TCP	|| \
	(flow_type) == RTE_ETH_FLOW_NONFRAG_IPV6_UDP)

/* Note: Flowdir support is only partial.
 * For ex: drop_queue, FDIR masks, flex_conf are not supported.
 * Parameters like pballoc/status fields are irrelevant here.
 */
int qede_check_fdir_support(struct rte_eth_dev *eth_dev)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);
	struct rte_fdir_conf *fdir = &eth_dev->data->dev_conf.fdir_conf;

	/* check FDIR modes */
	switch (fdir->mode) {
	case RTE_FDIR_MODE_NONE:
		qdev->fdir_info.arfs.arfs_enable = false;
		DP_INFO(edev, "flowdir is disabled\n");
	break;
	case RTE_FDIR_MODE_PERFECT:
		if (ECORE_IS_CMT(edev)) {
			DP_ERR(edev, "flowdir is not supported in 100G mode\n");
			qdev->fdir_info.arfs.arfs_enable = false;
			return -ENOTSUP;
		}
		qdev->fdir_info.arfs.arfs_enable = true;
		DP_INFO(edev, "flowdir is enabled\n");
	break;
	case RTE_FDIR_MODE_PERFECT_TUNNEL:
	case RTE_FDIR_MODE_SIGNATURE:
	case RTE_FDIR_MODE_PERFECT_MAC_VLAN:
		DP_ERR(edev, "Unsupported flowdir mode %d\n", fdir->mode);
		return -ENOTSUP;
	}

	return 0;
}

void qede_fdir_dealloc_resc(struct rte_eth_dev *eth_dev)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct qede_fdir_entry *tmp = NULL;

	SLIST_FOREACH(tmp, &qdev->fdir_info.fdir_list_head, list) {
		if (tmp) {
			if (tmp->mz)
				rte_memzone_free(tmp->mz);
			SLIST_REMOVE(&qdev->fdir_info.fdir_list_head, tmp,
				     qede_fdir_entry, list);
			rte_free(tmp);
		}
	}
}

static int
qede_config_cmn_fdir_filter(struct rte_eth_dev *eth_dev,
			    struct rte_eth_fdir_filter *fdir_filter,
			    bool add)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);
	char mz_name[RTE_MEMZONE_NAMESIZE] = {0};
	struct qede_fdir_entry *tmp = NULL;
	struct qede_fdir_entry *fdir = NULL;
	const struct rte_memzone *mz;
	struct ecore_hwfn *p_hwfn;
	enum _ecore_status_t rc;
	uint16_t pkt_len;
	void *pkt;

	if (add) {
		if (qdev->fdir_info.filter_count == QEDE_RFS_MAX_FLTR - 1) {
			DP_ERR(edev, "Reached max flowdir filter limit\n");
			return -EINVAL;
		}
		fdir = rte_malloc(NULL, sizeof(struct qede_fdir_entry),
				  RTE_CACHE_LINE_SIZE);
		if (!fdir) {
			DP_ERR(edev, "Did not allocate memory for fdir\n");
			return -ENOMEM;
		}
	}
	/* soft_id could have been used as memzone string, but soft_id is
	 * not currently used so it has no significance.
	 */
	snprintf(mz_name, sizeof(mz_name) - 1, "%lx",
		 (unsigned long)rte_get_timer_cycles());
	mz = rte_memzone_reserve_aligned(mz_name, QEDE_MAX_FDIR_PKT_LEN,
					 SOCKET_ID_ANY, 0, RTE_CACHE_LINE_SIZE);
	if (!mz) {
		DP_ERR(edev, "Failed to allocate memzone for fdir, err = %s\n",
		       rte_strerror(rte_errno));
		rc = -rte_errno;
		goto err1;
	}

	pkt = mz->addr;
	memset(pkt, 0, QEDE_MAX_FDIR_PKT_LEN);
	pkt_len = qede_fdir_construct_pkt(eth_dev, fdir_filter, pkt,
					  &qdev->fdir_info.arfs);
	if (pkt_len == 0) {
		rc = -EINVAL;
		goto err2;
	}
	DP_INFO(edev, "pkt_len = %u memzone = %s\n", pkt_len, mz_name);
	if (add) {
		SLIST_FOREACH(tmp, &qdev->fdir_info.fdir_list_head, list) {
			if (memcmp(tmp->mz->addr, pkt, pkt_len) == 0) {
				DP_ERR(edev, "flowdir filter exist\n");
				rc = -EEXIST;
				goto err2;
			}
		}
	} else {
		SLIST_FOREACH(tmp, &qdev->fdir_info.fdir_list_head, list) {
			if (memcmp(tmp->mz->addr, pkt, pkt_len) == 0)
				break;
		}
		if (!tmp) {
			DP_ERR(edev, "flowdir filter does not exist\n");
			rc = -EEXIST;
			goto err2;
		}
	}
	p_hwfn = ECORE_LEADING_HWFN(edev);
	if (add) {
		if (!qdev->fdir_info.arfs.arfs_enable) {
			/* Force update */
			eth_dev->data->dev_conf.fdir_conf.mode =
						RTE_FDIR_MODE_PERFECT;
			qdev->fdir_info.arfs.arfs_enable = true;
			DP_INFO(edev, "Force enable flowdir in perfect mode\n");
		}
		/* Enable ARFS searcher with updated flow_types */
		ecore_arfs_mode_configure(p_hwfn, p_hwfn->p_arfs_ptt,
					  &qdev->fdir_info.arfs);
	}
	/* configure filter with ECORE_SPQ_MODE_EBLOCK */
	rc = ecore_configure_rfs_ntuple_filter(p_hwfn, NULL,
					       (dma_addr_t)mz->iova,
					       pkt_len,
					       fdir_filter->action.rx_queue,
					       0, add);
	if (rc == ECORE_SUCCESS) {
		if (add) {
			fdir->rx_queue = fdir_filter->action.rx_queue;
			fdir->pkt_len = pkt_len;
			fdir->mz = mz;
			SLIST_INSERT_HEAD(&qdev->fdir_info.fdir_list_head,
					  fdir, list);
			qdev->fdir_info.filter_count++;
			DP_INFO(edev, "flowdir filter added, count = %d\n",
				qdev->fdir_info.filter_count);
		} else {
			rte_memzone_free(tmp->mz);
			SLIST_REMOVE(&qdev->fdir_info.fdir_list_head, tmp,
				     qede_fdir_entry, list);
			rte_free(tmp); /* the node deleted */
			rte_memzone_free(mz); /* temp node allocated */
			qdev->fdir_info.filter_count--;
			DP_INFO(edev, "Fdir filter deleted, count = %d\n",
				qdev->fdir_info.filter_count);
		}
	} else {
		DP_ERR(edev, "flowdir filter failed, rc=%d filter_count=%d\n",
		       rc, qdev->fdir_info.filter_count);
	}

	/* Disable ARFS searcher if there are no more filters */
	if (qdev->fdir_info.filter_count == 0) {
		memset(&qdev->fdir_info.arfs, 0,
		       sizeof(struct ecore_arfs_config_params));
		DP_INFO(edev, "Disabling flowdir\n");
		qdev->fdir_info.arfs.arfs_enable = false;
		ecore_arfs_mode_configure(p_hwfn, p_hwfn->p_arfs_ptt,
					  &qdev->fdir_info.arfs);
	}
	return 0;

err2:
	rte_memzone_free(mz);
err1:
	if (add)
		rte_free(fdir);
	return rc;
}

static int
qede_fdir_filter_add(struct rte_eth_dev *eth_dev,
		     struct rte_eth_fdir_filter *fdir,
		     bool add)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);

	if (!QEDE_VALID_FLOW(fdir->input.flow_type)) {
		DP_ERR(edev, "invalid flow_type input\n");
		return -EINVAL;
	}

	if (fdir->action.rx_queue >= QEDE_RSS_COUNT(qdev)) {
		DP_ERR(edev, "invalid queue number %u\n",
		       fdir->action.rx_queue);
		return -EINVAL;
	}

	if (fdir->input.flow_ext.is_vf) {
		DP_ERR(edev, "flowdir is not supported over VF\n");
		return -EINVAL;
	}

	return qede_config_cmn_fdir_filter(eth_dev, fdir, add);
}

/* Fills the L3/L4 headers and returns the actual length  of flowdir packet */
uint16_t
qede_fdir_construct_pkt(struct rte_eth_dev *eth_dev,
			struct rte_eth_fdir_filter *fdir,
			void *buff,
			struct ecore_arfs_config_params *params)

{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);
	uint16_t *ether_type;
	uint8_t *raw_pkt;
	struct rte_eth_fdir_input *input;
	static uint8_t vlan_frame[] = {0x81, 0, 0, 0};
	struct ipv4_hdr *ip;
	struct ipv6_hdr *ip6;
	struct udp_hdr *udp;
	struct tcp_hdr *tcp;
	uint16_t len;
	static const uint8_t next_proto[] = {
		[RTE_ETH_FLOW_NONFRAG_IPV4_TCP] = IPPROTO_TCP,
		[RTE_ETH_FLOW_NONFRAG_IPV4_UDP] = IPPROTO_UDP,
		[RTE_ETH_FLOW_NONFRAG_IPV6_TCP] = IPPROTO_TCP,
		[RTE_ETH_FLOW_NONFRAG_IPV6_UDP] = IPPROTO_UDP,
	};
	raw_pkt = (uint8_t *)buff;
	input = &fdir->input;
	DP_INFO(edev, "flow_type %d\n", input->flow_type);

	len =  2 * sizeof(struct ether_addr);
	raw_pkt += 2 * sizeof(struct ether_addr);
	if (input->flow_ext.vlan_tci) {
		DP_INFO(edev, "adding VLAN header\n");
		rte_memcpy(raw_pkt, vlan_frame, sizeof(vlan_frame));
		rte_memcpy(raw_pkt + sizeof(uint16_t),
			   &input->flow_ext.vlan_tci,
			   sizeof(uint16_t));
		raw_pkt += sizeof(vlan_frame);
		len += sizeof(vlan_frame);
	}
	ether_type = (uint16_t *)raw_pkt;
	raw_pkt += sizeof(uint16_t);
	len += sizeof(uint16_t);

	switch (input->flow_type) {
	case RTE_ETH_FLOW_NONFRAG_IPV4_TCP:
	case RTE_ETH_FLOW_NONFRAG_IPV4_UDP:
		/* fill the common ip header */
		ip = (struct ipv4_hdr *)raw_pkt;
		*ether_type = rte_cpu_to_be_16(ETHER_TYPE_IPv4);
		ip->version_ihl = QEDE_FDIR_IP_DEFAULT_VERSION_IHL;
		ip->total_length = sizeof(struct ipv4_hdr);
		ip->next_proto_id = input->flow.ip4_flow.proto ?
				    input->flow.ip4_flow.proto :
				    next_proto[input->flow_type];
		ip->time_to_live = input->flow.ip4_flow.ttl ?
				   input->flow.ip4_flow.ttl :
				   QEDE_FDIR_IPV4_DEF_TTL;
		ip->type_of_service = input->flow.ip4_flow.tos;
		ip->dst_addr = input->flow.ip4_flow.dst_ip;
		ip->src_addr = input->flow.ip4_flow.src_ip;
		len += sizeof(struct ipv4_hdr);
		params->ipv4 = true;

		raw_pkt = (uint8_t *)buff;
		/* UDP */
		if (input->flow_type == RTE_ETH_FLOW_NONFRAG_IPV4_UDP) {
			udp = (struct udp_hdr *)(raw_pkt + len);
			udp->dst_port = input->flow.udp4_flow.dst_port;
			udp->src_port = input->flow.udp4_flow.src_port;
			udp->dgram_len = sizeof(struct udp_hdr);
			len += sizeof(struct udp_hdr);
			/* adjust ip total_length */
			ip->total_length += sizeof(struct udp_hdr);
			params->udp = true;
		} else { /* TCP */
			tcp = (struct tcp_hdr *)(raw_pkt + len);
			tcp->src_port = input->flow.tcp4_flow.src_port;
			tcp->dst_port = input->flow.tcp4_flow.dst_port;
			tcp->data_off = QEDE_FDIR_TCP_DEFAULT_DATAOFF;
			len += sizeof(struct tcp_hdr);
			/* adjust ip total_length */
			ip->total_length += sizeof(struct tcp_hdr);
			params->tcp = true;
		}
		break;
	case RTE_ETH_FLOW_NONFRAG_IPV6_TCP:
	case RTE_ETH_FLOW_NONFRAG_IPV6_UDP:
		ip6 = (struct ipv6_hdr *)raw_pkt;
		*ether_type = rte_cpu_to_be_16(ETHER_TYPE_IPv6);
		ip6->proto = input->flow.ipv6_flow.proto ?
					input->flow.ipv6_flow.proto :
					next_proto[input->flow_type];
		rte_memcpy(&ip6->src_addr, &input->flow.ipv6_flow.dst_ip,
			   IPV6_ADDR_LEN);
		rte_memcpy(&ip6->dst_addr, &input->flow.ipv6_flow.src_ip,
			   IPV6_ADDR_LEN);
		len += sizeof(struct ipv6_hdr);

		raw_pkt = (uint8_t *)buff;
		/* UDP */
		if (input->flow_type == RTE_ETH_FLOW_NONFRAG_IPV6_UDP) {
			udp = (struct udp_hdr *)(raw_pkt + len);
			udp->src_port = input->flow.udp6_flow.dst_port;
			udp->dst_port = input->flow.udp6_flow.src_port;
			len += sizeof(struct udp_hdr);
			params->udp = true;
		} else { /* TCP */
			tcp = (struct tcp_hdr *)(raw_pkt + len);
			tcp->src_port = input->flow.tcp4_flow.src_port;
			tcp->dst_port = input->flow.tcp4_flow.dst_port;
			tcp->data_off = QEDE_FDIR_TCP_DEFAULT_DATAOFF;
			len += sizeof(struct tcp_hdr);
			params->tcp = true;
		}
		break;
	default:
		DP_ERR(edev, "Unsupported flow_type %u\n",
		       input->flow_type);
		return 0;
	}

	return len;
}

int
qede_fdir_filter_conf(struct rte_eth_dev *eth_dev,
		      enum rte_filter_op filter_op,
		      void *arg)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);
	struct rte_eth_fdir_filter *fdir;
	int ret;

	fdir = (struct rte_eth_fdir_filter *)arg;
	switch (filter_op) {
	case RTE_ETH_FILTER_NOP:
		/* Typically used to query flowdir support */
		if (ECORE_IS_CMT(edev)) {
			DP_ERR(edev, "flowdir is not supported in 100G mode\n");
			return -ENOTSUP;
		}
		return 0; /* means supported */
	case RTE_ETH_FILTER_ADD:
		ret = qede_fdir_filter_add(eth_dev, fdir, 1);
	break;
	case RTE_ETH_FILTER_DELETE:
		ret = qede_fdir_filter_add(eth_dev, fdir, 0);
	break;
	case RTE_ETH_FILTER_FLUSH:
	case RTE_ETH_FILTER_UPDATE:
	case RTE_ETH_FILTER_INFO:
		return -ENOTSUP;
	break;
	default:
		DP_ERR(edev, "unknown operation %u", filter_op);
		ret = -EINVAL;
	}

	return ret;
}

int qede_ntuple_filter_conf(struct rte_eth_dev *eth_dev,
			    enum rte_filter_op filter_op,
			    void *arg)
{
	struct qede_dev *qdev = QEDE_INIT_QDEV(eth_dev);
	struct ecore_dev *edev = QEDE_INIT_EDEV(qdev);
	struct rte_eth_ntuple_filter *ntuple;
	struct rte_eth_fdir_filter fdir_entry;
	struct rte_eth_tcpv4_flow *tcpv4_flow;
	struct rte_eth_udpv4_flow *udpv4_flow;
	bool add = false;

	switch (filter_op) {
	case RTE_ETH_FILTER_NOP:
		/* Typically used to query fdir support */
		if (ECORE_IS_CMT(edev)) {
			DP_ERR(edev, "flowdir is not supported in 100G mode\n");
			return -ENOTSUP;
		}
		return 0; /* means supported */
	case RTE_ETH_FILTER_ADD:
		add = true;
	break;
	case RTE_ETH_FILTER_DELETE:
	break;
	case RTE_ETH_FILTER_INFO:
	case RTE_ETH_FILTER_GET:
	case RTE_ETH_FILTER_UPDATE:
	case RTE_ETH_FILTER_FLUSH:
	case RTE_ETH_FILTER_SET:
	case RTE_ETH_FILTER_STATS:
	case RTE_ETH_FILTER_OP_MAX:
		DP_ERR(edev, "Unsupported filter_op %d\n", filter_op);
		return -ENOTSUP;
	}
	ntuple = (struct rte_eth_ntuple_filter *)arg;
	/* Internally convert ntuple to fdir entry */
	memset(&fdir_entry, 0, sizeof(fdir_entry));
	if (ntuple->proto == IPPROTO_TCP) {
		fdir_entry.input.flow_type = RTE_ETH_FLOW_NONFRAG_IPV4_TCP;
		tcpv4_flow = &fdir_entry.input.flow.tcp4_flow;
		tcpv4_flow->ip.src_ip = ntuple->src_ip;
		tcpv4_flow->ip.dst_ip = ntuple->dst_ip;
		tcpv4_flow->ip.proto = IPPROTO_TCP;
		tcpv4_flow->src_port = ntuple->src_port;
		tcpv4_flow->dst_port = ntuple->dst_port;
	} else {
		fdir_entry.input.flow_type = RTE_ETH_FLOW_NONFRAG_IPV4_UDP;
		udpv4_flow = &fdir_entry.input.flow.udp4_flow;
		udpv4_flow->ip.src_ip = ntuple->src_ip;
		udpv4_flow->ip.dst_ip = ntuple->dst_ip;
		udpv4_flow->ip.proto = IPPROTO_TCP;
		udpv4_flow->src_port = ntuple->src_port;
		udpv4_flow->dst_port = ntuple->dst_port;
	}
	return qede_config_cmn_fdir_filter(eth_dev, &fdir_entry, add);
}
