/* Copyright (c) 2018-2019, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * RMNET generic protocol optimization handlers
 *
 */

#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <net/ip.h>
#include <net/checksum.h>
#include <net/ip6_checksum.h>
#include <../drivers/net/ethernet/qualcomm/rmnet/rmnet_config.h>
#include <../drivers/net/ethernet/qualcomm/rmnet/rmnet_map.h>
#include "rmnet_perf_opt.h"
#include "rmnet_perf_tcp_opt.h"
#include "rmnet_perf_udp_opt.h"
#include "rmnet_perf_core.h"
#include "rmnet_perf_config.h"

/* If true then we allocate all large SKBs */
unsigned long int rmnet_perf_opt_skb_recycle_off = 1;
module_param(rmnet_perf_opt_skb_recycle_off, ulong, 0644);
MODULE_PARM_DESC(rmnet_perf_opt_skb_recycle_off, "Num skbs max held");

/* Stat showing reason for flushes of flow nodes */
unsigned long int
rmnet_perf_opt_flush_reason_cnt[RMNET_PERF_OPT_NUM_CONDITIONS];
module_param_array(rmnet_perf_opt_flush_reason_cnt, ulong, 0, 0444);
MODULE_PARM_DESC(rmnet_perf_opt_flush_reason_cnt,
		 "opt performance statistics");

/* Stat showing packets dropped due to lack of memory */
unsigned long int rmnet_perf_opt_oom_drops = 0;
module_param(rmnet_perf_opt_oom_drops, ulong, 0644);
MODULE_PARM_DESC(rmnet_perf_opt_oom_drops,
		 "Number of packets dropped because we couldn't allocate SKBs");

enum {
	RMNET_PERF_OPT_MODE_TCP,
	RMNET_PERF_OPT_MODE_UDP,
	RMNET_PERF_OPT_MODE_ALL,
	RMNET_PERF_OPT_MODE_NON,
};

/* What protocols we optimize */
static int rmnet_perf_opt_mode = RMNET_PERF_OPT_MODE_ALL;

/* flow hash table */
DEFINE_HASHTABLE(rmnet_perf_opt_fht, RMNET_PERF_FLOW_HASH_TABLE_BITS);

static void rmnet_perf_opt_flush_flow_nodes_by_protocol(u8 protocol)
{
	struct rmnet_perf_opt_flow_node *flow_node;
	int bkt_cursor;

	hash_for_each(rmnet_perf_opt_fht, bkt_cursor, flow_node, list) {
		if (flow_node->num_pkts_held > 0 &&
		    flow_node->trans_proto == protocol)
			rmnet_perf_opt_flush_single_flow_node(flow_node);
	}
}

static int rmnet_perf_set_opt_mode(const char *val,
				   const struct kernel_param *kp)
{
	int old_mode = rmnet_perf_opt_mode;
	int rc = -EINVAL;
	char value[4];

	strlcpy(value, val, 4);
	value[3] = '\0';

	rmnet_perf_core_grab_lock();

	if (!strcmp(value, "tcp"))
		rmnet_perf_opt_mode = RMNET_PERF_OPT_MODE_TCP;
	else if (!strcmp(value, "udp"))
		rmnet_perf_opt_mode = RMNET_PERF_OPT_MODE_UDP;
	else if (!strcmp(value, "all"))
		rmnet_perf_opt_mode = RMNET_PERF_OPT_MODE_ALL;
	else if (!strcmp(value, "non"))
		rmnet_perf_opt_mode = RMNET_PERF_OPT_MODE_NON;
	else
		goto out;

	rc = 0;

	/* If we didn't change anything, or if all we did was add the
	 * other protocol, no nodes need to be flushed out
	 */
	if (old_mode == rmnet_perf_opt_mode ||
	    rmnet_perf_opt_mode == RMNET_PERF_OPT_MODE_ALL)
		goto out;

	/* Flush out any nodes of the protocol we are no longer optimizing */
	switch (rmnet_perf_opt_mode) {
	case RMNET_PERF_OPT_MODE_TCP:
		rmnet_perf_opt_flush_flow_nodes_by_protocol(IPPROTO_UDP);
		break;
	case RMNET_PERF_OPT_MODE_UDP:
		rmnet_perf_opt_flush_flow_nodes_by_protocol(IPPROTO_TCP);
		break;
	case RMNET_PERF_OPT_MODE_NON:
		rmnet_perf_opt_flush_all_flow_nodes();
		break;
	}

out:
	rmnet_perf_core_release_lock();

	return rc;
}

