root/drivers/net/ethernet/marvell/octeontx2/nic/otx2_ethtool.c
// SPDX-License-Identifier: GPL-2.0
/* Marvell RVU Ethernet driver
 *
 * Copyright (C) 2020 Marvell.
 *
 */

#include <linux/pci.h>
#include <linux/ethtool.h>
#include <linux/stddef.h>
#include <linux/etherdevice.h>
#include <linux/log2.h>
#include <linux/net_tstamp.h>
#include <linux/linkmode.h>

#include "otx2_common.h"
#include "otx2_ptp.h"
#include <cgx_fw_if.h>

#define DRV_NAME        "rvu-nicpf"
#define DRV_VF_NAME     "rvu-nicvf"

struct otx2_stat {
        char name[ETH_GSTRING_LEN];
        unsigned int index;
};

/* HW device stats */
#define OTX2_DEV_STAT(stat) { \
        .name = #stat, \
        .index = offsetof(struct otx2_dev_stats, stat) / sizeof(u64), \
}

enum link_mode {
        OTX2_MODE_SUPPORTED,
        OTX2_MODE_ADVERTISED
};

static const struct otx2_stat otx2_dev_stats[] = {
        OTX2_DEV_STAT(rx_ucast_frames),
        OTX2_DEV_STAT(rx_bcast_frames),
        OTX2_DEV_STAT(rx_mcast_frames),

        OTX2_DEV_STAT(tx_ucast_frames),
        OTX2_DEV_STAT(tx_bcast_frames),
        OTX2_DEV_STAT(tx_mcast_frames),
};

/* Driver level stats */
#define OTX2_DRV_STAT(stat) { \
        .name = #stat, \
        .index = offsetof(struct otx2_drv_stats, stat) / sizeof(atomic_t), \
}

static const struct otx2_stat otx2_drv_stats[] = {
        OTX2_DRV_STAT(rx_fcs_errs),
        OTX2_DRV_STAT(rx_oversize_errs),
        OTX2_DRV_STAT(rx_undersize_errs),
        OTX2_DRV_STAT(rx_csum_errs),
        OTX2_DRV_STAT(rx_len_errs),
        OTX2_DRV_STAT(rx_other_errs),
};

static const struct otx2_stat otx2_queue_stats[] = {
        { "bytes", 0 },
        { "frames", 1 },
};

#define OTX2_FEC_MAX_INDEX 4

static const unsigned int otx2_n_dev_stats = ARRAY_SIZE(otx2_dev_stats);
static const unsigned int otx2_n_drv_stats = ARRAY_SIZE(otx2_drv_stats);
static const unsigned int otx2_n_queue_stats = ARRAY_SIZE(otx2_queue_stats);

static struct cgx_fw_data *otx2_get_fwdata(struct otx2_nic *pfvf);

static void otx2_get_drvinfo(struct net_device *netdev,
                             struct ethtool_drvinfo *info)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        strscpy(info->driver, DRV_NAME, sizeof(info->driver));
        strscpy(info->bus_info, pci_name(pfvf->pdev), sizeof(info->bus_info));
}

static void otx2_get_qset_strings(struct otx2_nic *pfvf, u8 **data, int qset)
{
        int start_qidx = qset * pfvf->hw.rx_queues;
        int qidx, stats;

        for (qidx = 0; qidx < pfvf->hw.rx_queues; qidx++)
                for (stats = 0; stats < otx2_n_queue_stats; stats++)
                        ethtool_sprintf(data, "rxq%d: %s", qidx + start_qidx,
                                        otx2_queue_stats[stats].name);

        for (qidx = 0; qidx < otx2_get_total_tx_queues(pfvf); qidx++)
                for (stats = 0; stats < otx2_n_queue_stats; stats++)
                        if (qidx >= pfvf->hw.non_qos_queues)
                                ethtool_sprintf(data, "txq_qos%d: %s",
                                                qidx + start_qidx -
                                                        pfvf->hw.non_qos_queues,
                                                otx2_queue_stats[stats].name);
                        else
                                ethtool_sprintf(data, "txq%d: %s",
                                                qidx + start_qidx,
                                                otx2_queue_stats[stats].name);
}

static void otx2_get_strings(struct net_device *netdev, u32 sset, u8 *data)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        int stats;

        if (sset != ETH_SS_STATS)
                return;

        for (stats = 0; stats < otx2_n_dev_stats; stats++)
                ethtool_puts(&data, otx2_dev_stats[stats].name);

        for (stats = 0; stats < otx2_n_drv_stats; stats++)
                ethtool_puts(&data, otx2_drv_stats[stats].name);

        otx2_get_qset_strings(pfvf, &data, 0);

        if (!test_bit(CN10K_RPM, &pfvf->hw.cap_flag)) {
                for (stats = 0; stats < CGX_RX_STATS_COUNT; stats++)
                        ethtool_sprintf(&data, "cgx_rxstat%d: ", stats);

                for (stats = 0; stats < CGX_TX_STATS_COUNT; stats++)
                        ethtool_sprintf(&data, "cgx_txstat%d: ", stats);
        }

        ethtool_puts(&data, "reset_count");
        ethtool_puts(&data, "Fec Corrected Errors: ");
        ethtool_puts(&data, "Fec Uncorrected Errors: ");
}

static void otx2_get_qset_stats(struct otx2_nic *pfvf,
                                struct ethtool_stats *stats, u64 **data)
{
        int stat, qidx;

        if (!pfvf)
                return;
        for (qidx = 0; qidx < pfvf->hw.rx_queues; qidx++) {
                if (!otx2_update_rq_stats(pfvf, qidx)) {
                        for (stat = 0; stat < otx2_n_queue_stats; stat++)
                                *((*data)++) = 0;
                        continue;
                }
                for (stat = 0; stat < otx2_n_queue_stats; stat++)
                        *((*data)++) = ((u64 *)&pfvf->qset.rq[qidx].stats)
                                [otx2_queue_stats[stat].index];
        }

        for (qidx = 0; qidx < otx2_get_total_tx_queues(pfvf); qidx++) {
                if (!otx2_update_sq_stats(pfvf, qidx)) {
                        for (stat = 0; stat < otx2_n_queue_stats; stat++)
                                *((*data)++) = 0;
                        continue;
                }
                for (stat = 0; stat < otx2_n_queue_stats; stat++)
                        *((*data)++) = ((u64 *)&pfvf->qset.sq[qidx].stats)
                                [otx2_queue_stats[stat].index];
        }
}

static int otx2_get_phy_fec_stats(struct otx2_nic *pfvf)
{
        struct msg_req *req;
        int rc = -ENOMEM;

        mutex_lock(&pfvf->mbox.lock);
        req = otx2_mbox_alloc_msg_cgx_get_phy_fec_stats(&pfvf->mbox);
        if (!req)
                goto end;

        if (!otx2_sync_mbox_msg(&pfvf->mbox))
                rc = 0;
end:
        mutex_unlock(&pfvf->mbox.lock);
        return rc;
}

