Commit 4bdb4411 authored by Jiawen Wu's avatar Jiawen Wu Committed by Paolo Abeni

net: txgbe: support Flow Director perfect filters

Support the addition and deletion of Flow Director filters.

Supported fields: src-ip, dst-ip, src-port, dst-port
Supported flow-types: tcp4, udp4, sctp4, ipv4
Signed-off-by: default avatarJiawen Wu <jiawenwu@trustnetic.com>
Reviewed-by: default avatarSimon Horman <horms@kernel.org>
Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent b501d261
...@@ -2705,6 +2705,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) ...@@ -2705,6 +2705,7 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
{ {
netdev_features_t changed = netdev->features ^ features; netdev_features_t changed = netdev->features ^ features;
struct wx *wx = netdev_priv(netdev); struct wx *wx = netdev_priv(netdev);
bool need_reset = false;
if (features & NETIF_F_RXHASH) { if (features & NETIF_F_RXHASH) {
wr32m(wx, WX_RDB_RA_CTL, WX_RDB_RA_CTL_RSS_EN, wr32m(wx, WX_RDB_RA_CTL, WX_RDB_RA_CTL_RSS_EN,
...@@ -2722,6 +2723,36 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features) ...@@ -2722,6 +2723,36 @@ int wx_set_features(struct net_device *netdev, netdev_features_t features)
else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER)) else if (changed & (NETIF_F_HW_VLAN_CTAG_RX | NETIF_F_HW_VLAN_CTAG_FILTER))
wx_set_rx_mode(netdev); wx_set_rx_mode(netdev);
if (!(test_bit(WX_FLAG_FDIR_CAPABLE, wx->flags)))
return 0;
/* Check if Flow Director n-tuple support was enabled or disabled. If
* the state changed, we need to reset.
*/
switch (features & NETIF_F_NTUPLE) {
case NETIF_F_NTUPLE:
/* turn off ATR, enable perfect filters and reset */
if (!(test_and_set_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
need_reset = true;
clear_bit(WX_FLAG_FDIR_HASH, wx->flags);
break;
default:
/* turn off perfect filters, enable ATR and reset */
if (test_and_clear_bit(WX_FLAG_FDIR_PERFECT, wx->flags))
need_reset = true;
/* We cannot enable ATR if RSS is disabled */
if (wx->ring_feature[RING_F_RSS].limit <= 1)
break;
set_bit(WX_FLAG_FDIR_HASH, wx->flags);
break;
}
if (need_reset)
wx->do_reset(netdev);
return 0; return 0;
} }
EXPORT_SYMBOL(wx_set_features); EXPORT_SYMBOL(wx_set_features);
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "../libwx/wx_type.h" #include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h" #include "../libwx/wx_lib.h"
#include "txgbe_type.h" #include "txgbe_type.h"
#include "txgbe_fdir.h"
#include "txgbe_ethtool.h" #include "txgbe_ethtool.h"
static int txgbe_set_ringparam(struct net_device *netdev, static int txgbe_set_ringparam(struct net_device *netdev,
...@@ -79,6 +80,430 @@ static int txgbe_set_channels(struct net_device *dev, ...@@ -79,6 +80,430 @@ static int txgbe_set_channels(struct net_device *dev,
return txgbe_setup_tc(dev, netdev_get_num_tc(dev)); return txgbe_setup_tc(dev, netdev_get_num_tc(dev));
} }
static int txgbe_get_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
union txgbe_atr_input *mask = &txgbe->fdir_mask;
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (fsp->location <= rule->sw_idx)
break;
}
if (!rule || fsp->location != rule->sw_idx)
return -EINVAL;
/* set flow type field */
switch (rule->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
fsp->flow_type = TCP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
fsp->flow_type = UDP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
fsp->flow_type = SCTP_V4_FLOW;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
fsp->flow_type = IP_USER_FLOW;
fsp->h_u.usr_ip4_spec.ip_ver = ETH_RX_NFC_IP4;
fsp->h_u.usr_ip4_spec.proto = 0;
fsp->m_u.usr_ip4_spec.proto = 0;
break;
default:
return -EINVAL;
}
fsp->h_u.tcp_ip4_spec.psrc = rule->filter.formatted.src_port;
fsp->m_u.tcp_ip4_spec.psrc = mask->formatted.src_port;
fsp->h_u.tcp_ip4_spec.pdst = rule->filter.formatted.dst_port;
fsp->m_u.tcp_ip4_spec.pdst = mask->formatted.dst_port;
fsp->h_u.tcp_ip4_spec.ip4src = rule->filter.formatted.src_ip[0];
fsp->m_u.tcp_ip4_spec.ip4src = mask->formatted.src_ip[0];
fsp->h_u.tcp_ip4_spec.ip4dst = rule->filter.formatted.dst_ip[0];
fsp->m_u.tcp_ip4_spec.ip4dst = mask->formatted.dst_ip[0];
fsp->h_ext.vlan_etype = rule->filter.formatted.flex_bytes;
fsp->m_ext.vlan_etype = mask->formatted.flex_bytes;
fsp->h_ext.data[1] = htonl(rule->filter.formatted.vm_pool);
fsp->m_ext.data[1] = htonl(mask->formatted.vm_pool);
fsp->flow_type |= FLOW_EXT;
/* record action */
if (rule->action == TXGBE_RDB_FDIR_DROP_QUEUE)
fsp->ring_cookie = RX_CLS_FLOW_DISC;
else
fsp->ring_cookie = rule->action;
return 0;
}
static int txgbe_get_ethtool_fdir_all(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct txgbe_fdir_filter *rule;
struct hlist_node *node;
int cnt = 0;
/* report total rule count */
cmd->data = (1024 << TXGBE_FDIR_PBALLOC_64K) - 2;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
if (cnt == cmd->rule_cnt)
return -EMSGSIZE;
rule_locs[cnt] = rule->sw_idx;
cnt++;
}
cmd->rule_cnt = cnt;
return 0;
}
static int txgbe_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
u32 *rule_locs)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_GRXRINGS:
cmd->data = wx->num_rx_queues;
ret = 0;
break;
case ETHTOOL_GRXCLSRLCNT:
cmd->rule_cnt = txgbe->fdir_filter_count;
ret = 0;
break;
case ETHTOOL_GRXCLSRULE:
ret = txgbe_get_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_GRXCLSRLALL:
ret = txgbe_get_ethtool_fdir_all(txgbe, cmd, (u32 *)rule_locs);
break;
default:
break;
}
return ret;
}
static int txgbe_flowspec_to_flow_type(struct ethtool_rx_flow_spec *fsp,
u8 *flow_type)
{
switch (fsp->flow_type & ~FLOW_EXT) {
case TCP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case UDP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case SCTP_V4_FLOW:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case IP_USER_FLOW:
switch (fsp->h_u.usr_ip4_spec.proto) {
case IPPROTO_TCP:
*flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
break;
case IPPROTO_UDP:
*flow_type = TXGBE_ATR_FLOW_TYPE_UDPV4;
break;
case IPPROTO_SCTP:
*flow_type = TXGBE_ATR_FLOW_TYPE_SCTPV4;
break;
case 0:
if (!fsp->m_u.usr_ip4_spec.proto) {
*flow_type = TXGBE_ATR_FLOW_TYPE_IPV4;
break;
}
fallthrough;
default:
return -EINVAL;
}
break;
default:
return -EINVAL;
}
return 0;
}
static bool txgbe_match_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input)
{
struct txgbe_fdir_filter *rule = NULL;
struct hlist_node *node2;
hlist_for_each_entry_safe(rule, node2, &txgbe->fdir_filter_list,
fdir_node) {
if (rule->filter.formatted.bkt_hash ==
input->filter.formatted.bkt_hash &&
rule->action == input->action) {
wx_dbg(txgbe->wx, "FDIR entry already exist\n");
return true;
}
}
return false;
}
static int txgbe_update_ethtool_fdir_entry(struct txgbe *txgbe,
struct txgbe_fdir_filter *input,
u16 sw_idx)
{
struct hlist_node *node = NULL, *parent = NULL;
struct txgbe_fdir_filter *rule;
struct wx *wx = txgbe->wx;
bool deleted = false;
int err;
hlist_for_each_entry_safe(rule, node, &txgbe->fdir_filter_list,
fdir_node) {
/* hash found, or no matching entry */
if (rule->sw_idx >= sw_idx)
break;
parent = node;
}
/* if there is an old rule occupying our place remove it */
if (rule && rule->sw_idx == sw_idx) {
/* hardware filters are only configured when interface is up,
* and we should not issue filter commands while the interface
* is down
*/
if (netif_running(wx->netdev) &&
(!input || rule->filter.formatted.bkt_hash !=
input->filter.formatted.bkt_hash)) {
err = txgbe_fdir_erase_perfect_filter(wx,
&rule->filter,
sw_idx);
if (err)
return -EINVAL;
}
hlist_del(&rule->fdir_node);
kfree(rule);
txgbe->fdir_filter_count--;
deleted = true;
}
/* If we weren't given an input, then this was a request to delete a
* filter. We should return -EINVAL if the filter wasn't found, but
* return 0 if the rule was successfully deleted.
*/
if (!input)
return deleted ? 0 : -EINVAL;
/* initialize node and set software index */
INIT_HLIST_NODE(&input->fdir_node);
/* add filter to the list */
if (parent)
hlist_add_behind(&input->fdir_node, parent);
else
hlist_add_head(&input->fdir_node,
&txgbe->fdir_filter_list);
/* update counts */
txgbe->fdir_filter_count++;
return 0;
}
static int txgbe_add_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
struct txgbe_fdir_filter *input;
union txgbe_atr_input mask;
struct wx *wx = txgbe->wx;
int err = -EINVAL;
u16 ptype = 0;
u8 queue;
if (!(test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)))
return -EOPNOTSUPP;
/* ring_cookie is a masked into a set of queues and txgbe pools or
* we use drop index
*/
if (fsp->ring_cookie == RX_CLS_FLOW_DISC) {
queue = TXGBE_RDB_FDIR_DROP_QUEUE;
} else {
u32 ring = ethtool_get_flow_spec_ring(fsp->ring_cookie);
if (ring >= wx->num_rx_queues)
return -EINVAL;
/* Map the ring onto the absolute queue index */
queue = wx->rx_ring[ring]->reg_idx;
}
/* Don't allow indexes to exist outside of available space */
if (fsp->location >= ((1024 << TXGBE_FDIR_PBALLOC_64K) - 2)) {
wx_err(wx, "Location out of range\n");
return -EINVAL;
}
input = kzalloc(sizeof(*input), GFP_ATOMIC);
if (!input)
return -ENOMEM;
memset(&mask, 0, sizeof(union txgbe_atr_input));
/* set SW index */
input->sw_idx = fsp->location;
/* record flow type */
if (txgbe_flowspec_to_flow_type(fsp,
&input->filter.formatted.flow_type)) {
wx_err(wx, "Unrecognized flow type\n");
goto err_out;
}
mask.formatted.flow_type = TXGBE_ATR_L4TYPE_IPV6_MASK |
TXGBE_ATR_L4TYPE_MASK;
if (input->filter.formatted.flow_type == TXGBE_ATR_FLOW_TYPE_IPV4)
mask.formatted.flow_type &= TXGBE_ATR_L4TYPE_IPV6_MASK;
/* Copy input into formatted structures */
input->filter.formatted.src_ip[0] = fsp->h_u.tcp_ip4_spec.ip4src;
mask.formatted.src_ip[0] = fsp->m_u.tcp_ip4_spec.ip4src;
input->filter.formatted.dst_ip[0] = fsp->h_u.tcp_ip4_spec.ip4dst;
mask.formatted.dst_ip[0] = fsp->m_u.tcp_ip4_spec.ip4dst;
input->filter.formatted.src_port = fsp->h_u.tcp_ip4_spec.psrc;
mask.formatted.src_port = fsp->m_u.tcp_ip4_spec.psrc;
input->filter.formatted.dst_port = fsp->h_u.tcp_ip4_spec.pdst;
mask.formatted.dst_port = fsp->m_u.tcp_ip4_spec.pdst;
if (fsp->flow_type & FLOW_EXT) {
input->filter.formatted.vm_pool =
(unsigned char)ntohl(fsp->h_ext.data[1]);
mask.formatted.vm_pool =
(unsigned char)ntohl(fsp->m_ext.data[1]);
input->filter.formatted.flex_bytes =
fsp->h_ext.vlan_etype;
mask.formatted.flex_bytes = fsp->m_ext.vlan_etype;
}
switch (input->filter.formatted.flow_type) {
case TXGBE_ATR_FLOW_TYPE_TCPV4:
ptype = WX_PTYPE_L2_IPV4_TCP;
break;
case TXGBE_ATR_FLOW_TYPE_UDPV4:
ptype = WX_PTYPE_L2_IPV4_UDP;
break;
case TXGBE_ATR_FLOW_TYPE_SCTPV4:
ptype = WX_PTYPE_L2_IPV4_SCTP;
break;
case TXGBE_ATR_FLOW_TYPE_IPV4:
ptype = WX_PTYPE_L2_IPV4;
break;
default:
break;
}
input->filter.formatted.vlan_id = htons(ptype);
if (mask.formatted.flow_type & TXGBE_ATR_L4TYPE_MASK)
mask.formatted.vlan_id = htons(0xFFFF);
else
mask.formatted.vlan_id = htons(0xFFF8);
/* determine if we need to drop or route the packet */
if (fsp->ring_cookie == RX_CLS_FLOW_DISC)
input->action = TXGBE_RDB_FDIR_DROP_QUEUE;
else
input->action = fsp->ring_cookie;
spin_lock(&txgbe->fdir_perfect_lock);
if (hlist_empty(&txgbe->fdir_filter_list)) {
/* save mask and program input mask into HW */
memcpy(&txgbe->fdir_mask, &mask, sizeof(mask));
err = txgbe_fdir_set_input_mask(wx, &mask);
if (err)
goto err_unlock;
} else if (memcmp(&txgbe->fdir_mask, &mask, sizeof(mask))) {
wx_err(wx, "Hardware only supports one mask per port. To change the mask you must first delete all the rules.\n");
goto err_unlock;
}
/* apply mask and compute/store hash */
txgbe_atr_compute_perfect_hash(&input->filter, &mask);
/* check if new entry does not exist on filter list */
if (txgbe_match_ethtool_fdir_entry(txgbe, input))
goto err_unlock;
/* only program filters to hardware if the net device is running, as
* we store the filters in the Rx buffer which is not allocated when
* the device is down
*/
if (netif_running(wx->netdev)) {
err = txgbe_fdir_write_perfect_filter(wx, &input->filter,
input->sw_idx, queue);
if (err)
goto err_unlock;
}
txgbe_update_ethtool_fdir_entry(txgbe, input, input->sw_idx);
spin_unlock(&txgbe->fdir_perfect_lock);
return 0;
err_unlock:
spin_unlock(&txgbe->fdir_perfect_lock);
err_out:
kfree(input);
return err;
}
static int txgbe_del_ethtool_fdir_entry(struct txgbe *txgbe,
struct ethtool_rxnfc *cmd)
{
struct ethtool_rx_flow_spec *fsp =
(struct ethtool_rx_flow_spec *)&cmd->fs;
int err = 0;
spin_lock(&txgbe->fdir_perfect_lock);
err = txgbe_update_ethtool_fdir_entry(txgbe, NULL, fsp->location);
spin_unlock(&txgbe->fdir_perfect_lock);
return err;
}
static int txgbe_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
{
struct wx *wx = netdev_priv(dev);
struct txgbe *txgbe = wx->priv;
int ret = -EOPNOTSUPP;
switch (cmd->cmd) {
case ETHTOOL_SRXCLSRLINS:
ret = txgbe_add_ethtool_fdir_entry(txgbe, cmd);
break;
case ETHTOOL_SRXCLSRLDEL:
ret = txgbe_del_ethtool_fdir_entry(txgbe, cmd);
break;
default:
break;
}
return ret;
}
static const struct ethtool_ops txgbe_ethtool_ops = { static const struct ethtool_ops txgbe_ethtool_ops = {
.supported_coalesce_params = ETHTOOL_COALESCE_USECS | .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ, ETHTOOL_COALESCE_TX_MAX_FRAMES_IRQ,
...@@ -100,6 +525,8 @@ static const struct ethtool_ops txgbe_ethtool_ops = { ...@@ -100,6 +525,8 @@ static const struct ethtool_ops txgbe_ethtool_ops = {
.set_coalesce = wx_set_coalesce, .set_coalesce = wx_set_coalesce,
.get_channels = wx_get_channels, .get_channels = wx_get_channels,
.set_channels = txgbe_set_channels, .set_channels = txgbe_set_channels,
.get_rxnfc = txgbe_get_rxnfc,
.set_rxnfc = txgbe_set_rxnfc,
.get_msglevel = wx_get_msglevel, .get_msglevel = wx_get_msglevel,
.set_msglevel = wx_set_msglevel, .set_msglevel = wx_set_msglevel,
}; };
......
...@@ -90,6 +90,71 @@ static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input, ...@@ -90,6 +90,71 @@ static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input,
*hash = sig_hash ^ bucket_hash; *hash = sig_hash ^ bucket_hash;
} }
#define TXGBE_COMPUTE_BKT_HASH_ITERATION(_n) \
do { \
u32 n = (_n); \
if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
bucket_hash ^= lo_hash_dword >> n; \
if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
bucket_hash ^= hi_hash_dword >> n; \
} while (0)
/**
* txgbe_atr_compute_perfect_hash - Compute the perfect filter hash
* @input: input bitstream to compute the hash on
* @input_mask: mask for the input bitstream
*
* This function serves two main purposes. First it applies the input_mask
* to the atr_input resulting in a cleaned up atr_input data stream.
* Secondly it computes the hash and stores it in the bkt_hash field at
* the end of the input byte stream. This way it will be available for
* future use without needing to recompute the hash.
**/
void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
union txgbe_atr_input *input_mask)
{
u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
u32 bucket_hash = 0;
__be32 hi_dword = 0;
u32 i = 0;
/* Apply masks to input data */
for (i = 0; i < 11; i++)
input->dword_stream[i] &= input_mask->dword_stream[i];
/* record the flow_vm_vlan bits as they are a key part to the hash */
flow_vm_vlan = ntohl(input->dword_stream[0]);
/* generate common hash dword */
for (i = 1; i <= 10; i++)
hi_dword ^= input->dword_stream[i];
hi_hash_dword = ntohl(hi_dword);
/* low dword is word swapped version of common */
lo_hash_dword = (hi_hash_dword >> 16) | (hi_hash_dword << 16);
/* apply flow ID/VM pool/VLAN ID bits to hash words */
hi_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan >> 16);
/* Process bits 0 and 16 */
TXGBE_COMPUTE_BKT_HASH_ITERATION(0);
/* apply flow ID/VM pool/VLAN ID bits to lo hash dword, we had to
* delay this because bit 0 of the stream should not be processed
* so we do not add the VLAN until after bit 0 was processed
*/
lo_hash_dword ^= flow_vm_vlan ^ (flow_vm_vlan << 16);
/* Process remaining 30 bit of the key */
for (i = 1; i <= 15; i++)
TXGBE_COMPUTE_BKT_HASH_ITERATION(i);
/* Limit hash to 13 bits since max bucket count is 8K.
* Store result at the end of the input stream.
*/
input->formatted.bkt_hash = (__force __be16)(bucket_hash & 0x1FFF);
}
static int txgbe_fdir_check_cmd_complete(struct wx *wx) static int txgbe_fdir_check_cmd_complete(struct wx *wx)
{ {
u32 val; u32 val;
...@@ -239,6 +304,188 @@ void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype) ...@@ -239,6 +304,188 @@ void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype)
ring->queue_index); ring->queue_index);
} }
int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask)
{
u32 fdirm = 0, fdirtcpm = 0, flex = 0;
/* Program the relevant mask registers. If src/dst_port or src/dst_addr
* are zero, then assume a full mask for that field. Also assume that
* a VLAN of 0 is unspecified, so mask that out as well. L4type
* cannot be masked out in this implementation.
*
* This also assumes IPv4 only. IPv6 masking isn't supported at this
* point in time.
*/
/* verify bucket hash is cleared on hash generation */
if (input_mask->formatted.bkt_hash)
wx_dbg(wx, "bucket hash should always be 0 in mask\n");
/* Program FDIRM and verify partial masks */
switch (input_mask->formatted.vm_pool & 0x7F) {
case 0x0:
fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_POOL;
break;
case 0x7F:
break;
default:
wx_err(wx, "Error on vm pool mask\n");
return -EINVAL;
}
switch (input_mask->formatted.flow_type & TXGBE_ATR_L4TYPE_MASK) {
case 0x0:
fdirm |= TXGBE_RDB_FDIR_OTHER_MSK_L4P;
if (input_mask->formatted.dst_port ||
input_mask->formatted.src_port) {
wx_err(wx, "Error on src/dst port mask\n");
return -EINVAL;
}
break;
case TXGBE_ATR_L4TYPE_MASK:
break;
default:
wx_err(wx, "Error on flow type mask\n");
return -EINVAL;
}
/* Now mask VM pool and destination IPv6 - bits 5 and 2 */
wr32(wx, TXGBE_RDB_FDIR_OTHER_MSK, fdirm);
flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0));
flex &= ~TXGBE_RDB_FDIR_FLEX_CFG_FIELD0;
flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6));
switch ((__force u16)input_mask->formatted.flex_bytes & 0xFFFF) {
case 0x0000:
/* Mask Flex Bytes */
flex |= TXGBE_RDB_FDIR_FLEX_CFG_MSK;
break;
case 0xFFFF:
break;
default:
wx_err(wx, "Error on flexible byte mask\n");
return -EINVAL;
}
wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(0), flex);
/* store the TCP/UDP port masks, bit reversed from port layout */
fdirtcpm = ntohs(input_mask->formatted.dst_port);
fdirtcpm <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
fdirtcpm |= ntohs(input_mask->formatted.src_port);
/* write both the same so that UDP and TCP use the same mask */
wr32(wx, TXGBE_RDB_FDIR_TCP_MSK, ~fdirtcpm);
wr32(wx, TXGBE_RDB_FDIR_UDP_MSK, ~fdirtcpm);
wr32(wx, TXGBE_RDB_FDIR_SCTP_MSK, ~fdirtcpm);
/* store source and destination IP masks (little-enian) */
wr32(wx, TXGBE_RDB_FDIR_SA4_MSK,
ntohl(~input_mask->formatted.src_ip[0]));
wr32(wx, TXGBE_RDB_FDIR_DA4_MSK,
ntohl(~input_mask->formatted.dst_ip[0]));
return 0;
}
int txgbe_fdir_write_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id, u8 queue)
{
u32 fdirport, fdirvlan, fdirhash, fdircmd;
int err = 0;
/* currently IPv6 is not supported, must be programmed with 0 */
wr32(wx, TXGBE_RDB_FDIR_IP6(2), ntohl(input->formatted.src_ip[0]));
wr32(wx, TXGBE_RDB_FDIR_IP6(1), ntohl(input->formatted.src_ip[1]));
wr32(wx, TXGBE_RDB_FDIR_IP6(0), ntohl(input->formatted.src_ip[2]));
/* record the source address (little-endian) */
wr32(wx, TXGBE_RDB_FDIR_SA, ntohl(input->formatted.src_ip[0]));
/* record the first 32 bits of the destination address
* (little-endian)
*/
wr32(wx, TXGBE_RDB_FDIR_DA, ntohl(input->formatted.dst_ip[0]));
/* record source and destination port (little-endian)*/
fdirport = ntohs(input->formatted.dst_port);
fdirport <<= TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT;
fdirport |= ntohs(input->formatted.src_port);
wr32(wx, TXGBE_RDB_FDIR_PORT, fdirport);
/* record packet type and flex_bytes (little-endian) */
fdirvlan = ntohs(input->formatted.flex_bytes);
fdirvlan <<= TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT;
fdirvlan |= ntohs(input->formatted.vlan_id);
wr32(wx, TXGBE_RDB_FDIR_FLEX, fdirvlan);
/* configure FDIRHASH register */
fdirhash = (__force u32)input->formatted.bkt_hash |
TXGBE_RDB_FDIR_HASH_BUCKET_VALID |
TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
/* flush all previous writes to make certain registers are
* programmed prior to issuing the command
*/
WX_WRITE_FLUSH(wx);
/* configure FDIRCMD register */
fdircmd = TXGBE_RDB_FDIR_CMD_CMD_ADD_FLOW |
TXGBE_RDB_FDIR_CMD_FILTER_UPDATE |
TXGBE_RDB_FDIR_CMD_LAST | TXGBE_RDB_FDIR_CMD_QUEUE_EN;
if (queue == TXGBE_RDB_FDIR_DROP_QUEUE)
fdircmd |= TXGBE_RDB_FDIR_CMD_DROP;
fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(input->formatted.flow_type);
fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);
fdircmd |= TXGBE_RDB_FDIR_CMD_VT_POOL(input->formatted.vm_pool);
wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);
err = txgbe_fdir_check_cmd_complete(wx);
if (err)
wx_err(wx, "Flow Director command did not complete!\n");
return err;
}
int txgbe_fdir_erase_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id)
{
u32 fdirhash, fdircmd;
int err = 0;
/* configure FDIRHASH register */
fdirhash = (__force u32)input->formatted.bkt_hash;
fdirhash |= TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(soft_id);
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
/* flush hash to HW */
WX_WRITE_FLUSH(wx);
/* Query if filter is present */
wr32(wx, TXGBE_RDB_FDIR_CMD, TXGBE_RDB_FDIR_CMD_CMD_QUERY_REM_FILT);
err = txgbe_fdir_check_cmd_complete(wx);
if (err) {
wx_err(wx, "Flow Director command did not complete!\n");
return err;
}
fdircmd = rd32(wx, TXGBE_RDB_FDIR_CMD);
/* if filter exists in hardware then remove it */
if (fdircmd & TXGBE_RDB_FDIR_CMD_FILTER_VALID) {
wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhash);
WX_WRITE_FLUSH(wx);
wr32(wx, TXGBE_RDB_FDIR_CMD,
TXGBE_RDB_FDIR_CMD_CMD_REMOVE_FLOW);
}
return 0;
}
/** /**
* txgbe_fdir_enable - Initialize Flow Director control registers * txgbe_fdir_enable - Initialize Flow Director control registers
* @wx: pointer to hardware structure * @wx: pointer to hardware structure
...@@ -291,12 +538,106 @@ static void txgbe_init_fdir_signature(struct wx *wx) ...@@ -291,12 +538,106 @@ static void txgbe_init_fdir_signature(struct wx *wx)
txgbe_fdir_enable(wx, fdirctrl); txgbe_fdir_enable(wx, fdirctrl);
} }
/**
* txgbe_init_fdir_perfect - Initialize Flow Director perfect filters
* @wx: pointer to hardware structure
**/
static void txgbe_init_fdir_perfect(struct wx *wx)
{
u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
/* Continue setup of fdirctrl register bits:
* Turn perfect match filtering on
* Report hash in RSS field of Rx wb descriptor
* Initialize the drop queue
* Move the flexible bytes to use the ethertype - shift 6 words
* Set the maximum length per hash bucket to 0xA filters
* Send interrupt when 64 (0x4 * 16) filters are left
*/
fdirctrl |= TXGBE_RDB_FDIR_CTL_PERFECT_MATCH |
TXGBE_RDB_FDIR_CTL_DROP_Q(TXGBE_RDB_FDIR_DROP_QUEUE) |
TXGBE_RDB_FDIR_CTL_HASH_BITS(0xF) |
TXGBE_RDB_FDIR_CTL_MAX_LENGTH(0xA) |
TXGBE_RDB_FDIR_CTL_FULL_THRESH(4);
/* write hashes and fdirctrl register, poll for completion */
txgbe_fdir_enable(wx, fdirctrl);
}
static void txgbe_fdir_filter_restore(struct wx *wx)
{
struct txgbe_fdir_filter *filter;
struct txgbe *txgbe = wx->priv;
struct hlist_node *node;
u8 queue = 0;
int ret = 0;
spin_lock(&txgbe->fdir_perfect_lock);
if (!hlist_empty(&txgbe->fdir_filter_list))
ret = txgbe_fdir_set_input_mask(wx, &txgbe->fdir_mask);
if (ret)
goto unlock;
hlist_for_each_entry_safe(filter, node,
&txgbe->fdir_filter_list, fdir_node) {
if (filter->action == TXGBE_RDB_FDIR_DROP_QUEUE) {
queue = TXGBE_RDB_FDIR_DROP_QUEUE;
} else {
u32 ring = ethtool_get_flow_spec_ring(filter->action);
if (ring >= wx->num_rx_queues) {
wx_err(wx, "FDIR restore failed, ring:%u\n",
ring);
continue;
}
/* Map the ring onto the absolute queue index */
queue = wx->rx_ring[ring]->reg_idx;
}
ret = txgbe_fdir_write_perfect_filter(wx,
&filter->filter,
filter->sw_idx,
queue);
if (ret)
wx_err(wx, "FDIR restore failed, index:%u\n",
filter->sw_idx);
}
unlock:
spin_unlock(&txgbe->fdir_perfect_lock);
}
void txgbe_configure_fdir(struct wx *wx) void txgbe_configure_fdir(struct wx *wx)
{ {
wx_disable_sec_rx_path(wx); wx_disable_sec_rx_path(wx);
if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) {
txgbe_init_fdir_signature(wx); txgbe_init_fdir_signature(wx);
} else if (test_bit(WX_FLAG_FDIR_PERFECT, wx->flags)) {
txgbe_init_fdir_perfect(wx);
txgbe_fdir_filter_restore(wx);
}
wx_enable_sec_rx_path(wx); wx_enable_sec_rx_path(wx);
} }
void txgbe_fdir_filter_exit(struct wx *wx)
{
struct txgbe_fdir_filter *filter;
struct txgbe *txgbe = wx->priv;
struct hlist_node *node;
spin_lock(&txgbe->fdir_perfect_lock);
hlist_for_each_entry_safe(filter, node,
&txgbe->fdir_filter_list, fdir_node) {
hlist_del(&filter->fdir_node);
kfree(filter);
}
txgbe->fdir_filter_count = 0;
spin_unlock(&txgbe->fdir_perfect_lock);
}
...@@ -4,7 +4,17 @@ ...@@ -4,7 +4,17 @@
#ifndef _TXGBE_FDIR_H_ #ifndef _TXGBE_FDIR_H_
#define _TXGBE_FDIR_H_ #define _TXGBE_FDIR_H_
void txgbe_atr_compute_perfect_hash(union txgbe_atr_input *input,
union txgbe_atr_input *input_mask);
void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype); void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype);
int txgbe_fdir_set_input_mask(struct wx *wx, union txgbe_atr_input *input_mask);
int txgbe_fdir_write_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id, u8 queue);
int txgbe_fdir_erase_perfect_filter(struct wx *wx,
union txgbe_atr_input *input,
u16 soft_id);
void txgbe_configure_fdir(struct wx *wx); void txgbe_configure_fdir(struct wx *wx);
void txgbe_fdir_filter_exit(struct wx *wx);
#endif /* _TXGBE_FDIR_H_ */ #endif /* _TXGBE_FDIR_H_ */
...@@ -283,6 +283,12 @@ static int txgbe_sw_init(struct wx *wx) ...@@ -283,6 +283,12 @@ static int txgbe_sw_init(struct wx *wx)
return 0; return 0;
} }
static void txgbe_init_fdir(struct txgbe *txgbe)
{
txgbe->fdir_filter_count = 0;
spin_lock_init(&txgbe->fdir_perfect_lock);
}
/** /**
* txgbe_open - Called when a network interface is made active * txgbe_open - Called when a network interface is made active
* @netdev: network interface device structure * @netdev: network interface device structure
...@@ -361,6 +367,7 @@ static int txgbe_close(struct net_device *netdev) ...@@ -361,6 +367,7 @@ static int txgbe_close(struct net_device *netdev)
txgbe_down(wx); txgbe_down(wx);
wx_free_irq(wx); wx_free_irq(wx);
wx_free_resources(wx); wx_free_resources(wx);
txgbe_fdir_filter_exit(wx);
wx_control_hw(wx, false); wx_control_hw(wx, false);
return 0; return 0;
...@@ -669,6 +676,8 @@ static int txgbe_probe(struct pci_dev *pdev, ...@@ -669,6 +676,8 @@ static int txgbe_probe(struct pci_dev *pdev,
txgbe->wx = wx; txgbe->wx = wx;
wx->priv = txgbe; wx->priv = txgbe;
txgbe_init_fdir(txgbe);
err = txgbe_setup_misc_irq(txgbe); err = txgbe_setup_misc_irq(txgbe);
if (err) if (err)
goto err_release_hw; goto err_release_hw;
......
...@@ -90,6 +90,7 @@ ...@@ -90,6 +90,7 @@
#define TXGBE_XPCS_IDA_DATA 0x13004 #define TXGBE_XPCS_IDA_DATA 0x13004
/********************************* Flow Director *****************************/ /********************************* Flow Director *****************************/
#define TXGBE_RDB_FDIR_DROP_QUEUE 127
#define TXGBE_RDB_FDIR_CTL 0x19500 #define TXGBE_RDB_FDIR_CTL 0x19500
#define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3) #define TXGBE_RDB_FDIR_CTL_INIT_DONE BIT(3)
#define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4) #define TXGBE_RDB_FDIR_CTL_PERFECT_MATCH BIT(4)
...@@ -97,6 +98,13 @@ ...@@ -97,6 +98,13 @@
#define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23, 20), v) #define TXGBE_RDB_FDIR_CTL_HASH_BITS(v) FIELD_PREP(GENMASK(23, 20), v)
#define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v) FIELD_PREP(GENMASK(27, 24), v) #define TXGBE_RDB_FDIR_CTL_MAX_LENGTH(v) FIELD_PREP(GENMASK(27, 24), v)
#define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v) FIELD_PREP(GENMASK(31, 28), v) #define TXGBE_RDB_FDIR_CTL_FULL_THRESH(v) FIELD_PREP(GENMASK(31, 28), v)
#define TXGBE_RDB_FDIR_IP6(_i) (0x1950C + ((_i) * 4)) /* 0-2 */
#define TXGBE_RDB_FDIR_SA 0x19518
#define TXGBE_RDB_FDIR_DA 0x1951C
#define TXGBE_RDB_FDIR_PORT 0x19520
#define TXGBE_RDB_FDIR_PORT_DESTINATION_SHIFT 16
#define TXGBE_RDB_FDIR_FLEX 0x19524
#define TXGBE_RDB_FDIR_FLEX_FLEX_SHIFT 16
#define TXGBE_RDB_FDIR_HASH 0x19528 #define TXGBE_RDB_FDIR_HASH 0x19528
#define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v) FIELD_PREP(GENMASK(31, 16), v) #define TXGBE_RDB_FDIR_HASH_SIG_SW_INDEX(v) FIELD_PREP(GENMASK(31, 16), v)
#define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15) #define TXGBE_RDB_FDIR_HASH_BUCKET_VALID BIT(15)
...@@ -114,8 +122,16 @@ ...@@ -114,8 +122,16 @@
#define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15) #define TXGBE_RDB_FDIR_CMD_QUEUE_EN BIT(15)
#define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v) FIELD_PREP(GENMASK(22, 16), v) #define TXGBE_RDB_FDIR_CMD_RX_QUEUE(v) FIELD_PREP(GENMASK(22, 16), v)
#define TXGBE_RDB_FDIR_CMD_VT_POOL(v) FIELD_PREP(GENMASK(29, 24), v) #define TXGBE_RDB_FDIR_CMD_VT_POOL(v) FIELD_PREP(GENMASK(29, 24), v)
#define TXGBE_RDB_FDIR_DA4_MSK 0x1953C
#define TXGBE_RDB_FDIR_SA4_MSK 0x19540
#define TXGBE_RDB_FDIR_TCP_MSK 0x19544
#define TXGBE_RDB_FDIR_UDP_MSK 0x19548
#define TXGBE_RDB_FDIR_SCTP_MSK 0x19560
#define TXGBE_RDB_FDIR_HKEY 0x19568 #define TXGBE_RDB_FDIR_HKEY 0x19568
#define TXGBE_RDB_FDIR_SKEY 0x1956C #define TXGBE_RDB_FDIR_SKEY 0x1956C
#define TXGBE_RDB_FDIR_OTHER_MSK 0x19570
#define TXGBE_RDB_FDIR_OTHER_MSK_POOL BIT(2)
#define TXGBE_RDB_FDIR_OTHER_MSK_L4P BIT(3)
#define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4)) #define TXGBE_RDB_FDIR_FLEX_CFG(_i) (0x19580 + ((_i) * 4))
#define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0) #define TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 GENMASK(7, 0)
#define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC FIELD_PREP(GENMASK(1, 0), 0) #define TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC FIELD_PREP(GENMASK(1, 0), 0)
...@@ -230,6 +246,13 @@ enum txgbe_fdir_pballoc_type { ...@@ -230,6 +246,13 @@ enum txgbe_fdir_pballoc_type {
TXGBE_FDIR_PBALLOC_256K = 3, TXGBE_FDIR_PBALLOC_256K = 3,
}; };
struct txgbe_fdir_filter {
struct hlist_node fdir_node;
union txgbe_atr_input filter;
u16 sw_idx;
u16 action;
};
/* TX/RX descriptor defines */ /* TX/RX descriptor defines */
#define TXGBE_DEFAULT_TXD 512 #define TXGBE_DEFAULT_TXD 512
#define TXGBE_DEFAULT_TX_WORK 256 #define TXGBE_DEFAULT_TX_WORK 256
...@@ -316,7 +339,10 @@ struct txgbe { ...@@ -316,7 +339,10 @@ struct txgbe {
unsigned int link_irq; unsigned int link_irq;
/* flow director */ /* flow director */
struct hlist_head fdir_filter_list;
union txgbe_atr_input fdir_mask; union txgbe_atr_input fdir_mask;
int fdir_filter_count;
spinlock_t fdir_perfect_lock; /* spinlock for FDIR */
}; };
#endif /* _TXGBE_TYPE_H_ */ #endif /* _TXGBE_TYPE_H_ */
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