static int rmnet_perf_get_opt_mode(char *buf,
				   const struct kernel_param *kp)
{
	switch (rmnet_perf_opt_mode) {
	case RMNET_PERF_OPT_MODE_TCP:
		strlcpy(buf, "tcp\n", 5);
		break;
	case RMNET_PERF_OPT_MODE_UDP:
		strlcpy(buf, "udp\n", 5);
		break;
	case RMNET_PERF_OPT_MODE_ALL:
		strlcpy(buf, "all\n", 5);
		break;
	case RMNET_PERF_OPT_MODE_NON:
		strlcpy(buf, "non\n", 5);
		break;
	}

	return strlen(buf);
}

static const struct kernel_param_ops rmnet_perf_opt_mode_ops = {
	.set = rmnet_perf_set_opt_mode,
	.get = rmnet_perf_get_opt_mode,
};

module_param_cb(rmnet_perf_opt_mode, &rmnet_perf_opt_mode_ops, NULL, 0644);

/* rmnet_perf_optimize_protocol() - Check if we should optimize the given
 * protocol
 * @protocol: The IP protocol number to check
 *
 * Return:
 *    - true if protocol should use the flow node infrastructure
 *    - false if packets og the given protocol should be flushed
 **/
static bool rmnet_perf_optimize_protocol(u8 protocol)
{
	if (rmnet_perf_opt_mode == RMNET_PERF_OPT_MODE_ALL)
		return true;
	else if (protocol == IPPROTO_TCP)
		return rmnet_perf_opt_mode == RMNET_PERF_OPT_MODE_TCP;
	else if (protocol == IPPROTO_UDP)
		return rmnet_perf_opt_mode == RMNET_PERF_OPT_MODE_UDP;

	return false;
}

/* rmnet_perf_opt_ip_flag_flush() - Check IP header flags to decide if
 *		immediate flush required
 * @pkt_info: characteristics of the current packet
 *
 * If the IP header has any flag set that GRO won't accept, we will flush the
 * packet right away.
 *
 * Return:
 *    - true if need flush
 *    - false if immediate flush may not be needed
 **/
static bool
rmnet_perf_opt_ip_flag_flush(struct rmnet_perf_opt_flow_node *flow_node,
			     struct rmnet_perf_pkt_info *pkt_info)
{
	struct iphdr *ip4h;
	struct ipv6hdr *ip6h;
	__be32 first_word;

	switch (pkt_info->ip_proto) {
	case 0x04:
		ip4h = pkt_info->ip_hdr.v4hdr;

		if ((ip4h->ttl ^ flow_node->ip_flags.ip4_flags.ip_ttl) ||
		    (ip4h->tos ^ flow_node->ip_flags.ip4_flags.ip_tos) ||
		    (ip4h->frag_off ^
		     flow_node->ip_flags.ip4_flags.ip_frag_off) ||
		     ip4h->ihl > 5)
			return true;

		break;
	case 0x06:
		ip6h = (struct ipv6hdr *) pkt_info->ip_hdr.v6hdr;
		first_word = *(__be32 *)ip6h ^ flow_node->ip_flags.first_word;

		if (!!(first_word & htonl(0x0FF00000)))
			return true;

		break;
	default:
		pr_err("Unsupported ip version %d", pkt_info->ip_proto);
		rmnet_perf_opt_flush_reason_cnt[
				RMNET_PERF_OPT_PACKET_CORRUPT_ERROR]++;
	}
	return false;
}

/* rmnet_perf_opt_identify_flow() - Tell whether packet corresponds to
 *		given flow
 * @flow_node: Node we are checking against
 * @pkt_info: characteristics of the current packet
 *
 * Checks to see if the incoming packet is a match for a given flow node
 *
 * Return:
 *		- true: it is a match
 *		- false: not a match
 **/
