// SPDX-License-Identifier: GPL-2.0

#include "hwpa_ppe_internal.h"
#include <ppe_drv_cc.h>

#define PPE_DRV_CC_TO_EXP(cc) ((cc) - 1)

static enum hwpa_backend_rv hwpa_ppe_exception_check_and_set_ipv4_flushed(struct flow_keys *keys)
{
	struct hwpa_ppe_context *ppe_ctx = hwpa_ppe_get_context();
	struct ppe_drv_v4_5tuple v4_5tuple = {0};
	struct hwpa_ppe_accelerator *accelerator;
	struct hwpa_ppe_session *hws;
	uint32_t hash;

	v4_5tuple.flow_ip = ntohl(keys->addrs.v4addrs.src);
	v4_5tuple.return_ip = ntohl(keys->addrs.v4addrs.dst);
	v4_5tuple.protocol = keys->basic.ip_proto;
	/*
	 * PPE support only TCP, UDP or UDP_LITE for 5-tuple
	 */
	if ((keys->basic.ip_proto == IPPROTO_TCP)
			|| (keys->basic.ip_proto == IPPROTO_UDP)
			|| (keys->basic.ip_proto == IPPROTO_UDPLITE)) {

		v4_5tuple.flow_ident = ntohs(keys->ports.src);
		v4_5tuple.return_ident = ntohs(keys->ports.dst);
	}
	hash = hwpa_ipv4_gen_session_hash_raw(v4_5tuple.flow_ip, v4_5tuple.flow_ident,
				v4_5tuple.return_ip, v4_5tuple.return_ident,
				v4_5tuple.protocol);
	rcu_read_lock();
	hash_for_each_possible_rcu(ppe_ctx->used_hws_hlist, hws, node, hash) {
		accelerator = hwpa_ppe_get_accelerator(hws);
		if ((hws->on_free_list)
			|| (hws->accel_type != HWPA_PPE_ACCELERATOR_PPE_IPV4)
			|| (!accelerator || !accelerator->set_flushed_session)) {
			continue;
		}
		if (accelerator->set_flushed_session(hws, &v4_5tuple) == HWPA_BACKEND_SUCCESS) {
			rcu_read_unlock();
			return HWPA_BACKEND_SUCCESS;
		}
	}
	rcu_read_unlock();

	PR_DEVEL("No matching ppe connetion found for\nhash: 0x%x  protocol: %d\n  from ip: %pI4h:%d\n  to ip: %pI4h:%d\n",
				hash, v4_5tuple.protocol,
				&v4_5tuple.flow_ip, v4_5tuple.flow_ident,
				&v4_5tuple.return_ip, v4_5tuple.return_ident);

	return HWPA_BACKEND_ERR_NO_MATCH;
}

static enum hwpa_backend_rv hwpa_ppe_exception_check_and_set_ipv6_flushed(struct flow_keys *keys)
{
	struct hwpa_ppe_context *ppe_ctx = hwpa_ppe_get_context();
	struct ppe_drv_v6_5tuple tuple = {0};
	struct hwpa_ppe_accelerator *accelerator;
	struct hwpa_ppe_session *hws;
	uint32_t hash;

	PPE_DRV_IN6_TO_IPV6(tuple.flow_ip, keys->addrs.v6addrs.src);
	PPE_DRV_IN6_TO_IPV6(tuple.return_ip, keys->addrs.v6addrs.dst);
	tuple.protocol = keys->basic.ip_proto;
	/*
	 * The PPE supports only TCP, UDP or UDP_LITE for 5-tuple
	 */
	if ((keys->basic.ip_proto == IPPROTO_TCP)
			|| (keys->basic.ip_proto == IPPROTO_UDP)
			|| (keys->basic.ip_proto == IPPROTO_UDPLITE)) {

		tuple.flow_ident = ntohs(keys->ports.src);
		tuple.return_ident = ntohs(keys->ports.dst);
	}
	hash = hwpa_ipv6_gen_session_hash_raw(tuple.flow_ip, tuple.flow_ident,
				tuple.return_ip, tuple.return_ident,
				tuple.protocol);
	rcu_read_lock();
	hash_for_each_possible_rcu(ppe_ctx->used_hws_hlist, hws, node, hash) {
		accelerator = hwpa_ppe_get_accelerator(hws);
		if ((hws->on_free_list)
			|| (hws->accel_type != HWPA_PPE_ACCELERATOR_PPE_IPV6)
			|| (!accelerator || !accelerator->set_flushed_session)) {
			continue;
		}
		if (accelerator->set_flushed_session(hws, &tuple) == HWPA_BACKEND_SUCCESS) {
			rcu_read_unlock();
			return HWPA_BACKEND_SUCCESS;
		}
	}
	rcu_read_unlock();

