sfc: Add support for configuring RX unicast/multicast default filters
authorBen Hutchings <bhutchings@solarflare.com>
Thu, 2 Feb 2012 22:41:49 +0000 (22:41 +0000)
committerBen Hutchings <bhutchings@solarflare.com>
Thu, 16 Feb 2012 00:11:24 +0000 (00:11 +0000)
On Siena all received packets that don't match a more specific filter
will match the unicast or multicast default filter.  Currently we
leave these set to the default values (RSS with base queue number of
0).  Allow them to be reconfigured to select a single RX queue.

These default filters are programmed through the FILTER_CTL register,
but we represent them internally as an additional table of size 2.

Signed-off-by: Ben Hutchings <bhutchings@solarflare.com>
drivers/net/ethernet/sfc/ethtool.c
drivers/net/ethernet/sfc/filter.c
drivers/net/ethernet/sfc/filter.h

index f887f65e4189c9a7762234271ed75bb13e44e088..83191151b6503e68c3ee097c79dad5a8f9e32808 100644 (file)
@@ -808,11 +808,16 @@ static int efx_ethtool_reset(struct net_device *net_dev, u32 *flags)
        return efx_reset(efx, rc);
 }
 
+/* MAC address mask including only MC flag */
+static const u8 mac_addr_mc_mask[ETH_ALEN] = { 0x01, 0, 0, 0, 0, 0 };
+
 static int efx_ethtool_get_class_rule(struct efx_nic *efx,
                                      struct ethtool_rx_flow_spec *rule)
 {
        struct ethtool_tcpip4_spec *ip_entry = &rule->h_u.tcp_ip4_spec;
        struct ethtool_tcpip4_spec *ip_mask = &rule->m_u.tcp_ip4_spec;
+       struct ethhdr *mac_entry = &rule->h_u.ether_spec;
+       struct ethhdr *mac_mask = &rule->m_u.ether_spec;
        struct efx_filter_spec spec;
        u16 vid;
        u8 proto;
@@ -828,11 +833,18 @@ static int efx_ethtool_get_class_rule(struct efx_nic *efx,
        else
                rule->ring_cookie = spec.dmaq_id;
 
-       rc = efx_filter_get_eth_local(&spec, &vid,
-                                     rule->h_u.ether_spec.h_dest);
+       if (spec.type == EFX_FILTER_MC_DEF || spec.type == EFX_FILTER_UC_DEF) {
+               rule->flow_type = ETHER_FLOW;
+               memcpy(mac_mask->h_dest, mac_addr_mc_mask, ETH_ALEN);
+               if (spec.type == EFX_FILTER_MC_DEF)
+                       memcpy(mac_entry->h_dest, mac_addr_mc_mask, ETH_ALEN);
+               return 0;
+       }
+
+       rc = efx_filter_get_eth_local(&spec, &vid, mac_entry->h_dest);
        if (rc == 0) {
                rule->flow_type = ETHER_FLOW;
-               memset(rule->m_u.ether_spec.h_dest, ~0, ETH_ALEN);
+               memset(mac_mask->h_dest, ~0, ETH_ALEN);
                if (vid != EFX_FILTER_VID_UNSPEC) {
                        rule->flow_type |= FLOW_EXT;
                        rule->h_ext.vlan_tci = htons(vid);
@@ -1001,27 +1013,40 @@ static int efx_ethtool_set_class_rule(struct efx_nic *efx,
        }
 
        case ETHER_FLOW | FLOW_EXT:
-               /* Must match all or none of VID */
-               if (rule->m_ext.vlan_tci != htons(0xfff) &&
-                   rule->m_ext.vlan_tci != 0)
-                       return -EINVAL;
-       case ETHER_FLOW:
-               /* Must match all of destination */
-               if (!is_broadcast_ether_addr(mac_mask->h_dest))
-                       return -EINVAL;
-               /* and nothing else */
+       case ETHER_FLOW: {
+               u16 vlan_tag_mask = (rule->flow_type & FLOW_EXT ?
+                                    ntohs(rule->m_ext.vlan_tci) : 0);
+
+               /* Must not match on source address or Ethertype */
                if (!is_zero_ether_addr(mac_mask->h_source) ||
                    mac_mask->h_proto)
                        return -EINVAL;
 
-               rc = efx_filter_set_eth_local(
-                       &spec,
-                       (rule->flow_type & FLOW_EXT && rule->m_ext.vlan_tci) ?
-                       ntohs(rule->h_ext.vlan_tci) : EFX_FILTER_VID_UNSPEC,
-                       mac_entry->h_dest);
+               /* Is it a default UC or MC filter? */
+               if (!compare_ether_addr(mac_mask->h_dest, mac_addr_mc_mask) &&
+                   vlan_tag_mask == 0) {
+                       if (is_multicast_ether_addr(mac_entry->h_dest))
+                               rc = efx_filter_set_mc_def(&spec);
+                       else
+                               rc = efx_filter_set_uc_def(&spec);
+               }
+               /* Otherwise, it must match all of destination and all
+                * or none of VID.
+                */
+               else if (is_broadcast_ether_addr(mac_mask->h_dest) &&
+                        (vlan_tag_mask == 0xfff || vlan_tag_mask == 0)) {
+                       rc = efx_filter_set_eth_local(
+                               &spec,
+                               vlan_tag_mask ?
+                               ntohs(rule->h_ext.vlan_tci) : EFX_FILTER_VID_UNSPEC,
+                               mac_entry->h_dest);
+               } else {
+                       rc = -EINVAL;
+               }
                if (rc)
                        return rc;
                break;
+       }
 
        default:
                return -EINVAL;
index 1fbbbee7b1ae225bdd54433ec04e354f5ff1182f..92517ea427157dbcb92af4a594ffd89bd5af49df 100644 (file)
 enum efx_filter_table_id {
        EFX_FILTER_TABLE_RX_IP = 0,
        EFX_FILTER_TABLE_RX_MAC,
+       EFX_FILTER_TABLE_RX_DEF,
        EFX_FILTER_TABLE_COUNT,
 };
 
+enum efx_filter_index {
+       EFX_FILTER_INDEX_UC_DEF,
+       EFX_FILTER_INDEX_MC_DEF,
+       EFX_FILTER_SIZE_RX_DEF,
+};
+
 struct efx_filter_table {
        enum efx_filter_table_id id;
        u32             offset;         /* address of table relative to BAR */
@@ -109,7 +116,7 @@ static void efx_filter_table_reset_search_depth(struct efx_filter_table *table)
        memset(table->search_depth, 0, sizeof(table->search_depth));
 }
 
-static void efx_filter_push_rx_limits(struct efx_nic *efx)
+static void efx_filter_push_rx_config(struct efx_nic *efx)
 {
        struct efx_filter_state *state = efx->filter_state;
        struct efx_filter_table *table;
@@ -143,6 +150,32 @@ static void efx_filter_push_rx_limits(struct efx_nic *efx)
                        FILTER_CTL_SRCH_FUDGE_WILD);
        }
 
+       table = &state->table[EFX_FILTER_TABLE_RX_DEF];
+       if (table->size) {
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_UNICAST_NOMATCH_Q_ID,
+                       table->spec[EFX_FILTER_INDEX_UC_DEF].dmaq_id);
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_UNICAST_NOMATCH_RSS_ENABLED,
+                       !!(table->spec[EFX_FILTER_INDEX_UC_DEF].flags &
+                          EFX_FILTER_FLAG_RX_RSS));
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_UNICAST_NOMATCH_IP_OVERRIDE,
+                       !!(table->spec[EFX_FILTER_INDEX_UC_DEF].flags &
+                          EFX_FILTER_FLAG_RX_OVERRIDE_IP));
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_MULTICAST_NOMATCH_Q_ID,
+                       table->spec[EFX_FILTER_INDEX_MC_DEF].dmaq_id);
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_MULTICAST_NOMATCH_RSS_ENABLED,
+                       !!(table->spec[EFX_FILTER_INDEX_MC_DEF].flags &
+                          EFX_FILTER_FLAG_RX_RSS));
+               EFX_SET_OWORD_FIELD(
+                       filter_ctl, FRF_CZ_MULTICAST_NOMATCH_IP_OVERRIDE,
+                       !!(table->spec[EFX_FILTER_INDEX_MC_DEF].flags &
+                          EFX_FILTER_FLAG_RX_OVERRIDE_IP));
+       }
+
        efx_writeo(efx, &filter_ctl, FR_BZ_RX_FILTER_CTL);
 }
 