static bool
rmnet_perf_opt_identify_flow(struct rmnet_perf_opt_flow_node *flow_node,
			     struct rmnet_perf_pkt_info *pkt_info)
{
	struct iphdr *ip4h;
	struct ipv6hdr *ip6h;
	/* Actually protocol generic. UDP and TCP headers have the source
	 * and dest ports in the same location. ;)
	 */
	struct udphdr *up = pkt_info->trans_hdr.up;

	/* if pkt count == 0 and hash is the same, then we give this one as
	 * pass as good enough since at this point there is no address stuff
	 * to check/verify.
	 */
	if (flow_node->num_pkts_held == 0 &&
	    flow_node->hash_value == pkt_info->hash_key)
		return true;

	/* protocol must match */
	if (flow_node->trans_proto != pkt_info->trans_proto)
		return false;

	/* cast iph to right ip header struct for ip_version */
	switch (pkt_info->ip_proto) {
	case 0x04:
		ip4h = pkt_info->ip_hdr.v4hdr;
		if (((__force u32)flow_node->saddr.saddr4 ^
		     (__force u32)ip4h->saddr) |
		    ((__force u32)flow_node->daddr.daddr4 ^
		     (__force u32)ip4h->daddr) |
		    ((__force u16)flow_node->src_port ^
		     (__force u16)up->source) |
		    ((__force u16)flow_node->dest_port ^
		     (__force u16)up->dest))
			return false;
		break;
	case 0x06:
		ip6h = pkt_info->ip_hdr.v6hdr;
		if ((ipv6_addr_cmp(&(flow_node->saddr.saddr6), &ip6h->saddr)) |
			(ipv6_addr_cmp(&(flow_node->daddr.daddr6),
				       &ip6h->daddr)) |
			((__force u16)flow_node->src_port ^
			 (__force u16)up->source) |
			((__force u16)flow_node->dest_port ^
			 (__force u16)up->dest))
			return false;
		break;
	default:
		pr_err("Unsupported ip version %d", pkt_info->ip_proto);
		rmnet_perf_opt_flush_reason_cnt[
				RMNET_PERF_OPT_PACKET_CORRUPT_ERROR]++;
		return false;
	}
	return true;
}

/* rmnet_perf_opt_add_flow_subfrags() - Associates the frag descriptor held by
 *		the flow_node to the main descriptor
 * @flow_node: opt structure containing packet we are allocating for
 *
 * Return:
 *		- void
 **/

static void
rmnet_perf_opt_add_flow_subfrags(struct rmnet_perf_opt_flow_node *flow_node)
{
	struct rmnet_perf *perf = rmnet_perf_config_get_perf();
	struct rmnet_perf_opt_pkt_node *pkt_list;
	struct rmnet_frag_descriptor *head_frag;
	u8 i;

	pkt_list = flow_node->pkt_list;
	head_frag = pkt_list[0].frag_desc;

	/* GSO segs might not be initialized yet (i.e. csum offload,
	 * RSB/RSC frames with only 1 packet, etc)
	 */
	if (!head_frag->gso_segs)
		head_frag->gso_segs = 1;

	head_frag->gso_size = flow_node->gso_len;

	for (i = 1; i < flow_node->num_pkts_held; i++) {
		struct rmnet_frag_descriptor *new_frag;

		new_frag = pkt_list[i].frag_desc;
		/* Pull headers if they're there */
		if (new_frag->hdr_ptr == rmnet_frag_data_ptr(new_frag)) {
			if (!rmnet_frag_pull(new_frag, perf->rmnet_port,
					     flow_node->ip_len +
					     flow_node->trans_len))
				continue;
		}

		/* Move the fragment onto the subfrags list */
		list_move_tail(&new_frag->list, &head_frag->sub_frags);
		head_frag->gso_segs += (new_frag->gso_segs) ?: 1;
	}
}

/* rmnet_perf_opt_alloc_flow_skb() - Allocate a new SKB for holding flow node
 *		data
 * @headlen: The amount of space to allocate for linear data. Does not include
 *		extra deaggregation headeroom.
 *
 * Allocates a new SKB large enough to hold the amount of data provided, or
 * returns a preallocated SKB if recycling is enabled and there are cached
 * buffers available.
 *
 * Return:
 *		- skb: the new SKb to use
 *		- NULL: memory failure
 **/