	PR_DEVEL("No matching PPE connection found for\nhash: 0x%x  protocol: %d\n  from IP: %pI6h:%d\n  to IP: %pI6h:%d\n",
				hash, tuple.protocol,
				tuple.flow_ip, tuple.flow_ident,
				tuple.return_ip, tuple.return_ident);

	return HWPA_BACKEND_ERR_NO_MATCH;
}

bool hwpa_ppe_callback(void *app_data, struct sk_buff *skb, void *cc_info)
{
	ppe_drv_cc_t cc = PPE_DRV_CC_TO_EXP(*(ppe_drv_cc_t *)cc_info);
	struct flow_keys keys = {0};

	switch (cc) {
	case PPE_DRV_CC_EXP_TCP_FLAGS_0:
	case PPE_DRV_CC_EXP_TCP_FLAGS_1:
	case PPE_DRV_CC_EXP_TCP_FLAGS_2:
		PR_DEVEL("TCP Flag exception received: %d\n", cc);
		break;
	case PPE_DRV_CC_L3_FLOW_DE_ACCELEARTE:
	case PPE_DRV_CC_L3_NO_ROUTE_ACTION:
		/* session already de-accelerated */
		return false;
	case PPE_DRV_CC_EXP_IPV6_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_IPV6_BAD_PAYLOAD_LEN:
	case PPE_DRV_CC_EXP_IPV6_DATA_INCOMPLETE:
	case PPE_DRV_CC_EXP_IPV6_SMALL_HOP_LIMIT:
	case PPE_DRV_CC_EXP_IPV6_FRAG:
	case PPE_DRV_CC_EXP_IPV6_ESP_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_UNKNOWN_L2_PROT:
	case PPE_DRV_CC_EXP_PPPOE_WRONG_VER_TYPE:
	case PPE_DRV_CC_EXP_PPPOE_WRONG_CODE:
	case PPE_DRV_CC_EXP_PPPOE_UNSUPPORTED_PPP_PROT:
	case PPE_DRV_CC_EXP_IPV4_SMALL_IHL:
	case PPE_DRV_CC_EXP_IPV4_WITH_OPTION:
	case PPE_DRV_CC_EXP_IPV4_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_IPV4_BAD_TOTAL_LEN:
	case PPE_DRV_CC_EXP_IPV4_DATA_INCOMPLETE:
	case PPE_DRV_CC_EXP_IPV4_FRAG:
	case PPE_DRV_CC_EXP_IPV4_SMALL_TTL:
	case PPE_DRV_CC_EXP_IPV4_CHECKSUM_ERR:
	case PPE_DRV_CC_EXP_IPV4_ESP_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_TCP_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_TCP_SMALL_DATA_OFFSET:
	case PPE_DRV_CC_EXP_UDP_HDR_INCOMPLETE:
	case PPE_DRV_CC_EXP_UDP_LITE_HDR_INCOMPLETE:
	default:
		PR_DEVEL("Unhandled ppe drv exception: %d\n", cc);
	}

	if (!skb_flow_dissect_flow_keys(skb, &keys,
			FLOW_DISSECTOR_F_PARSE_1ST_FRAG | FLOW_DISSECTOR_F_STOP_AT_ENCAP)) {
		PR_DEVEL("dissection failed for skb:%p", skb);
		return false;
	}

	switch (keys.control.addr_type) {
	case FLOW_DISSECTOR_KEY_IPV4_ADDRS:
		hwpa_ppe_exception_check_and_set_ipv4_flushed(&keys);
		break;

	case FLOW_DISSECTOR_KEY_IPV6_ADDRS:
		hwpa_ppe_exception_check_and_set_ipv6_flushed(&keys);
		break;
	default:
		PR_DEVEL("dissection failed for skb:%p", skb);
	}

	return false;
}

void hwpa_ppe_exception_init(void)
{
	uint32_t i;

	for (i = 0; i < PPE_DRV_CC_MAX; ++i)
		ppe_drv_cc_register_cb(i, hwpa_ppe_callback, NULL);
}

void hwpa_ppe_exception_exit(void)
{
	uint32_t i;

	for (i = 0; i < PPE_DRV_CC_MAX; ++i)
		ppe_drv_cc_unregister_cb(i);
}