@@ -319,6 +352,52 @@ int efx_filter_set_eth_local(struct efx_filter_spec *spec,
        return 0;
 }
 
+/**
+ * efx_filter_set_uc_def - specify matching otherwise-unmatched unicast
+ * @spec: Specification to initialise
+ */
+int efx_filter_set_uc_def(struct efx_filter_spec *spec)
+{
+       EFX_BUG_ON_PARANOID(!(spec->flags &
+                             (EFX_FILTER_FLAG_RX | EFX_FILTER_FLAG_TX)));
+
+       if (spec->type != EFX_FILTER_UNSPEC)
+               return -EINVAL;
+
+       spec->type = EFX_FILTER_UC_DEF;
+       memset(spec->data, 0, sizeof(spec->data)); /* ensure equality */
+       return 0;
+}
+
+/**
+ * efx_filter_set_mc_def - specify matching otherwise-unmatched multicast
+ * @spec: Specification to initialise
+ */
+int efx_filter_set_mc_def(struct efx_filter_spec *spec)
+{
+       EFX_BUG_ON_PARANOID(!(spec->flags &
+                             (EFX_FILTER_FLAG_RX | EFX_FILTER_FLAG_TX)));
+
+       if (spec->type != EFX_FILTER_UNSPEC)
+               return -EINVAL;
+
+       spec->type = EFX_FILTER_MC_DEF;
+       memset(spec->data, 0, sizeof(spec->data)); /* ensure equality */
+       return 0;
+}
+
+static void efx_filter_reset_rx_def(struct efx_nic *efx, unsigned filter_idx)
+{
+       struct efx_filter_state *state = efx->filter_state;
+       struct efx_filter_table *table = &state->table[EFX_FILTER_TABLE_RX_DEF];
+       struct efx_filter_spec *spec = &table->spec[filter_idx];
+
+       efx_filter_init_rx(spec, EFX_FILTER_PRI_MANUAL,
+                          EFX_FILTER_FLAG_RX_RSS, 0);
+       spec->type = EFX_FILTER_UC_DEF + filter_idx;
+       table->used_bitmap[0] |= 1 << filter_idx;
+}
+
 int efx_filter_get_eth_local(const struct efx_filter_spec *spec,
                             u16 *vid, u8 *addr)
 {
@@ -366,6 +445,13 @@ static u32 efx_filter_build(efx_oword_t *filter, struct efx_filter_spec *spec)
                break;
        }
 