static struct sk_buff *rmnet_perf_opt_alloc_flow_skb(u32 headlen)
{
	struct sk_buff *skb;

	/* Grab a preallocated SKB if possible */
	if (!rmnet_perf_opt_skb_recycle_off) {
		skb = rmnet_perf_core_elligible_for_cache_skb(headlen);
		if (skb)
			return skb;
	}

	skb = alloc_skb(headlen + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC);
	if (!skb)
		return NULL;

	skb_reserve(skb, RMNET_MAP_DEAGGR_HEADROOM);
	return skb;
}

/* rmnet_perf_opt_make_flow_skb() - Allocate and populate SKBs for flow node
 *		that is being pushed up the stack
 * @flow_node: opt structure containing packet we are allocating for
 *
 * Return:
 *		- skb: The new SKB to use
 *		- NULL: memory failure
 **/
static struct sk_buff *
rmnet_perf_opt_make_flow_skb(struct rmnet_perf_opt_flow_node *flow_node)
{
	struct sk_buff *skb;
	struct rmnet_perf_opt_pkt_node *pkt_list;
	int i;
	u32 alloc_len;
	u32 total_pkt_size = 0;

	pkt_list = flow_node->pkt_list;
	alloc_len = flow_node->len + flow_node->ip_len + flow_node->trans_len;
	skb = rmnet_perf_opt_alloc_flow_skb(alloc_len);
	if (!skb)
		return NULL;

	/* Copy the headers over */
	skb_put_data(skb, pkt_list[0].header_start,
		     flow_node->ip_len + flow_node->trans_len);

	for (i = 0; i < flow_node->num_pkts_held; i++) {
		skb_put_data(skb, pkt_list[i].data_start, pkt_list[i].data_len);
		total_pkt_size += pkt_list[i].data_len;
	}

	if (flow_node->len != total_pkt_size)
		pr_err("%s(): flow_node->len = %u, pkt_size = %u\n", __func__,
		       flow_node->len, total_pkt_size);

	return skb;
}

static void
rmnet_perf_opt_flow_skb_fixup(struct sk_buff *skb,
			      struct rmnet_perf_opt_flow_node *flow_node)
{
	struct skb_shared_info *shinfo = skb_shinfo(skb);
	struct iphdr *iph = (struct iphdr *)skb->data;
	struct tcphdr *tp;
	struct udphdr *up;
	__sum16 pseudo;
	u16 datagram_len;
	bool ipv4 = (iph->version == 4);

	/* Avoid recalculating the hash later on */
	skb->hash = flow_node->hash_value;
	skb->sw_hash = 1;
	/* We've already validated all data in the flow nodes */
	skb->ip_summed = CHECKSUM_UNNECESSARY;

	/* GSO information only needs to be added/updated if we actually
	 * coaleced any packets.
	*/
	if (flow_node->num_pkts_held <= 1)
		return;

	datagram_len = skb->len - flow_node->ip_len;

	/* Update headers to reflect the new packet length.
	 * Transport checksum needs to be set to the pseudo header checksum
	 * since we need to mark the SKB as CHECKSUM_PARTIAL so the stack can
	 * segment properly.
	 */
	if (ipv4) {
		iph->tot_len = htons(datagram_len + flow_node->ip_len);
		pseudo = ~csum_tcpudp_magic(iph->saddr, iph->daddr,
					    datagram_len,
					    flow_node->trans_proto, 0);
		iph->check = 0;
		iph->check = ip_fast_csum((u8 *)iph, iph->ihl);
	} else {
		struct ipv6hdr *ip6h = (struct ipv6hdr *)iph;

		/* Payload len includes any extension headers */
		ip6h->payload_len = htons(skb->len - sizeof(*ip6h));
		pseudo = ~csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					  datagram_len, flow_node->trans_proto,
					  0);
	}

	switch (flow_node->trans_proto) {
	case IPPROTO_TCP:
		tp = (struct tcphdr *)((u8 *)iph + flow_node->ip_len);
		tp->check = pseudo;
		skb->csum_start = (u8 *)tp - skb->head;
		skb->csum_offset = offsetof(struct tcphdr, check);
		shinfo->gso_type = (ipv4) ? SKB_GSO_TCPV4 : SKB_GSO_TCPV6;
		break;
	case IPPROTO_UDP:
		up = (struct udphdr *)((u8 *)iph + flow_node->ip_len);
		up->len = htons(datagram_len);
		up->check = pseudo;
		skb->csum_start = (u8 *)up - skb->head;
		skb->csum_offset = offsetof(struct udphdr, check);
		shinfo->gso_type = SKB_GSO_UDP_L4;
		break;
	default:
		return;
	}

	/* Update GSO metadata */
	shinfo->gso_size = flow_node->gso_len;
	skb->ip_summed = CHECKSUM_PARTIAL;
}

