root/drivers/net/ethernet/wangxun/txgbe/txgbe_fdir.c
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2024 Beijing WangXun Technology Co., Ltd. */

#include <linux/string.h>
#include <linux/types.h>
#include <linux/pci.h>

#include "../libwx/wx_type.h"
#include "../libwx/wx_lib.h"
#include "../libwx/wx_hw.h"
#include "txgbe_type.h"
#include "txgbe_fdir.h"

/* These defines allow us to quickly generate all of the necessary instructions
 * in the function below by simply calling out TXGBE_COMPUTE_SIG_HASH_ITERATION
 * for values 0 through 15
 */
#define TXGBE_ATR_COMMON_HASH_KEY \
                (TXGBE_ATR_BUCKET_HASH_KEY & TXGBE_ATR_SIGNATURE_HASH_KEY)
#define TXGBE_COMPUTE_SIG_HASH_ITERATION(_n) \
do { \
        u32 n = (_n); \
        if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << n)) \
                common_hash ^= lo_hash_dword >> n; \
        else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << n)) \
                bucket_hash ^= lo_hash_dword >> n; \
        else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << n)) \
                sig_hash ^= lo_hash_dword << (16 - n); \
        if (TXGBE_ATR_COMMON_HASH_KEY & (0x01 << (n + 16))) \
                common_hash ^= hi_hash_dword >> n; \
        else if (TXGBE_ATR_BUCKET_HASH_KEY & (0x01 << (n + 16))) \
                bucket_hash ^= hi_hash_dword >> n; \
        else if (TXGBE_ATR_SIGNATURE_HASH_KEY & (0x01 << (n + 16))) \
                sig_hash ^= hi_hash_dword << (16 - n); \
} while (0)

/**
 *  txgbe_atr_compute_sig_hash - Compute the signature hash
 *  @input: input bitstream to compute the hash on
 *  @common: compressed common input dword
 *  @hash: pointer to the computed hash
 *
 *  This function is almost identical to the function above but contains
 *  several optimizations such as unwinding all of the loops, letting the
 *  compiler work out all of the conditional ifs since the keys are static
 *  defines, and computing two keys at once since the hashed dword stream
 *  will be the same for both keys.
 **/
