// SPDX-License-Identifier: GPL-2.0-only 
 | 
/**************************************************************************** 
 | 
 * Driver for Solarflare network controllers and boards 
 | 
 * Copyright 2005-2018 Solarflare Communications Inc. 
 | 
 * Copyright 2019-2020 Xilinx 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 "mcdi_filters.h" 
 | 
#include "mcdi.h" 
 | 
#include "nic.h" 
 | 
#include "rx_common.h" 
 | 
  
 | 
/* The maximum size of a shared RSS context */ 
 | 
/* TODO: this should really be from the mcdi protocol export */ 
 | 
#define EFX_EF10_MAX_SHARED_RSS_CONTEXT_SIZE 64UL 
 | 
  
 | 
#define EFX_EF10_FILTER_ID_INVALID 0xffff 
 | 
  
 | 
/* An arbitrary search limit for the software hash table */ 
 | 
#define EFX_EF10_FILTER_SEARCH_LIMIT 200 
 | 
  
 | 
static struct efx_filter_spec * 
 | 
efx_mcdi_filter_entry_spec(const struct efx_mcdi_filter_table *table, 
 | 
               unsigned int filter_idx) 
 | 
{ 
 | 
    return (struct efx_filter_spec *)(table->entry[filter_idx].spec & 
 | 
                      ~EFX_EF10_FILTER_FLAGS); 
 | 
} 
 | 
  
 | 
static unsigned int 
 | 
efx_mcdi_filter_entry_flags(const struct efx_mcdi_filter_table *table, 
 | 
               unsigned int filter_idx) 
 | 
{ 
 | 
    return table->entry[filter_idx].spec & EFX_EF10_FILTER_FLAGS; 
 | 
} 
 | 
  
 | 
static u32 efx_mcdi_filter_get_unsafe_id(u32 filter_id) 
 | 
{ 
 | 
    WARN_ON_ONCE(filter_id == EFX_EF10_FILTER_ID_INVALID); 
 | 
    return filter_id & (EFX_MCDI_FILTER_TBL_ROWS - 1); 
 | 
} 
 | 
  
 | 
static unsigned int efx_mcdi_filter_get_unsafe_pri(u32 filter_id) 
 | 
{ 
 | 
    return filter_id / (EFX_MCDI_FILTER_TBL_ROWS * 2); 
 | 
} 
 | 
  
 | 
static u32 efx_mcdi_filter_make_filter_id(unsigned int pri, u16 idx) 
 | 
{ 
 | 
    return pri * EFX_MCDI_FILTER_TBL_ROWS * 2 + idx; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Decide whether a filter should be exclusive or else should allow 
 | 
 * delivery to additional recipients.  Currently we decide that 
 | 
 * filters for specific local unicast MAC and IP addresses are 
 | 
 * exclusive. 
 | 
 */ 
 | 
static bool efx_mcdi_filter_is_exclusive(const struct efx_filter_spec *spec) 
 | 
{ 
 | 
    if (spec->match_flags & EFX_FILTER_MATCH_LOC_MAC && 
 | 
        !is_multicast_ether_addr(spec->loc_mac)) 
 | 
        return true; 
 | 
  
 | 
    if ((spec->match_flags & 
 | 
         (EFX_FILTER_MATCH_ETHER_TYPE | EFX_FILTER_MATCH_LOC_HOST)) == 
 | 
        (EFX_FILTER_MATCH_ETHER_TYPE | EFX_FILTER_MATCH_LOC_HOST)) { 
 | 
        if (spec->ether_type == htons(ETH_P_IP) && 
 | 
            !ipv4_is_multicast(spec->loc_host[0])) 
 | 
            return true; 
 | 
        if (spec->ether_type == htons(ETH_P_IPV6) && 
 | 
            ((const u8 *)spec->loc_host)[0] != 0xff) 
 | 
            return true; 
 | 
    } 
 | 
  
 | 
    return false; 
 | 
} 
 | 
  
 | 
static void 
 | 
efx_mcdi_filter_set_entry(struct efx_mcdi_filter_table *table, 
 | 
              unsigned int filter_idx, 
 | 
              const struct efx_filter_spec *spec, 
 | 
              unsigned int flags) 
 | 
{ 
 | 
    table->entry[filter_idx].spec =    (unsigned long)spec | flags; 
 | 
} 
 | 
  
 | 
static void 
 | 
efx_mcdi_filter_push_prep_set_match_fields(struct efx_nic *efx, 
 | 
                       const struct efx_filter_spec *spec, 
 | 
                       efx_dword_t *inbuf) 
 | 
{ 
 | 
    enum efx_encap_type encap_type = efx_filter_get_encap_type(spec); 
 | 
    u32 match_fields = 0, uc_match, mc_match; 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_OP, 
 | 
               efx_mcdi_filter_is_exclusive(spec) ? 
 | 
               MC_CMD_FILTER_OP_IN_OP_INSERT : 
 | 
               MC_CMD_FILTER_OP_IN_OP_SUBSCRIBE); 
 | 
  
 | 
    /* 
 | 
     * Convert match flags and values.  Unlike almost 
 | 
     * everything else in MCDI, these fields are in 
 | 
     * network byte order. 
 | 
     */ 
 | 
#define COPY_VALUE(value, mcdi_field)                         \ 
 | 
    do {                                 \ 
 | 
        match_fields |=                         \ 
 | 
            1 << MC_CMD_FILTER_OP_IN_MATCH_ ##         \ 
 | 
            mcdi_field ## _LBN;                 \ 
 | 
        BUILD_BUG_ON(                         \ 
 | 
            MC_CMD_FILTER_OP_IN_ ## mcdi_field ## _LEN < \ 
 | 
            sizeof(value));                     \ 
 | 
        memcpy(MCDI_PTR(inbuf, FILTER_OP_IN_ ##    mcdi_field), \ 
 | 
               &value, sizeof(value));                 \ 
 | 
    } while (0) 
 | 
#define COPY_FIELD(gen_flag, gen_field, mcdi_field)                 \ 
 | 
    if (spec->match_flags & EFX_FILTER_MATCH_ ## gen_flag) {     \ 
 | 
        COPY_VALUE(spec->gen_field, mcdi_field);         \ 
 | 
    } 
 | 
    /* 
 | 
     * Handle encap filters first.  They will always be mismatch 
 | 
     * (unknown UC or MC) filters 
 | 
     */ 
 | 
    if (encap_type) { 
 | 
        /* 
 | 
         * ether_type and outer_ip_proto need to be variables 
 | 
         * because COPY_VALUE wants to memcpy them 
 | 
         */ 
 | 
        __be16 ether_type = 
 | 
            htons(encap_type & EFX_ENCAP_FLAG_IPV6 ? 
 | 
                  ETH_P_IPV6 : ETH_P_IP); 
 | 
        u8 vni_type = MC_CMD_FILTER_OP_EXT_IN_VNI_TYPE_GENEVE; 
 | 
        u8 outer_ip_proto; 
 | 
  
 | 
        switch (encap_type & EFX_ENCAP_TYPES_MASK) { 
 | 
        case EFX_ENCAP_TYPE_VXLAN: 
 | 
            vni_type = MC_CMD_FILTER_OP_EXT_IN_VNI_TYPE_VXLAN; 
 | 
            fallthrough; 
 | 
        case EFX_ENCAP_TYPE_GENEVE: 
 | 
            COPY_VALUE(ether_type, ETHER_TYPE); 
 | 
            outer_ip_proto = IPPROTO_UDP; 
 | 
            COPY_VALUE(outer_ip_proto, IP_PROTO); 
 | 
            /* 
 | 
             * We always need to set the type field, even 
 | 
             * though we're not matching on the TNI. 
 | 
             */ 
 | 
            MCDI_POPULATE_DWORD_1(inbuf, 
 | 
                FILTER_OP_EXT_IN_VNI_OR_VSID, 
 | 
                FILTER_OP_EXT_IN_VNI_TYPE, 
 | 
                vni_type); 
 | 
            break; 
 | 
        case EFX_ENCAP_TYPE_NVGRE: 
 | 
            COPY_VALUE(ether_type, ETHER_TYPE); 
 | 
            outer_ip_proto = IPPROTO_GRE; 
 | 
            COPY_VALUE(outer_ip_proto, IP_PROTO); 
 | 
            break; 
 | 
        default: 
 | 
            WARN_ON(1); 
 | 
        } 
 | 
  
 | 
        uc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_IFRM_UNKNOWN_UCAST_DST_LBN; 
 | 
        mc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_IFRM_UNKNOWN_MCAST_DST_LBN; 
 | 
    } else { 
 | 
        uc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_UNKNOWN_UCAST_DST_LBN; 
 | 
        mc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_UNKNOWN_MCAST_DST_LBN; 
 | 
    } 
 | 
  
 | 
    if (spec->match_flags & EFX_FILTER_MATCH_LOC_MAC_IG) 
 | 
        match_fields |= 
 | 
            is_multicast_ether_addr(spec->loc_mac) ? 
 | 
            1 << mc_match : 
 | 
            1 << uc_match; 
 | 
    COPY_FIELD(REM_HOST, rem_host, SRC_IP); 
 | 
    COPY_FIELD(LOC_HOST, loc_host, DST_IP); 
 | 
    COPY_FIELD(REM_MAC, rem_mac, SRC_MAC); 
 | 
    COPY_FIELD(REM_PORT, rem_port, SRC_PORT); 
 | 
    COPY_FIELD(LOC_MAC, loc_mac, DST_MAC); 
 | 
    COPY_FIELD(LOC_PORT, loc_port, DST_PORT); 
 | 
    COPY_FIELD(ETHER_TYPE, ether_type, ETHER_TYPE); 
 | 
    COPY_FIELD(INNER_VID, inner_vid, INNER_VLAN); 
 | 
    COPY_FIELD(OUTER_VID, outer_vid, OUTER_VLAN); 
 | 
    COPY_FIELD(IP_PROTO, ip_proto, IP_PROTO); 
 | 
#undef COPY_FIELD 
 | 
#undef COPY_VALUE 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_MATCH_FIELDS, 
 | 
               match_fields); 
 | 
} 
 | 
  
 | 
static void efx_mcdi_filter_push_prep(struct efx_nic *efx, 
 | 
                      const struct efx_filter_spec *spec, 
 | 
                      efx_dword_t *inbuf, u64 handle, 
 | 
                      struct efx_rss_context *ctx, 
 | 
                      bool replacing) 
 | 
{ 
 | 
    u32 flags = spec->flags; 
 | 
  
 | 
    memset(inbuf, 0, MC_CMD_FILTER_OP_EXT_IN_LEN); 
 | 
  
 | 
    /* If RSS filter, caller better have given us an RSS context */ 
 | 
    if (flags & EFX_FILTER_FLAG_RX_RSS) { 
 | 
        /* 
 | 
         * We don't have the ability to return an error, so we'll just 
 | 
         * log a warning and disable RSS for the filter. 
 | 
         */ 
 | 
        if (WARN_ON_ONCE(!ctx)) 
 | 
            flags &= ~EFX_FILTER_FLAG_RX_RSS; 
 | 
        else if (WARN_ON_ONCE(ctx->context_id == EFX_MCDI_RSS_CONTEXT_INVALID)) 
 | 
            flags &= ~EFX_FILTER_FLAG_RX_RSS; 
 | 
    } 
 | 
  
 | 
    if (replacing) { 
 | 
        MCDI_SET_DWORD(inbuf, FILTER_OP_IN_OP, 
 | 
                   MC_CMD_FILTER_OP_IN_OP_REPLACE); 
 | 
        MCDI_SET_QWORD(inbuf, FILTER_OP_IN_HANDLE, handle); 
 | 
    } else { 
 | 
        efx_mcdi_filter_push_prep_set_match_fields(efx, spec, inbuf); 
 | 
    } 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_PORT_ID, efx->vport_id); 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_RX_DEST, 
 | 
               spec->dmaq_id == EFX_FILTER_RX_DMAQ_ID_DROP ? 
 | 
               MC_CMD_FILTER_OP_IN_RX_DEST_DROP : 
 | 
               MC_CMD_FILTER_OP_IN_RX_DEST_HOST); 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_TX_DOMAIN, 0); 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_TX_DEST, 
 | 
               MC_CMD_FILTER_OP_IN_TX_DEST_DEFAULT); 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_RX_QUEUE, 
 | 
               spec->dmaq_id == EFX_FILTER_RX_DMAQ_ID_DROP ? 
 | 
               0 : spec->dmaq_id); 
 | 
    MCDI_SET_DWORD(inbuf, FILTER_OP_IN_RX_MODE, 
 | 
               (flags & EFX_FILTER_FLAG_RX_RSS) ? 
 | 
               MC_CMD_FILTER_OP_IN_RX_MODE_RSS : 
 | 
               MC_CMD_FILTER_OP_IN_RX_MODE_SIMPLE); 
 | 
    if (flags & EFX_FILTER_FLAG_RX_RSS) 
 | 
        MCDI_SET_DWORD(inbuf, FILTER_OP_IN_RX_CONTEXT, ctx->context_id); 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_push(struct efx_nic *efx, 
 | 
                const struct efx_filter_spec *spec, u64 *handle, 
 | 
                struct efx_rss_context *ctx, bool replacing) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_FILTER_OP_EXT_IN_LEN); 
 | 
    MCDI_DECLARE_BUF(outbuf, MC_CMD_FILTER_OP_EXT_OUT_LEN); 
 | 
    size_t outlen; 
 | 
    int rc; 
 | 
  
 | 
    efx_mcdi_filter_push_prep(efx, spec, inbuf, *handle, ctx, replacing); 
 | 
    rc = efx_mcdi_rpc_quiet(efx, MC_CMD_FILTER_OP, inbuf, sizeof(inbuf), 
 | 
                outbuf, sizeof(outbuf), &outlen); 
 | 
    if (rc && spec->priority != EFX_FILTER_PRI_HINT) 
 | 
        efx_mcdi_display_error(efx, MC_CMD_FILTER_OP, sizeof(inbuf), 
 | 
                       outbuf, outlen, rc); 
 | 
    if (rc == 0) 
 | 
        *handle = MCDI_QWORD(outbuf, FILTER_OP_OUT_HANDLE); 
 | 
    if (rc == -ENOSPC) 
 | 
        rc = -EBUSY; /* to match efx_farch_filter_insert() */ 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