/* Get device and per queue statistics */
static void otx2_get_ethtool_stats(struct net_device *netdev,
                                   struct ethtool_stats *stats, u64 *data)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        u64 fec_corr_blks, fec_uncorr_blks;
        struct cgx_fw_data *rsp;
        int stat;

        otx2_get_dev_stats(pfvf);
        for (stat = 0; stat < otx2_n_dev_stats; stat++)
                *(data++) = ((u64 *)&pfvf->hw.dev_stats)
                                [otx2_dev_stats[stat].index];

        for (stat = 0; stat < otx2_n_drv_stats; stat++)
                *(data++) = atomic_read(&((atomic_t *)&pfvf->hw.drv_stats)
                                                [otx2_drv_stats[stat].index]);

        otx2_get_qset_stats(pfvf, stats, &data);

        if (!test_bit(CN10K_RPM, &pfvf->hw.cap_flag)) {
                otx2_update_lmac_stats(pfvf);
                for (stat = 0; stat < CGX_RX_STATS_COUNT; stat++)
                        *(data++) = pfvf->hw.cgx_rx_stats[stat];
                for (stat = 0; stat < CGX_TX_STATS_COUNT; stat++)
                        *(data++) = pfvf->hw.cgx_tx_stats[stat];
        }

        *(data++) = pfvf->reset_count;

        fec_corr_blks = pfvf->hw.cgx_fec_corr_blks;
        fec_uncorr_blks = pfvf->hw.cgx_fec_uncorr_blks;

        rsp = otx2_get_fwdata(pfvf);
        if (!IS_ERR(rsp) && rsp->fwdata.phy.misc.has_fec_stats &&
            !otx2_get_phy_fec_stats(pfvf)) {
                /* Fetch fwdata again because it's been recently populated with
                 * latest PHY FEC stats.
                 */
                rsp = otx2_get_fwdata(pfvf);
                if (!IS_ERR(rsp)) {
                        struct fec_stats_s *p = &rsp->fwdata.phy.fec_stats;

                        if (pfvf->linfo.fec == OTX2_FEC_BASER) {
                                fec_corr_blks   = p->brfec_corr_blks;
                                fec_uncorr_blks = p->brfec_uncorr_blks;
                        } else {
                                fec_corr_blks   = p->rsfec_corr_cws;
                                fec_uncorr_blks = p->rsfec_uncorr_cws;
                        }
                }
        }

        *(data++) = fec_corr_blks;
        *(data++) = fec_uncorr_blks;
}

static int otx2_get_sset_count(struct net_device *netdev, int sset)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        int qstats_count, mac_stats = 0;

        if (sset != ETH_SS_STATS)
                return -EINVAL;

        qstats_count = otx2_n_queue_stats *
                       (pfvf->hw.rx_queues + otx2_get_total_tx_queues(pfvf));
        if (!test_bit(CN10K_RPM, &pfvf->hw.cap_flag))
                mac_stats = CGX_RX_STATS_COUNT + CGX_TX_STATS_COUNT;
        otx2_update_lmac_fec_stats(pfvf);

        return otx2_n_dev_stats + otx2_n_drv_stats + qstats_count +
               mac_stats + OTX2_FEC_STATS_CNT + 1;
}

/* Get no of queues device supports and current queue count */
static void otx2_get_channels(struct net_device *dev,
                              struct ethtool_channels *channel)
{
        struct otx2_nic *pfvf = netdev_priv(dev);

        channel->max_rx = pfvf->hw.max_queues;
        channel->max_tx = pfvf->hw.max_queues;

        channel->rx_count = pfvf->hw.rx_queues;
        channel->tx_count = pfvf->hw.tx_queues;
}

/* Set no of Tx, Rx queues to be used */
static int otx2_set_channels(struct net_device *dev,
                             struct ethtool_channels *channel)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        bool if_up = netif_running(dev);
        int err, qos_txqs;

        if (!channel->rx_count || !channel->tx_count)
                return -EINVAL;

        if (bitmap_weight(&pfvf->rq_bmap, pfvf->hw.rx_queues) > 1) {
                netdev_err(dev,
                           "Receive queues are in use by TC police action\n");
                return -EINVAL;
        }

        if (if_up)
                dev->netdev_ops->ndo_stop(dev);

        qos_txqs = bitmap_weight(pfvf->qos.qos_sq_bmap,
                                 OTX2_QOS_MAX_LEAF_NODES);

        err = otx2_set_real_num_queues(dev, channel->tx_count + qos_txqs,
                                       channel->rx_count);
        if (err)
                return err;

        pfvf->hw.rx_queues = channel->rx_count;
        pfvf->hw.tx_queues = channel->tx_count;
        if (pfvf->xdp_prog)
                pfvf->hw.xdp_queues = channel->rx_count;

        if (if_up)
                err = dev->netdev_ops->ndo_open(dev);

        netdev_info(dev, "Setting num Tx rings to %d, Rx rings to %d success\n",
                    pfvf->hw.tx_queues, pfvf->hw.rx_queues);

        return err;
}

static void otx2_get_pauseparam(struct net_device *netdev,
                                struct ethtool_pauseparam *pause)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct cgx_pause_frm_cfg *req, *rsp;

        if (is_otx2_lbkvf(pfvf->pdev) || is_otx2_sdp_rep(pfvf->pdev))
                return;

        mutex_lock(&pfvf->mbox.lock);
        req = otx2_mbox_alloc_msg_cgx_cfg_pause_frm(&pfvf->mbox);
        if (!req) {
                mutex_unlock(&pfvf->mbox.lock);
                return;
        }

        if (!otx2_sync_mbox_msg(&pfvf->mbox)) {
                rsp = (struct cgx_pause_frm_cfg *)
                       otx2_mbox_get_rsp(&pfvf->mbox.mbox, 0, &req->hdr);
                if (IS_ERR(rsp)) {
                        mutex_unlock(&pfvf->mbox.lock);
                        return;
                }

                pause->rx_pause = rsp->rx_pause;
                pause->tx_pause = rsp->tx_pause;
        }
        mutex_unlock(&pfvf->mbox.lock);
}

static int otx2_set_pauseparam(struct net_device *netdev,
                               struct ethtool_pauseparam *pause)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        if (pause->autoneg)
                return -EOPNOTSUPP;

        if (is_otx2_lbkvf(pfvf->pdev) || is_otx2_sdp_rep(pfvf->pdev))
                return -EOPNOTSUPP;

        if (pause->rx_pause)
                pfvf->flags |= OTX2_FLAG_RX_PAUSE_ENABLED;
        else
                pfvf->flags &= ~OTX2_FLAG_RX_PAUSE_ENABLED;

        if (pause->tx_pause)
                pfvf->flags |= OTX2_FLAG_TX_PAUSE_ENABLED;
        else
                pfvf->flags &= ~OTX2_FLAG_TX_PAUSE_ENABLED;

        return otx2_config_pause_frm(pfvf);
}

static void otx2_get_ringparam(struct net_device *netdev,
                               struct ethtool_ringparam *ring,
                               struct kernel_ethtool_ringparam *kernel_ring,
                               struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct otx2_qset *qs = &pfvf->qset;

        ring->rx_max_pending = Q_COUNT(Q_SIZE_MAX);
        ring->rx_pending = qs->rqe_cnt ? qs->rqe_cnt : Q_COUNT(Q_SIZE_256);
        ring->tx_max_pending = Q_COUNT(Q_SIZE_MAX);
        ring->tx_pending = qs->sqe_cnt ? qs->sqe_cnt : Q_COUNT(Q_SIZE_4K);
        kernel_ring->rx_buf_len = pfvf->hw.rbuf_len;
        kernel_ring->cqe_size = pfvf->hw.xqe_size;
}

