// SPDX-License-Identifier: GPL-2.0-only /**************************************************************************** * Driver for Solarflare network controllers and boards * Copyright 2023, Advanced Micro Devices, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 as published * by the Free Software Foundation, incorporated herein by reference. */ #include "tc_encap_actions.h" #include "tc.h" #include "mae.h" #include #include #include #include static const struct rhashtable_params efx_neigh_ht_params = { .key_len = offsetof(struct efx_neigh_binder, ha), .key_offset = 0, .head_offset = offsetof(struct efx_neigh_binder, linkage), }; static const struct rhashtable_params efx_tc_encap_ht_params = { .key_len = offsetofend(struct efx_tc_encap_action, key), .key_offset = 0, .head_offset = offsetof(struct efx_tc_encap_action, linkage), }; static void efx_tc_encap_free(void *ptr, void *__unused) { struct efx_tc_encap_action *enc = ptr; WARN_ON(refcount_read(&enc->ref)); kfree(enc); } static void efx_neigh_free(void *ptr, void *__unused) { struct efx_neigh_binder *neigh = ptr; WARN_ON(refcount_read(&neigh->ref)); WARN_ON(!list_empty(&neigh->users)); put_net_track(neigh->net, &neigh->ns_tracker); netdev_put(neigh->egdev, &neigh->dev_tracker); kfree(neigh); } int efx_tc_init_encap_actions(struct efx_nic *efx) { int rc; rc = rhashtable_init(&efx->tc->neigh_ht, &efx_neigh_ht_params); if (rc < 0) goto fail_neigh_ht; rc = rhashtable_init(&efx->tc->encap_ht, &efx_tc_encap_ht_params); if (rc < 0) goto fail_encap_ht; return 0; fail_encap_ht: rhashtable_destroy(&efx->tc->neigh_ht); fail_neigh_ht: return rc; } /* Only call this in init failure teardown. * Normal exit should fini instead as there may be entries in the table. */ void efx_tc_destroy_encap_actions(struct efx_nic *efx) { rhashtable_destroy(&efx->tc->encap_ht); rhashtable_destroy(&efx->tc->neigh_ht); } void efx_tc_fini_encap_actions(struct efx_nic *efx) { rhashtable_free_and_destroy(&efx->tc->encap_ht, efx_tc_encap_free, NULL); rhashtable_free_and_destroy(&efx->tc->neigh_ht, efx_neigh_free, NULL); } static void efx_neigh_update(struct work_struct *work); static int efx_bind_neigh(struct efx_nic *efx, struct efx_tc_encap_action *encap, struct net *net, struct netlink_ext_ack *extack) { struct efx_neigh_binder *neigh, *old; struct flowi6 flow6 = {}; struct flowi4 flow4 = {}; int rc; /* GCC stupidly thinks that only values explicitly listed in the enum * definition can _possibly_ be sensible case values, so without this * cast it complains about the IPv6 versions. */ switch ((int)encap->type) { case EFX_ENCAP_TYPE_VXLAN: case EFX_ENCAP_TYPE_GENEVE: flow4.flowi4_proto = IPPROTO_UDP; flow4.fl4_dport = encap->key.tp_dst; flow4.flowi4_tos = encap->key.tos; flow4.daddr = encap->key.u.ipv4.dst; flow4.saddr = encap->key.u.ipv4.src; break; case EFX_ENCAP_TYPE_VXLAN | EFX_ENCAP_FLAG_IPV6: case EFX_ENCAP_TYPE_GENEVE | EFX_ENCAP_FLAG_IPV6: flow6.flowi6_proto = IPPROTO_UDP; flow6.fl6_dport = encap->key.tp_dst; flow6.flowlabel = ip6_make_flowinfo(encap->key.tos, encap->key.label); flow6.daddr = encap->key.u.ipv6.dst; flow6.saddr = encap->key.u.ipv6.src; break; default: NL_SET_ERR_MSG_FMT_MOD(extack, "Unsupported encap type %d", (int)encap->type); return -EOPNOTSUPP; } neigh = kzalloc(sizeof(*neigh), GFP_KERNEL_ACCOUNT); if (!neigh) return -ENOMEM; neigh->net = get_net_track(net, &neigh->ns_tracker, GFP_KERNEL_ACCOUNT); neigh->dst_ip = flow4.daddr; neigh->dst_ip6 = flow6.daddr; old = rhashtable_lookup_get_insert_fast(&efx->tc->neigh_ht, &neigh->linkage, efx_neigh_ht_params); if (old) { /* don't need our new entry */ put_net_track(neigh->net, &neigh->ns_tracker); kfree(neigh); if (IS_ERR(old)) /* oh dear, it's actually an error */ return PTR_ERR(old); if (!refcount_inc_not_zero(&old->ref)) return -EAGAIN; /* existing entry found, ref taken */ neigh = old; } else { /* New entry. We need to initiate a lookup */ struct neighbour *n; struct rtable *rt; if (encap->type & EFX_ENCAP_FLAG_IPV6) { #if IS_ENABLED(CONFIG_IPV6) struct dst_entry *dst; dst = ipv6_stub->ipv6_dst_lookup_flow(net, NULL, &flow6, NULL); rc = PTR_ERR_OR_ZERO(dst); if (rc) { NL_SET_ERR_MSG_MOD(extack, "Failed to lookup route for IPv6 encap"); goto out_free; } neigh->egdev = dst->dev; netdev_hold(neigh->egdev, &neigh->dev_tracker, GFP_KERNEL_ACCOUNT); neigh->ttl = ip6_dst_hoplimit(dst); n = dst_neigh_lookup(dst, &flow6.daddr); dst_release(dst); #else /* We shouldn't ever get here, because if IPv6 isn't * enabled how did someone create an IPv6 tunnel_key? */ rc = -EOPNOTSUPP; NL_SET_ERR_MSG_MOD(extack, "No IPv6 support (neigh bind)"); goto out_free; #endif } else { rt = ip_route_output_key(net, &flow4); if (IS_ERR_OR_NULL(rt)) { rc = PTR_ERR_OR_ZERO(rt); if (!rc) rc = -EIO; NL_SET_ERR_MSG_MOD(extack, "Failed to lookup route for encap"); goto out_free; } neigh->egdev = rt->dst.dev; netdev_hold(neigh->egdev, &neigh->dev_tracker, GFP_KERNEL_ACCOUNT); neigh->ttl = ip4_dst_hoplimit(&rt->dst); n = dst_neigh_lookup(&rt->dst, &flow4.daddr); ip_rt_put(rt); } if (!n) { rc = -ENETUNREACH; NL_SET_ERR_MSG_MOD(extack, "Failed to lookup neighbour for encap"); netdev_put(neigh->egdev, &neigh->dev_tracker); goto out_free; } refcount_set(&neigh->ref, 1); INIT_LIST_HEAD(&neigh->users); read_lock_bh(&n->lock); ether_addr_copy(neigh->ha, n->ha); neigh->n_valid = n->nud_state & NUD_VALID; read_unlock_bh(&n->lock); rwlock_init(&neigh->lock); INIT_WORK(&neigh->work, efx_neigh_update); neigh->efx = efx; neigh->used = jiffies; if (!neigh->n_valid) /* Prod ARP to find us a neighbour */ neigh_event_send(n, NULL); neigh_release(n); } /* Add us to this neigh */ encap->neigh = neigh; list_add_tail(&encap->list, &neigh->users); return 0; out_free: /* cleanup common to several error paths */ rhashtable_remove_fast(&efx->tc->neigh_ht, &neigh->linkage, efx_neigh_ht_params); synchronize_rcu(); put_net_track(net, &neigh->ns_tracker); kfree(neigh); return rc; } static void efx_free_neigh(struct efx_neigh_binder *neigh) { struct efx_nic *efx = neigh->efx; rhashtable_remove_fast(&efx->tc->neigh_ht, &neigh->linkage, efx_neigh_ht_params); synchronize_rcu(); netdev_put(neigh->egdev, &neigh->dev_tracker); put_net_track(neigh->net, &neigh->ns_tracker); kfree(neigh); } static void efx_release_neigh(struct efx_nic *efx, struct efx_tc_encap_action *encap) { struct efx_neigh_binder *neigh = encap->neigh; if (!neigh) return; list_del(&encap->list); encap->neigh = NULL; if (!refcount_dec_and_test(&neigh->ref)) return; /* still in use */ efx_free_neigh(neigh); } static void efx_gen_tun_header_eth(struct efx_tc_encap_action *encap, u16 proto) { struct efx_neigh_binder *neigh = encap->neigh; struct ethhdr *eth; encap->encap_hdr_len = sizeof(*eth); eth = (struct ethhdr *)encap->encap_hdr; if (encap->neigh->n_valid) ether_addr_copy(eth->h_dest, neigh->ha); else eth_zero_addr(eth->h_dest); ether_addr_copy(eth->h_source, neigh->egdev->dev_addr); eth->h_proto = htons(proto); } static void efx_gen_tun_header_ipv4(struct efx_tc_encap_action *encap, u8 ipproto, u8 len) { struct efx_neigh_binder *neigh = encap->neigh; struct ip_tunnel_key *key = &encap->key; struct iphdr *ip; ip = (struct iphdr *)(encap->encap_hdr + encap->encap_hdr_len); encap->encap_hdr_len += sizeof(*ip); ip->daddr = key->u.ipv4.dst; ip->saddr = key->u.ipv4.src; ip->ttl = neigh->ttl; ip->protocol = ipproto; ip->version = 0x4; ip->ihl = 0x5; ip->tot_len = cpu_to_be16(ip->ihl * 4 + len); ip_send_check(ip); } #ifdef CONFIG_IPV6 static void efx_gen_tun_header_ipv6(struct efx_tc_encap_action *encap, u8 ipproto, u8 len) { struct efx_neigh_binder *neigh = encap->neigh; struct ip_tunnel_key *key = &encap->key; struct ipv6hdr *ip; ip = (struct ipv6hdr *)(encap->encap_hdr + encap->encap_hdr_len); encap->encap_hdr_len += sizeof(*ip); ip6_flow_hdr(ip, key->tos, key->label); ip->daddr = key->u.ipv6.dst; ip->saddr = key->u.ipv6.src; ip->hop_limit = neigh->ttl; ip->nexthdr = ipproto; ip->version = 0x6; ip->payload_len = cpu_to_be16(len); } #endif static void efx_gen_tun_header_udp(struct efx_tc_encap_action *encap, u8 len) { struct ip_tunnel_key *key = &encap->key; struct udphdr *udp; udp = (struct udphdr *)(encap->encap_hdr + encap->encap_hdr_len); encap->encap_hdr_len += sizeof(*udp); udp->dest = key->tp_dst; udp->len = cpu_to_be16(sizeof(*udp) + len); } static void efx_gen_tun_header_vxlan(struct efx_tc_encap_action *encap) { struct ip_tunnel_key *key = &encap->key; struct vxlanhdr *vxlan; vxlan = (struct vxlanhdr *)(encap->encap_hdr + encap->encap_hdr_len); encap->encap_hdr_len += sizeof(*vxlan); vxlan->vx_flags = VXLAN_HF_VNI; vxlan->vx_vni = vxlan_vni_field(tunnel_id_to_key32(key->tun_id)); } static void efx_gen_tun_header_geneve(struct efx_tc_encap_action *encap) { struct ip_tunnel_key *key = &encap->key; struct genevehdr *geneve; u32 vni; geneve = (struct genevehdr *)(encap->encap_hdr + encap->encap_hdr_len); encap->encap_hdr_len += sizeof(*geneve); geneve->proto_type = htons(ETH_P_TEB); /* convert tun_id to host-endian so we can use host arithmetic to * extract individual bytes. */ vni = ntohl(tunnel_id_to_key32(key->tun_id)); geneve->vni[0] = vni >> 16; geneve->vni[1] = vni >> 8; geneve->vni[2] = vni; } #define vxlan_header_l4_len (sizeof(struct udphdr) + sizeof(struct vxlanhdr)) #define vxlan4_header_len (sizeof(struct ethhdr) + sizeof(struct iphdr) + vxlan_header_l4_len) static void efx_gen_vxlan_header_ipv4(struct efx_tc_encap_action *encap) { BUILD_BUG_ON(sizeof(encap->encap_hdr) < vxlan4_header_len); efx_gen_tun_header_eth(encap, ETH_P_IP); efx_gen_tun_header_ipv4(encap, IPPROTO_UDP, vxlan_header_l4_len); efx_gen_tun_header_udp(encap, sizeof(struct vxlanhdr)); efx_gen_tun_header_vxlan(encap); } #define geneve_header_l4_len (sizeof(struct udphdr) + sizeof(struct genevehdr)) #define geneve4_header_len (sizeof(struct ethhdr) + sizeof(struct iphdr) + geneve_header_l4_len) static void efx_gen_geneve_header_ipv4(struct efx_tc_encap_action *encap) { BUILD_BUG_ON(sizeof(encap->encap_hdr) < geneve4_header_len); efx_gen_tun_header_eth(encap, ETH_P_IP); efx_gen_tun_header_ipv4(encap, IPPROTO_UDP, geneve_header_l4_len); efx_gen_tun_header_udp(encap, sizeof(struct genevehdr)); efx_gen_tun_header_geneve(encap); } #ifdef CONFIG_IPV6 #define vxlan6_header_len (sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + vxlan_header_l4_len) static void efx_gen_vxlan_header_ipv6(struct efx_tc_encap_action *encap) { BUILD_BUG_ON(sizeof(encap->encap_hdr) < vxlan6_header_len); efx_gen_tun_header_eth(encap, ETH_P_IPV6); efx_gen_tun_header_ipv6(encap, IPPROTO_UDP, vxlan_header_l4_len); efx_gen_tun_header_udp(encap, sizeof(struct vxlanhdr)); efx_gen_tun_header_vxlan(encap); } #define geneve6_header_len (sizeof(struct ethhdr) + sizeof(struct ipv6hdr) + geneve_header_l4_len) static void efx_gen_geneve_header_ipv6(struct efx_tc_encap_action *encap) { BUILD_BUG_ON(sizeof(encap->encap_hdr) < geneve6_header_len); efx_gen_tun_header_eth(encap, ETH_P_IPV6); efx_gen_tun_header_ipv6(encap, IPPROTO_UDP, geneve_header_l4_len); efx_gen_tun_header_udp(encap, sizeof(struct genevehdr)); efx_gen_tun_header_geneve(encap); } #endif static void efx_gen_encap_header(struct efx_nic *efx, struct efx_tc_encap_action *encap) { encap->n_valid = encap->neigh->n_valid; /* GCC stupidly thinks that only values explicitly listed in the enum * definition can _possibly_ be sensible case values, so without this * cast it complains about the IPv6 versions. */ switch ((int)encap->type) { case EFX_ENCAP_TYPE_VXLAN: efx_gen_vxlan_header_ipv4(encap); break; case EFX_ENCAP_TYPE_GENEVE: efx_gen_geneve_header_ipv4(encap); break; #ifdef CONFIG_IPV6 case EFX_ENCAP_TYPE_VXLAN | EFX_ENCAP_FLAG_IPV6: efx_gen_vxlan_header_ipv6(encap); break; case EFX_ENCAP_TYPE_GENEVE | EFX_ENCAP_FLAG_IPV6: efx_gen_geneve_header_ipv6(encap); break; #endif default: /* unhandled encap type, can't happen */ if (net_ratelimit()) netif_err(efx, drv, efx->net_dev, "Bogus encap type %d, can't generate\n", encap->type); /* Use fallback action. */ encap->n_valid = false; break; } } static void efx_tc_update_encap(struct efx_nic *efx, struct efx_tc_encap_action *encap) { struct efx_tc_action_set_list *acts, *fallback; struct efx_tc_flow_rule *rule; struct efx_tc_action_set *act; int rc; if (encap->n_valid) { /* Make sure no rules are using this encap while we change it */ list_for_each_entry(act, &encap->users, encap_user) { acts = act->user; if (WARN_ON(!acts)) /* can't happen */ continue; rule = container_of(acts, struct efx_tc_flow_rule, acts); if (rule->fallback) fallback = rule->fallback; else /* fallback fallback: deliver to PF */ fallback = &efx->tc->facts.pf; rc = efx_mae_update_rule(efx, fallback->fw_id, rule->fw_id); if (rc) netif_err(efx, drv, efx->net_dev, "Failed to update (f) rule %08x rc %d\n", rule->fw_id, rc); else netif_dbg(efx, drv, efx->net_dev, "Updated (f) rule %08x\n", rule->fw_id); } } /* Make sure we don't leak arbitrary bytes on the wire; * set an all-0s ethernet header. A successful call to * efx_gen_encap_header() will overwrite this. */ memset(encap->encap_hdr, 0, sizeof(encap->encap_hdr)); encap->encap_hdr_len = ETH_HLEN; if (encap->neigh) { read_lock_bh(&encap->neigh->lock); efx_gen_encap_header(efx, encap); read_unlock_bh(&encap->neigh->lock); } else { encap->n_valid = false; } rc = efx_mae_update_encap_md(efx, encap); if (rc) { netif_err(efx, drv, efx->net_dev, "Failed to update encap hdr %08x rc %d\n", encap->fw_id, rc); return; } netif_dbg(efx, drv, efx->net_dev, "Updated encap hdr %08x\n", encap->fw_id); if (!encap->n_valid) return; /* Update rule users: use the action if they are now ready */ list_for_each_entry(act, &encap->users, encap_user) { acts = act->user; if (WARN_ON(!acts)) /* can't happen */ continue; rule = container_of(acts, struct efx_tc_flow_rule, acts); if (!efx_tc_check_ready(efx, rule)) continue; rc = efx_mae_update_rule(efx, acts->fw_id, rule->fw_id); if (rc) netif_err(efx, drv, efx->net_dev, "Failed to update rule %08x rc %d\n", rule->fw_id, rc); else netif_dbg(efx, drv, efx->net_dev, "Updated rule %08x\n", rule->fw_id); } } static void efx_neigh_update(struct work_struct *work) { struct efx_neigh_binder *neigh = container_of(work, struct efx_neigh_binder, work); struct efx_tc_encap_action *encap; struct efx_nic *efx = neigh->efx; mutex_lock(&efx->tc->mutex); list_for_each_entry(encap, &neigh->users, list) efx_tc_update_encap(neigh->efx, encap); /* release ref taken in efx_neigh_event() */ if (refcount_dec_and_test(&neigh->ref)) efx_free_neigh(neigh); mutex_unlock(&efx->tc->mutex); } static int efx_neigh_event(struct efx_nic *efx, struct neighbour *n) { struct efx_neigh_binder keys = {NULL}, *neigh; bool n_valid, ipv6 = false; char ha[ETH_ALEN]; size_t keysize; if (WARN_ON(!efx->tc)) return NOTIFY_DONE; if (n->tbl == &arp_tbl) { keysize = sizeof(keys.dst_ip); #if IS_ENABLED(CONFIG_IPV6) } else if (n->tbl == ipv6_stub->nd_tbl) { ipv6 = true; keysize = sizeof(keys.dst_ip6); #endif } else { return NOTIFY_DONE; } if (!n->parms) { netif_warn(efx, drv, efx->net_dev, "neigh_event with no parms!\n"); return NOTIFY_DONE; } keys.net = read_pnet(&n->parms->net); if (n->tbl->key_len != keysize) { netif_warn(efx, drv, efx->net_dev, "neigh_event with bad key_len %u\n", n->tbl->key_len); return NOTIFY_DONE; } read_lock_bh(&n->lock); /* Get a consistent view */ memcpy(ha, n->ha, ETH_ALEN); n_valid = (n->nud_state & NUD_VALID) && !n->dead; read_unlock_bh(&n->lock); if (ipv6) memcpy(&keys.dst_ip6, n->primary_key, n->tbl->key_len); else memcpy(&keys.dst_ip, n->primary_key, n->tbl->key_len); rcu_read_lock(); neigh = rhashtable_lookup_fast(&efx->tc->neigh_ht, &keys, efx_neigh_ht_params); if (!neigh || neigh->dying) /* We're not interested in this neighbour */ goto done; write_lock_bh(&neigh->lock); if (n_valid == neigh->n_valid && !memcmp(ha, neigh->ha, ETH_ALEN)) { write_unlock_bh(&neigh->lock); /* Nothing has changed; no work to do */ goto done; } neigh->n_valid = n_valid; memcpy(neigh->ha, ha, ETH_ALEN); write_unlock_bh(&neigh->lock); if (refcount_inc_not_zero(&neigh->ref)) { rcu_read_unlock(); if (!schedule_work(&neigh->work)) /* failed to schedule, release the ref we just took */ if (refcount_dec_and_test(&neigh->ref)) efx_free_neigh(neigh); } else { done: rcu_read_unlock(); } return NOTIFY_DONE; } bool efx_tc_check_ready(struct efx_nic *efx, struct efx_tc_flow_rule *rule) { struct efx_tc_action_set *act; /* Encap actions can only be offloaded if they have valid * neighbour info for the outer Ethernet header. */ list_for_each_entry(act, &rule->acts.list, list) if (act->encap_md && !act->encap_md->n_valid) return false; return true; } struct efx_tc_encap_action *efx_tc_flower_create_encap_md( struct efx_nic *efx, const struct ip_tunnel_info *info, struct net_device *egdev, struct netlink_ext_ack *extack) { enum efx_encap_type type = efx_tc_indr_netdev_type(egdev); struct efx_tc_encap_action *encap, *old; struct efx_rep *to_efv; s64 rc; if (type == EFX_ENCAP_TYPE_NONE) { /* dest is not an encap device */ NL_SET_ERR_MSG_MOD(extack, "Not a (supported) tunnel device but tunnel_key is set"); return ERR_PTR(-EOPNOTSUPP); } rc = efx_mae_check_encap_type_supported(efx, type); if (rc < 0) { NL_SET_ERR_MSG_MOD(extack, "Firmware reports no support for this tunnel type"); return ERR_PTR(rc); } /* No support yet for Geneve options */ if (info->options_len) { NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel options"); return ERR_PTR(-EOPNOTSUPP); } switch (info->mode) { case IP_TUNNEL_INFO_TX: break; case IP_TUNNEL_INFO_TX | IP_TUNNEL_INFO_IPV6: type |= EFX_ENCAP_FLAG_IPV6; break; default: NL_SET_ERR_MSG_FMT_MOD(extack, "Unsupported tunnel mode %u", info->mode); return ERR_PTR(-EOPNOTSUPP); } encap = kzalloc(sizeof(*encap), GFP_KERNEL_ACCOUNT); if (!encap) return ERR_PTR(-ENOMEM); encap->type = type; encap->key = info->key; INIT_LIST_HEAD(&encap->users); old = rhashtable_lookup_get_insert_fast(&efx->tc->encap_ht, &encap->linkage, efx_tc_encap_ht_params); if (old) { /* don't need our new entry */ kfree(encap); if (IS_ERR(old)) /* oh dear, it's actually an error */ return ERR_CAST(old); if (!refcount_inc_not_zero(&old->ref)) return ERR_PTR(-EAGAIN); /* existing entry found, ref taken */ return old; } rc = efx_bind_neigh(efx, encap, dev_net(egdev), extack); if (rc < 0) goto out_remove; to_efv = efx_tc_flower_lookup_efv(efx, encap->neigh->egdev); if (IS_ERR(to_efv)) { /* neigh->egdev isn't ours */ NL_SET_ERR_MSG_MOD(extack, "Tunnel egress device not on switch"); rc = PTR_ERR(to_efv); goto out_release; } rc = efx_tc_flower_external_mport(efx, to_efv); if (rc < 0) { NL_SET_ERR_MSG_MOD(extack, "Failed to identify tunnel egress m-port"); goto out_release; } encap->dest_mport = rc; read_lock_bh(&encap->neigh->lock); efx_gen_encap_header(efx, encap); read_unlock_bh(&encap->neigh->lock); rc = efx_mae_allocate_encap_md(efx, encap); if (rc < 0) { NL_SET_ERR_MSG_MOD(extack, "Failed to write tunnel header to hw"); goto out_release; } /* ref and return */ refcount_set(&encap->ref, 1); return encap; out_release: efx_release_neigh(efx, encap); out_remove: rhashtable_remove_fast(&efx->tc->encap_ht, &encap->linkage, efx_tc_encap_ht_params); kfree(encap); return ERR_PTR(rc); } void efx_tc_flower_release_encap_md(struct efx_nic *efx, struct efx_tc_encap_action *encap) { if (!refcount_dec_and_test(&encap->ref)) return; /* still in use */ efx_release_neigh(efx, encap); rhashtable_remove_fast(&efx->tc->encap_ht, &encap->linkage, efx_tc_encap_ht_params); efx_mae_free_encap_md(efx, encap); kfree(encap); } static void efx_tc_remove_neigh_users(struct efx_nic *efx, struct efx_neigh_binder *neigh) { struct efx_tc_encap_action *encap, *next; list_for_each_entry_safe(encap, next, &neigh->users, list) { /* Should cause neigh usage count to fall to zero, freeing it */ efx_release_neigh(efx, encap); /* The encap has lost its neigh, so it's now unready */ efx_tc_update_encap(efx, encap); } } void efx_tc_unregister_egdev(struct efx_nic *efx, struct net_device *net_dev) { struct efx_neigh_binder *neigh; struct rhashtable_iter walk; mutex_lock(&efx->tc->mutex); rhashtable_walk_enter(&efx->tc->neigh_ht, &walk); rhashtable_walk_start(&walk); while ((neigh = rhashtable_walk_next(&walk)) != NULL) { if (IS_ERR(neigh)) continue; if (neigh->egdev != net_dev) continue; neigh->dying = true; rhashtable_walk_stop(&walk); synchronize_rcu(); /* Make sure any updates see dying flag */ efx_tc_remove_neigh_users(efx, neigh); /* might sleep */ rhashtable_walk_start(&walk); } rhashtable_walk_stop(&walk); rhashtable_walk_exit(&walk); mutex_unlock(&efx->tc->mutex); } int efx_tc_netevent_event(struct efx_nic *efx, unsigned long event, void *ptr) { if (efx->type->is_vf) return NOTIFY_DONE; switch (event) { case NETEVENT_NEIGH_UPDATE: return efx_neigh_event(efx, ptr); default: return NOTIFY_DONE; } }