/* rmnet_perf_opt_get_new_flow_index() - Pull flow node from node pool
 *
 * Fetch the flow node from the node pool. If we have already given
 * out all the flow nodes then we will always hit the else case and
 * thereafter we will use modulo arithmetic to choose which flow node
 * to evict and use.
 *
 * Return:
 *		- flow_node: node to be used by caller function
 **/
static struct rmnet_perf_opt_flow_node *rmnet_perf_opt_get_new_flow_index(void)
{
	struct rmnet_perf *perf = rmnet_perf_config_get_perf();
	struct rmnet_perf_opt_flow_node_pool *node_pool;
	struct rmnet_perf_opt_flow_node *flow_node_ejected;

	node_pool = perf->opt_meta->node_pool;
	/* once this value gets too big it never goes back down.
	 * from that point forward we use flow node repurposing techniques
	 * instead
	 */
	if (node_pool->num_flows_in_use < RMNET_PERF_NUM_FLOW_NODES)
		return node_pool->node_list[node_pool->num_flows_in_use++];

	flow_node_ejected = node_pool->node_list[
		node_pool->flow_recycle_counter++ % RMNET_PERF_NUM_FLOW_NODES];
	rmnet_perf_opt_flush_single_flow_node(flow_node_ejected);
	hash_del(&flow_node_ejected->list);
	return flow_node_ejected;
}

/* rmnet_perf_opt_update_flow() - Update stored IP flow information
 * @flow_node: opt structure containing flow information
 * @pkt_info: characteristics of the current packet
 *
 * Update IP-specific flags stored about the flow (i.e. ttl, tos/traffic class,
 * fragment information, flags).
 *
 * Return:
 *		- void
 **/
void
rmnet_perf_opt_update_flow(struct rmnet_perf_opt_flow_node *flow_node,
			   struct rmnet_perf_pkt_info *pkt_info)
{
	if (pkt_info->ip_proto == 0x04) {
		struct iphdr *iph = pkt_info->ip_hdr.v4hdr;
		/* Frags don't make it this far, so this is all we care about */
		__be16 flags = iph->frag_off & htons(IP_CE | IP_DF);

		flow_node->ip_flags.ip4_flags.ip_ttl = iph->ttl;
		flow_node->ip_flags.ip4_flags.ip_tos = iph->tos;
		flow_node->ip_flags.ip4_flags.ip_frag_off = flags;
	} else if (pkt_info->ip_proto == 0x06) {
		__be32 *word = (__be32 *)pkt_info->ip_hdr.v6hdr;

		flow_node->ip_flags.first_word = *word;
	}
}

/* rmnet_perf_opt_flush_single_flow_node() - Send a given flow node up
 *		NW stack.
 * @flow_node: opt structure containing packet we are allocating for
 *
 * Send a given flow up NW stack via specific VND
 *
 * Return:
 *    - Void
 **/
void rmnet_perf_opt_flush_single_flow_node(
				struct rmnet_perf_opt_flow_node *flow_node)
{
	if (flow_node->num_pkts_held) {
		if (!rmnet_perf_core_is_deag_mode()) {
			struct rmnet_frag_descriptor *frag_desc;

			rmnet_perf_opt_add_flow_subfrags(flow_node);
			frag_desc = flow_node->pkt_list[0].frag_desc;
			frag_desc->hash = flow_node->hash_value;
			rmnet_perf_core_send_desc(frag_desc);
		} else {
			struct sk_buff *skb;

			skb = rmnet_perf_opt_make_flow_skb(flow_node);
			if (skb) {
				rmnet_perf_opt_flow_skb_fixup(skb, flow_node);
				rmnet_perf_core_send_skb(skb, flow_node->ep);
			} else {
				rmnet_perf_opt_oom_drops +=
					flow_node->num_pkts_held;
			}
		}

		/* equivalent to memsetting the flow node */
		flow_node->num_pkts_held = 0;
		flow_node->len = 0;
	}
}

/* rmnet_perf_opt_flush_flow_by_hash() - Iterate through all flow nodes
 *	that match a certain hash and flush the match
 * @hash_val: hash value we are looking to match and hence flush
 *
 * Return:
 *    - void
 **/
void rmnet_perf_opt_flush_flow_by_hash(u32 hash_val)
{
	struct rmnet_perf_opt_flow_node *flow_node;

	hash_for_each_possible(rmnet_perf_opt_fht, flow_node, list, hash_val) {
		if (hash_val == flow_node->hash_value &&
		    flow_node->num_pkts_held > 0)
			rmnet_perf_opt_flush_single_flow_node(flow_node);
	}
}

/* rmnet_perf_opt_flush_all_flow_nodes() - Iterate through all flow nodes
 *		and flush them individually
 *
 * Return:
 *    - void
 **/
void rmnet_perf_opt_flush_all_flow_nodes(void)
{
	struct rmnet_perf_opt_flow_node *flow_node;
	int bkt_cursor;
	int num_pkts_held;
	u32 hash_val;

	hash_for_each(rmnet_perf_opt_fht, bkt_cursor, flow_node, list) {
		hash_val = flow_node->hash_value;
		num_pkts_held = flow_node->num_pkts_held;
		if (num_pkts_held > 0) {
			rmnet_perf_opt_flush_single_flow_node(flow_node);
		}
	}
}

/* rmnet_perf_opt_chain_end() - Handle end of SKB chain notification
 *
 * Return:
 *    - void
 **/
void rmnet_perf_opt_chain_end(void)
{
	rmnet_perf_core_grab_lock();
	rmnet_perf_opt_flush_reason_cnt[RMNET_PERF_OPT_CHAIN_END]++;
	rmnet_perf_opt_flush_all_flow_nodes();
	rmnet_perf_core_release_lock();
}

/* rmnet_perf_opt_insert_pkt_in_flow() - Inserts single IP packet into
 *		opt meta structure
 * @flow_node: flow node we are going to insert the ip packet into
 * @pkt_info: characteristics of the current packet
 *
 * Return:
 *    - void
 **/
void rmnet_perf_opt_insert_pkt_in_flow(
			struct rmnet_perf_opt_flow_node *flow_node,
			struct rmnet_perf_pkt_info *pkt_info)
{
	struct rmnet_perf_opt_pkt_node *pkt_node;
	struct tcphdr *tp = pkt_info->trans_hdr.tp;
	void *iph = (void *)pkt_info->ip_hdr.v4hdr;
	u16 header_len = pkt_info->ip_len + pkt_info->trans_len;
	u16 payload_len = pkt_info->payload_len;
	unsigned char ip_version = pkt_info->ip_proto;

	pkt_node = &flow_node->pkt_list[flow_node->num_pkts_held];
	pkt_node->header_start = (unsigned char *)iph;
	pkt_node->data_len = payload_len;
	flow_node->len += payload_len;
	flow_node->num_pkts_held++;

	/* Set appropriate data pointers based on mode */
	if (!rmnet_perf_core_is_deag_mode()) {
		pkt_node->frag_desc = pkt_info->frag_desc;
		pkt_node->data_start = rmnet_frag_data_ptr(pkt_info->frag_desc);
		pkt_node->data_start += header_len;
	} else {
		pkt_node->data_start = (unsigned char *)iph + header_len;
	}

