162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci#include <linux/if.h>
362306a36Sopenharmony_ci#include <linux/if_ether.h>
462306a36Sopenharmony_ci#include <linux/if_link.h>
562306a36Sopenharmony_ci#include <linux/netdevice.h>
662306a36Sopenharmony_ci#include <linux/in.h>
762306a36Sopenharmony_ci#include <linux/types.h>
862306a36Sopenharmony_ci#include <linux/skbuff.h>
962306a36Sopenharmony_ci#include <net/flow_dissector.h>
1062306a36Sopenharmony_ci#include "enic_res.h"
1162306a36Sopenharmony_ci#include "enic_clsf.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci/* enic_addfltr_5t - Add ipv4 5tuple filter
1462306a36Sopenharmony_ci *	@enic: enic struct of vnic
1562306a36Sopenharmony_ci *	@keys: flow_keys of ipv4 5tuple
1662306a36Sopenharmony_ci *	@rq: rq number to steer to
1762306a36Sopenharmony_ci *
1862306a36Sopenharmony_ci * This function returns filter_id(hardware_id) of the filter
1962306a36Sopenharmony_ci * added. In case of error it returns a negative number.
2062306a36Sopenharmony_ci */
2162306a36Sopenharmony_ciint enic_addfltr_5t(struct enic *enic, struct flow_keys *keys, u16 rq)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	int res;
2462306a36Sopenharmony_ci	struct filter data;
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	switch (keys->basic.ip_proto) {
2762306a36Sopenharmony_ci	case IPPROTO_TCP:
2862306a36Sopenharmony_ci		data.u.ipv4.protocol = PROTO_TCP;
2962306a36Sopenharmony_ci		break;
3062306a36Sopenharmony_ci	case IPPROTO_UDP:
3162306a36Sopenharmony_ci		data.u.ipv4.protocol = PROTO_UDP;
3262306a36Sopenharmony_ci		break;
3362306a36Sopenharmony_ci	default:
3462306a36Sopenharmony_ci		return -EPROTONOSUPPORT;
3562306a36Sopenharmony_ci	}
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	data.type = FILTER_IPV4_5TUPLE;
3862306a36Sopenharmony_ci	data.u.ipv4.src_addr = ntohl(keys->addrs.v4addrs.src);
3962306a36Sopenharmony_ci	data.u.ipv4.dst_addr = ntohl(keys->addrs.v4addrs.dst);
4062306a36Sopenharmony_ci	data.u.ipv4.src_port = ntohs(keys->ports.src);
4162306a36Sopenharmony_ci	data.u.ipv4.dst_port = ntohs(keys->ports.dst);
4262306a36Sopenharmony_ci	data.u.ipv4.flags = FILTER_FIELDS_IPV4_5TUPLE;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci	spin_lock_bh(&enic->devcmd_lock);
4562306a36Sopenharmony_ci	res = vnic_dev_classifier(enic->vdev, CLSF_ADD, &rq, &data);
4662306a36Sopenharmony_ci	spin_unlock_bh(&enic->devcmd_lock);
4762306a36Sopenharmony_ci	res = (res == 0) ? rq : res;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	return res;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/* enic_delfltr - Delete clsf filter
5362306a36Sopenharmony_ci *	@enic: enic struct of vnic
5462306a36Sopenharmony_ci *	@filter_id: filter_is(hardware_id) of filter to be deleted
5562306a36Sopenharmony_ci *
5662306a36Sopenharmony_ci * This function returns zero in case of success, negative number incase of
5762306a36Sopenharmony_ci * error.
5862306a36Sopenharmony_ci */
5962306a36Sopenharmony_ciint enic_delfltr(struct enic *enic, u16 filter_id)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	int ret;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	spin_lock_bh(&enic->devcmd_lock);
6462306a36Sopenharmony_ci	ret = vnic_dev_classifier(enic->vdev, CLSF_DEL, &filter_id, NULL);
6562306a36Sopenharmony_ci	spin_unlock_bh(&enic->devcmd_lock);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return ret;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/* enic_rfs_flw_tbl_init - initialize enic->rfs_h members
7162306a36Sopenharmony_ci *	@enic: enic data
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_civoid enic_rfs_flw_tbl_init(struct enic *enic)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	int i;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	spin_lock_init(&enic->rfs_h.lock);
7862306a36Sopenharmony_ci	for (i = 0; i <= ENIC_RFS_FLW_MASK; i++)
7962306a36Sopenharmony_ci		INIT_HLIST_HEAD(&enic->rfs_h.ht_head[i]);
8062306a36Sopenharmony_ci	enic->rfs_h.max = enic->config.num_arfs;
8162306a36Sopenharmony_ci	enic->rfs_h.free = enic->rfs_h.max;
8262306a36Sopenharmony_ci	enic->rfs_h.toclean = 0;
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_civoid enic_rfs_flw_tbl_free(struct enic *enic)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	int i;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	enic_rfs_timer_stop(enic);
9062306a36Sopenharmony_ci	spin_lock_bh(&enic->rfs_h.lock);
9162306a36Sopenharmony_ci	for (i = 0; i < (1 << ENIC_RFS_FLW_BITSHIFT); i++) {
9262306a36Sopenharmony_ci		struct hlist_head *hhead;
9362306a36Sopenharmony_ci		struct hlist_node *tmp;
9462306a36Sopenharmony_ci		struct enic_rfs_fltr_node *n;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		hhead = &enic->rfs_h.ht_head[i];
9762306a36Sopenharmony_ci		hlist_for_each_entry_safe(n, tmp, hhead, node) {
9862306a36Sopenharmony_ci			enic_delfltr(enic, n->fltr_id);
9962306a36Sopenharmony_ci			hlist_del(&n->node);
10062306a36Sopenharmony_ci			kfree(n);
10162306a36Sopenharmony_ci			enic->rfs_h.free++;
10262306a36Sopenharmony_ci		}
10362306a36Sopenharmony_ci	}
10462306a36Sopenharmony_ci	spin_unlock_bh(&enic->rfs_h.lock);
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistruct enic_rfs_fltr_node *htbl_fltr_search(struct enic *enic, u16 fltr_id)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	int i;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	for (i = 0; i < (1 << ENIC_RFS_FLW_BITSHIFT); i++) {
11262306a36Sopenharmony_ci		struct hlist_head *hhead;
11362306a36Sopenharmony_ci		struct hlist_node *tmp;
11462306a36Sopenharmony_ci		struct enic_rfs_fltr_node *n;
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci		hhead = &enic->rfs_h.ht_head[i];
11762306a36Sopenharmony_ci		hlist_for_each_entry_safe(n, tmp, hhead, node)
11862306a36Sopenharmony_ci			if (n->fltr_id == fltr_id)
11962306a36Sopenharmony_ci				return n;
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return NULL;
12362306a36Sopenharmony_ci}
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci#ifdef CONFIG_RFS_ACCEL
12662306a36Sopenharmony_civoid enic_flow_may_expire(struct timer_list *t)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	struct enic *enic = from_timer(enic, t, rfs_h.rfs_may_expire);
12962306a36Sopenharmony_ci	bool res;
13062306a36Sopenharmony_ci	int j;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	spin_lock_bh(&enic->rfs_h.lock);
13362306a36Sopenharmony_ci	for (j = 0; j < ENIC_CLSF_EXPIRE_COUNT; j++) {
13462306a36Sopenharmony_ci		struct hlist_head *hhead;
13562306a36Sopenharmony_ci		struct hlist_node *tmp;
13662306a36Sopenharmony_ci		struct enic_rfs_fltr_node *n;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci		hhead = &enic->rfs_h.ht_head[enic->rfs_h.toclean++];
13962306a36Sopenharmony_ci		hlist_for_each_entry_safe(n, tmp, hhead, node) {
14062306a36Sopenharmony_ci			res = rps_may_expire_flow(enic->netdev, n->rq_id,
14162306a36Sopenharmony_ci						  n->flow_id, n->fltr_id);
14262306a36Sopenharmony_ci			if (res) {
14362306a36Sopenharmony_ci				res = enic_delfltr(enic, n->fltr_id);
14462306a36Sopenharmony_ci				if (unlikely(res))
14562306a36Sopenharmony_ci					continue;
14662306a36Sopenharmony_ci				hlist_del(&n->node);
14762306a36Sopenharmony_ci				kfree(n);
14862306a36Sopenharmony_ci				enic->rfs_h.free++;
14962306a36Sopenharmony_ci			}
15062306a36Sopenharmony_ci		}
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci	spin_unlock_bh(&enic->rfs_h.lock);
15362306a36Sopenharmony_ci	mod_timer(&enic->rfs_h.rfs_may_expire, jiffies + HZ/4);
15462306a36Sopenharmony_ci}
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_cistatic struct enic_rfs_fltr_node *htbl_key_search(struct hlist_head *h,
15762306a36Sopenharmony_ci						  struct flow_keys *k)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	struct enic_rfs_fltr_node *tpos;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	hlist_for_each_entry(tpos, h, node)
16262306a36Sopenharmony_ci		if (tpos->keys.addrs.v4addrs.src == k->addrs.v4addrs.src &&
16362306a36Sopenharmony_ci		    tpos->keys.addrs.v4addrs.dst == k->addrs.v4addrs.dst &&
16462306a36Sopenharmony_ci		    tpos->keys.ports.ports == k->ports.ports &&
16562306a36Sopenharmony_ci		    tpos->keys.basic.ip_proto == k->basic.ip_proto &&
16662306a36Sopenharmony_ci		    tpos->keys.basic.n_proto == k->basic.n_proto)
16762306a36Sopenharmony_ci			return tpos;
16862306a36Sopenharmony_ci	return NULL;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ciint enic_rx_flow_steer(struct net_device *dev, const struct sk_buff *skb,
17262306a36Sopenharmony_ci		       u16 rxq_index, u32 flow_id)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct flow_keys keys;
17562306a36Sopenharmony_ci	struct enic_rfs_fltr_node *n;
17662306a36Sopenharmony_ci	struct enic *enic;
17762306a36Sopenharmony_ci	u16 tbl_idx;
17862306a36Sopenharmony_ci	int res, i;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	enic = netdev_priv(dev);
18162306a36Sopenharmony_ci	res = skb_flow_dissect_flow_keys(skb, &keys, 0);
18262306a36Sopenharmony_ci	if (!res || keys.basic.n_proto != htons(ETH_P_IP) ||
18362306a36Sopenharmony_ci	    (keys.basic.ip_proto != IPPROTO_TCP &&
18462306a36Sopenharmony_ci	     keys.basic.ip_proto != IPPROTO_UDP))
18562306a36Sopenharmony_ci		return -EPROTONOSUPPORT;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	tbl_idx = skb_get_hash_raw(skb) & ENIC_RFS_FLW_MASK;
18862306a36Sopenharmony_ci	spin_lock_bh(&enic->rfs_h.lock);
18962306a36Sopenharmony_ci	n = htbl_key_search(&enic->rfs_h.ht_head[tbl_idx], &keys);
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci	if (n) { /* entry already present  */
19262306a36Sopenharmony_ci		if (rxq_index == n->rq_id) {
19362306a36Sopenharmony_ci			res = -EEXIST;
19462306a36Sopenharmony_ci			goto ret_unlock;
19562306a36Sopenharmony_ci		}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci		/* desired rq changed for the flow, we need to delete
19862306a36Sopenharmony_ci		 * old fltr and add new one
19962306a36Sopenharmony_ci		 *
20062306a36Sopenharmony_ci		 * The moment we delete the fltr, the upcoming pkts
20162306a36Sopenharmony_ci		 * are put it default rq based on rss. When we add
20262306a36Sopenharmony_ci		 * new filter, upcoming pkts are put in desired queue.
20362306a36Sopenharmony_ci		 * This could cause ooo pkts.
20462306a36Sopenharmony_ci		 *
20562306a36Sopenharmony_ci		 * Lets 1st try adding new fltr and then del old one.
20662306a36Sopenharmony_ci		 */
20762306a36Sopenharmony_ci		i = --enic->rfs_h.free;
20862306a36Sopenharmony_ci		/* clsf tbl is full, we have to del old fltr first*/
20962306a36Sopenharmony_ci		if (unlikely(i < 0)) {
21062306a36Sopenharmony_ci			enic->rfs_h.free++;
21162306a36Sopenharmony_ci			res = enic_delfltr(enic, n->fltr_id);
21262306a36Sopenharmony_ci			if (unlikely(res < 0))
21362306a36Sopenharmony_ci				goto ret_unlock;
21462306a36Sopenharmony_ci			res = enic_addfltr_5t(enic, &keys, rxq_index);
21562306a36Sopenharmony_ci			if (res < 0) {
21662306a36Sopenharmony_ci				hlist_del(&n->node);
21762306a36Sopenharmony_ci				enic->rfs_h.free++;
21862306a36Sopenharmony_ci				goto ret_unlock;
21962306a36Sopenharmony_ci			}
22062306a36Sopenharmony_ci		/* add new fltr 1st then del old fltr */
22162306a36Sopenharmony_ci		} else {
22262306a36Sopenharmony_ci			int ret;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci			res = enic_addfltr_5t(enic, &keys, rxq_index);
22562306a36Sopenharmony_ci			if (res < 0) {
22662306a36Sopenharmony_ci				enic->rfs_h.free++;
22762306a36Sopenharmony_ci				goto ret_unlock;
22862306a36Sopenharmony_ci			}
22962306a36Sopenharmony_ci			ret = enic_delfltr(enic, n->fltr_id);
23062306a36Sopenharmony_ci			/* deleting old fltr failed. Add old fltr to list.
23162306a36Sopenharmony_ci			 * enic_flow_may_expire() will try to delete it later.
23262306a36Sopenharmony_ci			 */
23362306a36Sopenharmony_ci			if (unlikely(ret < 0)) {
23462306a36Sopenharmony_ci				struct enic_rfs_fltr_node *d;
23562306a36Sopenharmony_ci				struct hlist_head *head;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci				head = &enic->rfs_h.ht_head[tbl_idx];
23862306a36Sopenharmony_ci				d = kmalloc(sizeof(*d), GFP_ATOMIC);
23962306a36Sopenharmony_ci				if (d) {
24062306a36Sopenharmony_ci					d->fltr_id = n->fltr_id;
24162306a36Sopenharmony_ci					INIT_HLIST_NODE(&d->node);
24262306a36Sopenharmony_ci					hlist_add_head(&d->node, head);
24362306a36Sopenharmony_ci				}
24462306a36Sopenharmony_ci			} else {
24562306a36Sopenharmony_ci				enic->rfs_h.free++;
24662306a36Sopenharmony_ci			}
24762306a36Sopenharmony_ci		}
24862306a36Sopenharmony_ci		n->rq_id = rxq_index;
24962306a36Sopenharmony_ci		n->fltr_id = res;
25062306a36Sopenharmony_ci		n->flow_id = flow_id;
25162306a36Sopenharmony_ci	/* entry not present */
25262306a36Sopenharmony_ci	} else {
25362306a36Sopenharmony_ci		i = --enic->rfs_h.free;
25462306a36Sopenharmony_ci		if (i <= 0) {
25562306a36Sopenharmony_ci			enic->rfs_h.free++;
25662306a36Sopenharmony_ci			res = -EBUSY;
25762306a36Sopenharmony_ci			goto ret_unlock;
25862306a36Sopenharmony_ci		}
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		n = kmalloc(sizeof(*n), GFP_ATOMIC);
26162306a36Sopenharmony_ci		if (!n) {
26262306a36Sopenharmony_ci			res = -ENOMEM;
26362306a36Sopenharmony_ci			enic->rfs_h.free++;
26462306a36Sopenharmony_ci			goto ret_unlock;
26562306a36Sopenharmony_ci		}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci		res = enic_addfltr_5t(enic, &keys, rxq_index);
26862306a36Sopenharmony_ci		if (res < 0) {
26962306a36Sopenharmony_ci			kfree(n);
27062306a36Sopenharmony_ci			enic->rfs_h.free++;
27162306a36Sopenharmony_ci			goto ret_unlock;
27262306a36Sopenharmony_ci		}
27362306a36Sopenharmony_ci		n->rq_id = rxq_index;
27462306a36Sopenharmony_ci		n->fltr_id = res;
27562306a36Sopenharmony_ci		n->flow_id = flow_id;
27662306a36Sopenharmony_ci		n->keys = keys;
27762306a36Sopenharmony_ci		INIT_HLIST_NODE(&n->node);
27862306a36Sopenharmony_ci		hlist_add_head(&n->node, &enic->rfs_h.ht_head[tbl_idx]);
27962306a36Sopenharmony_ci	}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ciret_unlock:
28262306a36Sopenharmony_ci	spin_unlock_bh(&enic->rfs_h.lock);
28362306a36Sopenharmony_ci	return res;
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci#endif /* CONFIG_RFS_ACCEL */
287