static u32 efx_mcdi_filter_mcdi_flags_from_spec(const struct efx_filter_spec *spec) 
 | 
{ 
 | 
    enum efx_encap_type encap_type = efx_filter_get_encap_type(spec); 
 | 
    unsigned int match_flags = spec->match_flags; 
 | 
    unsigned int uc_match, mc_match; 
 | 
    u32 mcdi_flags = 0; 
 | 
  
 | 
#define MAP_FILTER_TO_MCDI_FLAG(gen_flag, mcdi_field, encap) {        \ 
 | 
        unsigned int  old_match_flags = match_flags;        \ 
 | 
        match_flags &= ~EFX_FILTER_MATCH_ ## gen_flag;        \ 
 | 
        if (match_flags != old_match_flags)            \ 
 | 
            mcdi_flags |=                    \ 
 | 
                (1 << ((encap) ?            \ 
 | 
                       MC_CMD_FILTER_OP_EXT_IN_MATCH_IFRM_ ## \ 
 | 
                       mcdi_field ## _LBN :        \ 
 | 
                       MC_CMD_FILTER_OP_EXT_IN_MATCH_ ##\ 
 | 
                       mcdi_field ## _LBN));        \ 
 | 
    } 
 | 
    /* inner or outer based on encap type */ 
 | 
    MAP_FILTER_TO_MCDI_FLAG(REM_HOST, SRC_IP, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(LOC_HOST, DST_IP, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(REM_MAC, SRC_MAC, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(REM_PORT, SRC_PORT, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(LOC_MAC, DST_MAC, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(LOC_PORT, DST_PORT, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(ETHER_TYPE, ETHER_TYPE, encap_type); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(IP_PROTO, IP_PROTO, encap_type); 
 | 
    /* always outer */ 
 | 
    MAP_FILTER_TO_MCDI_FLAG(INNER_VID, INNER_VLAN, false); 
 | 
    MAP_FILTER_TO_MCDI_FLAG(OUTER_VID, OUTER_VLAN, false); 
 | 
#undef MAP_FILTER_TO_MCDI_FLAG 
 | 
  
 | 
    /* special handling for encap type, and mismatch */ 
 | 
    if (encap_type) { 
 | 
        match_flags &= ~EFX_FILTER_MATCH_ENCAP_TYPE; 
 | 
        mcdi_flags |= 
 | 
            (1 << MC_CMD_FILTER_OP_EXT_IN_MATCH_ETHER_TYPE_LBN); 
 | 
        mcdi_flags |= (1 << MC_CMD_FILTER_OP_EXT_IN_MATCH_IP_PROTO_LBN); 
 | 
  
 | 
        uc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_IFRM_UNKNOWN_UCAST_DST_LBN; 
 | 
        mc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_IFRM_UNKNOWN_MCAST_DST_LBN; 
 | 
    } else { 
 | 
        uc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_UNKNOWN_UCAST_DST_LBN; 
 | 
        mc_match = MC_CMD_FILTER_OP_EXT_IN_MATCH_UNKNOWN_MCAST_DST_LBN; 
 | 
    } 
 | 
  
 | 
    if (match_flags & EFX_FILTER_MATCH_LOC_MAC_IG) { 
 | 
        match_flags &= ~EFX_FILTER_MATCH_LOC_MAC_IG; 
 | 
        mcdi_flags |= 
 | 
            is_multicast_ether_addr(spec->loc_mac) ? 
 | 
            1 << mc_match : 
 | 
            1 << uc_match; 
 | 
    } 
 | 
  
 | 
    /* Did we map them all? */ 
 | 
    WARN_ON_ONCE(match_flags); 
 | 
  
 | 
    return mcdi_flags; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_pri(struct efx_mcdi_filter_table *table, 
 | 
                   const struct efx_filter_spec *spec) 
 | 
{ 
 | 
    u32 mcdi_flags = efx_mcdi_filter_mcdi_flags_from_spec(spec); 
 | 
    unsigned int match_pri; 
 | 
  
 | 
    for (match_pri = 0; 
 | 
         match_pri < table->rx_match_count; 
 | 
         match_pri++) 
 | 
        if (table->rx_match_mcdi_flags[match_pri] == mcdi_flags) 
 | 
            return match_pri; 
 | 
  
 | 
    return -EPROTONOSUPPORT; 
 | 
} 
 | 
  
 | 
static s32 efx_mcdi_filter_insert_locked(struct efx_nic *efx, 
 | 
                     struct efx_filter_spec *spec, 
 | 
                     bool replace_equal) 
 | 
{ 
 | 
    DECLARE_BITMAP(mc_rem_map, EFX_EF10_FILTER_SEARCH_LIMIT); 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    struct efx_filter_spec *saved_spec; 
 | 
    struct efx_rss_context *ctx = NULL; 
 | 
    unsigned int match_pri, hash; 
 | 
    unsigned int priv_flags; 
 | 
    bool rss_locked = false; 
 | 
    bool replacing = false; 
 | 
    unsigned int depth, i; 
 | 
    int ins_index = -1; 
 | 
    DEFINE_WAIT(wait); 
 | 
    bool is_mc_recip; 
 | 
    s32 rc; 
 | 
  
 | 
    WARN_ON(!rwsem_is_locked(&efx->filter_sem)); 
 | 
    table = efx->filter_state; 
 | 
    down_write(&table->lock); 
 | 
  
 | 
    /* For now, only support RX filters */ 
 | 
    if ((spec->flags & (EFX_FILTER_FLAG_RX | EFX_FILTER_FLAG_TX)) != 
 | 
        EFX_FILTER_FLAG_RX) { 
 | 
        rc = -EINVAL; 
 | 
        goto out_unlock; 
 | 
    } 
 | 
  
 | 
    rc = efx_mcdi_filter_pri(table, spec); 
 | 
    if (rc < 0) 
 | 
        goto out_unlock; 
 | 
    match_pri = rc; 
 | 
  
 | 
    hash = efx_filter_spec_hash(spec); 
 | 
    is_mc_recip = efx_filter_is_mc_recipient(spec); 
 | 
    if (is_mc_recip) 
 | 
        bitmap_zero(mc_rem_map, EFX_EF10_FILTER_SEARCH_LIMIT); 
 | 
  
 | 
    if (spec->flags & EFX_FILTER_FLAG_RX_RSS) { 
 | 
        mutex_lock(&efx->rss_lock); 
 | 
        rss_locked = true; 
 | 
        if (spec->rss_context) 
 | 
            ctx = efx_find_rss_context_entry(efx, spec->rss_context); 
 | 
        else 
 | 
            ctx = &efx->rss_context; 
 | 
        if (!ctx) { 
 | 
            rc = -ENOENT; 
 | 
            goto out_unlock; 
 | 
        } 
 | 
        if (ctx->context_id == EFX_MCDI_RSS_CONTEXT_INVALID) { 
 | 
            rc = -EOPNOTSUPP; 
 | 
            goto out_unlock; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* Find any existing filters with the same match tuple or 
 | 
     * else a free slot to insert at. 
 | 
     */ 
 | 
    for (depth = 1; depth < EFX_EF10_FILTER_SEARCH_LIMIT; depth++) { 
 | 
        i = (hash + depth) & (EFX_MCDI_FILTER_TBL_ROWS - 1); 
 | 
        saved_spec = efx_mcdi_filter_entry_spec(table, i); 
 | 
  
 | 
        if (!saved_spec) { 
 | 
            if (ins_index < 0) 
 | 
                ins_index = i; 
 | 
        } else if (efx_filter_spec_equal(spec, saved_spec)) { 
 | 
            if (spec->priority < saved_spec->priority && 
 | 
                spec->priority != EFX_FILTER_PRI_AUTO) { 
 | 
                rc = -EPERM; 
 | 
                goto out_unlock; 
 | 
            } 
 | 
            if (!is_mc_recip) { 
 | 
                /* This is the only one */ 
 | 
                if (spec->priority == 
 | 
                    saved_spec->priority && 
 | 
                    !replace_equal) { 
 | 
                    rc = -EEXIST; 
 | 
                    goto out_unlock; 
 | 
                } 
 | 
                ins_index = i; 
 | 
                break; 
 | 
            } else if (spec->priority > 
 | 
                   saved_spec->priority || 
 | 
                   (spec->priority == 
 | 
                    saved_spec->priority && 
 | 
                    replace_equal)) { 
 | 
                if (ins_index < 0) 
 | 
                    ins_index = i; 
 | 
                else 
 | 
                    __set_bit(depth, mc_rem_map); 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* Once we reach the maximum search depth, use the first suitable 
 | 
     * slot, or return -EBUSY if there was none 
 | 
     */ 
 | 
    if (ins_index < 0) { 
 | 
        rc = -EBUSY; 
 | 
        goto out_unlock; 
 | 
    } 
 | 
  
 | 
    /* Create a software table entry if necessary. */ 
 | 
    saved_spec = efx_mcdi_filter_entry_spec(table, ins_index); 
 | 
    if (saved_spec) { 
 | 
        if (spec->priority == EFX_FILTER_PRI_AUTO && 
 | 
            saved_spec->priority >= EFX_FILTER_PRI_AUTO) { 
 | 
            /* Just make sure it won't be removed */ 
 | 
            if (saved_spec->priority > EFX_FILTER_PRI_AUTO) 
 | 
                saved_spec->flags |= EFX_FILTER_FLAG_RX_OVER_AUTO; 
 | 
            table->entry[ins_index].spec &= 
 | 
                ~EFX_EF10_FILTER_FLAG_AUTO_OLD; 
 | 
            rc = ins_index; 
 | 
            goto out_unlock; 
 | 
        } 
 | 
        replacing = true; 
 | 
        priv_flags = efx_mcdi_filter_entry_flags(table, ins_index); 
 | 
    } else { 
 | 
        saved_spec = kmalloc(sizeof(*spec), GFP_ATOMIC); 
 | 
        if (!saved_spec) { 
 | 
            rc = -ENOMEM; 
 | 
            goto out_unlock; 
 | 
        } 
 | 
        *saved_spec = *spec; 
 | 
        priv_flags = 0; 
 | 
    } 
 | 
    efx_mcdi_filter_set_entry(table, ins_index, saved_spec, priv_flags); 
 | 
  
 | 
    /* Actually insert the filter on the HW */ 
 | 
    rc = efx_mcdi_filter_push(efx, spec, &table->entry[ins_index].handle, 
 | 
                  ctx, replacing); 
 | 
  
 | 
    if (rc == -EINVAL && efx->must_realloc_vis) 
 | 
        /* The MC rebooted under us, causing it to reject our filter 
 | 
         * insertion as pointing to an invalid VI (spec->dmaq_id). 
 | 
         */ 
 | 
        rc = -EAGAIN; 
 | 
  
 | 
    /* Finalise the software table entry */ 
 | 
    if (rc == 0) { 
 | 
        if (replacing) { 
 | 
            /* Update the fields that may differ */ 
 | 
            if (saved_spec->priority == EFX_FILTER_PRI_AUTO) 
 | 
                saved_spec->flags |= 
 | 
                    EFX_FILTER_FLAG_RX_OVER_AUTO; 
 | 
            saved_spec->priority = spec->priority; 
 | 
            saved_spec->flags &= EFX_FILTER_FLAG_RX_OVER_AUTO; 
 | 
            saved_spec->flags |= spec->flags; 
 | 
            saved_spec->rss_context = spec->rss_context; 
 | 
            saved_spec->dmaq_id = spec->dmaq_id; 
 | 
        } 
 | 
    } else if (!replacing) { 
 | 
        kfree(saved_spec); 
 | 
        saved_spec = NULL; 
 | 
    } else { 
 | 
        /* We failed to replace, so the old filter is still present. 
 | 
         * Roll back the software table to reflect this.  In fact the 
 | 
         * efx_mcdi_filter_set_entry() call below will do the right 
 | 
         * thing, so nothing extra is needed here. 
 | 
         */ 
 | 
    } 
 | 
    efx_mcdi_filter_set_entry(table, ins_index, saved_spec, priv_flags); 
 | 
  
 | 
    /* Remove and finalise entries for lower-priority multicast 
 | 
     * recipients 
 | 
     */ 
 | 
    if (is_mc_recip) { 
 | 
        MCDI_DECLARE_BUF(inbuf, MC_CMD_FILTER_OP_EXT_IN_LEN); 
 | 
        unsigned int depth, i; 
 | 
  
 | 
        memset(inbuf, 0, sizeof(inbuf)); 
 | 
  
 | 
        for (depth = 0; depth < EFX_EF10_FILTER_SEARCH_LIMIT; depth++) { 
 | 
            if (!test_bit(depth, mc_rem_map)) 
 | 
                continue; 
 | 
  
 | 
            i = (hash + depth) & (EFX_MCDI_FILTER_TBL_ROWS - 1); 
 | 
            saved_spec = efx_mcdi_filter_entry_spec(table, i); 
 | 
            priv_flags = efx_mcdi_filter_entry_flags(table, i); 
 | 
  
 | 
            if (rc == 0) { 
 | 
                MCDI_SET_DWORD(inbuf, FILTER_OP_IN_OP, 
 | 
                           MC_CMD_FILTER_OP_IN_OP_UNSUBSCRIBE); 
 | 
                MCDI_SET_QWORD(inbuf, FILTER_OP_IN_HANDLE, 
 | 
                           table->entry[i].handle); 
 | 
                rc = efx_mcdi_rpc(efx, MC_CMD_FILTER_OP, 
 | 
                          inbuf, sizeof(inbuf), 
 | 
                          NULL, 0, NULL); 
 | 
            } 
 | 
  
 | 
            if (rc == 0) { 
 | 
                kfree(saved_spec); 
 | 
                saved_spec = NULL; 
 | 
                priv_flags = 0; 
 | 
            } 
 | 
            efx_mcdi_filter_set_entry(table, i, saved_spec, 
 | 
                          priv_flags); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    /* If successful, return the inserted filter ID */ 
 | 
    if (rc == 0) 
 | 
        rc = efx_mcdi_filter_make_filter_id(match_pri, ins_index); 
 | 
  
 | 
out_unlock: 
 | 
    if (rss_locked) 
 | 
        mutex_unlock(&efx->rss_lock); 
 | 
    up_write(&table->lock); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
s32 efx_mcdi_filter_insert(struct efx_nic *efx, struct efx_filter_spec *spec, 
 | 
               bool replace_equal) 
 | 
{ 
 | 
    s32 ret; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    ret = efx_mcdi_filter_insert_locked(efx, spec, replace_equal); 
 | 
    up_read(&efx->filter_sem); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Remove a filter. 
 | 
 * If !by_index, remove by ID 
 | 
 * If by_index, remove by index 
 | 
 * Filter ID may come from userland and must be range-checked. 
 | 
 * Caller must hold efx->filter_sem for read, and efx->filter_state->lock 
 | 
 * for write. 
 | 
 */ 
 | 
static int efx_mcdi_filter_remove_internal(struct efx_nic *efx, 
 | 
                       unsigned int priority_mask, 
 | 
                       u32 filter_id, bool by_index) 
 | 
{ 
 | 
    unsigned int filter_idx = efx_mcdi_filter_get_unsafe_id(filter_id); 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    MCDI_DECLARE_BUF(inbuf, 
 | 
             MC_CMD_FILTER_OP_IN_HANDLE_OFST + 
 | 
             MC_CMD_FILTER_OP_IN_HANDLE_LEN); 
 | 
    struct efx_filter_spec *spec; 
 | 
    DEFINE_WAIT(wait); 
 | 
    int rc; 
 | 
  
 | 
    spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
    if (!spec || 
 | 
        (!by_index && 
 | 
         efx_mcdi_filter_pri(table, spec) != 
 | 
         efx_mcdi_filter_get_unsafe_pri(filter_id))) 
 | 
        return -ENOENT; 
 | 
  
 | 
    if (spec->flags & EFX_FILTER_FLAG_RX_OVER_AUTO && 
 | 
        priority_mask == (1U << EFX_FILTER_PRI_AUTO)) { 
 | 
        /* Just remove flags */ 
 | 
        spec->flags &= ~EFX_FILTER_FLAG_RX_OVER_AUTO; 
 | 
        table->entry[filter_idx].spec &= ~EFX_EF10_FILTER_FLAG_AUTO_OLD; 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    if (!(priority_mask & (1U << spec->priority))) 
 | 
        return -ENOENT; 
 | 
  
 | 
    if (spec->flags & EFX_FILTER_FLAG_RX_OVER_AUTO) { 
 | 
        /* Reset to an automatic filter */ 
 | 
  
 | 
        struct efx_filter_spec new_spec = *spec; 
 | 
  
 | 
        new_spec.priority = EFX_FILTER_PRI_AUTO; 
 | 
        new_spec.flags = (EFX_FILTER_FLAG_RX | 
 | 
                  (efx_rss_active(&efx->rss_context) ? 
 | 
                   EFX_FILTER_FLAG_RX_RSS : 0)); 
 | 
        new_spec.dmaq_id = 0; 
 | 
        new_spec.rss_context = 0; 
 | 
        rc = efx_mcdi_filter_push(efx, &new_spec, 
 | 
                      &table->entry[filter_idx].handle, 
 | 
                      &efx->rss_context, 
 | 
                      true); 
 | 
  
 | 
        if (rc == 0) 
 | 
            *spec = new_spec; 
 | 
    } else { 
 | 
        /* Really remove the filter */ 
 | 
  
 | 
        MCDI_SET_DWORD(inbuf, FILTER_OP_IN_OP, 
 | 
                   efx_mcdi_filter_is_exclusive(spec) ? 
 | 
                   MC_CMD_FILTER_OP_IN_OP_REMOVE : 
 | 
                   MC_CMD_FILTER_OP_IN_OP_UNSUBSCRIBE); 
 | 
        MCDI_SET_QWORD(inbuf, FILTER_OP_IN_HANDLE, 
 | 
                   table->entry[filter_idx].handle); 
 | 
        rc = efx_mcdi_rpc_quiet(efx, MC_CMD_FILTER_OP, 
 | 
                    inbuf, sizeof(inbuf), NULL, 0, NULL); 
 | 
  
 | 
        if ((rc == 0) || (rc == -ENOENT)) { 
 | 
            /* Filter removed OK or didn't actually exist */ 
 | 
            kfree(spec); 
 | 
            efx_mcdi_filter_set_entry(table, filter_idx, NULL, 0); 
 | 
        } else { 
 | 
            efx_mcdi_display_error(efx, MC_CMD_FILTER_OP, 
 | 
                           MC_CMD_FILTER_OP_EXT_IN_LEN, 
 | 
                           NULL, 0, rc); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return rc; 
 | 
} 
 | 
  
 | 
/* Remove filters that weren't renewed. */ 
 | 
static void efx_mcdi_filter_remove_old(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    int remove_failed = 0; 
 | 
    int remove_noent = 0; 
 | 
    int rc; 
 | 
    int i; 
 | 
  
 | 
    down_write(&table->lock); 
 | 
    for (i = 0; i < EFX_MCDI_FILTER_TBL_ROWS; i++) { 
 | 
        if (READ_ONCE(table->entry[i].spec) & 
 | 
            EFX_EF10_FILTER_FLAG_AUTO_OLD) { 
 | 
            rc = efx_mcdi_filter_remove_internal(efx, 
 | 
                    1U << EFX_FILTER_PRI_AUTO, i, true); 
 | 
            if (rc == -ENOENT) 
 | 
                remove_noent++; 
 | 
            else if (rc) 
 | 
                remove_failed++; 
 | 
        } 
 | 
    } 
 | 
    up_write(&table->lock); 
 | 
  
 | 
    if (remove_failed) 
 | 
        netif_info(efx, drv, efx->net_dev, 
 | 
               "%s: failed to remove %d filters\n", 
 | 
               __func__, remove_failed); 
 | 
    if (remove_noent) 
 | 
        netif_info(efx, drv, efx->net_dev, 
 | 
               "%s: failed to remove %d non-existent filters\n", 
 | 
               __func__, remove_noent); 
 | 
} 
 | 
  
 | 
int efx_mcdi_filter_remove_safe(struct efx_nic *efx, 
 | 
                enum efx_filter_priority priority, 
 | 
                u32 filter_id) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    int rc; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_write(&table->lock); 
 | 
    rc = efx_mcdi_filter_remove_internal(efx, 1U << priority, filter_id, 
 | 
                         false); 
 | 
    up_write(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
/* Caller must hold efx->filter_sem for read */ 
 | 
static void efx_mcdi_filter_remove_unsafe(struct efx_nic *efx, 
 | 
                      enum efx_filter_priority priority, 
 | 
                      u32 filter_id) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
  
 | 
    if (filter_id == EFX_EF10_FILTER_ID_INVALID) 
 | 
        return; 
 | 
  
 | 
    down_write(&table->lock); 
 | 
    efx_mcdi_filter_remove_internal(efx, 1U << priority, filter_id, 
 | 
                    true); 
 | 
    up_write(&table->lock); 
 | 
} 
 | 
  
 | 
int efx_mcdi_filter_get_safe(struct efx_nic *efx, 
 | 
                 enum efx_filter_priority priority, 
 | 
                 u32 filter_id, struct efx_filter_spec *spec) 
 | 
{ 
 | 
    unsigned int filter_idx = efx_mcdi_filter_get_unsafe_id(filter_id); 
 | 
    const struct efx_filter_spec *saved_spec; 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    int rc; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_read(&table->lock); 
 | 
    saved_spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
    if (saved_spec && saved_spec->priority == priority && 
 | 
        efx_mcdi_filter_pri(table, saved_spec) == 
 | 
        efx_mcdi_filter_get_unsafe_pri(filter_id)) { 
 | 
        *spec = *saved_spec; 
 | 
        rc = 0; 
 | 
    } else { 
 | 
        rc = -ENOENT; 
 | 
    } 
 | 
    up_read(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_insert_addr_list(struct efx_nic *efx, 
 | 
                        struct efx_mcdi_filter_vlan *vlan, 
 | 
                        bool multicast, bool rollback) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_mcdi_dev_addr *addr_list; 
 | 
    enum efx_filter_flags filter_flags; 
 | 
    struct efx_filter_spec spec; 
 | 
    u8 baddr[ETH_ALEN]; 
 | 
    unsigned int i, j; 
 | 
    int addr_count; 
 | 
    u16 *ids; 
 | 
    int rc; 
 | 
  
 | 
    if (multicast) { 
 | 
        addr_list = table->dev_mc_list; 
 | 
        addr_count = table->dev_mc_count; 
 | 
        ids = vlan->mc; 
 | 
    } else { 
 | 
        addr_list = table->dev_uc_list; 
 | 
        addr_count = table->dev_uc_count; 
 | 
        ids = vlan->uc; 
 | 
    } 
 | 
  
 | 
    filter_flags = efx_rss_active(&efx->rss_context) ? EFX_FILTER_FLAG_RX_RSS : 0; 
 | 
  
 | 
    /* Insert/renew filters */ 
 | 
    for (i = 0; i < addr_count; i++) { 
 | 
        EFX_WARN_ON_PARANOID(ids[i] != EFX_EF10_FILTER_ID_INVALID); 
 | 
        efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, filter_flags, 0); 
 | 
        efx_filter_set_eth_local(&spec, vlan->vid, addr_list[i].addr); 
 | 
        rc = efx_mcdi_filter_insert_locked(efx, &spec, true); 
 | 
        if (rc < 0) { 
 | 
            if (rollback) { 
 | 
                netif_info(efx, drv, efx->net_dev, 
 | 
                       "efx_mcdi_filter_insert failed rc=%d\n", 
 | 
                       rc); 
 | 
                /* Fall back to promiscuous */ 
 | 
                for (j = 0; j < i; j++) { 
 | 
                    efx_mcdi_filter_remove_unsafe( 
 | 
                        efx, EFX_FILTER_PRI_AUTO, 
 | 
                        ids[j]); 
 | 
                    ids[j] = EFX_EF10_FILTER_ID_INVALID; 
 | 
                } 
 | 
                return rc; 
 | 
            } else { 
 | 
                /* keep invalid ID, and carry on */ 
 | 
            } 
 | 
        } else { 
 | 
            ids[i] = efx_mcdi_filter_get_unsafe_id(rc); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    if (multicast && rollback) { 
 | 
        /* Also need an Ethernet broadcast filter */ 
 | 
        EFX_WARN_ON_PARANOID(vlan->default_filters[EFX_EF10_BCAST] != 
 | 
                     EFX_EF10_FILTER_ID_INVALID); 
 | 
        efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, filter_flags, 0); 
 | 
        eth_broadcast_addr(baddr); 
 | 
        efx_filter_set_eth_local(&spec, vlan->vid, baddr); 
 | 
        rc = efx_mcdi_filter_insert_locked(efx, &spec, true); 
 | 
        if (rc < 0) { 
 | 
            netif_warn(efx, drv, efx->net_dev, 
 | 
                   "Broadcast filter insert failed rc=%d\n", rc); 
 | 
            /* Fall back to promiscuous */ 
 | 
            for (j = 0; j < i; j++) { 
 | 
                efx_mcdi_filter_remove_unsafe( 
 | 
                    efx, EFX_FILTER_PRI_AUTO, 
 | 
                    ids[j]); 
 | 
                ids[j] = EFX_EF10_FILTER_ID_INVALID; 
 | 
            } 
 | 
            return rc; 
 | 
        } else { 
 | 
            vlan->default_filters[EFX_EF10_BCAST] = 
 | 
                efx_mcdi_filter_get_unsafe_id(rc); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_insert_def(struct efx_nic *efx, 
 | 
                      struct efx_mcdi_filter_vlan *vlan, 
 | 
                      enum efx_encap_type encap_type, 
 | 
                      bool multicast, bool rollback) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    enum efx_filter_flags filter_flags; 
 | 
    struct efx_filter_spec spec; 
 | 
    u8 baddr[ETH_ALEN]; 
 | 
    int rc; 
 | 
    u16 *id; 
 | 
  
 | 
    filter_flags = efx_rss_active(&efx->rss_context) ? EFX_FILTER_FLAG_RX_RSS : 0; 
 | 
  
 | 
    efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, filter_flags, 0); 
 | 
  
 | 
    if (multicast) 
 | 
        efx_filter_set_mc_def(&spec); 
 | 
    else 
 | 
        efx_filter_set_uc_def(&spec); 
 | 
  
 | 
    if (encap_type) { 
 | 
        if (efx_has_cap(efx, VXLAN_NVGRE)) 
 | 
            efx_filter_set_encap_type(&spec, encap_type); 
 | 
        else 
 | 
            /* 
 | 
             * don't insert encap filters on non-supporting 
 | 
             * platforms. ID will be left as INVALID. 
 | 
             */ 
 | 
            return 0; 
 | 
    } 
 | 
  
 | 
    if (vlan->vid != EFX_FILTER_VID_UNSPEC) 
 | 
        efx_filter_set_eth_local(&spec, vlan->vid, NULL); 
 | 
  
 | 
    rc = efx_mcdi_filter_insert_locked(efx, &spec, true); 
 | 
    if (rc < 0) { 
 | 
        const char *um = multicast ? "Multicast" : "Unicast"; 
 | 
        const char *encap_name = ""; 
 | 
        const char *encap_ipv = ""; 
 | 
  
 | 
        if ((encap_type & EFX_ENCAP_TYPES_MASK) == 
 | 
            EFX_ENCAP_TYPE_VXLAN) 
 | 
            encap_name = "VXLAN "; 
 | 
        else if ((encap_type & EFX_ENCAP_TYPES_MASK) == 
 | 
             EFX_ENCAP_TYPE_NVGRE) 
 | 
            encap_name = "NVGRE "; 
 | 
        else if ((encap_type & EFX_ENCAP_TYPES_MASK) == 
 | 
             EFX_ENCAP_TYPE_GENEVE) 
 | 
            encap_name = "GENEVE "; 
 | 
        if (encap_type & EFX_ENCAP_FLAG_IPV6) 
 | 
            encap_ipv = "IPv6 "; 
 | 
        else if (encap_type) 
 | 
            encap_ipv = "IPv4 "; 
 | 
  
 | 
        /* 
 | 
         * unprivileged functions can't insert mismatch filters 
 | 
         * for encapsulated or unicast traffic, so downgrade 
 | 
         * those warnings to debug. 
 | 
         */ 
 | 
        netif_cond_dbg(efx, drv, efx->net_dev, 
 | 
                   rc == -EPERM && (encap_type || !multicast), warn, 
 | 
                   "%s%s%s mismatch filter insert failed rc=%d\n", 
 | 
                   encap_name, encap_ipv, um, rc); 
 | 
    } else if (multicast) { 
 | 
        /* mapping from encap types to default filter IDs (multicast) */ 
 | 
        static enum efx_mcdi_filter_default_filters map[] = { 
 | 
            [EFX_ENCAP_TYPE_NONE] = EFX_EF10_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_VXLAN] = EFX_EF10_VXLAN4_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_NVGRE] = EFX_EF10_NVGRE4_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_GENEVE] = EFX_EF10_GENEVE4_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_VXLAN | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_VXLAN6_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_NVGRE | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_NVGRE6_MCDEF, 
 | 
            [EFX_ENCAP_TYPE_GENEVE | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_GENEVE6_MCDEF, 
 | 
        }; 
 | 
  
 | 
        /* quick bounds check (BCAST result impossible) */ 
 | 
        BUILD_BUG_ON(EFX_EF10_BCAST != 0); 
 | 
        if (encap_type >= ARRAY_SIZE(map) || map[encap_type] == 0) { 
 | 
            WARN_ON(1); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
        /* then follow map */ 
 | 
        id = &vlan->default_filters[map[encap_type]]; 
 | 
  
 | 
        EFX_WARN_ON_PARANOID(*id != EFX_EF10_FILTER_ID_INVALID); 
 | 
        *id = efx_mcdi_filter_get_unsafe_id(rc); 
 | 
        if (!table->mc_chaining && !encap_type) { 
 | 
            /* Also need an Ethernet broadcast filter */ 
 | 
            efx_filter_init_rx(&spec, EFX_FILTER_PRI_AUTO, 
 | 
                       filter_flags, 0); 
 | 
            eth_broadcast_addr(baddr); 
 | 
            efx_filter_set_eth_local(&spec, vlan->vid, baddr); 
 | 
            rc = efx_mcdi_filter_insert_locked(efx, &spec, true); 
 | 
            if (rc < 0) { 
 | 
                netif_warn(efx, drv, efx->net_dev, 
 | 
                       "Broadcast filter insert failed rc=%d\n", 
 | 
                       rc); 
 | 
                if (rollback) { 
 | 
                    /* Roll back the mc_def filter */ 
 | 
                    efx_mcdi_filter_remove_unsafe( 
 | 
                            efx, EFX_FILTER_PRI_AUTO, 
 | 
                            *id); 
 | 
                    *id = EFX_EF10_FILTER_ID_INVALID; 
 | 
                    return rc; 
 | 
                } 
 | 
            } else { 
 | 
                EFX_WARN_ON_PARANOID( 
 | 
                    vlan->default_filters[EFX_EF10_BCAST] != 
 | 
                    EFX_EF10_FILTER_ID_INVALID); 
 | 
                vlan->default_filters[EFX_EF10_BCAST] = 
 | 
                    efx_mcdi_filter_get_unsafe_id(rc); 
 | 
            } 
 | 
        } 
 | 
        rc = 0; 
 | 
    } else { 
 | 
        /* mapping from encap types to default filter IDs (unicast) */ 
 | 
        static enum efx_mcdi_filter_default_filters map[] = { 
 | 
            [EFX_ENCAP_TYPE_NONE] = EFX_EF10_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_VXLAN] = EFX_EF10_VXLAN4_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_NVGRE] = EFX_EF10_NVGRE4_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_GENEVE] = EFX_EF10_GENEVE4_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_VXLAN | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_VXLAN6_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_NVGRE | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_NVGRE6_UCDEF, 
 | 
            [EFX_ENCAP_TYPE_GENEVE | EFX_ENCAP_FLAG_IPV6] = 
 | 
                EFX_EF10_GENEVE6_UCDEF, 
 | 
        }; 
 | 
  
 | 
        /* quick bounds check (BCAST result impossible) */ 
 | 
        BUILD_BUG_ON(EFX_EF10_BCAST != 0); 
 | 
        if (encap_type >= ARRAY_SIZE(map) || map[encap_type] == 0) { 
 | 
            WARN_ON(1); 
 | 
            return -EINVAL; 
 | 
        } 
 | 
        /* then follow map */ 
 | 
        id = &vlan->default_filters[map[encap_type]]; 
 | 
        EFX_WARN_ON_PARANOID(*id != EFX_EF10_FILTER_ID_INVALID); 
 | 
        *id = rc; 
 | 
        rc = 0; 
 | 
    } 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Caller must hold efx->filter_sem for read if race against 
 | 
 * efx_mcdi_filter_table_remove() is possible 
 | 
 */ 
 | 
static void efx_mcdi_filter_vlan_sync_rx_mode(struct efx_nic *efx, 
 | 
                          struct efx_mcdi_filter_vlan *vlan) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
  
 | 
    /* 
 | 
     * Do not install unspecified VID if VLAN filtering is enabled. 
 | 
     * Do not install all specified VIDs if VLAN filtering is disabled. 
 | 
     */ 
 | 
    if ((vlan->vid == EFX_FILTER_VID_UNSPEC) == table->vlan_filter) 
 | 
        return; 
 | 
  
 | 
    /* Insert/renew unicast filters */ 
 | 
    if (table->uc_promisc) { 
 | 
        efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_NONE, 
 | 
                       false, false); 
 | 
        efx_mcdi_filter_insert_addr_list(efx, vlan, false, false); 
 | 
    } else { 
 | 
        /* 
 | 
         * If any of the filters failed to insert, fall back to 
 | 
         * promiscuous mode - add in the uc_def filter.  But keep 
 | 
         * our individual unicast filters. 
 | 
         */ 
 | 
        if (efx_mcdi_filter_insert_addr_list(efx, vlan, false, false)) 
 | 
            efx_mcdi_filter_insert_def(efx, vlan, 
 | 
                           EFX_ENCAP_TYPE_NONE, 
 | 
                           false, false); 
 | 
    } 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_VXLAN, 
 | 
                   false, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_VXLAN | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   false, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_NVGRE, 
 | 
                   false, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_NVGRE | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   false, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_GENEVE, 
 | 
                   false, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_GENEVE | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   false, false); 
 | 
  
 | 
    /* 
 | 
     * Insert/renew multicast filters 
 | 
     * 
 | 
     * If changing promiscuous state with cascaded multicast filters, remove 
 | 
     * old filters first, so that packets are dropped rather than duplicated 
 | 
     */ 
 | 
    if (table->mc_chaining && table->mc_promisc_last != table->mc_promisc) 
 | 
        efx_mcdi_filter_remove_old(efx); 
 | 
    if (table->mc_promisc) { 
 | 
        if (table->mc_chaining) { 
 | 
            /* 
 | 
             * If we failed to insert promiscuous filters, rollback 
 | 
             * and fall back to individual multicast filters 
 | 
             */ 
 | 
            if (efx_mcdi_filter_insert_def(efx, vlan, 
 | 
                               EFX_ENCAP_TYPE_NONE, 
 | 
                               true, true)) { 
 | 
                /* Changing promisc state, so remove old filters */ 
 | 
                efx_mcdi_filter_remove_old(efx); 
 | 
                efx_mcdi_filter_insert_addr_list(efx, vlan, 
 | 
                                 true, false); 
 | 
            } 
 | 
        } else { 
 | 
            /* 
 | 
             * If we failed to insert promiscuous filters, don't 
 | 
             * rollback.  Regardless, also insert the mc_list, 
 | 
             * unless it's incomplete due to overflow 
 | 
             */ 
 | 
            efx_mcdi_filter_insert_def(efx, vlan, 
 | 
                           EFX_ENCAP_TYPE_NONE, 
 | 
                           true, false); 
 | 
            if (!table->mc_overflow) 
 | 
                efx_mcdi_filter_insert_addr_list(efx, vlan, 
 | 
                                 true, false); 
 | 
        } 
 | 
    } else { 
 | 
        /* 
 | 
         * If any filters failed to insert, rollback and fall back to 
 | 
         * promiscuous mode - mc_def filter and maybe broadcast.  If 
 | 
         * that fails, roll back again and insert as many of our 
 | 
         * individual multicast filters as we can. 
 | 
         */ 
 | 
        if (efx_mcdi_filter_insert_addr_list(efx, vlan, true, true)) { 
 | 
            /* Changing promisc state, so remove old filters */ 
 | 
            if (table->mc_chaining) 
 | 
                efx_mcdi_filter_remove_old(efx); 
 | 
            if (efx_mcdi_filter_insert_def(efx, vlan, 
 | 
                               EFX_ENCAP_TYPE_NONE, 
 | 
                               true, true)) 
 | 
                efx_mcdi_filter_insert_addr_list(efx, vlan, 
 | 
                                 true, false); 
 | 
        } 
 | 
    } 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_VXLAN, 
 | 
                   true, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_VXLAN | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   true, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_NVGRE, 
 | 
                   true, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_NVGRE | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   true, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_GENEVE, 
 | 
                   true, false); 
 | 
    efx_mcdi_filter_insert_def(efx, vlan, EFX_ENCAP_TYPE_GENEVE | 
 | 
                          EFX_ENCAP_FLAG_IPV6, 
 | 
                   true, false); 
 | 
} 
 | 
  
 | 
int efx_mcdi_filter_clear_rx(struct efx_nic *efx, 
 | 
                 enum efx_filter_priority priority) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    unsigned int priority_mask; 
 | 
    unsigned int i; 
 | 
    int rc; 
 | 
  
 | 
    priority_mask = (((1U << (priority + 1)) - 1) & 
 | 
             ~(1U << EFX_FILTER_PRI_AUTO)); 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_write(&table->lock); 
 | 
    for (i = 0; i < EFX_MCDI_FILTER_TBL_ROWS; i++) { 
 | 
        rc = efx_mcdi_filter_remove_internal(efx, priority_mask, 
 | 
                             i, true); 
 | 
        if (rc && rc != -ENOENT) 
 | 
            break; 
 | 
        rc = 0; 
 | 
    } 
 | 
  
 | 
    up_write(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
u32 efx_mcdi_filter_count_rx_used(struct efx_nic *efx, 
 | 
                 enum efx_filter_priority priority) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    unsigned int filter_idx; 
 | 
    s32 count = 0; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_read(&table->lock); 
 | 
    for (filter_idx = 0; filter_idx < EFX_MCDI_FILTER_TBL_ROWS; filter_idx++) { 
 | 
        if (table->entry[filter_idx].spec && 
 | 
            efx_mcdi_filter_entry_spec(table, filter_idx)->priority == 
 | 
            priority) 
 | 
            ++count; 
 | 
    } 
 | 
    up_read(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return count; 
 | 
} 
 | 
  
 | 
u32 efx_mcdi_filter_get_rx_id_limit(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
  
 | 
    return table->rx_match_count * EFX_MCDI_FILTER_TBL_ROWS * 2; 
 | 
} 
 | 
  
 | 
s32 efx_mcdi_filter_get_rx_ids(struct efx_nic *efx, 
 | 
                   enum efx_filter_priority priority, 
 | 
                   u32 *buf, u32 size) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    struct efx_filter_spec *spec; 
 | 
    unsigned int filter_idx; 
 | 
    s32 count = 0; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_read(&table->lock); 
 | 
  
 | 
    for (filter_idx = 0; filter_idx < EFX_MCDI_FILTER_TBL_ROWS; filter_idx++) { 
 | 
        spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
        if (spec && spec->priority == priority) { 
 | 
            if (count == size) { 
 | 
                count = -EMSGSIZE; 
 | 
                break; 
 | 
            } 
 | 
            buf[count++] = 
 | 
                efx_mcdi_filter_make_filter_id( 
 | 
                    efx_mcdi_filter_pri(table, spec), 
 | 
                    filter_idx); 
 | 
        } 
 | 
    } 
 | 
    up_read(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return count; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_match_flags_from_mcdi(bool encap, u32 mcdi_flags) 
 | 
{ 
 | 
    int match_flags = 0; 
 | 
  
 | 
#define MAP_FLAG(gen_flag, mcdi_field) do {                \ 
 | 
        u32 old_mcdi_flags = mcdi_flags;            \ 
 | 
        mcdi_flags &= ~(1 << MC_CMD_FILTER_OP_EXT_IN_MATCH_ ##    \ 
 | 
                     mcdi_field ## _LBN);        \ 
 | 
        if (mcdi_flags != old_mcdi_flags)            \ 
 | 
            match_flags |= EFX_FILTER_MATCH_ ## gen_flag;    \ 
 | 
    } while (0) 
 | 
  
 | 
    if (encap) { 
 | 
        /* encap filters must specify encap type */ 
 | 
        match_flags |= EFX_FILTER_MATCH_ENCAP_TYPE; 
 | 
        /* and imply ethertype and ip proto */ 
 | 
        mcdi_flags &= 
 | 
            ~(1 << MC_CMD_FILTER_OP_EXT_IN_MATCH_IP_PROTO_LBN); 
 | 
        mcdi_flags &= 
 | 
            ~(1 << MC_CMD_FILTER_OP_EXT_IN_MATCH_ETHER_TYPE_LBN); 
 | 
        /* VLAN tags refer to the outer packet */ 
 | 
        MAP_FLAG(INNER_VID, INNER_VLAN); 
 | 
        MAP_FLAG(OUTER_VID, OUTER_VLAN); 
 | 
        /* everything else refers to the inner packet */ 
 | 
        MAP_FLAG(LOC_MAC_IG, IFRM_UNKNOWN_UCAST_DST); 
 | 
        MAP_FLAG(LOC_MAC_IG, IFRM_UNKNOWN_MCAST_DST); 
 | 
        MAP_FLAG(REM_HOST, IFRM_SRC_IP); 
 | 
        MAP_FLAG(LOC_HOST, IFRM_DST_IP); 
 | 
        MAP_FLAG(REM_MAC, IFRM_SRC_MAC); 
 | 
        MAP_FLAG(REM_PORT, IFRM_SRC_PORT); 
 | 
        MAP_FLAG(LOC_MAC, IFRM_DST_MAC); 
 | 
        MAP_FLAG(LOC_PORT, IFRM_DST_PORT); 
 | 
        MAP_FLAG(ETHER_TYPE, IFRM_ETHER_TYPE); 
 | 
        MAP_FLAG(IP_PROTO, IFRM_IP_PROTO); 
 | 
    } else { 
 | 
        MAP_FLAG(LOC_MAC_IG, UNKNOWN_UCAST_DST); 
 | 
        MAP_FLAG(LOC_MAC_IG, UNKNOWN_MCAST_DST); 
 | 
        MAP_FLAG(REM_HOST, SRC_IP); 
 | 
        MAP_FLAG(LOC_HOST, DST_IP); 
 | 
        MAP_FLAG(REM_MAC, SRC_MAC); 
 | 
        MAP_FLAG(REM_PORT, SRC_PORT); 
 | 
        MAP_FLAG(LOC_MAC, DST_MAC); 
 | 
        MAP_FLAG(LOC_PORT, DST_PORT); 
 | 
        MAP_FLAG(ETHER_TYPE, ETHER_TYPE); 
 | 
        MAP_FLAG(INNER_VID, INNER_VLAN); 
 | 
        MAP_FLAG(OUTER_VID, OUTER_VLAN); 
 | 
        MAP_FLAG(IP_PROTO, IP_PROTO); 
 | 
    } 
 | 
#undef MAP_FLAG 
 | 
  
 | 
    /* Did we map them all? */ 
 | 
    if (mcdi_flags) 
 | 
        return -EINVAL; 
 | 
  
 | 
    return match_flags; 
 | 
} 
 | 
  
 | 
bool efx_mcdi_filter_match_supported(struct efx_mcdi_filter_table *table, 
 | 
                     bool encap, 
 | 
                     enum efx_filter_match_flags match_flags) 
 | 
{ 
 | 
    unsigned int match_pri; 
 | 
    int mf; 
 | 
  
 | 
    for (match_pri = 0; 
 | 
         match_pri < table->rx_match_count; 
 | 
         match_pri++) { 
 | 
        mf = efx_mcdi_filter_match_flags_from_mcdi(encap, 
 | 
                table->rx_match_mcdi_flags[match_pri]); 
 | 
        if (mf == match_flags) 
 | 
            return true; 
 | 
    } 
 | 
  
 | 
    return false; 
 | 
} 
 | 
  
 | 
static int 
 | 
efx_mcdi_filter_table_probe_matches(struct efx_nic *efx, 
 | 
                    struct efx_mcdi_filter_table *table, 
 | 
                    bool encap) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_GET_PARSER_DISP_INFO_IN_LEN); 
 | 
    MCDI_DECLARE_BUF(outbuf, MC_CMD_GET_PARSER_DISP_INFO_OUT_LENMAX); 
 | 
    unsigned int pd_match_pri, pd_match_count; 
 | 
    size_t outlen; 
 | 
    int rc; 
 | 
  
 | 
    /* Find out which RX filter types are supported, and their priorities */ 
 | 
    MCDI_SET_DWORD(inbuf, GET_PARSER_DISP_INFO_IN_OP, 
 | 
               encap ? 
 | 
               MC_CMD_GET_PARSER_DISP_INFO_IN_OP_GET_SUPPORTED_ENCAP_RX_MATCHES : 
 | 
               MC_CMD_GET_PARSER_DISP_INFO_IN_OP_GET_SUPPORTED_RX_MATCHES); 
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_GET_PARSER_DISP_INFO, 
 | 
              inbuf, sizeof(inbuf), outbuf, sizeof(outbuf), 
 | 
              &outlen); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    pd_match_count = MCDI_VAR_ARRAY_LEN( 
 | 
        outlen, GET_PARSER_DISP_INFO_OUT_SUPPORTED_MATCHES); 
 | 
  
 | 
    for (pd_match_pri = 0; pd_match_pri < pd_match_count; pd_match_pri++) { 
 | 
        u32 mcdi_flags = 
 | 
            MCDI_ARRAY_DWORD( 
 | 
                outbuf, 
 | 
                GET_PARSER_DISP_INFO_OUT_SUPPORTED_MATCHES, 
 | 
                pd_match_pri); 
 | 
        rc = efx_mcdi_filter_match_flags_from_mcdi(encap, mcdi_flags); 
 | 
        if (rc < 0) { 
 | 
            netif_dbg(efx, probe, efx->net_dev, 
 | 
                  "%s: fw flags %#x pri %u not supported in driver\n", 
 | 
                  __func__, mcdi_flags, pd_match_pri); 
 | 
        } else { 
 | 
            netif_dbg(efx, probe, efx->net_dev, 
 | 
                  "%s: fw flags %#x pri %u supported as driver flags %#x pri %u\n", 
 | 
                  __func__, mcdi_flags, pd_match_pri, 
 | 
                  rc, table->rx_match_count); 
 | 
            table->rx_match_mcdi_flags[table->rx_match_count] = mcdi_flags; 
 | 
            table->rx_match_count++; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int efx_mcdi_filter_table_probe(struct efx_nic *efx, bool multicast_chaining) 
 | 
{ 
 | 
    struct net_device *net_dev = efx->net_dev; 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    int rc; 
 | 
  
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return -EINVAL; 
 | 
  
 | 
    if (efx->filter_state) /* already probed */ 
 | 
        return 0; 
 | 
  
 | 
    table = kzalloc(sizeof(*table), GFP_KERNEL); 
 | 
    if (!table) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    table->mc_chaining = multicast_chaining; 
 | 
    table->rx_match_count = 0; 
 | 
    rc = efx_mcdi_filter_table_probe_matches(efx, table, false); 
 | 
    if (rc) 
 | 
        goto fail; 
 | 
    if (efx_has_cap(efx, VXLAN_NVGRE)) 
 | 
        rc = efx_mcdi_filter_table_probe_matches(efx, table, true); 
 | 
    if (rc) 
 | 
        goto fail; 
 | 
    if ((efx_supported_features(efx) & NETIF_F_HW_VLAN_CTAG_FILTER) && 
 | 
        !(efx_mcdi_filter_match_supported(table, false, 
 | 
        (EFX_FILTER_MATCH_OUTER_VID | EFX_FILTER_MATCH_LOC_MAC)) && 
 | 
          efx_mcdi_filter_match_supported(table, false, 
 | 
        (EFX_FILTER_MATCH_OUTER_VID | EFX_FILTER_MATCH_LOC_MAC_IG)))) { 
 | 
        netif_info(efx, probe, net_dev, 
 | 
               "VLAN filters are not supported in this firmware variant\n"); 
 | 
        net_dev->features &= ~NETIF_F_HW_VLAN_CTAG_FILTER; 
 | 
        efx->fixed_features &= ~NETIF_F_HW_VLAN_CTAG_FILTER; 
 | 
        net_dev->hw_features &= ~NETIF_F_HW_VLAN_CTAG_FILTER; 
 | 
    } 
 | 
  
 | 
    table->entry = vzalloc(array_size(EFX_MCDI_FILTER_TBL_ROWS, 
 | 
                      sizeof(*table->entry))); 
 | 
    if (!table->entry) { 
 | 
        rc = -ENOMEM; 
 | 
        goto fail; 
 | 
    } 
 | 
  
 | 
    table->mc_promisc_last = false; 
 | 
    table->vlan_filter = 
 | 
        !!(efx->net_dev->features & NETIF_F_HW_VLAN_CTAG_FILTER); 
 | 
    INIT_LIST_HEAD(&table->vlan_list); 
 | 
    init_rwsem(&table->lock); 
 | 
  
 | 
    efx->filter_state = table; 
 | 
  
 | 
    return 0; 
 | 
fail: 
 | 
    kfree(table); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
void efx_mcdi_filter_table_reset_mc_allocations(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
  
 | 
    if (table) { 
 | 
        table->must_restore_filters = true; 
 | 
        table->must_restore_rss_contexts = true; 
 | 
    } 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Caller must hold efx->filter_sem for read if race against 
 | 
 * efx_mcdi_filter_table_remove() is possible 
 | 
 */ 
 | 
void efx_mcdi_filter_table_restore(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    unsigned int invalid_filters = 0, failed = 0; 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
    struct efx_filter_spec *spec; 
 | 
    struct efx_rss_context *ctx; 
 | 
    unsigned int filter_idx; 
 | 
    u32 mcdi_flags; 
 | 
    int match_pri; 
 | 
    int rc, i; 
 | 
  
 | 
    WARN_ON(!rwsem_is_locked(&efx->filter_sem)); 
 | 
  
 | 
    if (!table || !table->must_restore_filters) 
 | 
        return; 
 | 
  
 | 
    down_write(&table->lock); 
 | 
    mutex_lock(&efx->rss_lock); 
 | 
  
 | 
    for (filter_idx = 0; filter_idx < EFX_MCDI_FILTER_TBL_ROWS; filter_idx++) { 
 | 
        spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
        if (!spec) 
 | 
            continue; 
 | 
  
 | 
        mcdi_flags = efx_mcdi_filter_mcdi_flags_from_spec(spec); 
 | 
        match_pri = 0; 
 | 
        while (match_pri < table->rx_match_count && 
 | 
               table->rx_match_mcdi_flags[match_pri] != mcdi_flags) 
 | 
            ++match_pri; 
 | 
        if (match_pri >= table->rx_match_count) { 
 | 
            invalid_filters++; 
 | 
            goto not_restored; 
 | 
        } 
 | 
        if (spec->rss_context) 
 | 
            ctx = efx_find_rss_context_entry(efx, spec->rss_context); 
 | 
        else 
 | 
            ctx = &efx->rss_context; 
 | 
        if (spec->flags & EFX_FILTER_FLAG_RX_RSS) { 
 | 
            if (!ctx) { 
 | 
                netif_warn(efx, drv, efx->net_dev, 
 | 
                       "Warning: unable to restore a filter with nonexistent RSS context %u.\n", 
 | 
                       spec->rss_context); 
 | 
                invalid_filters++; 
 | 
                goto not_restored; 
 | 
            } 
 | 
            if (ctx->context_id == EFX_MCDI_RSS_CONTEXT_INVALID) { 
 | 
                netif_warn(efx, drv, efx->net_dev, 
 | 
                       "Warning: unable to restore a filter with RSS context %u as it was not created.\n", 
 | 
                       spec->rss_context); 
 | 
                invalid_filters++; 
 | 
                goto not_restored; 
 | 
            } 
 | 
        } 
 | 
  
 | 
        rc = efx_mcdi_filter_push(efx, spec, 
 | 
                      &table->entry[filter_idx].handle, 
 | 
                      ctx, false); 
 | 
        if (rc) 
 | 
            failed++; 
 | 
  
 | 
        if (rc) { 
 | 
not_restored: 
 | 
            list_for_each_entry(vlan, &table->vlan_list, list) 
 | 
                for (i = 0; i < EFX_EF10_NUM_DEFAULT_FILTERS; ++i) 
 | 
                    if (vlan->default_filters[i] == filter_idx) 
 | 
                        vlan->default_filters[i] = 
 | 
                            EFX_EF10_FILTER_ID_INVALID; 
 | 
  
 | 
            kfree(spec); 
 | 
            efx_mcdi_filter_set_entry(table, filter_idx, NULL, 0); 
 | 
        } 
 | 
    } 
 | 
  
 | 
    mutex_unlock(&efx->rss_lock); 
 | 
    up_write(&table->lock); 
 | 
  
 | 
    /* 
 | 
     * This can happen validly if the MC's capabilities have changed, so 
 | 
     * is not an error. 
 | 
     */ 
 | 
    if (invalid_filters) 
 | 
        netif_dbg(efx, drv, efx->net_dev, 
 | 
              "Did not restore %u filters that are now unsupported.\n", 
 | 
              invalid_filters); 
 | 
  
 | 
    if (failed) 
 | 
        netif_err(efx, hw, efx->net_dev, 
 | 
              "unable to restore %u filters\n", failed); 
 | 
    else 
 | 
        table->must_restore_filters = false; 
 | 
} 
 | 
  
 | 
void efx_mcdi_filter_table_down(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_FILTER_OP_EXT_IN_LEN); 
 | 
    struct efx_filter_spec *spec; 
 | 
    unsigned int filter_idx; 
 | 
    int rc; 
 | 
  
 | 
    if (!table) 
 | 
        return; 
 | 
  
 | 
    efx_mcdi_filter_cleanup_vlans(efx); 
 | 
  
 | 
    for (filter_idx = 0; filter_idx < EFX_MCDI_FILTER_TBL_ROWS; filter_idx++) { 
 | 
        spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
        if (!spec) 
 | 
            continue; 
 | 
  
 | 
        MCDI_SET_DWORD(inbuf, FILTER_OP_IN_OP, 
 | 
                   efx_mcdi_filter_is_exclusive(spec) ? 
 | 
                   MC_CMD_FILTER_OP_IN_OP_REMOVE : 
 | 
                   MC_CMD_FILTER_OP_IN_OP_UNSUBSCRIBE); 
 | 
        MCDI_SET_QWORD(inbuf, FILTER_OP_IN_HANDLE, 
 | 
                   table->entry[filter_idx].handle); 
 | 
        rc = efx_mcdi_rpc_quiet(efx, MC_CMD_FILTER_OP, inbuf, 
 | 
                    sizeof(inbuf), NULL, 0, NULL); 
 | 
        if (rc) 
 | 
            netif_info(efx, drv, efx->net_dev, 
 | 
                   "%s: filter %04x remove failed\n", 
 | 
                   __func__, filter_idx); 
 | 
        kfree(spec); 
 | 
    } 
 | 
} 
 | 
  
 | 
void efx_mcdi_filter_table_remove(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
  
 | 
    efx_mcdi_filter_table_down(efx); 
 | 
  
 | 
    efx->filter_state = NULL; 
 | 
    /* 
 | 
     * If we were called without locking, then it's not safe to free 
 | 
     * the table as others might be using it.  So we just WARN, leak 
 | 
     * the memory, and potentially get an inconsistent filter table 
 | 
     * state. 
 | 
     * This should never actually happen. 
 | 
     */ 
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return; 
 | 
  
 | 
    if (!table) 
 | 
        return; 
 | 
  
 | 
    vfree(table->entry); 
 | 
    kfree(table); 
 | 
} 
 | 
  
 | 
static void efx_mcdi_filter_mark_one_old(struct efx_nic *efx, uint16_t *id) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    unsigned int filter_idx; 
 | 
  
 | 
    efx_rwsem_assert_write_locked(&table->lock); 
 | 
  
 | 
    if (*id != EFX_EF10_FILTER_ID_INVALID) { 
 | 
        filter_idx = efx_mcdi_filter_get_unsafe_id(*id); 
 | 
        if (!table->entry[filter_idx].spec) 
 | 
            netif_dbg(efx, drv, efx->net_dev, 
 | 
                  "marked null spec old %04x:%04x\n", *id, 
 | 
                  filter_idx); 
 | 
        table->entry[filter_idx].spec |= EFX_EF10_FILTER_FLAG_AUTO_OLD; 
 | 
        *id = EFX_EF10_FILTER_ID_INVALID; 
 | 
    } 
 | 
} 
 | 
  
 | 
/* Mark old per-VLAN filters that may need to be removed */ 
 | 
static void _efx_mcdi_filter_vlan_mark_old(struct efx_nic *efx, 
 | 
                       struct efx_mcdi_filter_vlan *vlan) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    unsigned int i; 
 | 
  
 | 
    for (i = 0; i < table->dev_uc_count; i++) 
 | 
        efx_mcdi_filter_mark_one_old(efx, &vlan->uc[i]); 
 | 
    for (i = 0; i < table->dev_mc_count; i++) 
 | 
        efx_mcdi_filter_mark_one_old(efx, &vlan->mc[i]); 
 | 
    for (i = 0; i < EFX_EF10_NUM_DEFAULT_FILTERS; i++) 
 | 
        efx_mcdi_filter_mark_one_old(efx, &vlan->default_filters[i]); 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Mark old filters that may need to be removed. 
 | 
 * Caller must hold efx->filter_sem for read if race against 
 | 
 * efx_mcdi_filter_table_remove() is possible 
 | 
 */ 
 | 
static void efx_mcdi_filter_mark_old(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
  
 | 
    down_write(&table->lock); 
 | 
    list_for_each_entry(vlan, &table->vlan_list, list) 
 | 
        _efx_mcdi_filter_vlan_mark_old(efx, vlan); 
 | 
    up_write(&table->lock); 
 | 
} 
 | 
  
 | 
int efx_mcdi_filter_add_vlan(struct efx_nic *efx, u16 vid) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
    unsigned int i; 
 | 
  
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return -EINVAL; 
 | 
  
 | 
    vlan = efx_mcdi_filter_find_vlan(efx, vid); 
 | 
    if (WARN_ON(vlan)) { 
 | 
        netif_err(efx, drv, efx->net_dev, 
 | 
              "VLAN %u already added\n", vid); 
 | 
        return -EALREADY; 
 | 
    } 
 | 
  
 | 
    vlan = kzalloc(sizeof(*vlan), GFP_KERNEL); 
 | 
    if (!vlan) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    vlan->vid = vid; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(vlan->uc); i++) 
 | 
        vlan->uc[i] = EFX_EF10_FILTER_ID_INVALID; 
 | 
    for (i = 0; i < ARRAY_SIZE(vlan->mc); i++) 
 | 
        vlan->mc[i] = EFX_EF10_FILTER_ID_INVALID; 
 | 
    for (i = 0; i < EFX_EF10_NUM_DEFAULT_FILTERS; i++) 
 | 
        vlan->default_filters[i] = EFX_EF10_FILTER_ID_INVALID; 
 | 
  
 | 
    list_add_tail(&vlan->list, &table->vlan_list); 
 | 
  
 | 
    if (efx_dev_registered(efx)) 
 | 
        efx_mcdi_filter_vlan_sync_rx_mode(efx, vlan); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static void efx_mcdi_filter_del_vlan_internal(struct efx_nic *efx, 
 | 
                          struct efx_mcdi_filter_vlan *vlan) 
 | 
{ 
 | 
    unsigned int i; 
 | 
  
 | 
    /* See comment in efx_mcdi_filter_table_remove() */ 
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return; 
 | 
  
 | 
    list_del(&vlan->list); 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(vlan->uc); i++) 
 | 
        efx_mcdi_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO, 
 | 
                          vlan->uc[i]); 
 | 
    for (i = 0; i < ARRAY_SIZE(vlan->mc); i++) 
 | 
        efx_mcdi_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO, 
 | 
                          vlan->mc[i]); 
 | 
    for (i = 0; i < EFX_EF10_NUM_DEFAULT_FILTERS; i++) 
 | 
        if (vlan->default_filters[i] != EFX_EF10_FILTER_ID_INVALID) 
 | 
            efx_mcdi_filter_remove_unsafe(efx, EFX_FILTER_PRI_AUTO, 
 | 
                              vlan->default_filters[i]); 
 | 
  
 | 
    kfree(vlan); 
 | 
} 
 | 
  
 | 
void efx_mcdi_filter_del_vlan(struct efx_nic *efx, u16 vid) 
 | 
{ 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
  
 | 
    /* See comment in efx_mcdi_filter_table_remove() */ 
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return; 
 | 
  
 | 
    vlan = efx_mcdi_filter_find_vlan(efx, vid); 
 | 
    if (!vlan) { 
 | 
        netif_err(efx, drv, efx->net_dev, 
 | 
              "VLAN %u not found in filter state\n", vid); 
 | 
        return; 
 | 
    } 
 | 
  
 | 
    efx_mcdi_filter_del_vlan_internal(efx, vlan); 
 | 
} 
 | 
  
 | 
struct efx_mcdi_filter_vlan *efx_mcdi_filter_find_vlan(struct efx_nic *efx, 
 | 
                               u16 vid) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
  
 | 
    WARN_ON(!rwsem_is_locked(&efx->filter_sem)); 
 | 
  
 | 
    list_for_each_entry(vlan, &table->vlan_list, list) { 
 | 
        if (vlan->vid == vid) 
 | 
            return vlan; 
 | 
    } 
 | 
  
 | 
    return NULL; 
 | 
} 
 | 
  
 | 
void efx_mcdi_filter_cleanup_vlans(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_mcdi_filter_vlan *vlan, *next_vlan; 
 | 
  
 | 
    /* See comment in efx_mcdi_filter_table_remove() */ 
 | 
    if (!efx_rwsem_assert_write_locked(&efx->filter_sem)) 
 | 
        return; 
 | 
  
 | 
    if (!table) 
 | 
        return; 
 | 
  
 | 
    list_for_each_entry_safe(vlan, next_vlan, &table->vlan_list, list) 
 | 
        efx_mcdi_filter_del_vlan_internal(efx, vlan); 
 | 
} 
 | 
  
 | 
static void efx_mcdi_filter_uc_addr_list(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct net_device *net_dev = efx->net_dev; 
 | 
    struct netdev_hw_addr *uc; 
 | 
    unsigned int i; 
 | 
  
 | 
    table->uc_promisc = !!(net_dev->flags & IFF_PROMISC); 
 | 
    ether_addr_copy(table->dev_uc_list[0].addr, net_dev->dev_addr); 
 | 
    i = 1; 
 | 
    netdev_for_each_uc_addr(uc, net_dev) { 
 | 
        if (i >= EFX_EF10_FILTER_DEV_UC_MAX) { 
 | 
            table->uc_promisc = true; 
 | 
            break; 
 | 
        } 
 | 
        ether_addr_copy(table->dev_uc_list[i].addr, uc->addr); 
 | 
        i++; 
 | 
    } 
 | 
  
 | 
    table->dev_uc_count = i; 
 | 
} 
 | 
  
 | 
static void efx_mcdi_filter_mc_addr_list(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct net_device *net_dev = efx->net_dev; 
 | 
    struct netdev_hw_addr *mc; 
 | 
    unsigned int i; 
 | 
  
 | 
    table->mc_overflow = false; 
 | 
    table->mc_promisc = !!(net_dev->flags & (IFF_PROMISC | IFF_ALLMULTI)); 
 | 
  
 | 
    i = 0; 
 | 
    netdev_for_each_mc_addr(mc, net_dev) { 
 | 
        if (i >= EFX_EF10_FILTER_DEV_MC_MAX) { 
 | 
            table->mc_promisc = true; 
 | 
            table->mc_overflow = true; 
 | 
            break; 
 | 
        } 
 | 
        ether_addr_copy(table->dev_mc_list[i].addr, mc->addr); 
 | 
        i++; 
 | 
    } 
 | 
  
 | 
    table->dev_mc_count = i; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Caller must hold efx->filter_sem for read if race against 
 | 
 * efx_mcdi_filter_table_remove() is possible 
 | 
 */ 
 | 
void efx_mcdi_filter_sync_rx_mode(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct net_device *net_dev = efx->net_dev; 
 | 
    struct efx_mcdi_filter_vlan *vlan; 
 | 
    bool vlan_filter; 
 | 
  
 | 
    if (!efx_dev_registered(efx)) 
 | 
        return; 
 | 
  
 | 
    if (!table) 
 | 
        return; 
 | 
  
 | 
    efx_mcdi_filter_mark_old(efx); 
 | 
  
 | 
    /* 
 | 
     * Copy/convert the address lists; add the primary station 
 | 
     * address and broadcast address 
 | 
     */ 
 | 
    netif_addr_lock_bh(net_dev); 
 | 
    efx_mcdi_filter_uc_addr_list(efx); 
 | 
    efx_mcdi_filter_mc_addr_list(efx); 
 | 
    netif_addr_unlock_bh(net_dev); 
 | 
  
 | 
    /* 
 | 
     * If VLAN filtering changes, all old filters are finally removed. 
 | 
     * Do it in advance to avoid conflicts for unicast untagged and 
 | 
     * VLAN 0 tagged filters. 
 | 
     */ 
 | 
    vlan_filter = !!(net_dev->features & NETIF_F_HW_VLAN_CTAG_FILTER); 
 | 
    if (table->vlan_filter != vlan_filter) { 
 | 
        table->vlan_filter = vlan_filter; 
 | 
        efx_mcdi_filter_remove_old(efx); 
 | 
    } 
 | 
  
 | 
    list_for_each_entry(vlan, &table->vlan_list, list) 
 | 
        efx_mcdi_filter_vlan_sync_rx_mode(efx, vlan); 
 | 
  
 | 
    efx_mcdi_filter_remove_old(efx); 
 | 
    table->mc_promisc_last = table->mc_promisc; 
 | 
} 
 | 
  
 | 
#ifdef CONFIG_RFS_ACCEL 
 | 
  
 | 
bool efx_mcdi_filter_rfs_expire_one(struct efx_nic *efx, u32 flow_id, 
 | 
                    unsigned int filter_idx) 
 | 
{ 
 | 
    struct efx_filter_spec *spec, saved_spec; 
 | 
    struct efx_mcdi_filter_table *table; 
 | 
    struct efx_arfs_rule *rule = NULL; 
 | 
    bool ret = true, force = false; 
 | 
    u16 arfs_id; 
 | 
  
 | 
    down_read(&efx->filter_sem); 
 | 
    table = efx->filter_state; 
 | 
    down_write(&table->lock); 
 | 
    spec = efx_mcdi_filter_entry_spec(table, filter_idx); 
 | 
  
 | 
    if (!spec || spec->priority != EFX_FILTER_PRI_HINT) 
 | 
        goto out_unlock; 
 | 
  
 | 
    spin_lock_bh(&efx->rps_hash_lock); 
 | 
    if (!efx->rps_hash_table) { 
 | 
        /* In the absence of the table, we always return 0 to ARFS. */ 
 | 
        arfs_id = 0; 
 | 
    } else { 
 | 
        rule = efx_rps_hash_find(efx, spec); 
 | 
        if (!rule) 
 | 
            /* ARFS table doesn't know of this filter, so remove it */ 
 | 
            goto expire; 
 | 
        arfs_id = rule->arfs_id; 
 | 
        ret = efx_rps_check_rule(rule, filter_idx, &force); 
 | 
        if (force) 
 | 
            goto expire; 
 | 
        if (!ret) { 
 | 
            spin_unlock_bh(&efx->rps_hash_lock); 
 | 
            goto out_unlock; 
 | 
        } 
 | 
    } 
 | 
    if (!rps_may_expire_flow(efx->net_dev, spec->dmaq_id, flow_id, arfs_id)) 
 | 
        ret = false; 
 | 
    else if (rule) 
 | 
        rule->filter_id = EFX_ARFS_FILTER_ID_REMOVING; 
 | 
expire: 
 | 
    saved_spec = *spec; /* remove operation will kfree spec */ 
 | 
    spin_unlock_bh(&efx->rps_hash_lock); 
 | 
    /* 
 | 
     * At this point (since we dropped the lock), another thread might queue 
 | 
     * up a fresh insertion request (but the actual insertion will be held 
 | 
     * up by our possession of the filter table lock).  In that case, it 
 | 
     * will set rule->filter_id to EFX_ARFS_FILTER_ID_PENDING, meaning that 
 | 
     * the rule is not removed by efx_rps_hash_del() below. 
 | 
     */ 
 | 
    if (ret) 
 | 
        ret = efx_mcdi_filter_remove_internal(efx, 1U << spec->priority, 
 | 
                              filter_idx, true) == 0; 
 | 
    /* 
 | 
     * While we can't safely dereference rule (we dropped the lock), we can 
 | 
     * still test it for NULL. 
 | 
     */ 
 | 
    if (ret && rule) { 
 | 
        /* Expiring, so remove entry from ARFS table */ 
 | 
        spin_lock_bh(&efx->rps_hash_lock); 
 | 
        efx_rps_hash_del(efx, &saved_spec); 
 | 
        spin_unlock_bh(&efx->rps_hash_lock); 
 | 
    } 
 | 
out_unlock: 
 | 
    up_write(&table->lock); 
 | 
    up_read(&efx->filter_sem); 
 | 
    return ret; 
 | 
} 
 | 
  
 | 
#endif /* CONFIG_RFS_ACCEL */ 
 | 
  
 | 
#define RSS_MODE_HASH_ADDRS    (1 << RSS_MODE_HASH_SRC_ADDR_LBN |\ 
 | 
                 1 << RSS_MODE_HASH_DST_ADDR_LBN) 
 | 
#define RSS_MODE_HASH_PORTS    (1 << RSS_MODE_HASH_SRC_PORT_LBN |\ 
 | 
                 1 << RSS_MODE_HASH_DST_PORT_LBN) 
 | 
#define RSS_CONTEXT_FLAGS_DEFAULT    (1 << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TOEPLITZ_IPV4_EN_LBN |\ 
 | 
                     1 << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TOEPLITZ_TCPV4_EN_LBN |\ 
 | 
                     1 << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TOEPLITZ_IPV6_EN_LBN |\ 
 | 
                     1 << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TOEPLITZ_TCPV6_EN_LBN |\ 
 | 
                     (RSS_MODE_HASH_ADDRS | RSS_MODE_HASH_PORTS) << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TCP_IPV4_RSS_MODE_LBN |\ 
 | 
                     RSS_MODE_HASH_ADDRS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_UDP_IPV4_RSS_MODE_LBN |\ 
 | 
                     RSS_MODE_HASH_ADDRS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_OTHER_IPV4_RSS_MODE_LBN |\ 
 | 
                     (RSS_MODE_HASH_ADDRS | RSS_MODE_HASH_PORTS) << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_TCP_IPV6_RSS_MODE_LBN |\ 
 | 
                     RSS_MODE_HASH_ADDRS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_UDP_IPV6_RSS_MODE_LBN |\ 
 | 
                     RSS_MODE_HASH_ADDRS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_OTHER_IPV6_RSS_MODE_LBN) 
 | 
  
 | 
int efx_mcdi_get_rss_context_flags(struct efx_nic *efx, u32 context, u32 *flags) 
 | 
{ 
 | 
    /* 
 | 
     * Firmware had a bug (sfc bug 61952) where it would not actually 
 | 
     * fill in the flags field in the response to MC_CMD_RSS_CONTEXT_GET_FLAGS. 
 | 
     * This meant that it would always contain whatever was previously 
 | 
     * in the MCDI buffer.  Fortunately, all firmware versions with 
 | 
     * this bug have the same default flags value for a newly-allocated 
 | 
     * RSS context, and the only time we want to get the flags is just 
 | 
     * after allocating.  Moreover, the response has a 32-bit hole 
 | 
     * where the context ID would be in the request, so we can use an 
 | 
     * overlength buffer in the request and pre-fill the flags field 
 | 
     * with what we believe the default to be.  Thus if the firmware 
 | 
     * has the bug, it will leave our pre-filled value in the flags 
 | 
     * field of the response, and we will get the right answer. 
 | 
     * 
 | 
     * However, this does mean that this function should NOT be used if 
 | 
     * the RSS context flags might not be their defaults - it is ONLY 
 | 
     * reliably correct for a newly-allocated RSS context. 
 | 
     */ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_LEN); 
 | 
    MCDI_DECLARE_BUF(outbuf, MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_LEN); 
 | 
    size_t outlen; 
 | 
    int rc; 
 | 
  
 | 
    /* Check we have a hole for the context ID */ 
 | 
    BUILD_BUG_ON(MC_CMD_RSS_CONTEXT_GET_FLAGS_IN_LEN != MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_FLAGS_OFST); 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_GET_FLAGS_IN_RSS_CONTEXT_ID, context); 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_GET_FLAGS_OUT_FLAGS, 
 | 
               RSS_CONTEXT_FLAGS_DEFAULT); 
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_GET_FLAGS, inbuf, 
 | 
              sizeof(inbuf), outbuf, sizeof(outbuf), &outlen); 
 | 
    if (rc == 0) { 
 | 
        if (outlen < MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_LEN) 
 | 
            rc = -EIO; 
 | 
        else 
 | 
            *flags = MCDI_DWORD(outbuf, RSS_CONTEXT_GET_FLAGS_OUT_FLAGS); 
 | 
    } 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
/* 
 | 
 * Attempt to enable 4-tuple UDP hashing on the specified RSS context. 
 | 
 * If we fail, we just leave the RSS context at its default hash settings, 
 | 
 * which is safe but may slightly reduce performance. 
 | 
 * Defaults are 4-tuple for TCP and 2-tuple for UDP and other-IP, so we 
 | 
 * just need to set the UDP ports flags (for both IP versions). 
 | 
 */ 
 | 
void efx_mcdi_set_rss_context_flags(struct efx_nic *efx, 
 | 
                    struct efx_rss_context *ctx) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_RSS_CONTEXT_SET_FLAGS_IN_LEN); 
 | 
    u32 flags; 
 | 
  
 | 
    BUILD_BUG_ON(MC_CMD_RSS_CONTEXT_SET_FLAGS_OUT_LEN != 0); 
 | 
  
 | 
    if (efx_mcdi_get_rss_context_flags(efx, ctx->context_id, &flags) != 0) 
 | 
        return; 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_SET_FLAGS_IN_RSS_CONTEXT_ID, 
 | 
               ctx->context_id); 
 | 
    flags |= RSS_MODE_HASH_PORTS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_UDP_IPV4_RSS_MODE_LBN; 
 | 
    flags |= RSS_MODE_HASH_PORTS << MC_CMD_RSS_CONTEXT_GET_FLAGS_OUT_UDP_IPV6_RSS_MODE_LBN; 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_SET_FLAGS_IN_FLAGS, flags); 
 | 
    if (!efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_SET_FLAGS, inbuf, sizeof(inbuf), 
 | 
              NULL, 0, NULL)) 
 | 
        /* Succeeded, so UDP 4-tuple is now enabled */ 
 | 
        ctx->rx_hash_udp_4tuple = true; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_alloc_rss_context(struct efx_nic *efx, bool exclusive, 
 | 
                         struct efx_rss_context *ctx, 
 | 
                         unsigned *context_size) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_RSS_CONTEXT_ALLOC_IN_LEN); 
 | 
    MCDI_DECLARE_BUF(outbuf, MC_CMD_RSS_CONTEXT_ALLOC_OUT_LEN); 
 | 
    size_t outlen; 
 | 
    int rc; 
 | 
    u32 alloc_type = exclusive ? 
 | 
                MC_CMD_RSS_CONTEXT_ALLOC_IN_TYPE_EXCLUSIVE : 
 | 
                MC_CMD_RSS_CONTEXT_ALLOC_IN_TYPE_SHARED; 
 | 
    unsigned rss_spread = exclusive ? 
 | 
                efx->rss_spread : 
 | 
                min(rounddown_pow_of_two(efx->rss_spread), 
 | 
                    EFX_EF10_MAX_SHARED_RSS_CONTEXT_SIZE); 
 | 
  
 | 
    if (!exclusive && rss_spread == 1) { 
 | 
        ctx->context_id = EFX_MCDI_RSS_CONTEXT_INVALID; 
 | 
        if (context_size) 
 | 
            *context_size = 1; 
 | 
        return 0; 
 | 
    } 
 | 
  
 | 
    if (efx_has_cap(efx, RX_RSS_LIMITED)) 
 | 
        return -EOPNOTSUPP; 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_ALLOC_IN_UPSTREAM_PORT_ID, 
 | 
               efx->vport_id); 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_ALLOC_IN_TYPE, alloc_type); 
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_ALLOC_IN_NUM_QUEUES, rss_spread); 
 | 
  
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_ALLOC, inbuf, sizeof(inbuf), 
 | 
        outbuf, sizeof(outbuf), &outlen); 
 | 
    if (rc != 0) 
 | 
        return rc; 
 | 
  
 | 
    if (outlen < MC_CMD_RSS_CONTEXT_ALLOC_OUT_LEN) 
 | 
        return -EIO; 
 | 
  
 | 
    ctx->context_id = MCDI_DWORD(outbuf, RSS_CONTEXT_ALLOC_OUT_RSS_CONTEXT_ID); 
 | 
  
 | 
    if (context_size) 
 | 
        *context_size = rss_spread; 
 | 
  
 | 
    if (efx_has_cap(efx, ADDITIONAL_RSS_MODES)) 
 | 
        efx_mcdi_set_rss_context_flags(efx, ctx); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_free_rss_context(struct efx_nic *efx, u32 context) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_RSS_CONTEXT_FREE_IN_LEN); 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_FREE_IN_RSS_CONTEXT_ID, 
 | 
               context); 
 | 
    return efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_FREE, inbuf, sizeof(inbuf), 
 | 
                NULL, 0, NULL); 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_populate_rss_table(struct efx_nic *efx, u32 context, 
 | 
                       const u32 *rx_indir_table, const u8 *key) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(tablebuf, MC_CMD_RSS_CONTEXT_SET_TABLE_IN_LEN); 
 | 
    MCDI_DECLARE_BUF(keybuf, MC_CMD_RSS_CONTEXT_SET_KEY_IN_LEN); 
 | 
    int i, rc; 
 | 
  
 | 
    MCDI_SET_DWORD(tablebuf, RSS_CONTEXT_SET_TABLE_IN_RSS_CONTEXT_ID, 
 | 
               context); 
 | 
    BUILD_BUG_ON(ARRAY_SIZE(efx->rss_context.rx_indir_table) != 
 | 
             MC_CMD_RSS_CONTEXT_SET_TABLE_IN_INDIRECTION_TABLE_LEN); 
 | 
  
 | 
    /* This iterates over the length of efx->rss_context.rx_indir_table, but 
 | 
     * copies bytes from rx_indir_table.  That's because the latter is a 
 | 
     * pointer rather than an array, but should have the same length. 
 | 
     * The efx->rss_context.rx_hash_key loop below is similar. 
 | 
     */ 
 | 
    for (i = 0; i < ARRAY_SIZE(efx->rss_context.rx_indir_table); ++i) 
 | 
        MCDI_PTR(tablebuf, 
 | 
             RSS_CONTEXT_SET_TABLE_IN_INDIRECTION_TABLE)[i] = 
 | 
                (u8) rx_indir_table[i]; 
 | 
  
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_SET_TABLE, tablebuf, 
 | 
              sizeof(tablebuf), NULL, 0, NULL); 
 | 
    if (rc != 0) 
 | 
        return rc; 
 | 
  
 | 
    MCDI_SET_DWORD(keybuf, RSS_CONTEXT_SET_KEY_IN_RSS_CONTEXT_ID, 
 | 
               context); 
 | 
    BUILD_BUG_ON(ARRAY_SIZE(efx->rss_context.rx_hash_key) != 
 | 
             MC_CMD_RSS_CONTEXT_SET_KEY_IN_TOEPLITZ_KEY_LEN); 
 | 
    for (i = 0; i < ARRAY_SIZE(efx->rss_context.rx_hash_key); ++i) 
 | 
        MCDI_PTR(keybuf, RSS_CONTEXT_SET_KEY_IN_TOEPLITZ_KEY)[i] = key[i]; 
 | 
  
 | 
    return efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_SET_KEY, keybuf, 
 | 
                sizeof(keybuf), NULL, 0, NULL); 
 | 
} 
 | 
  
 | 
