OSDN Git Service

net: aquantia: add support of L3/L4 ntuple filters
authorDmitry Bogdanov <dmitry.bogdanov@aquantia.com>
Mon, 12 Nov 2018 15:46:02 +0000 (15:46 +0000)
committerDavid S. Miller <davem@davemloft.net>
Wed, 14 Nov 2018 16:48:37 +0000 (08:48 -0800)
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: Dmitry Bogdanov <dmitry.bogdanov@aquantia.com>
Signed-off-by: Igor Russkikh <igor.russkikh@aquantia.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/aquantia/atlantic/aq_filters.c
drivers/net/ethernet/aquantia/atlantic/aq_hw.h
drivers/net/ethernet/aquantia/atlantic/aq_nic.h
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_b0.c
drivers/net/ethernet/aquantia/atlantic/hw_atl/hw_atl_utils.h

index 8cb4eae..34e2a28 100644 (file)
@@ -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;
index a1e70da..edc7d60 100644 (file)
 #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],
index c4a20dc..d3a087e 100644 (file)
@@ -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 {
index eba1eb7..b4bfe66 100644 (file)
@@ -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,
index 3613fca..0da227f 100644 (file)
@@ -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