static int otx2_set_ringparam(struct net_device *netdev,
                              struct ethtool_ringparam *ring,
                              struct kernel_ethtool_ringparam *kernel_ring,
                              struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        u32 rx_buf_len = kernel_ring->rx_buf_len;
        u32 old_rx_buf_len = pfvf->hw.rbuf_len;
        u32 xqe_size = kernel_ring->cqe_size;
        bool if_up = netif_running(netdev);
        struct otx2_qset *qs = &pfvf->qset;
        u32 rx_count, tx_count;

        if (ring->rx_mini_pending || ring->rx_jumbo_pending)
                return -EINVAL;

        /* Hardware supports max size of 32k for a receive buffer
         * and 1536 is typical ethernet frame size.
         */
        if (rx_buf_len && (rx_buf_len < 1536 || rx_buf_len > 32768)) {
                netdev_err(netdev,
                           "Receive buffer range is 1536 - 32768");
                return -EINVAL;
        }

        if (xqe_size != 128 && xqe_size != 512) {
                netdev_err(netdev,
                           "Completion event size must be 128 or 512");
                return -EINVAL;
        }

        /* Permitted lengths are 16 64 256 1K 4K 16K 64K 256K 1M  */
        rx_count = ring->rx_pending;
        /* On some silicon variants a skid or reserved CQEs are
         * needed to avoid CQ overflow.
         */
        if (rx_count < pfvf->hw.rq_skid)
                rx_count =  pfvf->hw.rq_skid;

        if (ring->rx_pending < 16) {
                netdev_err(netdev,
                           "rx ring size %u invalid, min is 16\n",
                           ring->rx_pending);
                return -EINVAL;
        }

        rx_count = Q_COUNT(Q_SIZE(rx_count, 3));

        /* Due pipelining impact minimum 2000 unused SQ CQE's
         * need to be maintained to avoid CQ overflow, hence the
         * minimum 4K size.
         */
        tx_count = clamp_t(u32, ring->tx_pending,
                           Q_COUNT(Q_SIZE_4K), Q_COUNT(Q_SIZE_MAX));
        tx_count = Q_COUNT(Q_SIZE(tx_count, 3));

        if (tx_count == qs->sqe_cnt && rx_count == qs->rqe_cnt &&
            rx_buf_len == old_rx_buf_len && xqe_size == pfvf->hw.xqe_size)
                return 0;

        if (if_up)
                netdev->netdev_ops->ndo_stop(netdev);

        /* Assigned to the nearest possible exponent. */
        qs->sqe_cnt = tx_count;
        qs->rqe_cnt = rx_count;

        pfvf->hw.rbuf_len = rx_buf_len;
        pfvf->hw.xqe_size = xqe_size;

        if (if_up)
                return netdev->netdev_ops->ndo_open(netdev);

        return 0;
}

static int otx2_get_coalesce(struct net_device *netdev,
                             struct ethtool_coalesce *cmd,
                             struct kernel_ethtool_coalesce *kernel_coal,
                             struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct otx2_hw *hw = &pfvf->hw;

        cmd->rx_coalesce_usecs = hw->cq_time_wait;
        cmd->rx_max_coalesced_frames = hw->cq_ecount_wait;
        cmd->tx_coalesce_usecs = hw->cq_time_wait;
        cmd->tx_max_coalesced_frames = hw->cq_ecount_wait;
        if ((pfvf->flags & OTX2_FLAG_ADPTV_INT_COAL_ENABLED) ==
                        OTX2_FLAG_ADPTV_INT_COAL_ENABLED) {
                cmd->use_adaptive_rx_coalesce = 1;
                cmd->use_adaptive_tx_coalesce = 1;
        } else {
                cmd->use_adaptive_rx_coalesce = 0;
                cmd->use_adaptive_tx_coalesce = 0;
        }

        return 0;
}

static int otx2_set_coalesce(struct net_device *netdev,
                             struct ethtool_coalesce *ec,
                             struct kernel_ethtool_coalesce *kernel_coal,
                             struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct otx2_hw *hw = &pfvf->hw;
        u8 priv_coalesce_status;
        int qidx;

        if (!ec->rx_max_coalesced_frames || !ec->tx_max_coalesced_frames)
                return 0;

        if (ec->use_adaptive_rx_coalesce != ec->use_adaptive_tx_coalesce) {
                netdev_err(netdev,
                           "adaptive-rx should be same as adaptive-tx");
                return -EINVAL;
        }

        /* Check and update coalesce status */
        if ((pfvf->flags & OTX2_FLAG_ADPTV_INT_COAL_ENABLED) ==
                        OTX2_FLAG_ADPTV_INT_COAL_ENABLED) {
                priv_coalesce_status = 1;
                if (!ec->use_adaptive_rx_coalesce)
                        pfvf->flags &= ~OTX2_FLAG_ADPTV_INT_COAL_ENABLED;
        } else {
                priv_coalesce_status = 0;
                if (ec->use_adaptive_rx_coalesce)
                        pfvf->flags |= OTX2_FLAG_ADPTV_INT_COAL_ENABLED;
        }

        /* 'cq_time_wait' is 8bit and is in multiple of 100ns,
         * so clamp the user given value to the range of 1 to 25usec.
         */
        ec->rx_coalesce_usecs = clamp_t(u32, ec->rx_coalesce_usecs,
                                        1, CQ_TIMER_THRESH_MAX);
        ec->tx_coalesce_usecs = clamp_t(u32, ec->tx_coalesce_usecs,
                                        1, CQ_TIMER_THRESH_MAX);

        /* Rx and Tx are mapped to same CQ, check which one
         * is changed, if both then choose the min.
         */
        if (hw->cq_time_wait == ec->rx_coalesce_usecs)
                hw->cq_time_wait = ec->tx_coalesce_usecs;
        else if (hw->cq_time_wait == ec->tx_coalesce_usecs)
                hw->cq_time_wait = ec->rx_coalesce_usecs;
        else
                hw->cq_time_wait = min_t(u8, ec->rx_coalesce_usecs,
                                         ec->tx_coalesce_usecs);

        /* Max ecount_wait supported is 16bit,
         * so clamp the user given value to the range of 1 to 64k.
         */
        ec->rx_max_coalesced_frames = clamp_t(u32, ec->rx_max_coalesced_frames,
                                              1, NAPI_POLL_WEIGHT);
        ec->tx_max_coalesced_frames = clamp_t(u32, ec->tx_max_coalesced_frames,
                                              1, NAPI_POLL_WEIGHT);

        /* Rx and Tx are mapped to same CQ, check which one
         * is changed, if both then choose the min.
         */
        if (hw->cq_ecount_wait == ec->rx_max_coalesced_frames)
                hw->cq_ecount_wait = ec->tx_max_coalesced_frames;
        else if (hw->cq_ecount_wait == ec->tx_max_coalesced_frames)
                hw->cq_ecount_wait = ec->rx_max_coalesced_frames;
        else
                hw->cq_ecount_wait = min_t(u16, ec->rx_max_coalesced_frames,
                                           ec->tx_max_coalesced_frames);

        /* Reset 'cq_time_wait' and 'cq_ecount_wait' to
         * default values if coalesce status changed from
         * 'on' to 'off'.
         */
        if (priv_coalesce_status &&
            ((pfvf->flags & OTX2_FLAG_ADPTV_INT_COAL_ENABLED) !=
             OTX2_FLAG_ADPTV_INT_COAL_ENABLED)) {
                hw->cq_time_wait = CQ_TIMER_THRESH_DEFAULT;
                hw->cq_ecount_wait = CQ_CQE_THRESH_DEFAULT;
        }

        if (netif_running(netdev)) {
                for (qidx = 0; qidx < pfvf->hw.cint_cnt; qidx++)
                        otx2_config_irq_coalescing(pfvf, qidx);
        }

        return 0;
}

static u32 otx2_get_rx_ring_count(struct net_device *dev)
{
        struct otx2_nic *pfvf = netdev_priv(dev);

        return pfvf->hw.rx_queues;
}

static int otx2_get_rss_hash_opts(struct net_device *dev,
                                  struct ethtool_rxfh_fields *nfc)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        struct otx2_rss_info *rss;

        rss = &pfvf->hw.rss_info;

        if (!(rss->flowkey_cfg &
            (NIX_FLOW_KEY_TYPE_IPV4 | NIX_FLOW_KEY_TYPE_IPV6)))
                return 0;

        /* Mimimum is IPv4 and IPv6, SIP/DIP */
        nfc->data = RXH_IP_SRC | RXH_IP_DST;
        if (rss->flowkey_cfg & NIX_FLOW_KEY_TYPE_VLAN)
                nfc->data |= RXH_VLAN;

        switch (nfc->flow_type) {
        case TCP_V4_FLOW:
        case TCP_V6_FLOW:
                if (rss->flowkey_cfg & NIX_FLOW_KEY_TYPE_TCP)
                        nfc->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
                break;
        case UDP_V4_FLOW:
        case UDP_V6_FLOW:
                if (rss->flowkey_cfg & NIX_FLOW_KEY_TYPE_UDP)
                        nfc->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
                break;
        case SCTP_V4_FLOW:
        case SCTP_V6_FLOW:
                if (rss->flowkey_cfg & NIX_FLOW_KEY_TYPE_SCTP)
                        nfc->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
                break;
        case AH_ESP_V4_FLOW:
        case AH_ESP_V6_FLOW:
                if (rss->flowkey_cfg & NIX_FLOW_KEY_TYPE_ESP)
                        nfc->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
                break;
        case AH_V4_FLOW:
        case ESP_V4_FLOW:
        case IPV4_FLOW:
                break;
        case AH_V6_FLOW:
        case ESP_V6_FLOW:
        case IPV6_FLOW:
                break;
        default:
                return -EINVAL;
        }

        return 0;
}

static int otx2_set_rss_hash_opts(struct net_device *dev,
                                  const struct ethtool_rxfh_fields *nfc,
                                  struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        u32 rxh_l4 = RXH_L4_B_0_1 | RXH_L4_B_2_3;
        struct otx2_rss_info *rss;
        u32 rss_cfg;

        rss = &pfvf->hw.rss_info;
        rss_cfg = rss->flowkey_cfg;

        if (!rss->enable) {
                netdev_err(pfvf->netdev,
                           "RSS is disabled, cannot change settings\n");
                return -EIO;
        }

        /* Mimimum is IPv4 and IPv6, SIP/DIP */
        if (!(nfc->data & RXH_IP_SRC) || !(nfc->data & RXH_IP_DST))
                return -EINVAL;

        if (nfc->data & RXH_VLAN)
                rss_cfg |=  NIX_FLOW_KEY_TYPE_VLAN;
        else
                rss_cfg &= ~NIX_FLOW_KEY_TYPE_VLAN;

        switch (nfc->flow_type) {
        case TCP_V4_FLOW:
        case TCP_V6_FLOW:
                /* Different config for v4 and v6 is not supported.
                 * Both of them have to be either 4-tuple or 2-tuple.
                 */
                switch (nfc->data & rxh_l4) {
                case 0:
                        rss_cfg &= ~NIX_FLOW_KEY_TYPE_TCP;
                        break;
                case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
                        rss_cfg |= NIX_FLOW_KEY_TYPE_TCP;
                        break;
                default:
                        return -EINVAL;
                }
                break;
        case UDP_V4_FLOW:
        case UDP_V6_FLOW:
                switch (nfc->data & rxh_l4) {
                case 0:
                        rss_cfg &= ~NIX_FLOW_KEY_TYPE_UDP;
                        break;
                case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
                        rss_cfg |= NIX_FLOW_KEY_TYPE_UDP;
                        break;
                default:
                        return -EINVAL;
                }
                break;
        case SCTP_V4_FLOW:
        case SCTP_V6_FLOW:
                switch (nfc->data & rxh_l4) {
                case 0:
                        rss_cfg &= ~NIX_FLOW_KEY_TYPE_SCTP;
                        break;
                case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
                        rss_cfg |= NIX_FLOW_KEY_TYPE_SCTP;
                        break;
                default:
                        return -EINVAL;
                }
                break;
        case AH_ESP_V4_FLOW:
        case AH_ESP_V6_FLOW:
                switch (nfc->data & rxh_l4) {
                case 0:
                        rss_cfg &= ~(NIX_FLOW_KEY_TYPE_ESP |
                                     NIX_FLOW_KEY_TYPE_AH);
                        rss_cfg |= NIX_FLOW_KEY_TYPE_VLAN |
                                   NIX_FLOW_KEY_TYPE_IPV4_PROTO;
                        break;
                case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
                        /* If VLAN hashing is also requested for ESP then do not
                         * allow because of hardware 40 bytes flow key limit.
                         */
                        if (rss_cfg & NIX_FLOW_KEY_TYPE_VLAN) {
                                netdev_err(pfvf->netdev,
                                           "RSS hash of ESP or AH with VLAN is not supported\n");
                                return -EOPNOTSUPP;
                        }

                        rss_cfg |= NIX_FLOW_KEY_TYPE_ESP | NIX_FLOW_KEY_TYPE_AH;
                        /* Disable IPv4 proto hashing since IPv6 SA+DA(32 bytes)
                         * and ESP SPI+sequence(8 bytes) uses hardware maximum
                         * limit of 40 byte flow key.
                         */
                        rss_cfg &= ~NIX_FLOW_KEY_TYPE_IPV4_PROTO;
                        break;
                default:
                        return -EINVAL;
                }
                break;
        case IPV4_FLOW:
        case IPV6_FLOW:
                rss_cfg = NIX_FLOW_KEY_TYPE_IPV4 | NIX_FLOW_KEY_TYPE_IPV6;
                break;
        default:
                return -EINVAL;
        }

        rss->flowkey_cfg = rss_cfg;
        otx2_set_flowkey_cfg(pfvf);
        return 0;
}

static int otx2_get_rxnfc(struct net_device *dev,
                          struct ethtool_rxnfc *nfc, u32 *rules)
{
        bool ntuple = !!(dev->features & NETIF_F_NTUPLE);
        struct otx2_nic *pfvf = netdev_priv(dev);
        int ret = -EOPNOTSUPP;

        switch (nfc->cmd) {
        case ETHTOOL_GRXCLSRLCNT:
                if (netif_running(dev) && ntuple) {
                        nfc->rule_cnt = pfvf->flow_cfg->nr_flows;
                        ret = 0;
                }
                break;
        case ETHTOOL_GRXCLSRULE:
                if (netif_running(dev) && ntuple)
                        ret = otx2_get_flow(pfvf, nfc,  nfc->fs.location);
                break;
        case ETHTOOL_GRXCLSRLALL:
                if (netif_running(dev) && ntuple)
                        ret = otx2_get_all_flows(pfvf, nfc, rules);
                break;
        default:
                break;
        }
        return ret;
}

static int otx2_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *nfc)
{
        bool ntuple = !!(dev->features & NETIF_F_NTUPLE);
        struct otx2_nic *pfvf = netdev_priv(dev);
        int ret = -EOPNOTSUPP;

        pfvf->flow_cfg->ntuple = ntuple;
        switch (nfc->cmd) {
        case ETHTOOL_SRXCLSRLINS:
                if (netif_running(dev) && ntuple)
                        ret = otx2_add_flow(pfvf, nfc);
                break;
        case ETHTOOL_SRXCLSRLDEL:
                if (netif_running(dev) && ntuple)
                        ret = otx2_remove_flow(pfvf, nfc->fs.location);
                break;
        default:
                break;
        }

        return ret;
}

static u32 otx2_get_rxfh_key_size(struct net_device *netdev)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct otx2_rss_info *rss;

        rss = &pfvf->hw.rss_info;

        return sizeof(rss->key);
}

static u32 otx2_get_rxfh_indir_size(struct net_device *dev)
{
        return  MAX_RSS_INDIR_TBL_SIZE;
}

static int otx2_create_rxfh(struct net_device *dev,
                            struct ethtool_rxfh_context *ctx,
                            const struct ethtool_rxfh_param *rxfh,
                            struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        struct otx2_rss_info *rss;
        unsigned int queues;
        u32 *ind_tbl;
        int idx;

        rss = &pfvf->hw.rss_info;
        queues = pfvf->hw.rx_queues;

        if (rxfh->hfunc && rxfh->hfunc != ETH_RSS_HASH_TOP)
                return -EOPNOTSUPP;
        ctx->hfunc = ETH_RSS_HASH_TOP;

        if (!rss->enable) {
                netdev_err(dev, "RSS is disabled, cannot change settings\n");
                return -EIO;
        }

        ind_tbl = rxfh->indir;
        if (!ind_tbl) {
                ind_tbl = ethtool_rxfh_context_indir(ctx);
                for (idx = 0; idx < rss->rss_size; idx++)
                        ind_tbl[idx] = ethtool_rxfh_indir_default(idx, queues);
        }

        otx2_set_rss_table(pfvf, rxfh->rss_context, ind_tbl);
        return 0;
}

static int otx2_modify_rxfh(struct net_device *dev,
                            struct ethtool_rxfh_context *ctx,
                            const struct ethtool_rxfh_param *rxfh,
                            struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(dev);

        if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
            rxfh->hfunc != ETH_RSS_HASH_TOP)
                return -EOPNOTSUPP;

        if (!pfvf->hw.rss_info.enable) {
                netdev_err(dev, "RSS is disabled, cannot change settings\n");
                return -EIO;
        }

        if (rxfh->indir)
                otx2_set_rss_table(pfvf, rxfh->rss_context, rxfh->indir);

        return 0;
}

static int otx2_remove_rxfh(struct net_device *dev,
                            struct ethtool_rxfh_context *ctx,
                            u32 rss_context,
                            struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(dev);

        if (!pfvf->hw.rss_info.enable) {
                netdev_err(dev, "RSS is disabled, cannot change settings\n");
                return -EIO;
        }

        otx2_rss_ctx_flow_del(pfvf, rss_context);
        return 0;
}

/* Configure RSS table and hash key */
static int otx2_set_rxfh(struct net_device *dev,
                         struct ethtool_rxfh_param *rxfh,
                         struct netlink_ext_ack *extack)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        struct otx2_rss_info *rss;
        int idx;

        if (rxfh->hfunc != ETH_RSS_HASH_NO_CHANGE &&
            rxfh->hfunc != ETH_RSS_HASH_TOP)
                return -EOPNOTSUPP;

        rss = &pfvf->hw.rss_info;

        if (!rss->enable) {
                netdev_err(dev, "RSS is disabled, cannot change settings\n");
                return -EIO;
        }

        if (rxfh->key) {
                memcpy(rss->key, rxfh->key, sizeof(rss->key));
                otx2_set_rss_key(pfvf);
        }

        if (rxfh->indir) {
                for (idx = 0; idx < rss->rss_size; idx++)
                        rss->ind_tbl[idx] = rxfh->indir[idx];
        }
        otx2_set_rss_table(pfvf, DEFAULT_RSS_CONTEXT_GROUP, NULL);

        return 0;
}

/* Get RSS configuration */
static int otx2_get_rxfh(struct net_device *dev,
                         struct ethtool_rxfh_param *rxfh)
{
        struct otx2_nic *pfvf = netdev_priv(dev);
        struct otx2_rss_info *rss;
        u32 *indir = rxfh->indir;
        int idx, rx_queues;

        rss = &pfvf->hw.rss_info;

        rxfh->hfunc = ETH_RSS_HASH_TOP;
        if (!indir)
                return 0;

        if (!rss->enable) {
                rx_queues = pfvf->hw.rx_queues;
                for (idx = 0; idx < MAX_RSS_INDIR_TBL_SIZE; idx++)
                        indir[idx] = ethtool_rxfh_indir_default(idx, rx_queues);
                return 0;
        }

        for (idx = 0; idx < rss->rss_size; idx++) {
                /* Ignore if the rx queue is AF_XDP zero copy enabled */
                if (test_bit(rss->ind_tbl[idx], pfvf->af_xdp_zc_qidx))
                        continue;
                indir[idx] = rss->ind_tbl[idx];
        }
        if (rxfh->key)
                memcpy(rxfh->key, rss->key, sizeof(rss->key));

        return 0;
}

static u32 otx2_get_msglevel(struct net_device *netdev)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        return pfvf->msg_enable;
}

static void otx2_set_msglevel(struct net_device *netdev, u32 val)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        pfvf->msg_enable = val;
}

static u32 otx2_get_link(struct net_device *netdev)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        /* LBK and SDP links are internal and always UP */
        if (is_otx2_lbkvf(pfvf->pdev) || is_otx2_sdp_rep(pfvf->pdev))
                return 1;
        return pfvf->linfo.link_up;
}