void efx_mcdi_rx_free_indir_table(struct efx_nic *efx) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    if (efx->rss_context.context_id != EFX_MCDI_RSS_CONTEXT_INVALID) { 
 | 
        rc = efx_mcdi_filter_free_rss_context(efx, efx->rss_context.context_id); 
 | 
        WARN_ON(rc != 0); 
 | 
    } 
 | 
    efx->rss_context.context_id = EFX_MCDI_RSS_CONTEXT_INVALID; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_rx_push_shared_rss_config(struct efx_nic *efx, 
 | 
                          unsigned *context_size) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    int rc = efx_mcdi_filter_alloc_rss_context(efx, false, &efx->rss_context, 
 | 
                        context_size); 
 | 
  
 | 
    if (rc != 0) 
 | 
        return rc; 
 | 
  
 | 
    table->rx_rss_context_exclusive = false; 
 | 
    efx_set_default_rx_indir_table(efx, &efx->rss_context); 
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int efx_mcdi_filter_rx_push_exclusive_rss_config(struct efx_nic *efx, 
 | 
                         const u32 *rx_indir_table, 
 | 
                         const u8 *key) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    u32 old_rx_rss_context = efx->rss_context.context_id; 
 | 
    int rc; 
 | 
  
 | 
    if (efx->rss_context.context_id == EFX_MCDI_RSS_CONTEXT_INVALID || 
 | 
        !table->rx_rss_context_exclusive) { 
 | 
        rc = efx_mcdi_filter_alloc_rss_context(efx, true, &efx->rss_context, 
 | 
                        NULL); 
 | 
        if (rc == -EOPNOTSUPP) 
 | 
            return rc; 
 | 
        else if (rc != 0) 
 | 
            goto fail1; 
 | 
    } 
 | 
  
 | 
    rc = efx_mcdi_filter_populate_rss_table(efx, efx->rss_context.context_id, 
 | 
                     rx_indir_table, key); 
 | 
    if (rc != 0) 
 | 
        goto fail2; 
 | 
  
 | 
    if (efx->rss_context.context_id != old_rx_rss_context && 
 | 
        old_rx_rss_context != EFX_MCDI_RSS_CONTEXT_INVALID) 
 | 
        WARN_ON(efx_mcdi_filter_free_rss_context(efx, old_rx_rss_context) != 0); 
 | 
    table->rx_rss_context_exclusive = true; 
 | 
    if (rx_indir_table != efx->rss_context.rx_indir_table) 
 | 
        memcpy(efx->rss_context.rx_indir_table, rx_indir_table, 
 | 
               sizeof(efx->rss_context.rx_indir_table)); 
 | 
    if (key != efx->rss_context.rx_hash_key) 
 | 
        memcpy(efx->rss_context.rx_hash_key, key, 
 | 
               efx->type->rx_hash_key_size); 
 | 
  
 | 
    return 0; 
 | 
  
 | 
