// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2014-2017 aQuantia Corporation. */
/* File aq_filters.c: RX filters related functions. */
#include "aq_filters.h"
static bool __must_check
aq_rule_is_approve(struct ethtool_rx_flow_spec *fsp)
{
if (fsp->flow_type & FLOW_MAC_EXT)
return false;
switch (fsp->flow_type & ~FLOW_EXT) {
case ETHER_FLOW:
case TCP_V4_FLOW:
case UDP_V4_FLOW:
case SCTP_V4_FLOW:
case TCP_V6_FLOW:
case UDP_V6_FLOW:
case SCTP_V6_FLOW:
case IPV4_FLOW:
case IPV6_FLOW:
return true;
case IP_USER_FLOW:
switch (fsp->h_u.usr_ip4_spec.proto) {
case IPPROTO_TCP:
case IPPROTO_UDP:
case IPPROTO_SCTP:
case IPPROTO_IP:
return true;
default:
return false;
}
case IPV6_USER_FLOW:
switch (fsp->h_u.usr_ip6_spec.l4_proto) {
case IPPROTO_TCP:
case IPPROTO_UDP:
case IPPROTO_SCTP:
case IPPROTO_IP:
return true;
default:
return false;
}
default:
return false;
}
return false;
}
static bool __must_check
aq_match_filter(struct ethtool_rx_flow_spec *fsp1,
struct ethtool_rx_flow_spec *fsp2)
{
if (fsp1->flow_type != fsp2->flow_type ||
memcmp(&fsp1->h_u, &fsp2->h_u, sizeof(fsp2->h_u)) ||
memcmp(&fsp1->h_ext, &fsp2->h_ext, sizeof(fsp2->h_ext)) ||
memcmp(&fsp1->m_u, &fsp2->m_u, sizeof(fsp2->m_u)) ||
memcmp(&fsp1->m_ext, &fsp2->m_ext, sizeof(fsp2->m_ext)))
return false;
return true;
}
static bool __must_check
aq_rule_already_exists(struct aq_nic_s *aq_nic,
struct ethtool_rx_flow_spec *fsp)
{
struct aq_rx_filter *rule;
struct hlist_node *aq_node2;
struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
hlist_for_each_entry_safe(rule, aq_node2,
&rx_fltrs->filter_list, aq_node) {
if (rule->aq_fsp.location == fsp->location)
continue;
if (aq_match_filter(&rule->aq_fsp, fsp)) {
netdev_err(aq_nic->ndev,
"ethtool: This filter is already set\n");
return true;
}
}
return false;
}
static int aq_check_approve_fl3l4(struct aq_nic_s *aq_nic,
struct aq_hw_rx_fltrs_s *rx_fltrs,
struct ethtool_rx_flow_spec *fsp)
{
if (fsp->location < AQ_RX_FIRST_LOC_FL3L4 ||
fsp->location > AQ_RX_LAST_LOC_FL3L4) {
netdev_err(aq_nic->ndev,
"ethtool: location must be in range [%d, %d]",
AQ_RX_FIRST_LOC_FL3L4,
AQ_RX_LAST_LOC_FL3L4);
return -EINVAL;
}
if (rx_fltrs->fl3l4.is_ipv6 && rx_fltrs->fl3l4.active_ipv4) {
rx_fltrs->fl3l4.is_ipv6 = false;
netdev_err(aq_nic->ndev,
"ethtool: mixing ipv4 and ipv6 is not allowed");
return -EINVAL;
} else if (!rx_fltrs->fl3l4.is_ipv6 && rx_fltrs->fl3l4.active_ipv6) {
rx_fltrs->fl3l4.is_ipv6 = true;
netdev_err(aq_nic->ndev,
"ethtool: mixing ipv4 and ipv6 is not allowed");
return -EINVAL;
} else if (rx_fltrs->fl3l4.is_ipv6 &&
fsp->location != AQ_RX_FIRST_LOC_FL3L4 + 4 &&
fsp->location != AQ_RX_FIRST_LOC_FL3L4) {
netdev_err(aq_nic->ndev,
"ethtool: The specified location for ipv6 must be %d or %d",
AQ_RX_FIRST_LOC_FL3L4, AQ_RX_FIRST_LOC_FL3L4 + 4);
return -EINVAL;
}
return 0;
}
static int __must_check
aq_check_approve_fl2(struct aq_nic_s *aq_nic,
struct aq_hw_rx_fltrs_s *rx_fltrs,
struct ethtool_rx_flow_spec *fsp)
{
if (fsp->location < AQ_RX_FIRST_LOC_FETHERT ||
fsp->location > AQ_RX_LAST_LOC_FETHERT) {
netdev_err(aq_nic->ndev,
"ethtool: location must be in range [%d, %d]",
AQ_RX_FIRST_LOC_FETHERT,
AQ_RX_LAST_LOC_FETHERT);
return -EINVAL;
}
if (be16_to_cpu(fsp->m_ext.vlan_tci) == VLAN_PRIO_MASK &&
fsp->m_u.ether_spec.h_proto == 0U) {
netdev_err(aq_nic->ndev,
"ethtool: proto (ether_type) parameter must be specified");
return -EINVAL;
}
return 0;
}
static int __must_check
aq_check_approve_fvlan(struct aq_nic_s *aq_nic,
struct aq_hw_rx_fltrs_s *rx_fltrs,
struct ethtool_rx_flow_spec *fsp)
{
if (fsp->location < AQ_RX_FIRST_LOC_FVLANID ||
fsp->location > AQ_RX_LAST_LOC_FVLANID) {