static int otx2_get_ts_info(struct net_device *netdev,
                            struct kernel_ethtool_ts_info *info)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        if (!pfvf->ptp)
                return ethtool_op_get_ts_info(netdev, info);

        info->so_timestamping = SOF_TIMESTAMPING_TX_SOFTWARE |
                                SOF_TIMESTAMPING_TX_HARDWARE |
                                SOF_TIMESTAMPING_RX_HARDWARE |
                                SOF_TIMESTAMPING_RAW_HARDWARE;

        info->phc_index = otx2_ptp_clock_index(pfvf);

        info->tx_types = BIT(HWTSTAMP_TX_OFF) | BIT(HWTSTAMP_TX_ON);
        if (test_bit(CN10K_PTP_ONESTEP, &pfvf->hw.cap_flag))
                info->tx_types |= BIT(HWTSTAMP_TX_ONESTEP_SYNC);

        info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
                           BIT(HWTSTAMP_FILTER_ALL);

        return 0;
}

static struct cgx_fw_data *otx2_get_fwdata(struct otx2_nic *pfvf)
{
        struct cgx_fw_data *rsp = NULL;
        struct msg_req *req;
        int err = 0;

        mutex_lock(&pfvf->mbox.lock);
        req = otx2_mbox_alloc_msg_cgx_get_aux_link_info(&pfvf->mbox);
        if (!req) {
                mutex_unlock(&pfvf->mbox.lock);
                return ERR_PTR(-ENOMEM);
        }

        err = otx2_sync_mbox_msg(&pfvf->mbox);
        if (!err) {
                rsp = (struct cgx_fw_data *)
                        otx2_mbox_get_rsp(&pfvf->mbox.mbox, 0, &req->hdr);
        } else {
                rsp = ERR_PTR(err);
        }

        mutex_unlock(&pfvf->mbox.lock);
        return rsp;
}

static int otx2_get_fecparam(struct net_device *netdev,
                             struct ethtool_fecparam *fecparam)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct cgx_fw_data *rsp;
        const int fec[] = {
                ETHTOOL_FEC_OFF,
                ETHTOOL_FEC_BASER,
                ETHTOOL_FEC_RS,
                ETHTOOL_FEC_BASER | ETHTOOL_FEC_RS};

        fecparam->active_fec = fec[pfvf->linfo.fec];

        rsp = otx2_get_fwdata(pfvf);
        if (IS_ERR(rsp))
                return PTR_ERR(rsp);

        if (rsp->fwdata.supported_fec < OTX2_FEC_MAX_INDEX) {
                if (!rsp->fwdata.supported_fec)
                        fecparam->fec = ETHTOOL_FEC_NONE;
                else
                        fecparam->fec = fec[rsp->fwdata.supported_fec];
        }
        return 0;
}

static int otx2_set_fecparam(struct net_device *netdev,
                             struct ethtool_fecparam *fecparam)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct mbox *mbox = &pfvf->mbox;
        struct fec_mode *req, *rsp;
        int err = 0, fec = 0;

        switch (fecparam->fec) {
        /* Firmware does not support AUTO mode consider it as FEC_OFF */
        case ETHTOOL_FEC_OFF:
        case ETHTOOL_FEC_AUTO:
                fec = OTX2_FEC_OFF;
                break;
        case ETHTOOL_FEC_RS:
                fec = OTX2_FEC_RS;
                break;
        case ETHTOOL_FEC_BASER:
                fec = OTX2_FEC_BASER;
                break;
        default:
                netdev_warn(pfvf->netdev, "Unsupported FEC mode: %d",
                            fecparam->fec);
                return -EINVAL;
        }

        if (fec == pfvf->linfo.fec)
                return 0;

        mutex_lock(&mbox->lock);
        req = otx2_mbox_alloc_msg_cgx_set_fec_param(&pfvf->mbox);
        if (!req) {
                err = -ENOMEM;
                goto end;
        }
        req->fec = fec;
        err = otx2_sync_mbox_msg(&pfvf->mbox);
        if (err)
                goto end;

        rsp = (struct fec_mode *)otx2_mbox_get_rsp(&pfvf->mbox.mbox,
                                                   0, &req->hdr);
        if (IS_ERR(rsp)) {
                err = PTR_ERR(rsp);
                goto end;
        }

        if (rsp->fec >= 0)
                pfvf->linfo.fec = rsp->fec;
        else
                err = rsp->fec;
end:
        mutex_unlock(&mbox->lock);
        return err;
}

static void otx2_get_fec_info(u64 index, int req_mode,
                              struct ethtool_link_ksettings *link_ksettings)
{
        __ETHTOOL_DECLARE_LINK_MODE_MASK(otx2_fec_modes) = { 0, };

        switch (index) {
        case OTX2_FEC_NONE:
                linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT,
                                 otx2_fec_modes);
                break;
        case OTX2_FEC_BASER:
                linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
                                 otx2_fec_modes);
                break;
        case OTX2_FEC_RS:
                linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
                                 otx2_fec_modes);
                break;
        case OTX2_FEC_BASER | OTX2_FEC_RS:
                linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT,
                                 otx2_fec_modes);
                linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT,
                                 otx2_fec_modes);
                break;
        }

        /* Add fec modes to existing modes */
        if (req_mode == OTX2_MODE_ADVERTISED)
                linkmode_or(link_ksettings->link_modes.advertising,
                            link_ksettings->link_modes.advertising,
                            otx2_fec_modes);
        else
                linkmode_or(link_ksettings->link_modes.supported,
                            link_ksettings->link_modes.supported,
                            otx2_fec_modes);
}

static void otx2_get_link_mode_info(u64 link_mode_bmap,
                                    bool req_mode,
                                    struct ethtool_link_ksettings
                                    *link_ksettings)
{
        __ETHTOOL_DECLARE_LINK_MODE_MASK(otx2_link_modes) = { 0, };
        /* CGX link modes to Ethtool link mode mapping */
        const int cgx_link_mode[CGX_MODE_MAX] = {
                0, /* SGMII  1000baseT */
                ETHTOOL_LINK_MODE_1000baseX_Full_BIT,
                ETHTOOL_LINK_MODE_10000baseT_Full_BIT,
                ETHTOOL_LINK_MODE_10000baseSR_Full_BIT,
                ETHTOOL_LINK_MODE_10000baseLR_Full_BIT,
                ETHTOOL_LINK_MODE_10000baseKR_Full_BIT,
                0,
                ETHTOOL_LINK_MODE_25000baseSR_Full_BIT,
                0,
                0,
                ETHTOOL_LINK_MODE_25000baseCR_Full_BIT,
                ETHTOOL_LINK_MODE_25000baseKR_Full_BIT,
                ETHTOOL_LINK_MODE_40000baseSR4_Full_BIT,
                ETHTOOL_LINK_MODE_40000baseLR4_Full_BIT,
                ETHTOOL_LINK_MODE_40000baseCR4_Full_BIT,
                ETHTOOL_LINK_MODE_40000baseKR4_Full_BIT,
                0,
                ETHTOOL_LINK_MODE_50000baseSR_Full_BIT,
                0,
                ETHTOOL_LINK_MODE_50000baseLR_ER_FR_Full_BIT,
                ETHTOOL_LINK_MODE_50000baseCR_Full_BIT,
                ETHTOOL_LINK_MODE_50000baseKR_Full_BIT,
                0,
                ETHTOOL_LINK_MODE_100000baseSR4_Full_BIT,
                ETHTOOL_LINK_MODE_100000baseLR4_ER4_Full_BIT,
                ETHTOOL_LINK_MODE_100000baseCR4_Full_BIT,
                ETHTOOL_LINK_MODE_100000baseKR4_Full_BIT
        };
        u8 bit;

        for_each_set_bit(bit, (unsigned long *)&link_mode_bmap, ARRAY_SIZE(cgx_link_mode)) {
                if (bit == CGX_MODE_SGMII_10M_BIT) {
                        linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Half_BIT, otx2_link_modes);
                        linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT_Full_BIT, otx2_link_modes);
                } else if (bit == CGX_MODE_SGMII_100M_BIT) {
                        linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Half_BIT, otx2_link_modes);
                        linkmode_set_bit(ETHTOOL_LINK_MODE_100baseT_Full_BIT, otx2_link_modes);
                } else if (bit == CGX_MODE_SGMII) {
                        linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Half_BIT, otx2_link_modes);
                        linkmode_set_bit(ETHTOOL_LINK_MODE_1000baseT_Full_BIT, otx2_link_modes);
                } else {
                        linkmode_set_bit(cgx_link_mode[bit], otx2_link_modes);
                }
        }

        if (req_mode == OTX2_MODE_ADVERTISED)
                linkmode_copy(link_ksettings->link_modes.advertising,
                              otx2_link_modes);
        else
                linkmode_copy(link_ksettings->link_modes.supported,
                              otx2_link_modes);
}