fail2: 
 | 
    if (old_rx_rss_context != efx->rss_context.context_id) { 
 | 
        WARN_ON(efx_mcdi_filter_free_rss_context(efx, efx->rss_context.context_id) != 0); 
 | 
        efx->rss_context.context_id = old_rx_rss_context; 
 | 
    } 
 | 
fail1: 
 | 
    netif_err(efx, hw, efx->net_dev, "%s: failed rc=%d\n", __func__, rc); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
int efx_mcdi_rx_push_rss_context_config(struct efx_nic *efx, 
 | 
                    struct efx_rss_context *ctx, 
 | 
                    const u32 *rx_indir_table, 
 | 
                    const u8 *key) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    WARN_ON(!mutex_is_locked(&efx->rss_lock)); 
 | 
  
 | 
    if (ctx->context_id == EFX_MCDI_RSS_CONTEXT_INVALID) { 
 | 
        rc = efx_mcdi_filter_alloc_rss_context(efx, true, ctx, NULL); 
 | 
        if (rc) 
 | 
            return rc; 
 | 
    } 
 | 
  
 | 
    if (!rx_indir_table) /* Delete this context */ 
 | 
        return efx_mcdi_filter_free_rss_context(efx, ctx->context_id); 
 | 
  
 | 
    rc = efx_mcdi_filter_populate_rss_table(efx, ctx->context_id, 
 | 
                     rx_indir_table, key); 
 | 
    if (rc) 
 | 
        return rc; 
 | 
  
 | 
    memcpy(ctx->rx_indir_table, rx_indir_table, 
 | 
           sizeof(efx->rss_context.rx_indir_table)); 
 | 
    memcpy(ctx->rx_hash_key, key, efx->type->rx_hash_key_size); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int efx_mcdi_rx_pull_rss_context_config(struct efx_nic *efx, 
 | 
                    struct efx_rss_context *ctx) 
 | 
{ 
 | 
    MCDI_DECLARE_BUF(inbuf, MC_CMD_RSS_CONTEXT_GET_TABLE_IN_LEN); 
 | 
    MCDI_DECLARE_BUF(tablebuf, MC_CMD_RSS_CONTEXT_GET_TABLE_OUT_LEN); 
 | 
    MCDI_DECLARE_BUF(keybuf, MC_CMD_RSS_CONTEXT_GET_KEY_OUT_LEN); 
 | 
    size_t outlen; 
 | 
    int rc, i; 
 | 
  
 | 
    WARN_ON(!mutex_is_locked(&efx->rss_lock)); 
 | 
  
 | 
    BUILD_BUG_ON(MC_CMD_RSS_CONTEXT_GET_TABLE_IN_LEN != 
 | 
             MC_CMD_RSS_CONTEXT_GET_KEY_IN_LEN); 
 | 
  
 | 
    if (ctx->context_id == EFX_MCDI_RSS_CONTEXT_INVALID) 
 | 
        return -ENOENT; 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_GET_TABLE_IN_RSS_CONTEXT_ID, 
 | 
               ctx->context_id); 
 | 
    BUILD_BUG_ON(ARRAY_SIZE(ctx->rx_indir_table) != 
 | 
             MC_CMD_RSS_CONTEXT_GET_TABLE_OUT_INDIRECTION_TABLE_LEN); 
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_GET_TABLE, inbuf, sizeof(inbuf), 
 | 
              tablebuf, sizeof(tablebuf), &outlen); 
 | 
    if (rc != 0) 
 | 
        return rc; 
 | 
  
 | 
    if (WARN_ON(outlen != MC_CMD_RSS_CONTEXT_GET_TABLE_OUT_LEN)) 
 | 
        return -EIO; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(ctx->rx_indir_table); i++) 
 | 
        ctx->rx_indir_table[i] = MCDI_PTR(tablebuf, 
 | 
                RSS_CONTEXT_GET_TABLE_OUT_INDIRECTION_TABLE)[i]; 
 | 
  
 | 
    MCDI_SET_DWORD(inbuf, RSS_CONTEXT_GET_KEY_IN_RSS_CONTEXT_ID, 
 | 
               ctx->context_id); 
 | 
    BUILD_BUG_ON(ARRAY_SIZE(ctx->rx_hash_key) != 
 | 
             MC_CMD_RSS_CONTEXT_SET_KEY_IN_TOEPLITZ_KEY_LEN); 
 | 
    rc = efx_mcdi_rpc(efx, MC_CMD_RSS_CONTEXT_GET_KEY, inbuf, sizeof(inbuf), 
 | 
              keybuf, sizeof(keybuf), &outlen); 
 | 
    if (rc != 0) 
 | 
        return rc; 
 | 
  
 | 
    if (WARN_ON(outlen != MC_CMD_RSS_CONTEXT_GET_KEY_OUT_LEN)) 
 | 
        return -EIO; 
 | 
  
 | 
    for (i = 0; i < ARRAY_SIZE(ctx->rx_hash_key); ++i) 
 | 
        ctx->rx_hash_key[i] = MCDI_PTR( 
 | 
                keybuf, RSS_CONTEXT_GET_KEY_OUT_TOEPLITZ_KEY)[i]; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
