Commit a6ed6f22 authored by Dmitry Bogdanov's avatar Dmitry Bogdanov Committed by David S. Miller

net: aquantia: add support of L3/L4 ntuple filters

Add support of L3/L4 5-tuple {protocol, src-ip, dst-ip, src-port, dst-port}
filters. Mask is not supported. Src-port and dst-port are only compared for
TCP/UDP/SCTP packets. Both IPv4 and IPv6 are supported.
The supported actions are the drop and the queue assignment.
Due to fixed order of the rules in the NIC, the location 32-39 are
reserved for L3/L4 5-tuple filters. The locations 32 and 36 are
reserved for IPv6 filters.

Examples:
sudo ethtool -N eth0 flow-type ip6 src-ip 2001:db8:0:f101::2 \
dst-ip 2001:db8:0:f101::5 action -1 loc 36

sudo ethtool -N eth0 flow-type udp4 src-ip 10.0.0.4 \
dst-ip 10.0.0.7 src-port 2000 dst-port 2001 action 2 loc 32
Signed-off-by: default avatarDmitry Bogdanov <dmitry.bogdanov@aquantia.com>
Signed-off-by: default avatarIgor Russkikh <igor.russkikh@aquantia.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 8d0bcb01
......@@ -85,11 +85,46 @@ aq_rule_already_exists(struct aq_nic_s *aq_nic,
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_filter(struct aq_nic_s *aq_nic,
struct ethtool_rx_flow_spec *fsp)
{
int err = 0;
struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
if (fsp->flow_type & FLOW_EXT) {
err = -EOPNOTSUPP;
......@@ -103,14 +138,16 @@ aq_check_filter(struct aq_nic_s *aq_nic,
case SCTP_V4_FLOW:
case IPV4_FLOW:
case IP_USER_FLOW:
err = -EOPNOTSUPP;
rx_fltrs->fl3l4.is_ipv6 = false;
err = aq_check_approve_fl3l4(aq_nic, rx_fltrs, fsp);
break;
case TCP_V6_FLOW:
case UDP_V6_FLOW:
case SCTP_V6_FLOW:
case IPV6_FLOW:
case IPV6_USER_FLOW:
err = -EOPNOTSUPP;
rx_fltrs->fl3l4.is_ipv6 = true;
err = aq_check_approve_fl3l4(aq_nic, rx_fltrs, fsp);
break;
default:
netdev_err(aq_nic->ndev,
......@@ -156,6 +193,11 @@ aq_rule_is_not_correct(struct aq_nic_s *aq_nic,
if (!aq_nic) {
rule_is_not_correct = true;
} else if (fsp->location > AQ_RX_MAX_RXNFC_LOC) {
netdev_err(aq_nic->ndev,
"ethtool: The specified number %u rule is invalid\n",
fsp->location);
rule_is_not_correct = true;
} else if (aq_check_filter(aq_nic, fsp)) {
rule_is_not_correct = true;
} else if (fsp->ring_cookie != RX_CLS_FLOW_DISC) {
......@@ -187,6 +229,125 @@ aq_check_rule(struct aq_nic_s *aq_nic,
return err;
}
static int aq_set_data_fl3l4(struct aq_nic_s *aq_nic,
struct aq_rx_filter *aq_rx_fltr,
struct aq_rx_filter_l3l4 *data, bool add)
{
struct aq_hw_rx_fltrs_s *rx_fltrs = aq_get_hw_rx_fltrs(aq_nic);
const struct ethtool_rx_flow_spec *fsp = &aq_rx_fltr->aq_fsp;
memset(data, 0, sizeof(*data));
data->is_ipv6 = rx_fltrs->fl3l4.is_ipv6;
data->location = HW_ATL_GET_REG_LOCATION_FL3L4(fsp->location);
if (!add) {
if (!data->is_ipv6)
rx_fltrs->fl3l4.active_ipv4 &= ~BIT(data->location);
else
rx_fltrs->fl3l4.active_ipv6 &=
~BIT((data->location) / 4);
return 0;
}
data->cmd |= HW_ATL_RX_ENABLE_FLTR_L3L4;
switch (fsp->flow_type) {
case TCP_V4_FLOW:
case TCP_V6_FLOW:
data->cmd |= HW_ATL_RX_ENABLE_CMP_PROT_L4;
break;
case UDP_V4_FLOW:
case UDP_V6_FLOW:
data->cmd |= HW_ATL_RX_UDP;
data->cmd |= HW_ATL_RX_ENABLE_CMP_PROT_L4;
break;
case SCTP_V4_FLOW:
case SCTP_V6_FLOW:
data->cmd |= HW_ATL_RX_SCTP;
data->cmd |= HW_ATL_RX_ENABLE_CMP_PROT_L4;
break;
default:
break;
}
if (!data->is_ipv6) {
data->ip_src[0] =
ntohl(fsp->h_u.tcp_ip4_spec.ip4src);
data->ip_dst[0] =
ntohl(fsp->h_u.tcp_ip4_spec.ip4dst);
rx_fltrs->fl3l4.active_ipv4 |= BIT(data->location);
} else {
int i;
rx_fltrs->fl3l4.active_ipv6 |= BIT((data->location) / 4);
for (i = 0; i < HW_ATL_RX_CNT_REG_ADDR_IPV6; ++i) {
data->ip_dst[i] =
ntohl(fsp->h_u.tcp_ip6_spec.ip6dst[i]);
data->ip_src[i] =
ntohl(fsp->h_u.tcp_ip6_spec.ip6src[i]);
}
data->cmd |= HW_ATL_RX_ENABLE_L3_IPV6;
}
if (fsp->flow_type != IP_USER_FLOW &&
fsp->flow_type != IPV6_USER_FLOW) {
if (!data->is_ipv6) {
data->p_dst =
ntohs(fsp->h_u.tcp_ip4_spec.pdst);
data->p_src =
ntohs(fsp->h_u.tcp_ip4_spec.psrc);
} else {
data->p_dst =
ntohs(fsp->h_u.tcp_ip6_spec.pdst);
data->p_src =
ntohs(fsp->h_u.tcp_ip6_spec.psrc);
}
}
if (data->ip_src[0] && !data->is_ipv6)
data->cmd |= HW_ATL_RX_ENABLE_CMP_SRC_ADDR_L3;
if (data->ip_dst[0] && !data->is_ipv6)
data->cmd |= HW_ATL_RX_ENABLE_CMP_DEST_ADDR_L3;
if (data->p_dst)
data->cmd |= HW_ATL_RX_ENABLE_CMP_DEST_PORT_L4;
if (data->p_src)
data->cmd |= HW_ATL_RX_ENABLE_CMP_SRC_PORT_L4;
if (fsp->ring_cookie != RX_CLS_FLOW_DISC) {
data->cmd |= HW_ATL_RX_HOST << HW_ATL_RX_ACTION_FL3F4_SHIFT;
data->cmd |= fsp->ring_cookie << HW_ATL_RX_QUEUE_FL3L4_SHIFT;
data->cmd |= HW_ATL_RX_ENABLE_QUEUE_L3L4;
} else {
data->cmd |= HW_ATL_RX_DISCARD << HW_ATL_RX_ACTION_FL3F4_SHIFT;
}
return 0;
}
static int aq_set_fl3l4(struct aq_hw_s *aq_hw,
const struct aq_hw_ops *aq_hw_ops,
struct aq_rx_filter_l3l4 *data)
{
if (unlikely(!aq_hw_ops->hw_filter_l3l4_set))
return -EOPNOTSUPP;
return aq_hw_ops->hw_filter_l3l4_set(aq_hw, data);
}
static int aq_add_del_fl3l4(struct aq_nic_s *aq_nic,
struct aq_rx_filter *aq_rx_fltr, bool add)
{
const struct aq_hw_ops *aq_hw_ops = aq_nic->aq_hw_ops;
struct aq_hw_s *aq_hw = aq_nic->aq_hw;
struct aq_rx_filter_l3l4 data;
if (unlikely(aq_rx_fltr->aq_fsp.location < AQ_RX_FIRST_LOC_FL3L4 ||
aq_rx_fltr->aq_fsp.location > AQ_RX_LAST_LOC_FL3L4 ||
aq_set_data_fl3l4(aq_nic, aq_rx_fltr, &data, add)))
return -EINVAL;
return aq_set_fl3l4(aq_hw, aq_hw_ops, &data);
}
static int aq_add_del_rule(struct aq_nic_s *aq_nic,
struct aq_rx_filter *aq_rx_fltr, bool add)
{
......@@ -207,7 +368,8 @@ static int aq_add_del_rule(struct aq_nic_s *aq_nic,
case UDP_V6_FLOW:
case SCTP_V6_FLOW:
case IPV6_USER_FLOW:
err = -EOPNOTSUPP;
aq_rx_fltr->type = aq_rx_filter_l3l4;
err = aq_add_del_fl3l4(aq_nic, aq_rx_fltr, add);
break;
default:
err = -EINVAL;
......
......@@ -18,6 +18,10 @@
#include "aq_rss.h"
#include "hw_atl/hw_atl_utils.h"
#define AQ_RX_FIRST_LOC_FL3L4 32U
#define AQ_RX_LAST_LOC_FL3L4 39U
#define AQ_RX_MAX_RXNFC_LOC AQ_RX_LAST_LOC_FL3L4
/* NIC H/W capabilities */
struct aq_hw_caps_s {
u64 hw_features;
......@@ -130,6 +134,7 @@ struct aq_hw_s {
struct aq_ring_s;
struct aq_ring_param_s;
struct sk_buff;
struct aq_rx_filter_l3l4;
struct aq_hw_ops {
......@@ -183,6 +188,12 @@ struct aq_hw_ops {
int (*hw_packet_filter_set)(struct aq_hw_s *self,
unsigned int packet_filter);
int (*hw_filter_l3l4_set)(struct aq_hw_s *self,
struct aq_rx_filter_l3l4 *data);
int (*hw_filter_l3l4_clear)(struct aq_hw_s *self,
struct aq_rx_filter_l3l4 *data);
int (*hw_multicast_list_set)(struct aq_hw_s *self,
u8 ar_mac[AQ_HW_MULTICAST_ADDRESS_MAX]
[ETH_ALEN],
......
......@@ -61,9 +61,16 @@ struct aq_nic_cfg_s {
#define AQ_NIC_TCVEC2RING(_NIC_, _TC_, _VEC_) \
((_TC_) * AQ_CFG_TCS_MAX + (_VEC_))
struct aq_hw_rx_fl3l4 {
u8 active_ipv4;
u8 active_ipv6:2;
u8 is_ipv6;
};
struct aq_hw_rx_fltrs_s {
struct hlist_head filter_list;
u16 active_filters;
struct aq_hw_rx_fl3l4 fl3l4;
};
struct aq_nic_s {
......
......@@ -946,6 +946,63 @@ static int hw_atl_b0_hw_ring_rx_stop(struct aq_hw_s *self,
return aq_hw_err_from_flags(self);
}
static int hw_atl_b0_hw_fl3l4_clear(struct aq_hw_s *self,
struct aq_rx_filter_l3l4 *data)
{
u8 location = data->location;
if (!data->is_ipv6) {
hw_atl_rpfl3l4_cmd_clear(self, location);
hw_atl_rpf_l4_spd_set(self, 0U, location);
hw_atl_rpf_l4_dpd_set(self, 0U, location);
hw_atl_rpfl3l4_ipv4_src_addr_clear(self, location);
hw_atl_rpfl3l4_ipv4_dest_addr_clear(self, location);
} else {
int i;
for (i = 0; i < HW_ATL_RX_CNT_REG_ADDR_IPV6; ++i) {
hw_atl_rpfl3l4_cmd_clear(self, location + i);
hw_atl_rpf_l4_spd_set(self, 0U, location + i);
hw_atl_rpf_l4_dpd_set(self, 0U, location + i);
}
hw_atl_rpfl3l4_ipv6_src_addr_clear(self, location);
hw_atl_rpfl3l4_ipv6_dest_addr_clear(self, location);
}
return aq_hw_err_from_flags(self);
}
static int hw_atl_b0_hw_fl3l4_set(struct aq_hw_s *self,
struct aq_rx_filter_l3l4 *data)
{
u8 location = data->location;
hw_atl_b0_hw_fl3l4_clear(self, data);
if (data->cmd) {
if (!data->is_ipv6) {
hw_atl_rpfl3l4_ipv4_dest_addr_set(self,
location,
data->ip_dst[0]);
hw_atl_rpfl3l4_ipv4_src_addr_set(self,
location,
data->ip_src[0]);
} else {
hw_atl_rpfl3l4_ipv6_dest_addr_set(self,
location,
data->ip_dst);
hw_atl_rpfl3l4_ipv6_src_addr_set(self,
location,
data->ip_src);
}
}
hw_atl_rpf_l4_dpd_set(self, data->p_dst, location);
hw_atl_rpf_l4_spd_set(self, data->p_src, location);
hw_atl_rpfl3l4_cmd_set(self, location, data->cmd);
return aq_hw_err_from_flags(self);
}
const struct aq_hw_ops hw_atl_ops_b0 = {
.hw_set_mac_address = hw_atl_b0_hw_mac_addr_set,
.hw_init = hw_atl_b0_hw_init,
......@@ -970,6 +1027,7 @@ const struct aq_hw_ops hw_atl_ops_b0 = {
.hw_ring_rx_init = hw_atl_b0_hw_ring_rx_init,
.hw_ring_tx_init = hw_atl_b0_hw_ring_tx_init,
.hw_packet_filter_set = hw_atl_b0_hw_packet_filter_set,
.hw_filter_l3l4_set = hw_atl_b0_hw_fl3l4_set,
.hw_multicast_list_set = hw_atl_b0_hw_multicast_list_set,
.hw_interrupt_moderation_set = hw_atl_b0_hw_interrupt_moderation_set,
.hw_rss_set = hw_atl_b0_hw_rss_set,
......
......@@ -240,6 +240,49 @@ struct __packed offload_info {
u8 buf[0];
};
enum hw_atl_rx_action_with_traffic {
HW_ATL_RX_DISCARD,
HW_ATL_RX_HOST,
};
struct aq_rx_filter_l3l4 {
u32 cmd;
u8 location;
u32 ip_dst[4];
u32 ip_src[4];
u16 p_dst;
u16 p_src;
u8 is_ipv6;
};
enum hw_atl_rx_protocol_value_l3l4 {
HW_ATL_RX_TCP,
HW_ATL_RX_UDP,
HW_ATL_RX_SCTP,
HW_ATL_RX_ICMP
};
enum hw_atl_rx_ctrl_registers_l3l4 {
HW_ATL_RX_ENABLE_MNGMNT_QUEUE_L3L4 = BIT(22),
HW_ATL_RX_ENABLE_QUEUE_L3L4 = BIT(23),
HW_ATL_RX_ENABLE_ARP_FLTR_L3 = BIT(24),
HW_ATL_RX_ENABLE_CMP_PROT_L4 = BIT(25),
HW_ATL_RX_ENABLE_CMP_DEST_PORT_L4 = BIT(26),
HW_ATL_RX_ENABLE_CMP_SRC_PORT_L4 = BIT(27),
HW_ATL_RX_ENABLE_CMP_DEST_ADDR_L3 = BIT(28),
HW_ATL_RX_ENABLE_CMP_SRC_ADDR_L3 = BIT(29),
HW_ATL_RX_ENABLE_L3_IPV6 = BIT(30),
HW_ATL_RX_ENABLE_FLTR_L3L4 = BIT(31)
};
#define HW_ATL_RX_QUEUE_FL3L4_SHIFT 8U
#define HW_ATL_RX_ACTION_FL3F4_SHIFT 16U
#define HW_ATL_RX_CNT_REG_ADDR_IPV6 4U
#define HW_ATL_GET_REG_LOCATION_FL3L4(location) \
((location) - AQ_RX_FIRST_LOC_FL3L4)
#define HAL_ATLANTIC_UTILS_CHIP_MIPS 0x00000001U
#define HAL_ATLANTIC_UTILS_CHIP_TPO2 0x00000002U
#define HAL_ATLANTIC_UTILS_CHIP_RPF2 0x00000004U
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment