162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * drivers/net/team/team_mode_loadbalance.c - Load-balancing mode for team
462306a36Sopenharmony_ci * Copyright (c) 2012 Jiri Pirko <jpirko@redhat.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/kernel.h>
862306a36Sopenharmony_ci#include <linux/types.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/init.h>
1162306a36Sopenharmony_ci#include <linux/errno.h>
1262306a36Sopenharmony_ci#include <linux/netdevice.h>
1362306a36Sopenharmony_ci#include <linux/etherdevice.h>
1462306a36Sopenharmony_ci#include <linux/filter.h>
1562306a36Sopenharmony_ci#include <linux/if_team.h>
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic rx_handler_result_t lb_receive(struct team *team, struct team_port *port,
1862306a36Sopenharmony_ci				      struct sk_buff *skb)
1962306a36Sopenharmony_ci{
2062306a36Sopenharmony_ci	if (unlikely(skb->protocol == htons(ETH_P_SLOW))) {
2162306a36Sopenharmony_ci		/* LACPDU packets should go to exact delivery */
2262306a36Sopenharmony_ci		const unsigned char *dest = eth_hdr(skb)->h_dest;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci		if (is_link_local_ether_addr(dest) && dest[5] == 0x02)
2562306a36Sopenharmony_ci			return RX_HANDLER_EXACT;
2662306a36Sopenharmony_ci	}
2762306a36Sopenharmony_ci	return RX_HANDLER_ANOTHER;
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistruct lb_priv;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_citypedef struct team_port *lb_select_tx_port_func_t(struct team *,
3362306a36Sopenharmony_ci						   unsigned char);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define LB_TX_HASHTABLE_SIZE 256 /* hash is a char */
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistruct lb_stats {
3862306a36Sopenharmony_ci	u64 tx_bytes;
3962306a36Sopenharmony_ci};
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistruct lb_pcpu_stats {
4262306a36Sopenharmony_ci	struct lb_stats hash_stats[LB_TX_HASHTABLE_SIZE];
4362306a36Sopenharmony_ci	struct u64_stats_sync syncp;
4462306a36Sopenharmony_ci};
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistruct lb_stats_info {
4762306a36Sopenharmony_ci	struct lb_stats stats;
4862306a36Sopenharmony_ci	struct lb_stats last_stats;
4962306a36Sopenharmony_ci	struct team_option_inst_info *opt_inst_info;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct lb_port_mapping {
5362306a36Sopenharmony_ci	struct team_port __rcu *port;
5462306a36Sopenharmony_ci	struct team_option_inst_info *opt_inst_info;
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistruct lb_priv_ex {
5862306a36Sopenharmony_ci	struct team *team;
5962306a36Sopenharmony_ci	struct lb_port_mapping tx_hash_to_port_mapping[LB_TX_HASHTABLE_SIZE];
6062306a36Sopenharmony_ci	struct sock_fprog_kern *orig_fprog;
6162306a36Sopenharmony_ci	struct {
6262306a36Sopenharmony_ci		unsigned int refresh_interval; /* in tenths of second */
6362306a36Sopenharmony_ci		struct delayed_work refresh_dw;
6462306a36Sopenharmony_ci		struct lb_stats_info info[LB_TX_HASHTABLE_SIZE];
6562306a36Sopenharmony_ci	} stats;
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistruct lb_priv {
6962306a36Sopenharmony_ci	struct bpf_prog __rcu *fp;
7062306a36Sopenharmony_ci	lb_select_tx_port_func_t __rcu *select_tx_port_func;
7162306a36Sopenharmony_ci	struct lb_pcpu_stats __percpu *pcpu_stats;
7262306a36Sopenharmony_ci	struct lb_priv_ex *ex; /* priv extension */
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic struct lb_priv *get_lb_priv(struct team *team)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	return (struct lb_priv *) &team->mode_priv;
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistruct lb_port_priv {
8162306a36Sopenharmony_ci	struct lb_stats __percpu *pcpu_stats;
8262306a36Sopenharmony_ci	struct lb_stats_info stats_info;
8362306a36Sopenharmony_ci};
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic struct lb_port_priv *get_lb_port_priv(struct team_port *port)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	return (struct lb_port_priv *) &port->mode_priv;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci#define LB_HTPM_PORT_BY_HASH(lp_priv, hash) \
9162306a36Sopenharmony_ci	(lb_priv)->ex->tx_hash_to_port_mapping[hash].port
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci#define LB_HTPM_OPT_INST_INFO_BY_HASH(lp_priv, hash) \
9462306a36Sopenharmony_ci	(lb_priv)->ex->tx_hash_to_port_mapping[hash].opt_inst_info
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_cistatic void lb_tx_hash_to_port_mapping_null_port(struct team *team,
9762306a36Sopenharmony_ci						 struct team_port *port)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
10062306a36Sopenharmony_ci	bool changed = false;
10162306a36Sopenharmony_ci	int i;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	for (i = 0; i < LB_TX_HASHTABLE_SIZE; i++) {
10462306a36Sopenharmony_ci		struct lb_port_mapping *pm;
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci		pm = &lb_priv->ex->tx_hash_to_port_mapping[i];
10762306a36Sopenharmony_ci		if (rcu_access_pointer(pm->port) == port) {
10862306a36Sopenharmony_ci			RCU_INIT_POINTER(pm->port, NULL);
10962306a36Sopenharmony_ci			team_option_inst_set_change(pm->opt_inst_info);
11062306a36Sopenharmony_ci			changed = true;
11162306a36Sopenharmony_ci		}
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci	if (changed)
11462306a36Sopenharmony_ci		team_options_change_check(team);
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci/* Basic tx selection based solely by hash */
11862306a36Sopenharmony_cistatic struct team_port *lb_hash_select_tx_port(struct team *team,
11962306a36Sopenharmony_ci						unsigned char hash)
12062306a36Sopenharmony_ci{
12162306a36Sopenharmony_ci	int port_index = team_num_to_port_index(team, hash);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return team_get_port_by_index_rcu(team, port_index);
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/* Hash to port mapping select tx port */
12762306a36Sopenharmony_cistatic struct team_port *lb_htpm_select_tx_port(struct team *team,
12862306a36Sopenharmony_ci						unsigned char hash)
12962306a36Sopenharmony_ci{
13062306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
13162306a36Sopenharmony_ci	struct team_port *port;
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	port = rcu_dereference_bh(LB_HTPM_PORT_BY_HASH(lb_priv, hash));
13462306a36Sopenharmony_ci	if (likely(port))
13562306a36Sopenharmony_ci		return port;
13662306a36Sopenharmony_ci	/* If no valid port in the table, fall back to simple hash */
13762306a36Sopenharmony_ci	return lb_hash_select_tx_port(team, hash);
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistruct lb_select_tx_port {
14162306a36Sopenharmony_ci	char *name;
14262306a36Sopenharmony_ci	lb_select_tx_port_func_t *func;
14362306a36Sopenharmony_ci};
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_cistatic const struct lb_select_tx_port lb_select_tx_port_list[] = {
14662306a36Sopenharmony_ci	{
14762306a36Sopenharmony_ci		.name = "hash",
14862306a36Sopenharmony_ci		.func = lb_hash_select_tx_port,
14962306a36Sopenharmony_ci	},
15062306a36Sopenharmony_ci	{
15162306a36Sopenharmony_ci		.name = "hash_to_port_mapping",
15262306a36Sopenharmony_ci		.func = lb_htpm_select_tx_port,
15362306a36Sopenharmony_ci	},
15462306a36Sopenharmony_ci};
15562306a36Sopenharmony_ci#define LB_SELECT_TX_PORT_LIST_COUNT ARRAY_SIZE(lb_select_tx_port_list)
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_cistatic char *lb_select_tx_port_get_name(lb_select_tx_port_func_t *func)
15862306a36Sopenharmony_ci{
15962306a36Sopenharmony_ci	int i;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	for (i = 0; i < LB_SELECT_TX_PORT_LIST_COUNT; i++) {
16262306a36Sopenharmony_ci		const struct lb_select_tx_port *item;
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci		item = &lb_select_tx_port_list[i];
16562306a36Sopenharmony_ci		if (item->func == func)
16662306a36Sopenharmony_ci			return item->name;
16762306a36Sopenharmony_ci	}
16862306a36Sopenharmony_ci	return NULL;
16962306a36Sopenharmony_ci}
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_cistatic lb_select_tx_port_func_t *lb_select_tx_port_get_func(const char *name)
17262306a36Sopenharmony_ci{
17362306a36Sopenharmony_ci	int i;
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	for (i = 0; i < LB_SELECT_TX_PORT_LIST_COUNT; i++) {
17662306a36Sopenharmony_ci		const struct lb_select_tx_port *item;
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci		item = &lb_select_tx_port_list[i];
17962306a36Sopenharmony_ci		if (!strcmp(item->name, name))
18062306a36Sopenharmony_ci			return item->func;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci	return NULL;
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic unsigned int lb_get_skb_hash(struct lb_priv *lb_priv,
18662306a36Sopenharmony_ci				    struct sk_buff *skb)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	struct bpf_prog *fp;
18962306a36Sopenharmony_ci	uint32_t lhash;
19062306a36Sopenharmony_ci	unsigned char *c;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	fp = rcu_dereference_bh(lb_priv->fp);
19362306a36Sopenharmony_ci	if (unlikely(!fp))
19462306a36Sopenharmony_ci		return 0;
19562306a36Sopenharmony_ci	lhash = bpf_prog_run(fp, skb);
19662306a36Sopenharmony_ci	c = (char *) &lhash;
19762306a36Sopenharmony_ci	return c[0] ^ c[1] ^ c[2] ^ c[3];
19862306a36Sopenharmony_ci}
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_cistatic void lb_update_tx_stats(unsigned int tx_bytes, struct lb_priv *lb_priv,
20162306a36Sopenharmony_ci			       struct lb_port_priv *lb_port_priv,
20262306a36Sopenharmony_ci			       unsigned char hash)
20362306a36Sopenharmony_ci{
20462306a36Sopenharmony_ci	struct lb_pcpu_stats *pcpu_stats;
20562306a36Sopenharmony_ci	struct lb_stats *port_stats;
20662306a36Sopenharmony_ci	struct lb_stats *hash_stats;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci	pcpu_stats = this_cpu_ptr(lb_priv->pcpu_stats);
20962306a36Sopenharmony_ci	port_stats = this_cpu_ptr(lb_port_priv->pcpu_stats);
21062306a36Sopenharmony_ci	hash_stats = &pcpu_stats->hash_stats[hash];
21162306a36Sopenharmony_ci	u64_stats_update_begin(&pcpu_stats->syncp);
21262306a36Sopenharmony_ci	port_stats->tx_bytes += tx_bytes;
21362306a36Sopenharmony_ci	hash_stats->tx_bytes += tx_bytes;
21462306a36Sopenharmony_ci	u64_stats_update_end(&pcpu_stats->syncp);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic bool lb_transmit(struct team *team, struct sk_buff *skb)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
22062306a36Sopenharmony_ci	lb_select_tx_port_func_t *select_tx_port_func;
22162306a36Sopenharmony_ci	struct team_port *port;
22262306a36Sopenharmony_ci	unsigned char hash;
22362306a36Sopenharmony_ci	unsigned int tx_bytes = skb->len;
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci	hash = lb_get_skb_hash(lb_priv, skb);
22662306a36Sopenharmony_ci	select_tx_port_func = rcu_dereference_bh(lb_priv->select_tx_port_func);
22762306a36Sopenharmony_ci	port = select_tx_port_func(team, hash);
22862306a36Sopenharmony_ci	if (unlikely(!port))
22962306a36Sopenharmony_ci		goto drop;
23062306a36Sopenharmony_ci	if (team_dev_queue_xmit(team, port, skb))
23162306a36Sopenharmony_ci		return false;
23262306a36Sopenharmony_ci	lb_update_tx_stats(tx_bytes, lb_priv, get_lb_port_priv(port), hash);
23362306a36Sopenharmony_ci	return true;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cidrop:
23662306a36Sopenharmony_ci	dev_kfree_skb_any(skb);
23762306a36Sopenharmony_ci	return false;
23862306a36Sopenharmony_ci}
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_cistatic void lb_bpf_func_get(struct team *team, struct team_gsetter_ctx *ctx)
24162306a36Sopenharmony_ci{
24262306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!lb_priv->ex->orig_fprog) {
24562306a36Sopenharmony_ci		ctx->data.bin_val.len = 0;
24662306a36Sopenharmony_ci		ctx->data.bin_val.ptr = NULL;
24762306a36Sopenharmony_ci		return;
24862306a36Sopenharmony_ci	}
24962306a36Sopenharmony_ci	ctx->data.bin_val.len = lb_priv->ex->orig_fprog->len *
25062306a36Sopenharmony_ci				sizeof(struct sock_filter);
25162306a36Sopenharmony_ci	ctx->data.bin_val.ptr = lb_priv->ex->orig_fprog->filter;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_cistatic int __fprog_create(struct sock_fprog_kern **pfprog, u32 data_len,
25562306a36Sopenharmony_ci			  const void *data)
25662306a36Sopenharmony_ci{
25762306a36Sopenharmony_ci	struct sock_fprog_kern *fprog;
25862306a36Sopenharmony_ci	struct sock_filter *filter = (struct sock_filter *) data;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	if (data_len % sizeof(struct sock_filter))
26162306a36Sopenharmony_ci		return -EINVAL;
26262306a36Sopenharmony_ci	fprog = kmalloc(sizeof(*fprog), GFP_KERNEL);
26362306a36Sopenharmony_ci	if (!fprog)
26462306a36Sopenharmony_ci		return -ENOMEM;
26562306a36Sopenharmony_ci	fprog->filter = kmemdup(filter, data_len, GFP_KERNEL);
26662306a36Sopenharmony_ci	if (!fprog->filter) {
26762306a36Sopenharmony_ci		kfree(fprog);
26862306a36Sopenharmony_ci		return -ENOMEM;
26962306a36Sopenharmony_ci	}
27062306a36Sopenharmony_ci	fprog->len = data_len / sizeof(struct sock_filter);
27162306a36Sopenharmony_ci	*pfprog = fprog;
27262306a36Sopenharmony_ci	return 0;
27362306a36Sopenharmony_ci}
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_cistatic void __fprog_destroy(struct sock_fprog_kern *fprog)
27662306a36Sopenharmony_ci{
27762306a36Sopenharmony_ci	kfree(fprog->filter);
27862306a36Sopenharmony_ci	kfree(fprog);
27962306a36Sopenharmony_ci}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_cistatic int lb_bpf_func_set(struct team *team, struct team_gsetter_ctx *ctx)
28262306a36Sopenharmony_ci{
28362306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
28462306a36Sopenharmony_ci	struct bpf_prog *fp = NULL;
28562306a36Sopenharmony_ci	struct bpf_prog *orig_fp = NULL;
28662306a36Sopenharmony_ci	struct sock_fprog_kern *fprog = NULL;
28762306a36Sopenharmony_ci	int err;
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	if (ctx->data.bin_val.len) {
29062306a36Sopenharmony_ci		err = __fprog_create(&fprog, ctx->data.bin_val.len,
29162306a36Sopenharmony_ci				     ctx->data.bin_val.ptr);
29262306a36Sopenharmony_ci		if (err)
29362306a36Sopenharmony_ci			return err;
29462306a36Sopenharmony_ci		err = bpf_prog_create(&fp, fprog);
29562306a36Sopenharmony_ci		if (err) {
29662306a36Sopenharmony_ci			__fprog_destroy(fprog);
29762306a36Sopenharmony_ci			return err;
29862306a36Sopenharmony_ci		}
29962306a36Sopenharmony_ci	}
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	if (lb_priv->ex->orig_fprog) {
30262306a36Sopenharmony_ci		/* Clear old filter data */
30362306a36Sopenharmony_ci		__fprog_destroy(lb_priv->ex->orig_fprog);
30462306a36Sopenharmony_ci		orig_fp = rcu_dereference_protected(lb_priv->fp,
30562306a36Sopenharmony_ci						lockdep_is_held(&team->lock));
30662306a36Sopenharmony_ci	}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci	rcu_assign_pointer(lb_priv->fp, fp);
30962306a36Sopenharmony_ci	lb_priv->ex->orig_fprog = fprog;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci	if (orig_fp) {
31262306a36Sopenharmony_ci		synchronize_rcu();
31362306a36Sopenharmony_ci		bpf_prog_destroy(orig_fp);
31462306a36Sopenharmony_ci	}
31562306a36Sopenharmony_ci	return 0;
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic void lb_bpf_func_free(struct team *team)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
32162306a36Sopenharmony_ci	struct bpf_prog *fp;
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	if (!lb_priv->ex->orig_fprog)
32462306a36Sopenharmony_ci		return;
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	__fprog_destroy(lb_priv->ex->orig_fprog);
32762306a36Sopenharmony_ci	fp = rcu_dereference_protected(lb_priv->fp,
32862306a36Sopenharmony_ci				       lockdep_is_held(&team->lock));
32962306a36Sopenharmony_ci	bpf_prog_destroy(fp);
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistatic void lb_tx_method_get(struct team *team, struct team_gsetter_ctx *ctx)
33362306a36Sopenharmony_ci{
33462306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
33562306a36Sopenharmony_ci	lb_select_tx_port_func_t *func;
33662306a36Sopenharmony_ci	char *name;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	func = rcu_dereference_protected(lb_priv->select_tx_port_func,
33962306a36Sopenharmony_ci					 lockdep_is_held(&team->lock));
34062306a36Sopenharmony_ci	name = lb_select_tx_port_get_name(func);
34162306a36Sopenharmony_ci	BUG_ON(!name);
34262306a36Sopenharmony_ci	ctx->data.str_val = name;
34362306a36Sopenharmony_ci}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_cistatic int lb_tx_method_set(struct team *team, struct team_gsetter_ctx *ctx)
34662306a36Sopenharmony_ci{
34762306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
34862306a36Sopenharmony_ci	lb_select_tx_port_func_t *func;
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_ci	func = lb_select_tx_port_get_func(ctx->data.str_val);
35162306a36Sopenharmony_ci	if (!func)
35262306a36Sopenharmony_ci		return -EINVAL;
35362306a36Sopenharmony_ci	rcu_assign_pointer(lb_priv->select_tx_port_func, func);
35462306a36Sopenharmony_ci	return 0;
35562306a36Sopenharmony_ci}
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic void lb_tx_hash_to_port_mapping_init(struct team *team,
35862306a36Sopenharmony_ci					    struct team_option_inst_info *info)
35962306a36Sopenharmony_ci{
36062306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
36162306a36Sopenharmony_ci	unsigned char hash = info->array_index;
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_ci	LB_HTPM_OPT_INST_INFO_BY_HASH(lb_priv, hash) = info;
36462306a36Sopenharmony_ci}
36562306a36Sopenharmony_ci
36662306a36Sopenharmony_cistatic void lb_tx_hash_to_port_mapping_get(struct team *team,
36762306a36Sopenharmony_ci					   struct team_gsetter_ctx *ctx)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
37062306a36Sopenharmony_ci	struct team_port *port;
37162306a36Sopenharmony_ci	unsigned char hash = ctx->info->array_index;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	port = LB_HTPM_PORT_BY_HASH(lb_priv, hash);
37462306a36Sopenharmony_ci	ctx->data.u32_val = port ? port->dev->ifindex : 0;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic int lb_tx_hash_to_port_mapping_set(struct team *team,
37862306a36Sopenharmony_ci					  struct team_gsetter_ctx *ctx)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
38162306a36Sopenharmony_ci	struct team_port *port;
38262306a36Sopenharmony_ci	unsigned char hash = ctx->info->array_index;
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_ci	list_for_each_entry(port, &team->port_list, list) {
38562306a36Sopenharmony_ci		if (ctx->data.u32_val == port->dev->ifindex &&
38662306a36Sopenharmony_ci		    team_port_enabled(port)) {
38762306a36Sopenharmony_ci			rcu_assign_pointer(LB_HTPM_PORT_BY_HASH(lb_priv, hash),
38862306a36Sopenharmony_ci					   port);
38962306a36Sopenharmony_ci			return 0;
39062306a36Sopenharmony_ci		}
39162306a36Sopenharmony_ci	}
39262306a36Sopenharmony_ci	return -ENODEV;
39362306a36Sopenharmony_ci}
39462306a36Sopenharmony_ci
39562306a36Sopenharmony_cistatic void lb_hash_stats_init(struct team *team,
39662306a36Sopenharmony_ci			       struct team_option_inst_info *info)
39762306a36Sopenharmony_ci{
39862306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
39962306a36Sopenharmony_ci	unsigned char hash = info->array_index;
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_ci	lb_priv->ex->stats.info[hash].opt_inst_info = info;
40262306a36Sopenharmony_ci}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic void lb_hash_stats_get(struct team *team, struct team_gsetter_ctx *ctx)
40562306a36Sopenharmony_ci{
40662306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
40762306a36Sopenharmony_ci	unsigned char hash = ctx->info->array_index;
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	ctx->data.bin_val.ptr = &lb_priv->ex->stats.info[hash].stats;
41062306a36Sopenharmony_ci	ctx->data.bin_val.len = sizeof(struct lb_stats);
41162306a36Sopenharmony_ci}
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_cistatic void lb_port_stats_init(struct team *team,
41462306a36Sopenharmony_ci			       struct team_option_inst_info *info)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	struct team_port *port = info->port;
41762306a36Sopenharmony_ci	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
41862306a36Sopenharmony_ci
41962306a36Sopenharmony_ci	lb_port_priv->stats_info.opt_inst_info = info;
42062306a36Sopenharmony_ci}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_cistatic void lb_port_stats_get(struct team *team, struct team_gsetter_ctx *ctx)
42362306a36Sopenharmony_ci{
42462306a36Sopenharmony_ci	struct team_port *port = ctx->info->port;
42562306a36Sopenharmony_ci	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_ci	ctx->data.bin_val.ptr = &lb_port_priv->stats_info.stats;
42862306a36Sopenharmony_ci	ctx->data.bin_val.len = sizeof(struct lb_stats);
42962306a36Sopenharmony_ci}
43062306a36Sopenharmony_ci
43162306a36Sopenharmony_cistatic void __lb_stats_info_refresh_prepare(struct lb_stats_info *s_info)
43262306a36Sopenharmony_ci{
43362306a36Sopenharmony_ci	memcpy(&s_info->last_stats, &s_info->stats, sizeof(struct lb_stats));
43462306a36Sopenharmony_ci	memset(&s_info->stats, 0, sizeof(struct lb_stats));
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_cistatic bool __lb_stats_info_refresh_check(struct lb_stats_info *s_info,
43862306a36Sopenharmony_ci					  struct team *team)
43962306a36Sopenharmony_ci{
44062306a36Sopenharmony_ci	if (memcmp(&s_info->last_stats, &s_info->stats,
44162306a36Sopenharmony_ci	    sizeof(struct lb_stats))) {
44262306a36Sopenharmony_ci		team_option_inst_set_change(s_info->opt_inst_info);
44362306a36Sopenharmony_ci		return true;
44462306a36Sopenharmony_ci	}
44562306a36Sopenharmony_ci	return false;
44662306a36Sopenharmony_ci}
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_cistatic void __lb_one_cpu_stats_add(struct lb_stats *acc_stats,
44962306a36Sopenharmony_ci				   struct lb_stats *cpu_stats,
45062306a36Sopenharmony_ci				   struct u64_stats_sync *syncp)
45162306a36Sopenharmony_ci{
45262306a36Sopenharmony_ci	unsigned int start;
45362306a36Sopenharmony_ci	struct lb_stats tmp;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	do {
45662306a36Sopenharmony_ci		start = u64_stats_fetch_begin(syncp);
45762306a36Sopenharmony_ci		tmp.tx_bytes = cpu_stats->tx_bytes;
45862306a36Sopenharmony_ci	} while (u64_stats_fetch_retry(syncp, start));
45962306a36Sopenharmony_ci	acc_stats->tx_bytes += tmp.tx_bytes;
46062306a36Sopenharmony_ci}
46162306a36Sopenharmony_ci
46262306a36Sopenharmony_cistatic void lb_stats_refresh(struct work_struct *work)
46362306a36Sopenharmony_ci{
46462306a36Sopenharmony_ci	struct team *team;
46562306a36Sopenharmony_ci	struct lb_priv *lb_priv;
46662306a36Sopenharmony_ci	struct lb_priv_ex *lb_priv_ex;
46762306a36Sopenharmony_ci	struct lb_pcpu_stats *pcpu_stats;
46862306a36Sopenharmony_ci	struct lb_stats *stats;
46962306a36Sopenharmony_ci	struct lb_stats_info *s_info;
47062306a36Sopenharmony_ci	struct team_port *port;
47162306a36Sopenharmony_ci	bool changed = false;
47262306a36Sopenharmony_ci	int i;
47362306a36Sopenharmony_ci	int j;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	lb_priv_ex = container_of(work, struct lb_priv_ex,
47662306a36Sopenharmony_ci				  stats.refresh_dw.work);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	team = lb_priv_ex->team;
47962306a36Sopenharmony_ci	lb_priv = get_lb_priv(team);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	if (!mutex_trylock(&team->lock)) {
48262306a36Sopenharmony_ci		schedule_delayed_work(&lb_priv_ex->stats.refresh_dw, 0);
48362306a36Sopenharmony_ci		return;
48462306a36Sopenharmony_ci	}
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	for (j = 0; j < LB_TX_HASHTABLE_SIZE; j++) {
48762306a36Sopenharmony_ci		s_info = &lb_priv->ex->stats.info[j];
48862306a36Sopenharmony_ci		__lb_stats_info_refresh_prepare(s_info);
48962306a36Sopenharmony_ci		for_each_possible_cpu(i) {
49062306a36Sopenharmony_ci			pcpu_stats = per_cpu_ptr(lb_priv->pcpu_stats, i);
49162306a36Sopenharmony_ci			stats = &pcpu_stats->hash_stats[j];
49262306a36Sopenharmony_ci			__lb_one_cpu_stats_add(&s_info->stats, stats,
49362306a36Sopenharmony_ci					       &pcpu_stats->syncp);
49462306a36Sopenharmony_ci		}
49562306a36Sopenharmony_ci		changed |= __lb_stats_info_refresh_check(s_info, team);
49662306a36Sopenharmony_ci	}
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	list_for_each_entry(port, &team->port_list, list) {
49962306a36Sopenharmony_ci		struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_ci		s_info = &lb_port_priv->stats_info;
50262306a36Sopenharmony_ci		__lb_stats_info_refresh_prepare(s_info);
50362306a36Sopenharmony_ci		for_each_possible_cpu(i) {
50462306a36Sopenharmony_ci			pcpu_stats = per_cpu_ptr(lb_priv->pcpu_stats, i);
50562306a36Sopenharmony_ci			stats = per_cpu_ptr(lb_port_priv->pcpu_stats, i);
50662306a36Sopenharmony_ci			__lb_one_cpu_stats_add(&s_info->stats, stats,
50762306a36Sopenharmony_ci					       &pcpu_stats->syncp);
50862306a36Sopenharmony_ci		}
50962306a36Sopenharmony_ci		changed |= __lb_stats_info_refresh_check(s_info, team);
51062306a36Sopenharmony_ci	}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	if (changed)
51362306a36Sopenharmony_ci		team_options_change_check(team);
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	schedule_delayed_work(&lb_priv_ex->stats.refresh_dw,
51662306a36Sopenharmony_ci			      (lb_priv_ex->stats.refresh_interval * HZ) / 10);
51762306a36Sopenharmony_ci
51862306a36Sopenharmony_ci	mutex_unlock(&team->lock);
51962306a36Sopenharmony_ci}
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_cistatic void lb_stats_refresh_interval_get(struct team *team,
52262306a36Sopenharmony_ci					  struct team_gsetter_ctx *ctx)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	ctx->data.u32_val = lb_priv->ex->stats.refresh_interval;
52762306a36Sopenharmony_ci}
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_cistatic int lb_stats_refresh_interval_set(struct team *team,
53062306a36Sopenharmony_ci					 struct team_gsetter_ctx *ctx)
53162306a36Sopenharmony_ci{
53262306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
53362306a36Sopenharmony_ci	unsigned int interval;
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ci	interval = ctx->data.u32_val;
53662306a36Sopenharmony_ci	if (lb_priv->ex->stats.refresh_interval == interval)
53762306a36Sopenharmony_ci		return 0;
53862306a36Sopenharmony_ci	lb_priv->ex->stats.refresh_interval = interval;
53962306a36Sopenharmony_ci	if (interval)
54062306a36Sopenharmony_ci		schedule_delayed_work(&lb_priv->ex->stats.refresh_dw, 0);
54162306a36Sopenharmony_ci	else
54262306a36Sopenharmony_ci		cancel_delayed_work(&lb_priv->ex->stats.refresh_dw);
54362306a36Sopenharmony_ci	return 0;
54462306a36Sopenharmony_ci}
54562306a36Sopenharmony_ci
54662306a36Sopenharmony_cistatic const struct team_option lb_options[] = {
54762306a36Sopenharmony_ci	{
54862306a36Sopenharmony_ci		.name = "bpf_hash_func",
54962306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_BINARY,
55062306a36Sopenharmony_ci		.getter = lb_bpf_func_get,
55162306a36Sopenharmony_ci		.setter = lb_bpf_func_set,
55262306a36Sopenharmony_ci	},
55362306a36Sopenharmony_ci	{
55462306a36Sopenharmony_ci		.name = "lb_tx_method",
55562306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_STRING,
55662306a36Sopenharmony_ci		.getter = lb_tx_method_get,
55762306a36Sopenharmony_ci		.setter = lb_tx_method_set,
55862306a36Sopenharmony_ci	},
55962306a36Sopenharmony_ci	{
56062306a36Sopenharmony_ci		.name = "lb_tx_hash_to_port_mapping",
56162306a36Sopenharmony_ci		.array_size = LB_TX_HASHTABLE_SIZE,
56262306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_U32,
56362306a36Sopenharmony_ci		.init = lb_tx_hash_to_port_mapping_init,
56462306a36Sopenharmony_ci		.getter = lb_tx_hash_to_port_mapping_get,
56562306a36Sopenharmony_ci		.setter = lb_tx_hash_to_port_mapping_set,
56662306a36Sopenharmony_ci	},
56762306a36Sopenharmony_ci	{
56862306a36Sopenharmony_ci		.name = "lb_hash_stats",
56962306a36Sopenharmony_ci		.array_size = LB_TX_HASHTABLE_SIZE,
57062306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_BINARY,
57162306a36Sopenharmony_ci		.init = lb_hash_stats_init,
57262306a36Sopenharmony_ci		.getter = lb_hash_stats_get,
57362306a36Sopenharmony_ci	},
57462306a36Sopenharmony_ci	{
57562306a36Sopenharmony_ci		.name = "lb_port_stats",
57662306a36Sopenharmony_ci		.per_port = true,
57762306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_BINARY,
57862306a36Sopenharmony_ci		.init = lb_port_stats_init,
57962306a36Sopenharmony_ci		.getter = lb_port_stats_get,
58062306a36Sopenharmony_ci	},
58162306a36Sopenharmony_ci	{
58262306a36Sopenharmony_ci		.name = "lb_stats_refresh_interval",
58362306a36Sopenharmony_ci		.type = TEAM_OPTION_TYPE_U32,
58462306a36Sopenharmony_ci		.getter = lb_stats_refresh_interval_get,
58562306a36Sopenharmony_ci		.setter = lb_stats_refresh_interval_set,
58662306a36Sopenharmony_ci	},
58762306a36Sopenharmony_ci};
58862306a36Sopenharmony_ci
58962306a36Sopenharmony_cistatic int lb_init(struct team *team)
59062306a36Sopenharmony_ci{
59162306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
59262306a36Sopenharmony_ci	lb_select_tx_port_func_t *func;
59362306a36Sopenharmony_ci	int i, err;
59462306a36Sopenharmony_ci
59562306a36Sopenharmony_ci	/* set default tx port selector */
59662306a36Sopenharmony_ci	func = lb_select_tx_port_get_func("hash");
59762306a36Sopenharmony_ci	BUG_ON(!func);
59862306a36Sopenharmony_ci	rcu_assign_pointer(lb_priv->select_tx_port_func, func);
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	lb_priv->ex = kzalloc(sizeof(*lb_priv->ex), GFP_KERNEL);
60162306a36Sopenharmony_ci	if (!lb_priv->ex)
60262306a36Sopenharmony_ci		return -ENOMEM;
60362306a36Sopenharmony_ci	lb_priv->ex->team = team;
60462306a36Sopenharmony_ci
60562306a36Sopenharmony_ci	lb_priv->pcpu_stats = alloc_percpu(struct lb_pcpu_stats);
60662306a36Sopenharmony_ci	if (!lb_priv->pcpu_stats) {
60762306a36Sopenharmony_ci		err = -ENOMEM;
60862306a36Sopenharmony_ci		goto err_alloc_pcpu_stats;
60962306a36Sopenharmony_ci	}
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	for_each_possible_cpu(i) {
61262306a36Sopenharmony_ci		struct lb_pcpu_stats *team_lb_stats;
61362306a36Sopenharmony_ci		team_lb_stats = per_cpu_ptr(lb_priv->pcpu_stats, i);
61462306a36Sopenharmony_ci		u64_stats_init(&team_lb_stats->syncp);
61562306a36Sopenharmony_ci	}
61662306a36Sopenharmony_ci
61762306a36Sopenharmony_ci
61862306a36Sopenharmony_ci	INIT_DELAYED_WORK(&lb_priv->ex->stats.refresh_dw, lb_stats_refresh);
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	err = team_options_register(team, lb_options, ARRAY_SIZE(lb_options));
62162306a36Sopenharmony_ci	if (err)
62262306a36Sopenharmony_ci		goto err_options_register;
62362306a36Sopenharmony_ci	return 0;
62462306a36Sopenharmony_ci
62562306a36Sopenharmony_cierr_options_register:
62662306a36Sopenharmony_ci	free_percpu(lb_priv->pcpu_stats);
62762306a36Sopenharmony_cierr_alloc_pcpu_stats:
62862306a36Sopenharmony_ci	kfree(lb_priv->ex);
62962306a36Sopenharmony_ci	return err;
63062306a36Sopenharmony_ci}
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_cistatic void lb_exit(struct team *team)
63362306a36Sopenharmony_ci{
63462306a36Sopenharmony_ci	struct lb_priv *lb_priv = get_lb_priv(team);
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	team_options_unregister(team, lb_options,
63762306a36Sopenharmony_ci				ARRAY_SIZE(lb_options));
63862306a36Sopenharmony_ci	lb_bpf_func_free(team);
63962306a36Sopenharmony_ci	cancel_delayed_work_sync(&lb_priv->ex->stats.refresh_dw);
64062306a36Sopenharmony_ci	free_percpu(lb_priv->pcpu_stats);
64162306a36Sopenharmony_ci	kfree(lb_priv->ex);
64262306a36Sopenharmony_ci}
64362306a36Sopenharmony_ci
64462306a36Sopenharmony_cistatic int lb_port_enter(struct team *team, struct team_port *port)
64562306a36Sopenharmony_ci{
64662306a36Sopenharmony_ci	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	lb_port_priv->pcpu_stats = alloc_percpu(struct lb_stats);
64962306a36Sopenharmony_ci	if (!lb_port_priv->pcpu_stats)
65062306a36Sopenharmony_ci		return -ENOMEM;
65162306a36Sopenharmony_ci	return 0;
65262306a36Sopenharmony_ci}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_cistatic void lb_port_leave(struct team *team, struct team_port *port)
65562306a36Sopenharmony_ci{
65662306a36Sopenharmony_ci	struct lb_port_priv *lb_port_priv = get_lb_port_priv(port);
65762306a36Sopenharmony_ci
65862306a36Sopenharmony_ci	free_percpu(lb_port_priv->pcpu_stats);
65962306a36Sopenharmony_ci}
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_cistatic void lb_port_disabled(struct team *team, struct team_port *port)
66262306a36Sopenharmony_ci{
66362306a36Sopenharmony_ci	lb_tx_hash_to_port_mapping_null_port(team, port);
66462306a36Sopenharmony_ci}
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_cistatic const struct team_mode_ops lb_mode_ops = {
66762306a36Sopenharmony_ci	.init			= lb_init,
66862306a36Sopenharmony_ci	.exit			= lb_exit,
66962306a36Sopenharmony_ci	.port_enter		= lb_port_enter,
67062306a36Sopenharmony_ci	.port_leave		= lb_port_leave,
67162306a36Sopenharmony_ci	.port_disabled		= lb_port_disabled,
67262306a36Sopenharmony_ci	.receive		= lb_receive,
67362306a36Sopenharmony_ci	.transmit		= lb_transmit,
67462306a36Sopenharmony_ci};
67562306a36Sopenharmony_ci
67662306a36Sopenharmony_cistatic const struct team_mode lb_mode = {
67762306a36Sopenharmony_ci	.kind		= "loadbalance",
67862306a36Sopenharmony_ci	.owner		= THIS_MODULE,
67962306a36Sopenharmony_ci	.priv_size	= sizeof(struct lb_priv),
68062306a36Sopenharmony_ci	.port_priv_size	= sizeof(struct lb_port_priv),
68162306a36Sopenharmony_ci	.ops		= &lb_mode_ops,
68262306a36Sopenharmony_ci	.lag_tx_type	= NETDEV_LAG_TX_TYPE_HASH,
68362306a36Sopenharmony_ci};
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_cistatic int __init lb_init_module(void)
68662306a36Sopenharmony_ci{
68762306a36Sopenharmony_ci	return team_mode_register(&lb_mode);
68862306a36Sopenharmony_ci}
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_cistatic void __exit lb_cleanup_module(void)
69162306a36Sopenharmony_ci{
69262306a36Sopenharmony_ci	team_mode_unregister(&lb_mode);
69362306a36Sopenharmony_ci}
69462306a36Sopenharmony_ci
69562306a36Sopenharmony_cimodule_init(lb_init_module);
69662306a36Sopenharmony_cimodule_exit(lb_cleanup_module);
69762306a36Sopenharmony_ci
69862306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
69962306a36Sopenharmony_ciMODULE_AUTHOR("Jiri Pirko <jpirko@redhat.com>");
70062306a36Sopenharmony_ciMODULE_DESCRIPTION("Load-balancing mode for team");
70162306a36Sopenharmony_ciMODULE_ALIAS_TEAM_MODE("loadbalance");
702