+       case EFX_FILTER_TABLE_RX_DEF:
+               /* One filter spec per type */
+               BUILD_BUG_ON(EFX_FILTER_INDEX_UC_DEF != 0);
+               BUILD_BUG_ON(EFX_FILTER_INDEX_MC_DEF !=
+                            EFX_FILTER_MC_DEF - EFX_FILTER_UC_DEF);
+               return spec->type - EFX_FILTER_UC_DEF;
+
        case EFX_FILTER_TABLE_RX_MAC: {
                bool is_wild = spec->type == EFX_FILTER_MAC_WILD;
                EFX_POPULATE_OWORD_8(
@@ -448,23 +534,40 @@ static int efx_filter_search(struct efx_filter_table *table,
  * MAC filters without overriding behaviour.
  */
 
+#define EFX_FILTER_MATCH_PRI_RX_MAC_OVERRIDE_IP        0
+#define EFX_FILTER_MATCH_PRI_RX_DEF_OVERRIDE_IP        1
+#define EFX_FILTER_MATCH_PRI_NORMAL_BASE       2
+
 #define EFX_FILTER_INDEX_WIDTH 13
 #define EFX_FILTER_INDEX_MASK  ((1 << EFX_FILTER_INDEX_WIDTH) - 1)
 
 static inline u32 efx_filter_make_id(enum efx_filter_table_id table_id,
                                     unsigned int index, u8 flags)
 {
-       return (table_id == EFX_FILTER_TABLE_RX_MAC &&
-               flags & EFX_FILTER_FLAG_RX_OVERRIDE_IP) ?
-               index :
-               (table_id + 1) << EFX_FILTER_INDEX_WIDTH | index;
+       unsigned int match_pri = EFX_FILTER_MATCH_PRI_NORMAL_BASE + table_id;
+
+       if (flags & EFX_FILTER_FLAG_RX_OVERRIDE_IP) {
+               if (table_id == EFX_FILTER_TABLE_RX_MAC)
+                       match_pri = EFX_FILTER_MATCH_PRI_RX_MAC_OVERRIDE_IP;
+               else if (table_id == EFX_FILTER_TABLE_RX_DEF)
+                       match_pri = EFX_FILTER_MATCH_PRI_RX_DEF_OVERRIDE_IP;
+       }
+
+       return match_pri << EFX_FILTER_INDEX_WIDTH | index;
 }
 
 static inline enum efx_filter_table_id efx_filter_id_table_id(u32 id)
 {
-       return (id <= EFX_FILTER_INDEX_MASK) ?
-               EFX_FILTER_TABLE_RX_MAC :
-               (id >> EFX_FILTER_INDEX_WIDTH) - 1;
+       unsigned int match_pri = id >> EFX_FILTER_INDEX_WIDTH;
+
+       switch (match_pri) {
+       case EFX_FILTER_MATCH_PRI_RX_MAC_OVERRIDE_IP:
+               return EFX_FILTER_TABLE_RX_MAC;
+       case EFX_FILTER_MATCH_PRI_RX_DEF_OVERRIDE_IP:
+               return EFX_FILTER_TABLE_RX_DEF;
+       default:
+               return match_pri - EFX_FILTER_MATCH_PRI_NORMAL_BASE;
+       }
 }
 
 static inline unsigned int efx_filter_id_index(u32 id)
@@ -474,23 +577,27 @@ static inline unsigned int efx_filter_id_index(u32 id)
 
 static inline u8 efx_filter_id_flags(u32 id)
 {
-       return (id <= EFX_FILTER_INDEX_MASK) ?
-               EFX_FILTER_FLAG_RX | EFX_FILTER_FLAG_RX_OVERRIDE_IP :
-               EFX_FILTER_FLAG_RX;
+       unsigned int match_pri = id >> EFX_FILTER_INDEX_WIDTH;
+
+       if (match_pri < EFX_FILTER_MATCH_PRI_NORMAL_BASE)
+               return EFX_FILTER_FLAG_RX | EFX_FILTER_FLAG_RX_OVERRIDE_IP;
+       else
+               return EFX_FILTER_FLAG_RX;
 }
 
 u32 efx_filter_get_rx_id_limit(struct efx_nic *efx)
 {
        struct efx_filter_state *state = efx->filter_state;
+       unsigned int table_id = EFX_FILTER_TABLE_RX_DEF;
 
-       if (state->table[EFX_FILTER_TABLE_RX_MAC].size != 0)
-               return ((EFX_FILTER_TABLE_RX_MAC + 1) << EFX_FILTER_INDEX_WIDTH)
-                       + state->table[EFX_FILTER_TABLE_RX_MAC].size;
-       else if (state->table[EFX_FILTER_TABLE_RX_IP].size != 0)
-               return ((EFX_FILTER_TABLE_RX_IP + 1) << EFX_FILTER_INDEX_WIDTH)
-                       + state->table[EFX_FILTER_TABLE_RX_IP].size;
-       else
-               return 0;
+       do {
+               if (state->table[table_id].size != 0)
+                       return ((EFX_FILTER_MATCH_PRI_NORMAL_BASE + table_id)
+                               << EFX_FILTER_INDEX_WIDTH) +
+                               state->table[table_id].size;
+       } while (table_id--);
+
+       return 0;
 }
 
 /**
@@ -548,12 +655,17 @@ s32 efx_filter_insert_filter(struct efx_nic *efx, struct efx_filter_spec *spec,
        }
        *saved_spec = *spec;
 
-       if (table->search_depth[spec->type] < depth) {
-               table->search_depth[spec->type] = depth;
-               efx_filter_push_rx_limits(efx);
-       }
+       if (table->id == EFX_FILTER_TABLE_RX_DEF) {
+               efx_filter_push_rx_config(efx);
+       } else {
+               if (table->search_depth[spec->type] < depth) {
+                       table->search_depth[spec->type] = depth;
+                       efx_filter_push_rx_config(efx);
+               }
 
-       efx_writeo(efx, &filter, table->offset + table->step * filter_idx);
+               efx_writeo(efx, &filter,
+                          table->offset + table->step * filter_idx);
+       }
 
        netif_vdbg(efx, hw, efx->net_dev,
                   "%s: filter type %d index %d rxq %u set",
@@ -571,7 +683,11 @@ static void efx_filter_table_clear_entry(struct efx_nic *efx,
 {
        static efx_oword_t filter;
 
-       if (test_bit(filter_idx, table->used_bitmap)) {
+       if (table->id == EFX_FILTER_TABLE_RX_DEF) {
+               /* RX default filters must always exist */
+               efx_filter_reset_rx_def(efx, filter_idx);
+               efx_filter_push_rx_config(efx);
+       } else if (test_bit(filter_idx, table->used_bitmap)) {
                __clear_bit(filter_idx, table->used_bitmap);
                --table->used;
                memset(&table->spec[filter_idx], 0, sizeof(table->spec[0]));
@@ -617,7 +733,8 @@ int efx_filter_remove_id_safe(struct efx_nic *efx,
        spin_lock_bh(&state->lock);
 
        if (test_bit(filter_idx, table->used_bitmap) &&
-           spec->priority == priority && spec->flags == filter_flags) {
+           spec->priority == priority &&
+           !((spec->flags ^ filter_flags) & EFX_FILTER_FLAG_RX_OVERRIDE_IP)) {
                efx_filter_table_clear_entry(efx, table, filter_idx);
                if (table->used == 0)
                        efx_filter_table_reset_search_depth(table);
@@ -668,7 +785,8 @@ int efx_filter_get_filter_safe(struct efx_nic *efx,
        spin_lock_bh(&state->lock);
 
        if (test_bit(filter_idx, table->used_bitmap) &&
-           spec->priority == priority && spec->flags == filter_flags) {
+           spec->priority == priority &&
+           !((spec->flags ^ filter_flags) & EFX_FILTER_FLAG_RX_OVERRIDE_IP)) {
                *spec_buf = *spec;
                rc = 0;
        } else {
@@ -722,7 +840,7 @@ u32 efx_filter_count_rx_used(struct efx_nic *efx,
        spin_lock_bh(&state->lock);
 
        for (table_id = EFX_FILTER_TABLE_RX_IP;
-            table_id <= EFX_FILTER_TABLE_RX_MAC;
+            table_id <= EFX_FILTER_TABLE_RX_DEF;
             table_id++) {
                table = &state->table[table_id];
                for (filter_idx = 0; filter_idx < table->size; filter_idx++) {
@@ -750,7 +868,7 @@ s32 efx_filter_get_rx_ids(struct efx_nic *efx,
        spin_lock_bh(&state->lock);
 
        for (table_id = EFX_FILTER_TABLE_RX_IP;
-            table_id <= EFX_FILTER_TABLE_RX_MAC;
+            table_id <= EFX_FILTER_TABLE_RX_DEF;
             table_id++) {
                table = &state->table[table_id];
                for (filter_idx = 0; filter_idx < table->size; filter_idx++) {
@@ -785,6 +903,11 @@ void efx_restore_filters(struct efx_nic *efx)
 
        for (table_id = 0; table_id < EFX_FILTER_TABLE_COUNT; table_id++) {
                table = &state->table[table_id];
+
+               /* Check whether this is a regular register table */
+               if (table->step == 0)
+                       continue;
+
                for (filter_idx = 0; filter_idx < table->size; filter_idx++) {
                        if (!test_bit(filter_idx, table->used_bitmap))
                                continue;
@@ -794,7 +917,7 @@ void efx_restore_filters(struct efx_nic *efx)
                }
        }
 
-       efx_filter_push_rx_limits(efx);
+       efx_filter_push_rx_config(efx);
 
        spin_unlock_bh(&state->lock);
 }
@@ -833,6 +956,10 @@ int efx_probe_filters(struct efx_nic *efx)
                table->offset = FR_CZ_RX_MAC_FILTER_TBL0;
                table->size = FR_CZ_RX_MAC_FILTER_TBL0_ROWS;
                table->step = FR_CZ_RX_MAC_FILTER_TBL0_STEP;
+
+               table = &state->table[EFX_FILTER_TABLE_RX_DEF];
+               table->id = EFX_FILTER_TABLE_RX_DEF;
+               table->size = EFX_FILTER_SIZE_RX_DEF;
        }
 
        for (table_id = 0; table_id < EFX_FILTER_TABLE_COUNT; table_id++) {
@@ -849,6 +976,15 @@ int efx_probe_filters(struct efx_nic *efx)
                        goto fail;
        }
 
+       if (state->table[EFX_FILTER_TABLE_RX_DEF].size) {
+               /* RX default filters must always exist */
+               unsigned i;
+               for (i = 0; i < EFX_FILTER_SIZE_RX_DEF; i++)
+                       efx_filter_reset_rx_def(efx, i);
+       }
+
+       efx_filter_push_rx_config(efx);
+
        return 0;
 
 fail:
index 3d4108cd90caceac01834d6634a9566a9fcf0c33..451997841dc9f98dfdacfe7c15d103b85c7e516e 100644 (file)
@@ -20,6 +20,8 @@
  * @EFX_FILTER_UDP_WILD: Matching UDP/IPv4 destination (host, port)
  * @EFX_FILTER_MAC_FULL: Matching Ethernet destination MAC address, VID
  * @EFX_FILTER_MAC_WILD: Matching Ethernet destination MAC address
+ * @EFX_FILTER_UC_DEF: Matching all otherwise unmatched unicast
+ * @EFX_FILTER_MC_DEF: Matching all otherwise unmatched multicast
  * @EFX_FILTER_UNSPEC: Match type is unspecified
  *
  * Falcon NICs only support the TCP/IPv4 and UDP/IPv4 filter types.
@@ -31,6 +33,8 @@ enum efx_filter_type {
        EFX_FILTER_UDP_WILD,
        EFX_FILTER_MAC_FULL = 4,
        EFX_FILTER_MAC_WILD,
+       EFX_FILTER_UC_DEF = 8,
+       EFX_FILTER_MC_DEF,
        EFX_FILTER_TYPE_COUNT,          /* number of specific types */
        EFX_FILTER_UNSPEC = 0xf,
 };
@@ -117,6 +121,8 @@ extern int efx_filter_set_eth_local(struct efx_filter_spec *spec,
                                    u16 vid, const u8 *addr);
 extern int efx_filter_get_eth_local(const struct efx_filter_spec *spec,
                                    u16 *vid, u8 *addr);
+extern int efx_filter_set_uc_def(struct efx_filter_spec *spec);
+extern int efx_filter_set_mc_def(struct efx_filter_spec *spec);
 enum {
        EFX_FILTER_VID_UNSPEC = 0xffff,
 };