static int otx2_get_link_ksettings(struct net_device *netdev,
                                   struct ethtool_link_ksettings *cmd)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct cgx_fw_data *rsp = NULL;

        cmd->base.duplex  = pfvf->linfo.full_duplex;
        cmd->base.speed   = pfvf->linfo.speed;
        cmd->base.autoneg = pfvf->linfo.an;

        rsp = otx2_get_fwdata(pfvf);
        if (IS_ERR(rsp))
                return PTR_ERR(rsp);

        if (rsp->fwdata.supported_an)
                ethtool_link_ksettings_add_link_mode(cmd,
                                                     supported,
                                                     Autoneg);

        otx2_get_link_mode_info(rsp->fwdata.advertised_link_modes,
                                OTX2_MODE_ADVERTISED, cmd);
        otx2_get_fec_info(rsp->fwdata.advertised_fec,
                          OTX2_MODE_ADVERTISED, cmd);
        otx2_get_link_mode_info(rsp->fwdata.supported_link_modes,
                                OTX2_MODE_SUPPORTED, cmd);
        otx2_get_fec_info(rsp->fwdata.supported_fec,
                          OTX2_MODE_SUPPORTED, cmd);
        return 0;
}

static int otx2_set_link_ksettings(struct net_device *netdev,
                                   const struct ethtool_link_ksettings *cmd)
{
        __ETHTOOL_DECLARE_LINK_MODE_MASK(mask) = { 0, };
        struct otx2_nic *pf = netdev_priv(netdev);
        struct ethtool_link_ksettings cur_ks;
        struct cgx_set_link_mode_req *req;
        struct mbox *mbox = &pf->mbox;
        int err = 0;

        memset(&cur_ks, 0, sizeof(struct ethtool_link_ksettings));

        if (!ethtool_validate_speed(cmd->base.speed) ||
            !ethtool_validate_duplex(cmd->base.duplex))
                return -EINVAL;

        if (cmd->base.autoneg != AUTONEG_ENABLE &&
            cmd->base.autoneg != AUTONEG_DISABLE)
                return -EINVAL;

        otx2_get_link_ksettings(netdev, &cur_ks);

        /* Check requested modes against supported modes by hardware */
        if (!linkmode_subset(cmd->link_modes.advertising,
                             cur_ks.link_modes.supported))
                return -EINVAL;

        mutex_lock(&mbox->lock);
        req = otx2_mbox_alloc_msg_cgx_set_link_mode(&pf->mbox);
        if (!req) {
                err = -ENOMEM;
                goto end;
        }

        req->args.speed = cmd->base.speed;
        /* firmware expects 1 for half duplex and 0 for full duplex
         * hence inverting
         */
        req->args.duplex = cmd->base.duplex ^ 0x1;
        req->args.an = cmd->base.autoneg;
        /* Mask unsupported modes and send message to AF */
        linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_NONE_BIT, mask);
        linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_BASER_BIT, mask);
        linkmode_set_bit(ETHTOOL_LINK_MODE_FEC_RS_BIT, mask);

        linkmode_copy(req->args.advertising,
                      cmd->link_modes.advertising);
        linkmode_andnot(req->args.advertising,
                        req->args.advertising, mask);

        /* inform AF that we need parse this differently */
        if (bitmap_weight(req->args.advertising,
                          __ETHTOOL_LINK_MODE_MASK_NBITS) >= 2)
                req->args.multimode = true;

        err = otx2_sync_mbox_msg(&pf->mbox);
end:
        mutex_unlock(&mbox->lock);
        return err;
}

static void otx2_get_fec_stats(struct net_device *netdev,
                               struct ethtool_fec_stats *fec_stats,
                               struct ethtool_fec_hist *hist)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);
        struct cgx_fw_data *rsp;

        otx2_update_lmac_fec_stats(pfvf);

        /* Report MAC FEC stats */
        fec_stats->corrected_blocks.total     = pfvf->hw.cgx_fec_corr_blks;
        fec_stats->uncorrectable_blocks.total = pfvf->hw.cgx_fec_uncorr_blks;

        rsp = otx2_get_fwdata(pfvf);
        if (!IS_ERR(rsp) && rsp->fwdata.phy.misc.has_fec_stats &&
            !otx2_get_phy_fec_stats(pfvf)) {
                /* Fetch fwdata again because it's been recently populated with
                 * latest PHY FEC stats.
                 */
                rsp = otx2_get_fwdata(pfvf);
                if (!IS_ERR(rsp)) {
                        struct fec_stats_s *p = &rsp->fwdata.phy.fec_stats;

                        if (pfvf->linfo.fec == OTX2_FEC_BASER) {
                                fec_stats->corrected_blocks.total = p->brfec_corr_blks;
                                fec_stats->uncorrectable_blocks.total = p->brfec_uncorr_blks;
                        } else {
                                fec_stats->corrected_blocks.total = p->rsfec_corr_cws;
                                fec_stats->uncorrectable_blocks.total = p->rsfec_uncorr_cws;
                        }
                }
        }
}