int efx_mcdi_rx_pull_rss_config(struct efx_nic *efx) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    mutex_lock(&efx->rss_lock); 
 | 
    rc = efx_mcdi_rx_pull_rss_context_config(efx, &efx->rss_context); 
 | 
    mutex_unlock(&efx->rss_lock); 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
void efx_mcdi_rx_restore_rss_contexts(struct efx_nic *efx) 
 | 
{ 
 | 
    struct efx_mcdi_filter_table *table = efx->filter_state; 
 | 
    struct efx_rss_context *ctx; 
 | 
    int rc; 
 | 
  
 | 
    WARN_ON(!mutex_is_locked(&efx->rss_lock)); 
 | 
  
 | 
    if (!table->must_restore_rss_contexts) 
 | 
        return; 
 | 
  
 | 
    list_for_each_entry(ctx, &efx->rss_context.list, list) { 
 | 
        /* previous NIC RSS context is gone */ 
 | 
        ctx->context_id = EFX_MCDI_RSS_CONTEXT_INVALID; 
 | 
        /* so try to allocate a new one */ 
 | 
        rc = efx_mcdi_rx_push_rss_context_config(efx, ctx, 
 | 
                             ctx->rx_indir_table, 
 | 
                             ctx->rx_hash_key); 
 | 
        if (rc) 
 | 
            netif_warn(efx, probe, efx->net_dev, 
 | 
                   "failed to restore RSS context %u, rc=%d" 
 | 
                   "; RSS filters may fail to be applied\n", 
 | 
                   ctx->user_id, rc); 
 | 
    } 
 | 
    table->must_restore_rss_contexts = false; 
 | 
} 
 | 
  
 | 
