igb: add support of RX network flow classification
[linux-2.6-microblaze.git] / drivers / net / ethernet / intel / igb / igb_ethtool.c
index 64e91c5..2599826 100644 (file)
@@ -2431,6 +2431,48 @@ static int igb_get_ts_info(struct net_device *dev,
        }
 }
 
+static int igb_get_ethtool_nfc_entry(struct igb_adapter *adapter,
+                                    struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp = &cmd->fs;
+       struct igb_nfc_filter *rule = NULL;
+
+       /* report total rule count */
+       cmd->data = IGB_MAX_RXNFC_FILTERS;
+
+       hlist_for_each_entry(rule, &adapter->nfc_filter_list, nfc_node) {
+               if (fsp->location <= rule->sw_idx)
+                       break;
+       }
+
+       if (!rule || fsp->location != rule->sw_idx)
+               return -EINVAL;
+
+       return -EINVAL;
+}
+
+static int igb_get_ethtool_nfc_all(struct igb_adapter *adapter,
+                                  struct ethtool_rxnfc *cmd,
+                                  u32 *rule_locs)
+{
+       struct igb_nfc_filter *rule;
+       int cnt = 0;
+
+       /* report total rule count */
+       cmd->data = IGB_MAX_RXNFC_FILTERS;
+
+       hlist_for_each_entry(rule, &adapter->nfc_filter_list, nfc_node) {
+               if (cnt == cmd->rule_cnt)
+                       return -EMSGSIZE;
+               rule_locs[cnt] = rule->sw_idx;
+               cnt++;
+       }
+
+       cmd->rule_cnt = cnt;
+
+       return 0;
+}
+
 static int igb_get_rss_hash_opts(struct igb_adapter *adapter,
                                 struct ethtool_rxnfc *cmd)
 {
@@ -2484,6 +2526,16 @@ static int igb_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
                cmd->data = adapter->num_rx_queues;
                ret = 0;
                break;
+       case ETHTOOL_GRXCLSRLCNT:
+               cmd->rule_cnt = adapter->nfc_filter_count;
+               ret = 0;
+               break;
+       case ETHTOOL_GRXCLSRULE:
+               ret = igb_get_ethtool_nfc_entry(adapter, cmd);
+               break;
+       case ETHTOOL_GRXCLSRLALL:
+               ret = igb_get_ethtool_nfc_all(adapter, cmd, rule_locs);
+               break;
        case ETHTOOL_GRXFH:
                ret = igb_get_rss_hash_opts(adapter, cmd);
                break;
@@ -2598,6 +2650,142 @@ static int igb_set_rss_hash_opt(struct igb_adapter *adapter,
        return 0;
 }
 
+int igb_add_filter(struct igb_adapter *adapter, struct igb_nfc_filter *input)
+{
+       return -EINVAL;
+}
+
+int igb_erase_filter(struct igb_adapter *adapter, struct igb_nfc_filter *input)
+{
+       return 0;
+}
+
+static int igb_update_ethtool_nfc_entry(struct igb_adapter *adapter,
+                                       struct igb_nfc_filter *input,
+                                       u16 sw_idx)
+{
+       struct igb_nfc_filter *rule, *parent;
+       int err = -EINVAL;
+
+       parent = NULL;
+       rule = NULL;
+
+       hlist_for_each_entry(rule, &adapter->nfc_filter_list, nfc_node) {
+               /* hash found, or no matching entry */
+               if (rule->sw_idx >= sw_idx)
+                       break;
+               parent = rule;
+       }
+
+       /* if there is an old rule occupying our place remove it */
+       if (rule && (rule->sw_idx == sw_idx)) {
+               if (!input)
+                       err = igb_erase_filter(adapter, rule);
+
+               hlist_del(&rule->nfc_node);
+               kfree(rule);
+               adapter->nfc_filter_count--;
+       }
+
+       /* If no input this was a delete, err should be 0 if a rule was
+        * successfully found and removed from the list else -EINVAL
+        */
+       if (!input)
+               return err;
+
+       /* initialize node */
+       INIT_HLIST_NODE(&input->nfc_node);
+
+       /* add filter to the list */
+       if (parent)
+               hlist_add_behind(&parent->nfc_node, &input->nfc_node);
+       else
+               hlist_add_head(&input->nfc_node, &adapter->nfc_filter_list);
+
+       /* update counts */
+       adapter->nfc_filter_count++;
+
+       return 0;
+}
+
+static int igb_add_ethtool_nfc_entry(struct igb_adapter *adapter,
+                                    struct ethtool_rxnfc *cmd)
+{
+       struct net_device *netdev = adapter->netdev;
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       struct igb_nfc_filter *input, *rule;
+       int err = 0;
+
+       if (!(netdev->hw_features & NETIF_F_NTUPLE))
+               return -ENOTSUPP;
+
+       /* Don't allow programming if the action is a queue greater than
+        * the number of online Rx queues.
+        */
+       if ((fsp->ring_cookie == RX_CLS_FLOW_DISC) ||
+           (fsp->ring_cookie >= adapter->num_rx_queues)) {
+               dev_err(&adapter->pdev->dev, "ethtool -N: The specified action is invalid\n");
+               return -EINVAL;
+       }
+
+       /* Don't allow indexes to exist outside of available space */
+       if (fsp->location >= IGB_MAX_RXNFC_FILTERS) {
+               dev_err(&adapter->pdev->dev, "Location out of range\n");
+               return -EINVAL;
+       }
+
+       if ((fsp->flow_type & ~FLOW_EXT) != ETHER_FLOW)
+               return -EINVAL;
+
+       input = kzalloc(sizeof(*input), GFP_KERNEL);
+       if (!input)
+               return -ENOMEM;
+
+       input->action = fsp->ring_cookie;
+       input->sw_idx = fsp->location;
+
+       spin_lock(&adapter->nfc_lock);
+
+       hlist_for_each_entry(rule, &adapter->nfc_filter_list, nfc_node) {
+               if (!memcmp(&input->filter, &rule->filter,
+                           sizeof(input->filter))) {
+                       err = -EEXIST;
+                       dev_err(&adapter->pdev->dev,
+                               "ethtool: this filter is already set\n");
+                       goto err_out_w_lock;
+               }
+       }
+
+       err = igb_add_filter(adapter, input);
+       if (err)
+               goto err_out_w_lock;
+
+       igb_update_ethtool_nfc_entry(adapter, input, input->sw_idx);
+
+       spin_unlock(&adapter->nfc_lock);
+       return 0;
+
+err_out_w_lock:
+       spin_unlock(&adapter->nfc_lock);
+       kfree(input);
+       return err;
+}
+
+static int igb_del_ethtool_nfc_entry(struct igb_adapter *adapter,
+                                    struct ethtool_rxnfc *cmd)
+{
+       struct ethtool_rx_flow_spec *fsp =
+               (struct ethtool_rx_flow_spec *)&cmd->fs;
+       int err;
+
+       spin_lock(&adapter->nfc_lock);
+       err = igb_update_ethtool_nfc_entry(adapter, NULL, fsp->location);
+       spin_unlock(&adapter->nfc_lock);
+
+       return err;
+}
+
 static int igb_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
 {
        struct igb_adapter *adapter = netdev_priv(dev);
@@ -2607,6 +2795,11 @@ static int igb_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
        case ETHTOOL_SRXFH:
                ret = igb_set_rss_hash_opt(adapter, cmd);
                break;
+       case ETHTOOL_SRXCLSRLINS:
+               ret = igb_add_ethtool_nfc_entry(adapter, cmd);
+               break;
+       case ETHTOOL_SRXCLSRLDEL:
+               ret = igb_del_ethtool_nfc_entry(adapter, cmd);
        default:
                break;
        }