// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB /* Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. */ #include #include "lag.h" enum { MLX5_LAG_FT_LEVEL_TTC, MLX5_LAG_FT_LEVEL_INNER_TTC, MLX5_LAG_FT_LEVEL_DEFINER, }; static struct mlx5_flow_group * mlx5_create_hash_flow_group(struct mlx5_flow_table *ft, struct mlx5_flow_definer *definer) { int inlen = MLX5_ST_SZ_BYTES(create_flow_group_in); struct mlx5_flow_group *fg; u32 *in; in = kvzalloc(inlen, GFP_KERNEL); if (!in) return ERR_PTR(-ENOMEM); MLX5_SET(create_flow_group_in, in, match_definer_id, mlx5_get_match_definer_id(definer)); MLX5_SET(create_flow_group_in, in, start_flow_index, 0); MLX5_SET(create_flow_group_in, in, end_flow_index, MLX5_MAX_PORTS - 1); MLX5_SET(create_flow_group_in, in, group_type, MLX5_CREATE_FLOW_GROUP_IN_GROUP_TYPE_HASH_SPLIT); fg = mlx5_create_flow_group(ft, in); kvfree(in); return fg; } static int mlx5_lag_create_port_sel_table(struct mlx5_lag *ldev, struct mlx5_lag_definer *lag_definer, u8 port1, u8 port2) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_flow_table_attr ft_attr = {}; struct mlx5_flow_destination dest = {}; MLX5_DECLARE_FLOW_ACT(flow_act); struct mlx5_flow_namespace *ns; int err, i; ft_attr.max_fte = MLX5_MAX_PORTS; ft_attr.level = MLX5_LAG_FT_LEVEL_DEFINER; ns = mlx5_get_flow_namespace(dev, MLX5_FLOW_NAMESPACE_PORT_SEL); if (!ns) { mlx5_core_warn(dev, "Failed to get port selection namespace\n"); return -EOPNOTSUPP; } lag_definer->ft = mlx5_create_flow_table(ns, &ft_attr); if (IS_ERR(lag_definer->ft)) { mlx5_core_warn(dev, "Failed to create port selection table\n"); return PTR_ERR(lag_definer->ft); } lag_definer->fg = mlx5_create_hash_flow_group(lag_definer->ft, lag_definer->definer); if (IS_ERR(lag_definer->fg)) { err = PTR_ERR(lag_definer->fg); goto destroy_ft; } dest.type = MLX5_FLOW_DESTINATION_TYPE_UPLINK; dest.vport.flags |= MLX5_FLOW_DEST_VPORT_VHCA_ID; flow_act.flags |= FLOW_ACT_NO_APPEND; for (i = 0; i < MLX5_MAX_PORTS; i++) { u8 affinity = i == 0 ? port1 : port2; dest.vport.vhca_id = MLX5_CAP_GEN(ldev->pf[affinity - 1].dev, vhca_id); lag_definer->rules[i] = mlx5_add_flow_rules(lag_definer->ft, NULL, &flow_act, &dest, 1); if (IS_ERR(lag_definer->rules[i])) { err = PTR_ERR(lag_definer->rules[i]); while (i--) mlx5_del_flow_rules(lag_definer->rules[i]); goto destroy_fg; } } return 0; destroy_fg: mlx5_destroy_flow_group(lag_definer->fg); destroy_ft: mlx5_destroy_flow_table(lag_definer->ft); return err; } static int mlx5_lag_set_definer_inner(u32 *match_definer_mask, enum mlx5_traffic_types tt) { int format_id; u8 *ipv6; switch (tt) { case MLX5_TT_IPV4_UDP: case MLX5_TT_IPV4_TCP: format_id = 23; MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_l4_sport); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_l4_dport); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_ip_src_addr); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_ip_dest_addr); break; case MLX5_TT_IPV4: format_id = 23; MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_l3_type); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_dmac_15_0); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_smac_15_0); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_ip_src_addr); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_ip_dest_addr); break; case MLX5_TT_IPV6_TCP: case MLX5_TT_IPV6_UDP: format_id = 31; MLX5_SET_TO_ONES(match_definer_format_31, match_definer_mask, inner_l4_sport); MLX5_SET_TO_ONES(match_definer_format_31, match_definer_mask, inner_l4_dport); ipv6 = MLX5_ADDR_OF(match_definer_format_31, match_definer_mask, inner_ip_dest_addr); memset(ipv6, 0xff, 16); ipv6 = MLX5_ADDR_OF(match_definer_format_31, match_definer_mask, inner_ip_src_addr); memset(ipv6, 0xff, 16); break; case MLX5_TT_IPV6: format_id = 32; ipv6 = MLX5_ADDR_OF(match_definer_format_32, match_definer_mask, inner_ip_dest_addr); memset(ipv6, 0xff, 16); ipv6 = MLX5_ADDR_OF(match_definer_format_32, match_definer_mask, inner_ip_src_addr); memset(ipv6, 0xff, 16); MLX5_SET_TO_ONES(match_definer_format_32, match_definer_mask, inner_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_32, match_definer_mask, inner_dmac_15_0); MLX5_SET_TO_ONES(match_definer_format_32, match_definer_mask, inner_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_32, match_definer_mask, inner_smac_15_0); break; default: format_id = 23; MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_l3_type); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_dmac_15_0); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_23, match_definer_mask, inner_smac_15_0); break; } return format_id; } static int mlx5_lag_set_definer(u32 *match_definer_mask, enum mlx5_traffic_types tt, bool tunnel, enum netdev_lag_hash hash) { int format_id; u8 *ipv6; if (tunnel) return mlx5_lag_set_definer_inner(match_definer_mask, tt); switch (tt) { case MLX5_TT_IPV4_UDP: case MLX5_TT_IPV4_TCP: format_id = 22; MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_l4_sport); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_l4_dport); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_ip_src_addr); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_ip_dest_addr); break; case MLX5_TT_IPV4: format_id = 22; MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_l3_type); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_dmac_15_0); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_smac_15_0); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_ip_src_addr); MLX5_SET_TO_ONES(match_definer_format_22, match_definer_mask, outer_ip_dest_addr); break; case MLX5_TT_IPV6_TCP: case MLX5_TT_IPV6_UDP: format_id = 29; MLX5_SET_TO_ONES(match_definer_format_29, match_definer_mask, outer_l4_sport); MLX5_SET_TO_ONES(match_definer_format_29, match_definer_mask, outer_l4_dport); ipv6 = MLX5_ADDR_OF(match_definer_format_29, match_definer_mask, outer_ip_dest_addr); memset(ipv6, 0xff, 16); ipv6 = MLX5_ADDR_OF(match_definer_format_29, match_definer_mask, outer_ip_src_addr); memset(ipv6, 0xff, 16); break; case MLX5_TT_IPV6: format_id = 30; ipv6 = MLX5_ADDR_OF(match_definer_format_30, match_definer_mask, outer_ip_dest_addr); memset(ipv6, 0xff, 16); ipv6 = MLX5_ADDR_OF(match_definer_format_30, match_definer_mask, outer_ip_src_addr); memset(ipv6, 0xff, 16); MLX5_SET_TO_ONES(match_definer_format_30, match_definer_mask, outer_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_30, match_definer_mask, outer_dmac_15_0); MLX5_SET_TO_ONES(match_definer_format_30, match_definer_mask, outer_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_30, match_definer_mask, outer_smac_15_0); break; default: format_id = 0; MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_smac_47_16); MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_smac_15_0); if (hash == NETDEV_LAG_HASH_VLAN_SRCMAC) { MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_first_vlan_vid); break; } MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_ethertype); MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_dmac_47_16); MLX5_SET_TO_ONES(match_definer_format_0, match_definer_mask, outer_dmac_15_0); break; } return format_id; } static struct mlx5_lag_definer * mlx5_lag_create_definer(struct mlx5_lag *ldev, enum netdev_lag_hash hash, enum mlx5_traffic_types tt, bool tunnel, u8 port1, u8 port2) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_lag_definer *lag_definer; u32 *match_definer_mask; int format_id, err; lag_definer = kzalloc(sizeof(*lag_definer), GFP_KERNEL); if (!lag_definer) return ERR_PTR(-ENOMEM); match_definer_mask = kvzalloc(MLX5_FLD_SZ_BYTES(match_definer, match_mask), GFP_KERNEL); if (!match_definer_mask) { err = -ENOMEM; goto free_lag_definer; } format_id = mlx5_lag_set_definer(match_definer_mask, tt, tunnel, hash); lag_definer->definer = mlx5_create_match_definer(dev, MLX5_FLOW_NAMESPACE_PORT_SEL, format_id, match_definer_mask); if (IS_ERR(lag_definer->definer)) { err = PTR_ERR(lag_definer->definer); goto free_mask; } err = mlx5_lag_create_port_sel_table(ldev, lag_definer, port1, port2); if (err) goto destroy_match_definer; kvfree(match_definer_mask); return lag_definer; destroy_match_definer: mlx5_destroy_match_definer(dev, lag_definer->definer); free_mask: kvfree(match_definer_mask); free_lag_definer: kfree(lag_definer); return ERR_PTR(err); } static void mlx5_lag_destroy_definer(struct mlx5_lag *ldev, struct mlx5_lag_definer *lag_definer) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; int i; for (i = 0; i < MLX5_MAX_PORTS; i++) mlx5_del_flow_rules(lag_definer->rules[i]); mlx5_destroy_flow_group(lag_definer->fg); mlx5_destroy_flow_table(lag_definer->ft); mlx5_destroy_match_definer(dev, lag_definer->definer); kfree(lag_definer); } static void mlx5_lag_destroy_definers(struct mlx5_lag *ldev) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; int tt; for_each_set_bit(tt, port_sel->tt_map, MLX5_NUM_TT) { if (port_sel->outer.definers[tt]) mlx5_lag_destroy_definer(ldev, port_sel->outer.definers[tt]); if (port_sel->inner.definers[tt]) mlx5_lag_destroy_definer(ldev, port_sel->inner.definers[tt]); } } static int mlx5_lag_create_definers(struct mlx5_lag *ldev, enum netdev_lag_hash hash_type, u8 port1, u8 port2) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct mlx5_lag_definer *lag_definer; int tt, err; for_each_set_bit(tt, port_sel->tt_map, MLX5_NUM_TT) { lag_definer = mlx5_lag_create_definer(ldev, hash_type, tt, false, port1, port2); if (IS_ERR(lag_definer)) { err = PTR_ERR(lag_definer); goto destroy_definers; } port_sel->outer.definers[tt] = lag_definer; if (!port_sel->tunnel) continue; lag_definer = mlx5_lag_create_definer(ldev, hash_type, tt, true, port1, port2); if (IS_ERR(lag_definer)) { err = PTR_ERR(lag_definer); goto destroy_definers; } port_sel->inner.definers[tt] = lag_definer; } return 0; destroy_definers: mlx5_lag_destroy_definers(ldev); return err; } static void set_tt_map(struct mlx5_lag_port_sel *port_sel, enum netdev_lag_hash hash) { port_sel->tunnel = false; switch (hash) { case NETDEV_LAG_HASH_E34: port_sel->tunnel = true; fallthrough; case NETDEV_LAG_HASH_L34: set_bit(MLX5_TT_IPV4_TCP, port_sel->tt_map); set_bit(MLX5_TT_IPV4_UDP, port_sel->tt_map); set_bit(MLX5_TT_IPV6_TCP, port_sel->tt_map); set_bit(MLX5_TT_IPV6_UDP, port_sel->tt_map); set_bit(MLX5_TT_IPV4, port_sel->tt_map); set_bit(MLX5_TT_IPV6, port_sel->tt_map); set_bit(MLX5_TT_ANY, port_sel->tt_map); break; case NETDEV_LAG_HASH_E23: port_sel->tunnel = true; fallthrough; case NETDEV_LAG_HASH_L23: set_bit(MLX5_TT_IPV4, port_sel->tt_map); set_bit(MLX5_TT_IPV6, port_sel->tt_map); set_bit(MLX5_TT_ANY, port_sel->tt_map); break; default: set_bit(MLX5_TT_ANY, port_sel->tt_map); break; } } #define SET_IGNORE_DESTS_BITS(tt_map, dests) \ do { \ int idx; \ \ for_each_clear_bit(idx, tt_map, MLX5_NUM_TT) \ set_bit(idx, dests); \ } while (0) static void mlx5_lag_set_inner_ttc_params(struct mlx5_lag *ldev, struct ttc_params *ttc_params) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct mlx5_flow_table_attr *ft_attr; int tt; ttc_params->ns = mlx5_get_flow_namespace(dev, MLX5_FLOW_NAMESPACE_PORT_SEL); ft_attr = &ttc_params->ft_attr; ft_attr->level = MLX5_LAG_FT_LEVEL_INNER_TTC; for_each_set_bit(tt, port_sel->tt_map, MLX5_NUM_TT) { ttc_params->dests[tt].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; ttc_params->dests[tt].ft = port_sel->inner.definers[tt]->ft; } SET_IGNORE_DESTS_BITS(port_sel->tt_map, ttc_params->ignore_dests); } static void mlx5_lag_set_outer_ttc_params(struct mlx5_lag *ldev, struct ttc_params *ttc_params) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct mlx5_flow_table_attr *ft_attr; int tt; ttc_params->ns = mlx5_get_flow_namespace(dev, MLX5_FLOW_NAMESPACE_PORT_SEL); ft_attr = &ttc_params->ft_attr; ft_attr->level = MLX5_LAG_FT_LEVEL_TTC; for_each_set_bit(tt, port_sel->tt_map, MLX5_NUM_TT) { ttc_params->dests[tt].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; ttc_params->dests[tt].ft = port_sel->outer.definers[tt]->ft; } SET_IGNORE_DESTS_BITS(port_sel->tt_map, ttc_params->ignore_dests); ttc_params->inner_ttc = port_sel->tunnel; if (!port_sel->tunnel) return; for (tt = 0; tt < MLX5_NUM_TUNNEL_TT; tt++) { ttc_params->tunnel_dests[tt].type = MLX5_FLOW_DESTINATION_TYPE_FLOW_TABLE; ttc_params->tunnel_dests[tt].ft = mlx5_get_ttc_flow_table(port_sel->inner.ttc); } } static int mlx5_lag_create_ttc_table(struct mlx5_lag *ldev) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct ttc_params ttc_params = {}; mlx5_lag_set_outer_ttc_params(ldev, &ttc_params); port_sel->outer.ttc = mlx5_create_ttc_table(dev, &ttc_params); if (IS_ERR(port_sel->outer.ttc)) return PTR_ERR(port_sel->outer.ttc); return 0; } static int mlx5_lag_create_inner_ttc_table(struct mlx5_lag *ldev) { struct mlx5_core_dev *dev = ldev->pf[MLX5_LAG_P1].dev; struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct ttc_params ttc_params = {}; mlx5_lag_set_inner_ttc_params(ldev, &ttc_params); port_sel->inner.ttc = mlx5_create_ttc_table(dev, &ttc_params); if (IS_ERR(port_sel->inner.ttc)) return PTR_ERR(port_sel->inner.ttc); return 0; } int mlx5_lag_port_sel_create(struct mlx5_lag *ldev, enum netdev_lag_hash hash_type, u8 port1, u8 port2) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; int err; set_tt_map(port_sel, hash_type); err = mlx5_lag_create_definers(ldev, hash_type, port1, port2); if (err) return err; if (port_sel->tunnel) { err = mlx5_lag_create_inner_ttc_table(ldev); if (err) goto destroy_definers; } err = mlx5_lag_create_ttc_table(ldev); if (err) goto destroy_inner; return 0; destroy_inner: if (port_sel->tunnel) mlx5_destroy_ttc_table(port_sel->inner.ttc); destroy_definers: mlx5_lag_destroy_definers(ldev); return err; } static int mlx5_lag_modify_definers_destinations(struct mlx5_lag *ldev, struct mlx5_lag_definer **definers, u8 port1, u8 port2) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; struct mlx5_flow_destination dest = {}; int err; int tt; dest.type = MLX5_FLOW_DESTINATION_TYPE_UPLINK; dest.vport.flags |= MLX5_FLOW_DEST_VPORT_VHCA_ID; for_each_set_bit(tt, port_sel->tt_map, MLX5_NUM_TT) { struct mlx5_flow_handle **rules = definers[tt]->rules; if (ldev->v2p_map[MLX5_LAG_P1] != port1) { dest.vport.vhca_id = MLX5_CAP_GEN(ldev->pf[port1 - 1].dev, vhca_id); err = mlx5_modify_rule_destination(rules[MLX5_LAG_P1], &dest, NULL); if (err) return err; } if (ldev->v2p_map[MLX5_LAG_P2] != port2) { dest.vport.vhca_id = MLX5_CAP_GEN(ldev->pf[port2 - 1].dev, vhca_id); err = mlx5_modify_rule_destination(rules[MLX5_LAG_P2], &dest, NULL); if (err) return err; } } return 0; } int mlx5_lag_port_sel_modify(struct mlx5_lag *ldev, u8 port1, u8 port2) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; int err; err = mlx5_lag_modify_definers_destinations(ldev, port_sel->outer.definers, port1, port2); if (err) return err; if (!port_sel->tunnel) return 0; return mlx5_lag_modify_definers_destinations(ldev, port_sel->inner.definers, port1, port2); } void mlx5_lag_port_sel_destroy(struct mlx5_lag *ldev) { struct mlx5_lag_port_sel *port_sel = &ldev->port_sel; mlx5_destroy_ttc_table(port_sel->outer.ttc); if (port_sel->tunnel) mlx5_destroy_ttc_table(port_sel->inner.ttc); mlx5_lag_destroy_definers(ldev); }