int efx_mcdi_pf_rx_push_rss_config(struct efx_nic *efx, bool user, 
 | 
                   const u32 *rx_indir_table, 
 | 
                   const u8 *key) 
 | 
{ 
 | 
    int rc; 
 | 
  
 | 
    if (efx->rss_spread == 1) 
 | 
        return 0; 
 | 
  
 | 
    if (!key) 
 | 
        key = efx->rss_context.rx_hash_key; 
 | 
  
 | 
    rc = efx_mcdi_filter_rx_push_exclusive_rss_config(efx, rx_indir_table, key); 
 | 
  
 | 
    if (rc == -ENOBUFS && !user) { 
 | 
        unsigned context_size; 
 | 
        bool mismatch = false; 
 | 
        size_t i; 
 | 
  
 | 
        for (i = 0; 
 | 
             i < ARRAY_SIZE(efx->rss_context.rx_indir_table) && !mismatch; 
 | 
             i++) 
 | 
            mismatch = rx_indir_table[i] != 
 | 
                ethtool_rxfh_indir_default(i, efx->rss_spread); 
 | 
  
 | 
        rc = efx_mcdi_filter_rx_push_shared_rss_config(efx, &context_size); 
 | 
        if (rc == 0) { 
 | 
            if (context_size != efx->rss_spread) 
 | 
                netif_warn(efx, probe, efx->net_dev, 
 | 
                       "Could not allocate an exclusive RSS" 
 | 
                       " context; allocated a shared one of" 
 | 
                       " different size." 
 | 
                       " Wanted %u, got %u.\n", 
 | 
                       efx->rss_spread, context_size); 
 | 
            else if (mismatch) 
 | 
                netif_warn(efx, probe, efx->net_dev, 
 | 
                       "Could not allocate an exclusive RSS" 
 | 
                       " context; allocated a shared one but" 
 | 
                       " could not apply custom" 
 | 
                       " indirection.\n"); 
 | 
            else 
 | 
                netif_info(efx, probe, efx->net_dev, 
 | 
                       "Could not allocate an exclusive RSS" 
 | 
                       " context; allocated a shared one.\n"); 
 | 
        } 
 | 
    } 
 | 
    return rc; 
 | 
} 
 | 
  
 | 
