// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2021, Intel Corporation. */

/* advanced RSS configuration ethtool support for iavf */

#include "iavf.h"

/**
 * iavf_fill_adv_rss_ip4_hdr - fill the IPv4 RSS protocol header
 * @hdr: the virtchnl message protocol header data structure
 * @hash_flds: the RSS configuration protocol hash fields
 */
static void
iavf_fill_adv_rss_ip4_hdr(struct virtchnl_proto_hdr *hdr, u64 hash_flds)
{
	VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, IPV4);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_IPV4_SA)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, SRC);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_IPV4_DA)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV4, DST);
}

/**
 * iavf_fill_adv_rss_ip6_hdr - fill the IPv6 RSS protocol header
 * @hdr: the virtchnl message protocol header data structure
 * @hash_flds: the RSS configuration protocol hash fields
 */
static void
iavf_fill_adv_rss_ip6_hdr(struct virtchnl_proto_hdr *hdr, u64 hash_flds)
{
	VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, IPV6);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_IPV6_SA)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, SRC);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_IPV6_DA)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, IPV6, DST);
}

/**
 * iavf_fill_adv_rss_tcp_hdr - fill the TCP RSS protocol header
 * @hdr: the virtchnl message protocol header data structure
 * @hash_flds: the RSS configuration protocol hash fields
 */
static void
iavf_fill_adv_rss_tcp_hdr(struct virtchnl_proto_hdr *hdr, u64 hash_flds)
{
	VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, TCP);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_TCP_SRC_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, TCP, SRC_PORT);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_TCP_DST_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, TCP, DST_PORT);
}

/**
 * iavf_fill_adv_rss_udp_hdr - fill the UDP RSS protocol header
 * @hdr: the virtchnl message protocol header data structure
 * @hash_flds: the RSS configuration protocol hash fields
 */
static void
iavf_fill_adv_rss_udp_hdr(struct virtchnl_proto_hdr *hdr, u64 hash_flds)
{
	VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, UDP);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_UDP_SRC_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, UDP, SRC_PORT);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_UDP_DST_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, UDP, DST_PORT);
}

/**
 * iavf_fill_adv_rss_sctp_hdr - fill the SCTP RSS protocol header
 * @hdr: the virtchnl message protocol header data structure
 * @hash_flds: the RSS configuration protocol hash fields
 */
static void
iavf_fill_adv_rss_sctp_hdr(struct virtchnl_proto_hdr *hdr, u64 hash_flds)
{
	VIRTCHNL_SET_PROTO_HDR_TYPE(hdr, SCTP);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_SCTP_SRC_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, SCTP, SRC_PORT);

	if (hash_flds & IAVF_ADV_RSS_HASH_FLD_SCTP_DST_PORT)
		VIRTCHNL_ADD_PROTO_HDR_FIELD_BIT(hdr, SCTP, DST_PORT);
}

/**
 * iavf_fill_adv_rss_cfg_msg - fill the RSS configuration into virtchnl message
 * @rss_cfg: the virtchnl message to be filled with RSS configuration setting
 * @packet_hdrs: the RSS configuration protocol header types
 * @hash_flds: the RSS configuration protocol hash fields
 *
 * Returns 0 if the RSS configuration virtchnl message is filled successfully
 */
int
iavf_fill_adv_rss_cfg_msg(struct virtchnl_rss_cfg *rss_cfg,
			  u32 packet_hdrs, u64 hash_flds)
{
	struct virtchnl_proto_hdrs *proto_hdrs = &rss_cfg->proto_hdrs;
	struct virtchnl_proto_hdr *hdr;

	rss_cfg->rss_algorithm = VIRTCHNL_RSS_ALG_TOEPLITZ_ASYMMETRIC;

	proto_hdrs->tunnel_level = 0;	/* always outer layer */

	hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
	switch (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_L3) {
	case IAVF_ADV_RSS_FLOW_SEG_HDR_IPV4:
		iavf_fill_adv_rss_ip4_hdr(hdr, hash_flds);
		break;
	case IAVF_ADV_RSS_FLOW_SEG_HDR_IPV6:
		iavf_fill_adv_rss_ip6_hdr(hdr, hash_flds);
		break;
	default:
		return -EINVAL;
	}

	hdr = &proto_hdrs->proto_hdr[proto_hdrs->count++];
	switch (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_L4) {
	case IAVF_ADV_RSS_FLOW_SEG_HDR_TCP:
		iavf_fill_adv_rss_tcp_hdr(hdr, hash_flds);
		break;
	case IAVF_ADV_RSS_FLOW_SEG_HDR_UDP:
		iavf_fill_adv_rss_udp_hdr(hdr, hash_flds);
		break;
	case IAVF_ADV_RSS_FLOW_SEG_HDR_SCTP:
		iavf_fill_adv_rss_sctp_hdr(hdr, hash_flds);
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

/**
 * iavf_find_adv_rss_cfg_by_hdrs - find RSS configuration with header type
 * @adapter: pointer to the VF adapter structure
 * @packet_hdrs: protocol header type to find.
 *
 * Returns pointer to advance RSS configuration if found or null
 */
struct iavf_adv_rss *
iavf_find_adv_rss_cfg_by_hdrs(struct iavf_adapter *adapter, u32 packet_hdrs)
{
	struct iavf_adv_rss *rss;

	list_for_each_entry(rss, &adapter->adv_rss_list_head, list)
		if (rss->packet_hdrs == packet_hdrs)
			return rss;

	return NULL;
}

/**
 * iavf_print_adv_rss_cfg
 * @adapter: pointer to the VF adapter structure
 * @rss: pointer to the advance RSS configuration to print
 * @action: the string description about how to handle the RSS
 * @result: the string description about the virtchnl result
 *
 * Print the advance RSS configuration
 **/
void
iavf_print_adv_rss_cfg(struct iavf_adapter *adapter, struct iavf_adv_rss *rss,
		       const char *action, const char *result)
{
	u32 packet_hdrs = rss->packet_hdrs;
	u64 hash_flds = rss->hash_flds;
	static char hash_opt[300];
	const char *proto;

	if (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_TCP)
		proto = "TCP";
	else if (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_UDP)
		proto = "UDP";
	else if (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_SCTP)
		proto = "SCTP";
	else
		return;

	memset(hash_opt, 0, sizeof(hash_opt));

	strcat(hash_opt, proto);
	if (packet_hdrs & IAVF_ADV_RSS_FLOW_SEG_HDR_IPV4)
		strcat(hash_opt, "v4 ");
	else
		strcat(hash_opt, "v6 ");

	if (hash_flds & (IAVF_ADV_RSS_HASH_FLD_IPV4_SA |
			 IAVF_ADV_RSS_HASH_FLD_IPV6_SA))
		strcat(hash_opt, "IP SA,");
	if (hash_flds & (IAVF_ADV_RSS_HASH_FLD_IPV4_DA |
			 IAVF_ADV_RSS_HASH_FLD_IPV6_DA))
		strcat(hash_opt, "IP DA,");
	if (hash_flds & (IAVF_ADV_RSS_HASH_FLD_TCP_SRC_PORT |
			 IAVF_ADV_RSS_HASH_FLD_UDP_SRC_PORT |
			 IAVF_ADV_RSS_HASH_FLD_SCTP_SRC_PORT))
		strcat(hash_opt, "src port,");
	if (hash_flds & (IAVF_ADV_RSS_HASH_FLD_TCP_DST_PORT |
			 IAVF_ADV_RSS_HASH_FLD_UDP_DST_PORT |
			 IAVF_ADV_RSS_HASH_FLD_SCTP_DST_PORT))
		strcat(hash_opt, "dst port,");

	if (!action)
		action = "";

	if (!result)
		result = "";

	dev_info(&adapter->pdev->dev, "%s %s %s\n", action, hash_opt, result);
}