	if (pkt_info->first_packet) {
		/* Copy over flow information */
		flow_node->ep = pkt_info->ep;
		flow_node->ip_proto = ip_version;
		flow_node->trans_proto = pkt_info->trans_proto;
		flow_node->src_port = tp->source;
		flow_node->dest_port = tp->dest;
		flow_node->ip_len = pkt_info->ip_len;
		flow_node->trans_len = pkt_info->trans_len;
		flow_node->hash_value = pkt_info->hash_key;
		/* Use already stamped gso_size if available */
		if (!rmnet_perf_core_is_deag_mode() &&
		    pkt_info->frag_desc->gso_size)
			flow_node->gso_len = pkt_info->frag_desc->gso_size;
		else
			flow_node->gso_len = payload_len;

		if (ip_version == 0x04) {
			flow_node->saddr.saddr4 =
				(__be32)((struct iphdr *)iph)->saddr;
			flow_node->daddr.daddr4 =
				(__be32)((struct iphdr *)iph)->daddr;
			flow_node->trans_proto =
				((struct iphdr *)iph)->protocol;
		} else {
			flow_node->saddr.saddr6 =
				((struct ipv6hdr *)iph)->saddr;
			flow_node->daddr.daddr6 =
				((struct ipv6hdr *)iph)->daddr;
			flow_node->trans_proto =
				((struct ipv6hdr *)iph)->nexthdr;
		}

		/* Set initial TCP SEQ number */
		if (pkt_info->trans_proto == IPPROTO_TCP) {
			if (pkt_info->frag_desc &&
			    pkt_info->frag_desc->tcp_seq_set) {
				__be32 seq = pkt_info->frag_desc->tcp_seq;

				flow_node->next_seq = ntohl(seq);
			} else {
				flow_node->next_seq = ntohl(tp->seq);
			}
		}

	}

	if (pkt_info->trans_proto == IPPROTO_TCP)
		flow_node->next_seq += payload_len;
}
void
rmnet_perf_free_hash_table(void)
{
	int i;
	struct rmnet_perf_opt_flow_node *flow_node;
	struct hlist_node *tmp;

	hash_for_each_safe(rmnet_perf_opt_fht, i, tmp, flow_node, list) {
		hash_del(&flow_node->list);
	}

}

/* rmnet_perf_opt_ingress() - Core business logic of optimization framework
 * @pkt_info: characteristics of the current packet
 *
 * Makes determination of what to do with a given incoming
 * ip packet. Find matching flow if it exists and call protocol-
 * specific helper to try and insert the packet and handle any
 * flushing needed.
 *
 * Return:
 *		- true if packet has been handled
 *		- false if caller needs to flush packet
 **/
bool rmnet_perf_opt_ingress(struct rmnet_perf_pkt_info *pkt_info)
{
	struct rmnet_perf_opt_flow_node *flow_node;
	struct rmnet_perf_opt_flow_node *flow_node_recycled;
	bool flush;
	bool handled = false;
	bool flow_node_exists = false;

	if (!rmnet_perf_optimize_protocol(pkt_info->trans_proto))
		goto out;

handle_pkt:
	hash_for_each_possible(rmnet_perf_opt_fht, flow_node, list,
			       pkt_info->hash_key) {
		if (!rmnet_perf_opt_identify_flow(flow_node, pkt_info))
			continue;

		flush = rmnet_perf_opt_ip_flag_flush(flow_node, pkt_info);

		/* set this to true by default. Let the protocol helpers
		 * change this if it is needed.
		 */
		pkt_info->first_packet = true;
		flow_node_exists = true;

		switch (pkt_info->trans_proto) {
		case IPPROTO_TCP:
			rmnet_perf_tcp_opt_ingress(flow_node, pkt_info, flush);
			handled = true;
			goto out;
		case IPPROTO_UDP:
			rmnet_perf_udp_opt_ingress(flow_node, pkt_info, flush);
			handled = true;
			goto out;
		default:
			pr_err("%s(): Unhandled protocol %u\n",
			       __func__, pkt_info->trans_proto);
			goto out;
		}
	}

	/* If we didn't find the flow, we need to add it and try again */
	if (!flow_node_exists) {
		flow_node_recycled = rmnet_perf_opt_get_new_flow_index();
		flow_node_recycled->hash_value = pkt_info->hash_key;
		rmnet_perf_opt_update_flow(flow_node_recycled, pkt_info);
		hash_add(rmnet_perf_opt_fht, &flow_node_recycled->list,
			 pkt_info->hash_key);
		goto handle_pkt;
	}

out:
	return handled;
}