int efx_mcdi_vf_rx_push_rss_config(struct efx_nic *efx, bool user, 
 | 
                   const u32 *rx_indir_table 
 | 
                   __attribute__ ((unused)), 
 | 
                   const u8 *key 
 | 
                   __attribute__ ((unused))) 
 | 
{ 
 | 
    if (user) 
 | 
        return -EOPNOTSUPP; 
 | 
    if (efx->rss_context.context_id != EFX_MCDI_RSS_CONTEXT_INVALID) 
 | 
        return 0; 
 | 
    return efx_mcdi_filter_rx_push_shared_rss_config(efx, NULL); 
 | 
} 
 | 
  
 | 
int efx_mcdi_push_default_indir_table(struct efx_nic *efx, 
 | 
                      unsigned int rss_spread) 
 | 
{ 
 | 
    int rc = 0; 
 | 
  
 | 
    if (efx->rss_spread == rss_spread) 
 | 
        return 0; 
 | 
  
 | 
    efx->rss_spread = rss_spread; 
 | 
    if (!efx->filter_state) 
 | 
        return 0; 
 | 
  
 | 
    efx_mcdi_rx_free_indir_table(efx); 
 | 
    if (rss_spread > 1) { 
 | 
        efx_set_default_rx_indir_table(efx, &efx->rss_context); 
 | 
        rc = efx->type->rx_push_rss_config(efx, false, 
 | 
                   efx->rss_context.rx_indir_table, NULL); 
 | 
    } 
 | 
    return rc; 
 | 
} 
 |