|
@@ -2431,6 +2431,63 @@ static int igb_get_ts_info(struct net_device *dev,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#define ETHER_TYPE_FULL_MASK ((__force __be16)~0)
|
|
|
+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;
|
|
|
+
|
|
|
+ if (rule->filter.match_flags) {
|
|
|
+ fsp->flow_type = ETHER_FLOW;
|
|
|
+ fsp->ring_cookie = rule->action;
|
|
|
+ if (rule->filter.match_flags & IGB_FILTER_FLAG_ETHER_TYPE) {
|
|
|
+ fsp->h_u.ether_spec.h_proto = rule->filter.etype;
|
|
|
+ fsp->m_u.ether_spec.h_proto = ETHER_TYPE_FULL_MASK;
|
|
|
+ }
|
|
|
+ if (rule->filter.match_flags & IGB_FILTER_FLAG_VLAN_TCI) {
|
|
|
+ fsp->flow_type |= FLOW_EXT;
|
|
|
+ fsp->h_ext.vlan_tci = rule->filter.vlan_tci;
|
|
|
+ fsp->m_ext.vlan_tci = htons(VLAN_PRIO_MASK);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ 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 +2541,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 +2665,279 @@ static int igb_set_rss_hash_opt(struct igb_adapter *adapter,
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+static int igb_rxnfc_write_etype_filter(struct igb_adapter *adapter,
|
|
|
+ struct igb_nfc_filter *input)
|
|
|
+{
|
|
|
+ struct e1000_hw *hw = &adapter->hw;
|
|
|
+ u8 i;
|
|
|
+ u32 etqf;
|
|
|
+ u16 etype;
|
|
|
+
|
|
|
+ /* find an empty etype filter register */
|
|
|
+ for (i = 0; i < MAX_ETYPE_FILTER; ++i) {
|
|
|
+ if (!adapter->etype_bitmap[i])
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (i == MAX_ETYPE_FILTER) {
|
|
|
+ dev_err(&adapter->pdev->dev, "ethtool -N: etype filters are all used.\n");
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ adapter->etype_bitmap[i] = true;
|
|
|
+
|
|
|
+ etqf = rd32(E1000_ETQF(i));
|
|
|
+ etype = ntohs(input->filter.etype & ETHER_TYPE_FULL_MASK);
|
|
|
+
|
|
|
+ etqf |= E1000_ETQF_FILTER_ENABLE;
|
|
|
+ etqf &= ~E1000_ETQF_ETYPE_MASK;
|
|
|
+ etqf |= (etype & E1000_ETQF_ETYPE_MASK);
|
|
|
+
|
|
|
+ etqf &= ~E1000_ETQF_QUEUE_MASK;
|
|
|
+ etqf |= ((input->action << E1000_ETQF_QUEUE_SHIFT)
|
|
|
+ & E1000_ETQF_QUEUE_MASK);
|
|
|
+ etqf |= E1000_ETQF_QUEUE_ENABLE;
|
|
|
+
|
|
|
+ wr32(E1000_ETQF(i), etqf);
|
|
|
+
|
|
|
+ input->etype_reg_index = i;
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int igb_rxnfc_write_vlan_prio_filter(struct igb_adapter *adapter,
|
|
|
+ struct igb_nfc_filter *input)
|
|
|
+{
|
|
|
+ struct e1000_hw *hw = &adapter->hw;
|
|
|
+ u8 vlan_priority;
|
|
|
+ u16 queue_index;
|
|
|
+ u32 vlapqf;
|
|
|
+
|
|
|
+ vlapqf = rd32(E1000_VLAPQF);
|
|
|
+ vlan_priority = (ntohs(input->filter.vlan_tci) & VLAN_PRIO_MASK)
|
|
|
+ >> VLAN_PRIO_SHIFT;
|
|
|
+ queue_index = (vlapqf >> (vlan_priority * 4)) & E1000_VLAPQF_QUEUE_MASK;
|
|
|
+
|
|
|
+ /* check whether this vlan prio is already set */
|
|
|
+ if ((vlapqf & E1000_VLAPQF_P_VALID(vlan_priority)) &&
|
|
|
+ (queue_index != input->action)) {
|
|
|
+ dev_err(&adapter->pdev->dev, "ethtool rxnfc set vlan prio filter failed.\n");
|
|
|
+ return -EEXIST;
|
|
|
+ }
|
|
|
+
|
|
|
+ vlapqf |= E1000_VLAPQF_P_VALID(vlan_priority);
|
|
|
+ vlapqf |= E1000_VLAPQF_QUEUE_SEL(vlan_priority, input->action);
|
|
|
+
|
|
|
+ wr32(E1000_VLAPQF, vlapqf);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+int igb_add_filter(struct igb_adapter *adapter, struct igb_nfc_filter *input)
|
|
|
+{
|
|
|
+ int err = -EINVAL;
|
|
|
+
|
|
|
+ if (input->filter.match_flags & IGB_FILTER_FLAG_ETHER_TYPE) {
|
|
|
+ err = igb_rxnfc_write_etype_filter(adapter, input);
|
|
|
+ if (err)
|
|
|
+ return err;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (input->filter.match_flags & IGB_FILTER_FLAG_VLAN_TCI)
|
|
|
+ err = igb_rxnfc_write_vlan_prio_filter(adapter, input);
|
|
|
+
|
|
|
+ return err;
|
|
|
+}
|
|
|
+
|
|
|
+static void igb_clear_etype_filter_regs(struct igb_adapter *adapter,
|
|
|
+ u16 reg_index)
|
|
|
+{
|
|
|
+ struct e1000_hw *hw = &adapter->hw;
|
|
|
+ u32 etqf = rd32(E1000_ETQF(reg_index));
|
|
|
+
|
|
|
+ etqf &= ~E1000_ETQF_QUEUE_ENABLE;
|
|
|
+ etqf &= ~E1000_ETQF_QUEUE_MASK;
|
|
|
+ etqf &= ~E1000_ETQF_FILTER_ENABLE;
|
|
|
+
|
|
|
+ wr32(E1000_ETQF(reg_index), etqf);
|
|
|
+
|
|
|
+ adapter->etype_bitmap[reg_index] = false;
|
|
|
+}
|
|
|
+
|
|
|
+static void igb_clear_vlan_prio_filter(struct igb_adapter *adapter,
|
|
|
+ u16 vlan_tci)
|
|
|
+{
|
|
|
+ struct e1000_hw *hw = &adapter->hw;
|
|
|
+ u8 vlan_priority;
|
|
|
+ u32 vlapqf;
|
|
|
+
|
|
|
+ vlan_priority = (vlan_tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT;
|
|
|
+
|
|
|
+ vlapqf = rd32(E1000_VLAPQF);
|
|
|
+ vlapqf &= ~E1000_VLAPQF_P_VALID(vlan_priority);
|
|
|
+ vlapqf &= ~E1000_VLAPQF_QUEUE_SEL(vlan_priority,
|
|
|
+ E1000_VLAPQF_QUEUE_MASK);
|
|
|
+
|
|
|
+ wr32(E1000_VLAPQF, vlapqf);
|
|
|
+}
|
|
|
+
|
|
|
+int igb_erase_filter(struct igb_adapter *adapter, struct igb_nfc_filter *input)
|
|
|
+{
|
|
|
+ if (input->filter.match_flags & IGB_FILTER_FLAG_ETHER_TYPE)
|
|
|
+ igb_clear_etype_filter_regs(adapter,
|
|
|
+ input->etype_reg_index);
|
|
|
+
|
|
|
+ if (input->filter.match_flags & IGB_FILTER_FLAG_VLAN_TCI)
|
|
|
+ igb_clear_vlan_prio_filter(adapter,
|
|
|
+ ntohs(input->filter.vlan_tci));
|
|
|
+
|
|
|
+ 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 -EOPNOTSUPP;
|
|
|
+
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ if (fsp->m_u.ether_spec.h_proto != ETHER_TYPE_FULL_MASK &&
|
|
|
+ fsp->m_ext.vlan_tci != htons(VLAN_PRIO_MASK))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ input = kzalloc(sizeof(*input), GFP_KERNEL);
|
|
|
+ if (!input)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ if (fsp->m_u.ether_spec.h_proto == ETHER_TYPE_FULL_MASK) {
|
|
|
+ input->filter.etype = fsp->h_u.ether_spec.h_proto;
|
|
|
+ input->filter.match_flags = IGB_FILTER_FLAG_ETHER_TYPE;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((fsp->flow_type & FLOW_EXT) && fsp->m_ext.vlan_tci) {
|
|
|
+ if (fsp->m_ext.vlan_tci != htons(VLAN_PRIO_MASK)) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto err_out;
|
|
|
+ }
|
|
|
+ input->filter.vlan_tci = fsp->h_ext.vlan_tci;
|
|
|
+ input->filter.match_flags |= IGB_FILTER_FLAG_VLAN_TCI;
|
|
|
+ }
|
|
|
+
|
|
|
+ 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);
|
|
|
+err_out:
|
|
|
+ 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 +2947,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;
|
|
|
}
|