static const struct ethtool_ops otx2_ethtool_ops = {
        .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
                                     ETHTOOL_COALESCE_MAX_FRAMES |
                                     ETHTOOL_COALESCE_USE_ADAPTIVE,
        .supported_ring_params  = ETHTOOL_RING_USE_RX_BUF_LEN |
                                  ETHTOOL_RING_USE_CQE_SIZE,
        .rxfh_max_num_contexts  = MAX_RSS_GROUPS,
        .get_link               = otx2_get_link,
        .get_drvinfo            = otx2_get_drvinfo,
        .get_strings            = otx2_get_strings,
        .get_ethtool_stats      = otx2_get_ethtool_stats,
        .get_sset_count         = otx2_get_sset_count,
        .set_channels           = otx2_set_channels,
        .get_channels           = otx2_get_channels,
        .get_ringparam          = otx2_get_ringparam,
        .set_ringparam          = otx2_set_ringparam,
        .get_coalesce           = otx2_get_coalesce,
        .set_coalesce           = otx2_set_coalesce,
        .get_rxnfc              = otx2_get_rxnfc,
        .set_rxnfc              = otx2_set_rxnfc,
        .get_rx_ring_count      = otx2_get_rx_ring_count,
        .get_rxfh_key_size      = otx2_get_rxfh_key_size,
        .get_rxfh_indir_size    = otx2_get_rxfh_indir_size,
        .get_rxfh               = otx2_get_rxfh,
        .set_rxfh               = otx2_set_rxfh,
        .get_rxfh_fields        = otx2_get_rss_hash_opts,
        .set_rxfh_fields        = otx2_set_rss_hash_opts,
        .create_rxfh_context    = otx2_create_rxfh,
        .modify_rxfh_context    = otx2_modify_rxfh,
        .remove_rxfh_context    = otx2_remove_rxfh,
        .get_msglevel           = otx2_get_msglevel,
        .set_msglevel           = otx2_set_msglevel,
        .get_pauseparam         = otx2_get_pauseparam,
        .set_pauseparam         = otx2_set_pauseparam,
        .get_ts_info            = otx2_get_ts_info,
        .get_fec_stats          = otx2_get_fec_stats,
        .get_fecparam           = otx2_get_fecparam,
        .set_fecparam           = otx2_set_fecparam,
        .get_link_ksettings     = otx2_get_link_ksettings,
        .set_link_ksettings     = otx2_set_link_ksettings,
};

void otx2_set_ethtool_ops(struct net_device *netdev)
{
        netdev->ethtool_ops = &otx2_ethtool_ops;
}

/* VF's ethtool APIs */
static void otx2vf_get_drvinfo(struct net_device *netdev,
                               struct ethtool_drvinfo *info)
{
        struct otx2_nic *vf = netdev_priv(netdev);

        strscpy(info->driver, DRV_VF_NAME, sizeof(info->driver));
        strscpy(info->bus_info, pci_name(vf->pdev), sizeof(info->bus_info));
}

static void otx2vf_get_strings(struct net_device *netdev, u32 sset, u8 *data)
{
        struct otx2_nic *vf = netdev_priv(netdev);
        int stats;

        if (sset != ETH_SS_STATS)
                return;

        for (stats = 0; stats < otx2_n_dev_stats; stats++)
                ethtool_puts(&data, otx2_dev_stats[stats].name);

        for (stats = 0; stats < otx2_n_drv_stats; stats++)
                ethtool_puts(&data, otx2_drv_stats[stats].name);

        otx2_get_qset_strings(vf, &data, 0);

        ethtool_puts(&data, "reset_count");
}

static void otx2vf_get_ethtool_stats(struct net_device *netdev,
                                     struct ethtool_stats *stats, u64 *data)
{
        struct otx2_nic *vf = netdev_priv(netdev);
        int stat;

        otx2_get_dev_stats(vf);
        for (stat = 0; stat < otx2_n_dev_stats; stat++)
                *(data++) = ((u64 *)&vf->hw.dev_stats)
                                [otx2_dev_stats[stat].index];

        for (stat = 0; stat < otx2_n_drv_stats; stat++)
                *(data++) = atomic_read(&((atomic_t *)&vf->hw.drv_stats)
                                                [otx2_drv_stats[stat].index]);

        otx2_get_qset_stats(vf, stats, &data);
        *(data++) = vf->reset_count;
}

static int otx2vf_get_sset_count(struct net_device *netdev, int sset)
{
        struct otx2_nic *vf = netdev_priv(netdev);
        int qstats_count;

        if (sset != ETH_SS_STATS)
                return -EINVAL;

        qstats_count = otx2_n_queue_stats *
                       (vf->hw.rx_queues + otx2_get_total_tx_queues(vf));

        return otx2_n_dev_stats + otx2_n_drv_stats + qstats_count + 1;
}

static int otx2vf_get_link_ksettings(struct net_device *netdev,
                                     struct ethtool_link_ksettings *cmd)
{
        struct otx2_nic *pfvf = netdev_priv(netdev);

        if (is_otx2_lbkvf(pfvf->pdev) || is_otx2_sdp_rep(pfvf->pdev)) {
                cmd->base.duplex = DUPLEX_FULL;
                cmd->base.speed = SPEED_100000;
        } else {
                return otx2_get_link_ksettings(netdev, cmd);
        }
        return 0;
}

static const struct ethtool_ops otx2vf_ethtool_ops = {
        .supported_coalesce_params = ETHTOOL_COALESCE_USECS |
                                     ETHTOOL_COALESCE_MAX_FRAMES |
                                     ETHTOOL_COALESCE_USE_ADAPTIVE,
        .supported_ring_params  = ETHTOOL_RING_USE_RX_BUF_LEN |
                                  ETHTOOL_RING_USE_CQE_SIZE,
        .rxfh_max_num_contexts  = MAX_RSS_GROUPS,
        .get_link               = otx2_get_link,
        .get_drvinfo            = otx2vf_get_drvinfo,
        .get_strings            = otx2vf_get_strings,
        .get_ethtool_stats      = otx2vf_get_ethtool_stats,
        .get_sset_count         = otx2vf_get_sset_count,
        .set_channels           = otx2_set_channels,
        .get_channels           = otx2_get_channels,
        .get_rxnfc              = otx2_get_rxnfc,
        .set_rxnfc              = otx2_set_rxnfc,
        .get_rx_ring_count      = otx2_get_rx_ring_count,
        .get_rxfh_key_size      = otx2_get_rxfh_key_size,
        .get_rxfh_indir_size    = otx2_get_rxfh_indir_size,
        .get_rxfh               = otx2_get_rxfh,
        .set_rxfh               = otx2_set_rxfh,
        .get_rxfh_fields        = otx2_get_rss_hash_opts,
        .set_rxfh_fields        = otx2_set_rss_hash_opts,
        .create_rxfh_context    = otx2_create_rxfh,
        .modify_rxfh_context    = otx2_modify_rxfh,
        .remove_rxfh_context    = otx2_remove_rxfh,
        .get_ringparam          = otx2_get_ringparam,
        .set_ringparam          = otx2_set_ringparam,
        .get_coalesce           = otx2_get_coalesce,
        .set_coalesce           = otx2_set_coalesce,
        .get_msglevel           = otx2_get_msglevel,
        .set_msglevel           = otx2_set_msglevel,
        .get_pauseparam         = otx2_get_pauseparam,
        .set_pauseparam         = otx2_set_pauseparam,
        .get_link_ksettings     = otx2vf_get_link_ksettings,
        .get_ts_info            = otx2_get_ts_info,
};

void otx2vf_set_ethtool_ops(struct net_device *netdev)
{
        netdev->ethtool_ops = &otx2vf_ethtool_ops;
}
EXPORT_SYMBOL(otx2vf_set_ethtool_ops);