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