// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 /* Copyright (c) 2018 Mellanox Technologies. All rights reserved */ #include #include #include #include #include #include #include #include #include #include "reg.h" #include "spectrum.h" #include "spectrum_nve.h" const struct mlxsw_sp_nve_ops *mlxsw_sp1_nve_ops_arr[] = { [MLXSW_SP_NVE_TYPE_VXLAN] = &mlxsw_sp1_nve_vxlan_ops, }; const struct mlxsw_sp_nve_ops *mlxsw_sp2_nve_ops_arr[] = { [MLXSW_SP_NVE_TYPE_VXLAN] = &mlxsw_sp2_nve_vxlan_ops, }; struct mlxsw_sp_nve_mc_entry; struct mlxsw_sp_nve_mc_record; struct mlxsw_sp_nve_mc_list; struct mlxsw_sp_nve_mc_record_ops { enum mlxsw_reg_tnumt_record_type type; int (*entry_add)(struct mlxsw_sp_nve_mc_record *mc_record, struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr); void (*entry_del)(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry); void (*entry_set)(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, char *tnumt_pl, unsigned int entry_index); bool (*entry_compare)(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr); }; struct mlxsw_sp_nve_mc_list_key { u16 fid_index; }; struct mlxsw_sp_nve_mc_ipv6_entry { struct in6_addr addr6; u32 addr6_kvdl_index; }; struct mlxsw_sp_nve_mc_entry { union { __be32 addr4; struct mlxsw_sp_nve_mc_ipv6_entry ipv6_entry; }; u8 valid:1; }; struct mlxsw_sp_nve_mc_record { struct list_head list; enum mlxsw_sp_l3proto proto; unsigned int num_entries; struct mlxsw_sp *mlxsw_sp; struct mlxsw_sp_nve_mc_list *mc_list; const struct mlxsw_sp_nve_mc_record_ops *ops; u32 kvdl_index; struct mlxsw_sp_nve_mc_entry entries[]; }; struct mlxsw_sp_nve_mc_list { struct list_head records_list; struct rhash_head ht_node; struct mlxsw_sp_nve_mc_list_key key; }; static const struct rhashtable_params mlxsw_sp_nve_mc_list_ht_params = { .key_len = sizeof(struct mlxsw_sp_nve_mc_list_key), .key_offset = offsetof(struct mlxsw_sp_nve_mc_list, key), .head_offset = offsetof(struct mlxsw_sp_nve_mc_list, ht_node), }; static int mlxsw_sp_nve_mc_record_ipv4_entry_add(struct mlxsw_sp_nve_mc_record *mc_record, struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr) { mc_entry->addr4 = addr->addr4; return 0; } static void mlxsw_sp_nve_mc_record_ipv4_entry_del(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry) { } static void mlxsw_sp_nve_mc_record_ipv4_entry_set(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, char *tnumt_pl, unsigned int entry_index) { u32 udip = be32_to_cpu(mc_entry->addr4); mlxsw_reg_tnumt_udip_set(tnumt_pl, entry_index, udip); } static bool mlxsw_sp_nve_mc_record_ipv4_entry_compare(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr) { return mc_entry->addr4 == addr->addr4; } static const struct mlxsw_sp_nve_mc_record_ops mlxsw_sp_nve_mc_record_ipv4_ops = { .type = MLXSW_REG_TNUMT_RECORD_TYPE_IPV4, .entry_add = &mlxsw_sp_nve_mc_record_ipv4_entry_add, .entry_del = &mlxsw_sp_nve_mc_record_ipv4_entry_del, .entry_set = &mlxsw_sp_nve_mc_record_ipv4_entry_set, .entry_compare = &mlxsw_sp_nve_mc_record_ipv4_entry_compare, }; static int mlxsw_sp_nve_mc_record_ipv6_entry_add(struct mlxsw_sp_nve_mc_record *mc_record, struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr) { WARN_ON(1); return -EINVAL; } static void mlxsw_sp_nve_mc_record_ipv6_entry_del(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry) { } static void mlxsw_sp_nve_mc_record_ipv6_entry_set(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, char *tnumt_pl, unsigned int entry_index) { u32 udip_ptr = mc_entry->ipv6_entry.addr6_kvdl_index; mlxsw_reg_tnumt_udip_ptr_set(tnumt_pl, entry_index, udip_ptr); } static bool mlxsw_sp_nve_mc_record_ipv6_entry_compare(const struct mlxsw_sp_nve_mc_record *mc_record, const struct mlxsw_sp_nve_mc_entry *mc_entry, const union mlxsw_sp_l3addr *addr) { return ipv6_addr_equal(&mc_entry->ipv6_entry.addr6, &addr->addr6); } static const struct mlxsw_sp_nve_mc_record_ops mlxsw_sp_nve_mc_record_ipv6_ops = { .type = MLXSW_REG_TNUMT_RECORD_TYPE_IPV6, .entry_add = &mlxsw_sp_nve_mc_record_ipv6_entry_add, .entry_del = &mlxsw_sp_nve_mc_record_ipv6_entry_del, .entry_set = &mlxsw_sp_nve_mc_record_ipv6_entry_set, .entry_compare = &mlxsw_sp_nve_mc_record_ipv6_entry_compare, }; static const struct mlxsw_sp_nve_mc_record_ops * mlxsw_sp_nve_mc_record_ops_arr[] = { [MLXSW_SP_L3_PROTO_IPV4] = &mlxsw_sp_nve_mc_record_ipv4_ops, [MLXSW_SP_L3_PROTO_IPV6] = &mlxsw_sp_nve_mc_record_ipv6_ops, }; int mlxsw_sp_nve_learned_ip_resolve(struct mlxsw_sp *mlxsw_sp, u32 uip, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr) { switch (proto) { case MLXSW_SP_L3_PROTO_IPV4: addr->addr4 = cpu_to_be32(uip); return 0; default: WARN_ON(1); return -EINVAL; } } static struct mlxsw_sp_nve_mc_list * mlxsw_sp_nve_mc_list_find(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_nve_mc_list_key *key) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; return rhashtable_lookup_fast(&nve->mc_list_ht, key, mlxsw_sp_nve_mc_list_ht_params); } static struct mlxsw_sp_nve_mc_list * mlxsw_sp_nve_mc_list_create(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_nve_mc_list_key *key) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; struct mlxsw_sp_nve_mc_list *mc_list; int err; mc_list = kmalloc(sizeof(*mc_list), GFP_KERNEL); if (!mc_list) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&mc_list->records_list); mc_list->key = *key; err = rhashtable_insert_fast(&nve->mc_list_ht, &mc_list->ht_node, mlxsw_sp_nve_mc_list_ht_params); if (err) goto err_rhashtable_insert; return mc_list; err_rhashtable_insert: kfree(mc_list); return ERR_PTR(err); } static void mlxsw_sp_nve_mc_list_destroy(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; rhashtable_remove_fast(&nve->mc_list_ht, &mc_list->ht_node, mlxsw_sp_nve_mc_list_ht_params); WARN_ON(!list_empty(&mc_list->records_list)); kfree(mc_list); } static struct mlxsw_sp_nve_mc_list * mlxsw_sp_nve_mc_list_get(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_nve_mc_list_key *key) { struct mlxsw_sp_nve_mc_list *mc_list; mc_list = mlxsw_sp_nve_mc_list_find(mlxsw_sp, key); if (mc_list) return mc_list; return mlxsw_sp_nve_mc_list_create(mlxsw_sp, key); } static void mlxsw_sp_nve_mc_list_put(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list) { if (!list_empty(&mc_list->records_list)) return; mlxsw_sp_nve_mc_list_destroy(mlxsw_sp, mc_list); } static struct mlxsw_sp_nve_mc_record * mlxsw_sp_nve_mc_record_create(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list, enum mlxsw_sp_l3proto proto) { unsigned int num_max_entries = mlxsw_sp->nve->num_max_mc_entries[proto]; struct mlxsw_sp_nve_mc_record *mc_record; int err; mc_record = kzalloc(struct_size(mc_record, entries, num_max_entries), GFP_KERNEL); if (!mc_record) return ERR_PTR(-ENOMEM); err = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_TNUMT, 1, &mc_record->kvdl_index); if (err) goto err_kvdl_alloc; mc_record->ops = mlxsw_sp_nve_mc_record_ops_arr[proto]; mc_record->mlxsw_sp = mlxsw_sp; mc_record->mc_list = mc_list; mc_record->proto = proto; list_add_tail(&mc_record->list, &mc_list->records_list); return mc_record; err_kvdl_alloc: kfree(mc_record); return ERR_PTR(err); } static void mlxsw_sp_nve_mc_record_destroy(struct mlxsw_sp_nve_mc_record *mc_record) { struct mlxsw_sp *mlxsw_sp = mc_record->mlxsw_sp; list_del(&mc_record->list); mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_TNUMT, 1, mc_record->kvdl_index); WARN_ON(mc_record->num_entries); kfree(mc_record); } static struct mlxsw_sp_nve_mc_record * mlxsw_sp_nve_mc_record_get(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list, enum mlxsw_sp_l3proto proto) { struct mlxsw_sp_nve_mc_record *mc_record; list_for_each_entry_reverse(mc_record, &mc_list->records_list, list) { unsigned int num_entries = mc_record->num_entries; struct mlxsw_sp_nve *nve = mlxsw_sp->nve; if (mc_record->proto == proto && num_entries < nve->num_max_mc_entries[proto]) return mc_record; } return mlxsw_sp_nve_mc_record_create(mlxsw_sp, mc_list, proto); } static void mlxsw_sp_nve_mc_record_put(struct mlxsw_sp_nve_mc_record *mc_record) { if (mc_record->num_entries != 0) return; mlxsw_sp_nve_mc_record_destroy(mc_record); } static struct mlxsw_sp_nve_mc_entry * mlxsw_sp_nve_mc_free_entry_find(struct mlxsw_sp_nve_mc_record *mc_record) { struct mlxsw_sp_nve *nve = mc_record->mlxsw_sp->nve; unsigned int num_max_entries; int i; num_max_entries = nve->num_max_mc_entries[mc_record->proto]; for (i = 0; i < num_max_entries; i++) { if (mc_record->entries[i].valid) continue; return &mc_record->entries[i]; } return NULL; } static int mlxsw_sp_nve_mc_record_refresh(struct mlxsw_sp_nve_mc_record *mc_record) { enum mlxsw_reg_tnumt_record_type type = mc_record->ops->type; struct mlxsw_sp_nve_mc_list *mc_list = mc_record->mc_list; struct mlxsw_sp *mlxsw_sp = mc_record->mlxsw_sp; char tnumt_pl[MLXSW_REG_TNUMT_LEN]; unsigned int num_max_entries; unsigned int num_entries = 0; u32 next_kvdl_index = 0; bool next_valid = false; int i; if (!list_is_last(&mc_record->list, &mc_list->records_list)) { struct mlxsw_sp_nve_mc_record *next_record; next_record = list_next_entry(mc_record, list); next_kvdl_index = next_record->kvdl_index; next_valid = true; } mlxsw_reg_tnumt_pack(tnumt_pl, type, MLXSW_REG_TNUMT_TUNNEL_PORT_NVE, mc_record->kvdl_index, next_valid, next_kvdl_index, mc_record->num_entries); num_max_entries = mlxsw_sp->nve->num_max_mc_entries[mc_record->proto]; for (i = 0; i < num_max_entries; i++) { struct mlxsw_sp_nve_mc_entry *mc_entry; mc_entry = &mc_record->entries[i]; if (!mc_entry->valid) continue; mc_record->ops->entry_set(mc_record, mc_entry, tnumt_pl, num_entries++); } WARN_ON(num_entries != mc_record->num_entries); return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(tnumt), tnumt_pl); } static bool mlxsw_sp_nve_mc_record_is_first(struct mlxsw_sp_nve_mc_record *mc_record) { struct mlxsw_sp_nve_mc_list *mc_list = mc_record->mc_list; struct mlxsw_sp_nve_mc_record *first_record; first_record = list_first_entry(&mc_list->records_list, struct mlxsw_sp_nve_mc_record, list); return mc_record == first_record; } static struct mlxsw_sp_nve_mc_entry * mlxsw_sp_nve_mc_entry_find(struct mlxsw_sp_nve_mc_record *mc_record, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve *nve = mc_record->mlxsw_sp->nve; unsigned int num_max_entries; int i; num_max_entries = nve->num_max_mc_entries[mc_record->proto]; for (i = 0; i < num_max_entries; i++) { struct mlxsw_sp_nve_mc_entry *mc_entry; mc_entry = &mc_record->entries[i]; if (!mc_entry->valid) continue; if (mc_record->ops->entry_compare(mc_record, mc_entry, addr)) return mc_entry; } return NULL; } static int mlxsw_sp_nve_mc_record_ip_add(struct mlxsw_sp_nve_mc_record *mc_record, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve_mc_entry *mc_entry = NULL; int err; mc_entry = mlxsw_sp_nve_mc_free_entry_find(mc_record); if (WARN_ON(!mc_entry)) return -EINVAL; err = mc_record->ops->entry_add(mc_record, mc_entry, addr); if (err) return err; mc_record->num_entries++; mc_entry->valid = true; err = mlxsw_sp_nve_mc_record_refresh(mc_record); if (err) goto err_record_refresh; /* If this is a new record and not the first one, then we need to * update the next pointer of the previous entry */ if (mc_record->num_entries != 1 || mlxsw_sp_nve_mc_record_is_first(mc_record)) return 0; err = mlxsw_sp_nve_mc_record_refresh(list_prev_entry(mc_record, list)); if (err) goto err_prev_record_refresh; return 0; err_prev_record_refresh: err_record_refresh: mc_entry->valid = false; mc_record->num_entries--; mc_record->ops->entry_del(mc_record, mc_entry); return err; } static void mlxsw_sp_nve_mc_record_entry_del(struct mlxsw_sp_nve_mc_record *mc_record, struct mlxsw_sp_nve_mc_entry *mc_entry) { struct mlxsw_sp_nve_mc_list *mc_list = mc_record->mc_list; mc_entry->valid = false; mc_record->num_entries--; /* When the record continues to exist we only need to invalidate * the requested entry */ if (mc_record->num_entries != 0) { mlxsw_sp_nve_mc_record_refresh(mc_record); mc_record->ops->entry_del(mc_record, mc_entry); return; } /* If the record needs to be deleted, but it is not the first, * then we need to make sure that the previous record no longer * points to it. Remove deleted record from the list to reflect * that and then re-add it at the end, so that it could be * properly removed by the record destruction code */ if (!mlxsw_sp_nve_mc_record_is_first(mc_record)) { struct mlxsw_sp_nve_mc_record *prev_record; prev_record = list_prev_entry(mc_record, list); list_del(&mc_record->list); mlxsw_sp_nve_mc_record_refresh(prev_record); list_add_tail(&mc_record->list, &mc_list->records_list); mc_record->ops->entry_del(mc_record, mc_entry); return; } /* If the first record needs to be deleted, but the list is not * singular, then the second record needs to be written in the * first record's address, as this address is stored as a property * of the FID */ if (mlxsw_sp_nve_mc_record_is_first(mc_record) && !list_is_singular(&mc_list->records_list)) { struct mlxsw_sp_nve_mc_record *next_record; next_record = list_next_entry(mc_record, list); swap(mc_record->kvdl_index, next_record->kvdl_index); mlxsw_sp_nve_mc_record_refresh(next_record); mc_record->ops->entry_del(mc_record, mc_entry); return; } /* This is the last case where the last remaining record needs to * be deleted. Simply delete the entry */ mc_record->ops->entry_del(mc_record, mc_entry); } static struct mlxsw_sp_nve_mc_record * mlxsw_sp_nve_mc_record_find(struct mlxsw_sp_nve_mc_list *mc_list, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr, struct mlxsw_sp_nve_mc_entry **mc_entry) { struct mlxsw_sp_nve_mc_record *mc_record; list_for_each_entry(mc_record, &mc_list->records_list, list) { if (mc_record->proto != proto) continue; *mc_entry = mlxsw_sp_nve_mc_entry_find(mc_record, addr); if (*mc_entry) return mc_record; } return NULL; } static int mlxsw_sp_nve_mc_list_ip_add(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve_mc_record *mc_record; int err; mc_record = mlxsw_sp_nve_mc_record_get(mlxsw_sp, mc_list, proto); if (IS_ERR(mc_record)) return PTR_ERR(mc_record); err = mlxsw_sp_nve_mc_record_ip_add(mc_record, addr); if (err) goto err_ip_add; return 0; err_ip_add: mlxsw_sp_nve_mc_record_put(mc_record); return err; } static void mlxsw_sp_nve_mc_list_ip_del(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_mc_list *mc_list, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve_mc_record *mc_record; struct mlxsw_sp_nve_mc_entry *mc_entry; mc_record = mlxsw_sp_nve_mc_record_find(mc_list, proto, addr, &mc_entry); if (!mc_record) return; mlxsw_sp_nve_mc_record_entry_del(mc_record, mc_entry); mlxsw_sp_nve_mc_record_put(mc_record); } static int mlxsw_sp_nve_fid_flood_index_set(struct mlxsw_sp_fid *fid, struct mlxsw_sp_nve_mc_list *mc_list) { struct mlxsw_sp_nve_mc_record *mc_record; /* The address of the first record in the list is a property of * the FID and we never change it. It only needs to be set when * a new list is created */ if (mlxsw_sp_fid_nve_flood_index_is_set(fid)) return 0; mc_record = list_first_entry(&mc_list->records_list, struct mlxsw_sp_nve_mc_record, list); return mlxsw_sp_fid_nve_flood_index_set(fid, mc_record->kvdl_index); } static void mlxsw_sp_nve_fid_flood_index_clear(struct mlxsw_sp_fid *fid, struct mlxsw_sp_nve_mc_list *mc_list) { struct mlxsw_sp_nve_mc_record *mc_record; /* The address of the first record needs to be invalidated only when * the last record is about to be removed */ if (!list_is_singular(&mc_list->records_list)) return; mc_record = list_first_entry(&mc_list->records_list, struct mlxsw_sp_nve_mc_record, list); if (mc_record->num_entries != 1) return; return mlxsw_sp_fid_nve_flood_index_clear(fid); } int mlxsw_sp_nve_flood_ip_add(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *fid, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve_mc_list_key key = { 0 }; struct mlxsw_sp_nve_mc_list *mc_list; int err; key.fid_index = mlxsw_sp_fid_index(fid); mc_list = mlxsw_sp_nve_mc_list_get(mlxsw_sp, &key); if (IS_ERR(mc_list)) return PTR_ERR(mc_list); err = mlxsw_sp_nve_mc_list_ip_add(mlxsw_sp, mc_list, proto, addr); if (err) goto err_add_ip; err = mlxsw_sp_nve_fid_flood_index_set(fid, mc_list); if (err) goto err_fid_flood_index_set; return 0; err_fid_flood_index_set: mlxsw_sp_nve_mc_list_ip_del(mlxsw_sp, mc_list, proto, addr); err_add_ip: mlxsw_sp_nve_mc_list_put(mlxsw_sp, mc_list); return err; } void mlxsw_sp_nve_flood_ip_del(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *fid, enum mlxsw_sp_l3proto proto, union mlxsw_sp_l3addr *addr) { struct mlxsw_sp_nve_mc_list_key key = { 0 }; struct mlxsw_sp_nve_mc_list *mc_list; key.fid_index = mlxsw_sp_fid_index(fid); mc_list = mlxsw_sp_nve_mc_list_find(mlxsw_sp, &key); if (!mc_list) return; mlxsw_sp_nve_fid_flood_index_clear(fid, mc_list); mlxsw_sp_nve_mc_list_ip_del(mlxsw_sp, mc_list, proto, addr); mlxsw_sp_nve_mc_list_put(mlxsw_sp, mc_list); } static void mlxsw_sp_nve_mc_record_delete(struct mlxsw_sp_nve_mc_record *mc_record) { struct mlxsw_sp_nve *nve = mc_record->mlxsw_sp->nve; unsigned int num_max_entries; int i; num_max_entries = nve->num_max_mc_entries[mc_record->proto]; for (i = 0; i < num_max_entries; i++) { struct mlxsw_sp_nve_mc_entry *mc_entry = &mc_record->entries[i]; if (!mc_entry->valid) continue; mlxsw_sp_nve_mc_record_entry_del(mc_record, mc_entry); } WARN_ON(mc_record->num_entries); mlxsw_sp_nve_mc_record_put(mc_record); } static void mlxsw_sp_nve_flood_ip_flush(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *fid) { struct mlxsw_sp_nve_mc_record *mc_record, *tmp; struct mlxsw_sp_nve_mc_list_key key = { 0 }; struct mlxsw_sp_nve_mc_list *mc_list; if (!mlxsw_sp_fid_nve_flood_index_is_set(fid)) return; mlxsw_sp_fid_nve_flood_index_clear(fid); key.fid_index = mlxsw_sp_fid_index(fid); mc_list = mlxsw_sp_nve_mc_list_find(mlxsw_sp, &key); if (WARN_ON(!mc_list)) return; list_for_each_entry_safe(mc_record, tmp, &mc_list->records_list, list) mlxsw_sp_nve_mc_record_delete(mc_record); WARN_ON(!list_empty(&mc_list->records_list)); mlxsw_sp_nve_mc_list_put(mlxsw_sp, mc_list); } static int mlxsw_sp_nve_tunnel_init(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_nve_config *config) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; const struct mlxsw_sp_nve_ops *ops; int err; if (nve->num_nve_tunnels++ != 0) return 0; nve->config = *config; err = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ, 1, &nve->tunnel_index); if (err) goto err_kvdl_alloc; ops = nve->nve_ops_arr[config->type]; err = ops->init(nve, config); if (err) goto err_ops_init; return 0; err_ops_init: mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ, 1, nve->tunnel_index); err_kvdl_alloc: memset(&nve->config, 0, sizeof(nve->config)); nve->num_nve_tunnels--; return err; } static void mlxsw_sp_nve_tunnel_fini(struct mlxsw_sp *mlxsw_sp) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; const struct mlxsw_sp_nve_ops *ops; ops = nve->nve_ops_arr[nve->config.type]; if (mlxsw_sp->nve->num_nve_tunnels == 1) { ops->fini(nve); mlxsw_sp_kvdl_free(mlxsw_sp, MLXSW_SP_KVDL_ENTRY_TYPE_ADJ, 1, nve->tunnel_index); memset(&nve->config, 0, sizeof(nve->config)); } nve->num_nve_tunnels--; } static void mlxsw_sp_nve_fdb_flush_by_fid(struct mlxsw_sp *mlxsw_sp, u16 fid_index) { char sfdf_pl[MLXSW_REG_SFDF_LEN]; mlxsw_reg_sfdf_pack(sfdf_pl, MLXSW_REG_SFDF_FLUSH_PER_NVE_AND_FID); mlxsw_reg_sfdf_fid_set(sfdf_pl, fid_index); mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(sfdf), sfdf_pl); } static void mlxsw_sp_nve_fdb_clear_offload(struct mlxsw_sp *mlxsw_sp, const struct mlxsw_sp_fid *fid, const struct net_device *nve_dev, __be32 vni) { const struct mlxsw_sp_nve_ops *ops; enum mlxsw_sp_nve_type type; if (WARN_ON(mlxsw_sp_fid_nve_type(fid, &type))) return; ops = mlxsw_sp->nve->nve_ops_arr[type]; ops->fdb_clear_offload(nve_dev, vni); } int mlxsw_sp_nve_fid_enable(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *fid, struct mlxsw_sp_nve_params *params, struct netlink_ext_ack *extack) { struct mlxsw_sp_nve *nve = mlxsw_sp->nve; const struct mlxsw_sp_nve_ops *ops; struct mlxsw_sp_nve_config config; int err; ops = nve->nve_ops_arr[params->type]; if (!ops->can_offload(nve, params->dev, extack)) return -EINVAL; memset(&config, 0, sizeof(config)); ops->nve_config(nve, params->dev, &config); if (nve->num_nve_tunnels && memcmp(&config, &nve->config, sizeof(config))) { NL_SET_ERR_MSG_MOD(extack, "Conflicting NVE tunnels configuration"); return -EINVAL; } err = mlxsw_sp_nve_tunnel_init(mlxsw_sp, &config); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to initialize NVE tunnel"); return err; } err = mlxsw_sp_fid_vni_set(fid, params->type, params->vni, params->dev->ifindex); if (err) { NL_SET_ERR_MSG_MOD(extack, "Failed to set VNI on FID"); goto err_fid_vni_set; } err = ops->fdb_replay(params->dev, params->vni, extack); if (err) goto err_fdb_replay; return 0; err_fdb_replay: mlxsw_sp_fid_vni_clear(fid); err_fid_vni_set: mlxsw_sp_nve_tunnel_fini(mlxsw_sp); return err; } void mlxsw_sp_nve_fid_disable(struct mlxsw_sp *mlxsw_sp, struct mlxsw_sp_fid *fid) { u16 fid_index = mlxsw_sp_fid_index(fid); struct net_device *nve_dev; int nve_ifindex; __be32 vni; mlxsw_sp_nve_flood_ip_flush(mlxsw_sp, fid); mlxsw_sp_nve_fdb_flush_by_fid(mlxsw_sp, fid_index); if (WARN_ON(mlxsw_sp_fid_nve_ifindex(fid, &nve_ifindex) || mlxsw_sp_fid_vni(fid, &vni))) goto out; nve_dev = dev_get_by_index(mlxsw_sp_net(mlxsw_sp), nve_ifindex); if (!nve_dev) goto out; mlxsw_sp_nve_fdb_clear_offload(mlxsw_sp, fid, nve_dev, vni); mlxsw_sp_fid_fdb_clear_offload(fid, nve_dev); dev_put(nve_dev); out: mlxsw_sp_fid_vni_clear(fid); mlxsw_sp_nve_tunnel_fini(mlxsw_sp); } int mlxsw_sp_port_nve_init(struct mlxsw_sp_port *mlxsw_sp_port) { struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; char tnqdr_pl[MLXSW_REG_TNQDR_LEN]; mlxsw_reg_tnqdr_pack(tnqdr_pl, mlxsw_sp_port->local_port); return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(tnqdr), tnqdr_pl); } void mlxsw_sp_port_nve_fini(struct mlxsw_sp_port *mlxsw_sp_port) { } static int mlxsw_sp_nve_qos_init(struct mlxsw_sp *mlxsw_sp) { char tnqcr_pl[MLXSW_REG_TNQCR_LEN]; mlxsw_reg_tnqcr_pack(tnqcr_pl); return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(tnqcr), tnqcr_pl); } static int mlxsw_sp_nve_ecn_encap_init(struct mlxsw_sp *mlxsw_sp) { int i; /* Iterate over inner ECN values */ for (i = INET_ECN_NOT_ECT; i <= INET_ECN_CE; i++) { u8 outer_ecn = INET_ECN_encapsulate(0, i); char tneem_pl[MLXSW_REG_TNEEM_LEN]; int err; mlxsw_reg_tneem_pack(tneem_pl, i, outer_ecn); err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(tneem), tneem_pl); if (err) return err; } return 0; } static int __mlxsw_sp_nve_ecn_decap_init(struct mlxsw_sp *mlxsw_sp, u8 inner_ecn, u8 outer_ecn) { char tndem_pl[MLXSW_REG_TNDEM_LEN]; u8 new_inner_ecn; bool trap_en; new_inner_ecn = mlxsw_sp_tunnel_ecn_decap(outer_ecn, inner_ecn, &trap_en); mlxsw_reg_tndem_pack(tndem_pl, outer_ecn, inner_ecn, new_inner_ecn, trap_en, trap_en ? MLXSW_TRAP_ID_DECAP_ECN0 : 0); return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(tndem), tndem_pl); } static int mlxsw_sp_nve_ecn_decap_init(struct mlxsw_sp *mlxsw_sp) { int i; /* Iterate over inner ECN values */ for (i = INET_ECN_NOT_ECT; i <= INET_ECN_CE; i++) { int j; /* Iterate over outer ECN values */ for (j = INET_ECN_NOT_ECT; j <= INET_ECN_CE; j++) { int err; err = __mlxsw_sp_nve_ecn_decap_init(mlxsw_sp, i, j); if (err) return err; } } return 0; } static int mlxsw_sp_nve_ecn_init(struct mlxsw_sp *mlxsw_sp) { int err; err = mlxsw_sp_nve_ecn_encap_init(mlxsw_sp); if (err) return err; return mlxsw_sp_nve_ecn_decap_init(mlxsw_sp); } static int mlxsw_sp_nve_resources_query(struct mlxsw_sp *mlxsw_sp) { unsigned int max; if (!MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_NVE_MC_ENTRIES_IPV4) || !MLXSW_CORE_RES_VALID(mlxsw_sp->core, MAX_NVE_MC_ENTRIES_IPV6)) return -EIO; max = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_NVE_MC_ENTRIES_IPV4); mlxsw_sp->nve->num_max_mc_entries[MLXSW_SP_L3_PROTO_IPV4] = max; max = MLXSW_CORE_RES_GET(mlxsw_sp->core, MAX_NVE_MC_ENTRIES_IPV6); mlxsw_sp->nve->num_max_mc_entries[MLXSW_SP_L3_PROTO_IPV6] = max; return 0; } int mlxsw_sp_nve_init(struct mlxsw_sp *mlxsw_sp) { struct mlxsw_sp_nve *nve; int err; nve = kzalloc(sizeof(*mlxsw_sp->nve), GFP_KERNEL); if (!nve) return -ENOMEM; mlxsw_sp->nve = nve; nve->mlxsw_sp = mlxsw_sp; nve->nve_ops_arr = mlxsw_sp->nve_ops_arr; err = rhashtable_init(&nve->mc_list_ht, &mlxsw_sp_nve_mc_list_ht_params); if (err) goto err_rhashtable_init; err = mlxsw_sp_nve_qos_init(mlxsw_sp); if (err) goto err_nve_qos_init; err = mlxsw_sp_nve_ecn_init(mlxsw_sp); if (err) goto err_nve_ecn_init; err = mlxsw_sp_nve_resources_query(mlxsw_sp); if (err) goto err_nve_resources_query; return 0; err_nve_resources_query: err_nve_ecn_init: err_nve_qos_init: rhashtable_destroy(&nve->mc_list_ht); err_rhashtable_init: mlxsw_sp->nve = NULL; kfree(nve); return err; } void mlxsw_sp_nve_fini(struct mlxsw_sp *mlxsw_sp) { WARN_ON(mlxsw_sp->nve->num_nve_tunnels); rhashtable_destroy(&mlxsw_sp->nve->mc_list_ht); kfree(mlxsw_sp->nve); mlxsw_sp->nve = NULL; }