static void txgbe_atr_compute_sig_hash(union txgbe_atr_hash_dword input,
                                       union txgbe_atr_hash_dword common,
                                       u32 *hash)
{
        u32 sig_hash = 0, bucket_hash = 0, common_hash = 0;
        u32 hi_hash_dword, lo_hash_dword, flow_vm_vlan;
        u32 i;

        /* record the flow_vm_vlan bits as they are a key part to the hash */
        flow_vm_vlan = ntohl(input.dword);

        /* generate common hash dword */
        hi_hash_dword = ntohl(common.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_SIG_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_SIG_HASH_ITERATION(i);

        /* combine common_hash result with signature and bucket hashes */
        bucket_hash ^= common_hash;
        bucket_hash &= TXGBE_ATR_HASH_MASK;

        sig_hash ^= common_hash << 16;
        sig_hash &= TXGBE_ATR_HASH_MASK << 16;

        /* return completed signature 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)
{
        u32 val;

        return read_poll_timeout_atomic(rd32, val,
                                        !(val & TXGBE_RDB_FDIR_CMD_CMD_MASK),
                                        10, 100, false,
                                        wx, TXGBE_RDB_FDIR_CMD);
}

/**
 *  txgbe_fdir_add_signature_filter - Adds a signature hash filter
 *  @wx: pointer to hardware structure
 *  @input: unique input dword
 *  @common: compressed common input dword
 *  @queue: queue index to direct traffic to
 *
 *  @return: 0 on success and negative on failure
 **/
static int txgbe_fdir_add_signature_filter(struct wx *wx,
                                           union txgbe_atr_hash_dword input,
                                           union txgbe_atr_hash_dword common,
                                           u8 queue)
{
        u32 fdirhashcmd, fdircmd;
        u8 flow_type;
        int err;

        /* Get the flow_type in order to program FDIRCMD properly
         * lowest 2 bits are FDIRCMD.L4TYPE, third lowest bit is FDIRCMD.IPV6
         * fifth is FDIRCMD.TUNNEL_FILTER
         */
        flow_type = input.formatted.flow_type;
        switch (flow_type) {
        case TXGBE_ATR_FLOW_TYPE_TCPV4:
        case TXGBE_ATR_FLOW_TYPE_UDPV4:
        case TXGBE_ATR_FLOW_TYPE_SCTPV4:
        case TXGBE_ATR_FLOW_TYPE_TCPV6:
        case TXGBE_ATR_FLOW_TYPE_UDPV6:
        case TXGBE_ATR_FLOW_TYPE_SCTPV6:
                break;
        default:
                wx_err(wx, "Error on flow type input\n");
                return -EINVAL;
        }

        /* 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;
        fdircmd |= TXGBE_RDB_FDIR_CMD_FLOW_TYPE(flow_type);
        fdircmd |= TXGBE_RDB_FDIR_CMD_RX_QUEUE(queue);

        txgbe_atr_compute_sig_hash(input, common, &fdirhashcmd);
        fdirhashcmd |= TXGBE_RDB_FDIR_HASH_BUCKET_VALID;
        wr32(wx, TXGBE_RDB_FDIR_HASH, fdirhashcmd);
        wr32(wx, TXGBE_RDB_FDIR_CMD, fdircmd);

        wx_dbg(wx, "Tx Queue=%x hash=%x\n", queue, (u32)fdirhashcmd);

        err = txgbe_fdir_check_cmd_complete(wx);
        if (err)
                wx_err(wx, "Flow Director command did not complete!\n");

        return err;
}

void txgbe_atr(struct wx_ring *ring, struct wx_tx_buffer *first, u8 ptype)
{
        union txgbe_atr_hash_dword common = { .dword = 0 };
        union txgbe_atr_hash_dword input = { .dword = 0 };
        struct wx_q_vector *q_vector = ring->q_vector;
        struct wx_dec_ptype dptype;
        union network_header {
                struct ipv6hdr *ipv6;
                struct iphdr *ipv4;
                void *raw;
        } hdr;
        struct tcphdr *th;

        /* if ring doesn't have a interrupt vector, cannot perform ATR */
        if (!q_vector)
                return;

        ring->atr_count++;
        dptype = wx_decode_ptype(ptype);
        if (dptype.etype) {
                if (WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
                        return;
                hdr.raw = (void *)skb_inner_network_header(first->skb);
                th = inner_tcp_hdr(first->skb);
        } else {
                if (WX_PTYPE_PKT(ptype) != WX_PTYPE_PKT_IP ||
                    WX_PTYPE_TYPL4(ptype) != WX_PTYPE_TYP_TCP)
                        return;
                hdr.raw = (void *)skb_network_header(first->skb);
                th = tcp_hdr(first->skb);
        }

        /* skip this packet since it is invalid or the socket is closing */
        if (!th || th->fin)
                return;

        /* sample on all syn packets or once every atr sample count */
        if (!th->syn && ring->atr_count < ring->atr_sample_rate)
                return;

        /* reset sample count */
        ring->atr_count = 0;

        /* src and dst are inverted, think how the receiver sees them
         *
         * The input is broken into two sections, a non-compressed section
         * containing vm_pool, vlan_id, and flow_type.  The rest of the data
         * is XORed together and stored in the compressed dword.
         */
        input.formatted.vlan_id = htons((u16)ptype);

        /* since src port and flex bytes occupy the same word XOR them together
         * and write the value to source port portion of compressed dword
         */
        if (first->tx_flags & WX_TX_FLAGS_SW_VLAN)
                common.port.src ^= th->dest ^ first->skb->protocol;
        else if (first->tx_flags & WX_TX_FLAGS_HW_VLAN)
                common.port.src ^= th->dest ^ first->skb->vlan_proto;
        else
                common.port.src ^= th->dest ^ first->protocol;
        common.port.dst ^= th->source;

        if (WX_PTYPE_PKT_IPV6 & WX_PTYPE_PKT(ptype)) {
                input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV6;
                common.ip ^= hdr.ipv6->saddr.s6_addr32[0] ^
                                         hdr.ipv6->saddr.s6_addr32[1] ^
                                         hdr.ipv6->saddr.s6_addr32[2] ^
                                         hdr.ipv6->saddr.s6_addr32[3] ^
                                         hdr.ipv6->daddr.s6_addr32[0] ^
                                         hdr.ipv6->daddr.s6_addr32[1] ^
                                         hdr.ipv6->daddr.s6_addr32[2] ^
                                         hdr.ipv6->daddr.s6_addr32[3];
        } else {
                input.formatted.flow_type = TXGBE_ATR_FLOW_TYPE_TCPV4;
                common.ip ^= hdr.ipv4->saddr ^ hdr.ipv4->daddr;
        }

        /* This assumes the Rx queue and Tx queue are bound to the same CPU */
        txgbe_fdir_add_signature_filter(q_vector->wx, input, common,
                                        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;
        int index, offset;

        /* 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);

        index = VMDQ_P(0) / 4;
        offset = VMDQ_P(0) % 4;
        flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index));
        flex &= ~(TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 << (offset * 8));
        flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
                 TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6)) << (offset * 8);

        switch ((__force u16)input_mask->formatted.flex_bytes & 0xFFFF) {
        case 0x0000:
                /* Mask Flex Bytes */
                flex |= TXGBE_RDB_FDIR_FLEX_CFG_MSK << (offset * 8);
                break;
        case 0xFFFF:
                break;
        default:
                wx_err(wx, "Error on flexible byte mask\n");
                return -EINVAL;
        }
        wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index), 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
 *  @wx: pointer to hardware structure
 *  @fdirctrl: value to write to flow director control register
 **/
static void txgbe_fdir_enable(struct wx *wx, u32 fdirctrl)
{
        u32 val;
        int ret;

        /* Prime the keys for hashing */
        wr32(wx, TXGBE_RDB_FDIR_HKEY, TXGBE_ATR_BUCKET_HASH_KEY);
        wr32(wx, TXGBE_RDB_FDIR_SKEY, TXGBE_ATR_SIGNATURE_HASH_KEY);

        wr32(wx, TXGBE_RDB_FDIR_CTL, fdirctrl);
        WX_WRITE_FLUSH(wx);
        ret = read_poll_timeout(rd32, val, val & TXGBE_RDB_FDIR_CTL_INIT_DONE,
                                1000, 10000, false, wx, TXGBE_RDB_FDIR_CTL);

        if (ret < 0)
                wx_dbg(wx, "Flow Director poll time exceeded!\n");
}

/**
 *  txgbe_init_fdir_signature -Initialize Flow Director sig filters
 *  @wx: pointer to hardware structure
 **/
static void txgbe_init_fdir_signature(struct wx *wx)
{
        u32 fdirctrl = TXGBE_FDIR_PBALLOC_64K;
        int index = VMDQ_P(0) / 4;
        int offset = VMDQ_P(0) % 4;
        u32 flex = 0;

        flex = rd32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index));
        flex &= ~(TXGBE_RDB_FDIR_FLEX_CFG_FIELD0 << (offset * 8));

        flex |= (TXGBE_RDB_FDIR_FLEX_CFG_BASE_MAC |
                 TXGBE_RDB_FDIR_FLEX_CFG_OFST(0x6)) << (offset * 8);
        wr32(wx, TXGBE_RDB_FDIR_FLEX_CFG(index), flex);

        /* Continue setup of fdirctrl register bits:
         *  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 filters are left
         */
        fdirctrl |= 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);
}

/**
 *  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)
{
        wx_disable_sec_rx_path(wx);

        if (test_bit(WX_FLAG_FDIR_HASH, wx->flags)) {
                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);
}

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);
}