[PATCH net-next v2 13/21] motorcomm:yt6801: Implement some ethtool_ops function

Frank Sae posted 21 patches 1 day, 9 hours ago
[PATCH net-next v2 13/21] motorcomm:yt6801: Implement some ethtool_ops function
Posted by Frank Sae 1 day, 9 hours ago
Implement following callback function
.get_drvinfo
.get_link
.get_msglevel
.set_msglevel
.get_channels
.get_coalesce
.set_coalesce
.supported_coalesce_params
.get_strings
.get_sset_count
.get_ethtool_stats
.get_regs_len
.get_regs
.get_ringparam
.set_ringparam
.get_rxnfc
.set_rxnfc
.get_rxfh_indir_size
.get_rxfh_key_size
.get_rxfh
.set_rxfh
.nway_reset
.reset
.get_link_ksettings
.set_link_ksettings
.get_pauseparam
.set_pauseparam

Signed-off-by: Frank Sae <Frank.Sae@motor-comm.com>
---
 .../motorcomm/yt6801/yt6801_ethtool.c         | 738 ++++++++++++++++++
 1 file changed, 738 insertions(+)
 create mode 100644 drivers/net/ethernet/motorcomm/yt6801/yt6801_ethtool.c

diff --git a/drivers/net/ethernet/motorcomm/yt6801/yt6801_ethtool.c b/drivers/net/ethernet/motorcomm/yt6801/yt6801_ethtool.c
new file mode 100644
index 000000000..7989ccbc3
--- /dev/null
+++ b/drivers/net/ethernet/motorcomm/yt6801/yt6801_ethtool.c
@@ -0,0 +1,738 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2022 - 2024 Motorcomm Electronic Technology Co.,Ltd. */
+
+#include <linux/netdevice.h>
+#include <linux/ethtool.h>
+#include <linux/kernel.h>
+
+#include "yt6801_desc.h"
+#include "yt6801_net.h"
+
+struct fxgmac_stats_desc {
+	char stat_string[ETH_GSTRING_LEN];
+	int stat_offset;
+};
+
+#define FXGMAC_STAT(str, var)                                                  \
+	{                                                                      \
+		str, offsetof(struct fxgmac_pdata, stats.var),                 \
+	}
+
+static const struct fxgmac_stats_desc fxgmac_gstring_stats[] = {
+	/* MMC TX counters */
+	FXGMAC_STAT("tx_bytes", txoctetcount_gb),
+	FXGMAC_STAT("tx_bytes_good", txoctetcount_g),
+	FXGMAC_STAT("tx_packets", txframecount_gb),
+	FXGMAC_STAT("tx_packets_good", txframecount_g),
+	FXGMAC_STAT("tx_unicast_packets", txunicastframes_gb),
+	FXGMAC_STAT("tx_broadcast_packets", txbroadcastframes_gb),
+	FXGMAC_STAT("tx_broadcast_packets_good", txbroadcastframes_g),
+	FXGMAC_STAT("tx_multicast_packets", txmulticastframes_gb),
+	FXGMAC_STAT("tx_multicast_packets_good", txmulticastframes_g),
+	FXGMAC_STAT("tx_vlan_packets_good", txvlanframes_g),
+	FXGMAC_STAT("tx_64_byte_packets", tx64octets_gb),
+	FXGMAC_STAT("tx_65_to_127_byte_packets", tx65to127octets_gb),
+	FXGMAC_STAT("tx_128_to_255_byte_packets", tx128to255octets_gb),
+	FXGMAC_STAT("tx_256_to_511_byte_packets", tx256to511octets_gb),
+	FXGMAC_STAT("tx_512_to_1023_byte_packets", tx512to1023octets_gb),
+	FXGMAC_STAT("tx_1024_to_max_byte_packets", tx1024tomaxoctets_gb),
+	FXGMAC_STAT("tx_underflow_errors", txunderflowerror),
+	FXGMAC_STAT("tx_pause_frames", txpauseframes),
+	FXGMAC_STAT("tx_single_collision", txsinglecollision_g),
+	FXGMAC_STAT("tx_multiple_collision", txmultiplecollision_g),
+	FXGMAC_STAT("tx_deferred_frames", txdeferredframes),
+	FXGMAC_STAT("tx_late_collision_frames", txlatecollisionframes),
+	FXGMAC_STAT("tx_excessive_collision_frames",
+		    txexcessivecollisionframes),
+	FXGMAC_STAT("tx_carrier_error_frames", txcarriererrorframes),
+	FXGMAC_STAT("tx_excessive_deferral_error", txexcessivedeferralerror),
+	FXGMAC_STAT("tx_oversize_frames_good", txoversize_g),
+
+	/* MMC RX counters */
+	FXGMAC_STAT("rx_bytes", rxoctetcount_gb),
+	FXGMAC_STAT("rx_bytes_good", rxoctetcount_g),
+	FXGMAC_STAT("rx_packets", rxframecount_gb),
+	FXGMAC_STAT("rx_unicast_packets_good", rxunicastframes_g),
+	FXGMAC_STAT("rx_broadcast_packets_good", rxbroadcastframes_g),
+	FXGMAC_STAT("rx_multicast_packets_good", rxmulticastframes_g),
+	FXGMAC_STAT("rx_vlan_packets_mac", rxvlanframes_gb),
+	FXGMAC_STAT("rx_64_byte_packets", rx64octets_gb),
+	FXGMAC_STAT("rx_65_to_127_byte_packets", rx65to127octets_gb),
+	FXGMAC_STAT("rx_128_to_255_byte_packets", rx128to255octets_gb),
+	FXGMAC_STAT("rx_256_to_511_byte_packets", rx256to511octets_gb),
+	FXGMAC_STAT("rx_512_to_1023_byte_packets", rx512to1023octets_gb),
+	FXGMAC_STAT("rx_1024_to_max_byte_packets", rx1024tomaxoctets_gb),
+	FXGMAC_STAT("rx_undersize_packets_good", rxundersize_g),
+	FXGMAC_STAT("rx_oversize_packets_good", rxoversize_g),
+	FXGMAC_STAT("rx_crc_errors", rxcrcerror),
+	FXGMAC_STAT("rx_align_error", rxalignerror),
+	FXGMAC_STAT("rx_crc_errors_small_packets", rxrunterror),
+	FXGMAC_STAT("rx_crc_errors_giant_packets", rxjabbererror),
+	FXGMAC_STAT("rx_length_errors", rxlengtherror),
+	FXGMAC_STAT("rx_out_of_range_errors", rxoutofrangetype),
+	FXGMAC_STAT("rx_fifo_overflow_errors", rxfifooverflow),
+	FXGMAC_STAT("rx_watchdog_errors", rxwatchdogerror),
+	FXGMAC_STAT("rx_pause_frames", rxpauseframes),
+	FXGMAC_STAT("rx_receive_error_frames", rxreceiveerrorframe),
+	FXGMAC_STAT("rx_control_frames_good", rxcontrolframe_g),
+
+	/* Extra counters */
+	FXGMAC_STAT("tx_tso_packets", tx_tso_packets),
+	FXGMAC_STAT("rx_split_header_packets", rx_split_header_packets),
+	FXGMAC_STAT("tx_process_stopped", tx_process_stopped),
+	FXGMAC_STAT("rx_process_stopped", rx_process_stopped),
+	FXGMAC_STAT("tx_buffer_unavailable", tx_buffer_unavailable),
+	FXGMAC_STAT("rx_buffer_unavailable", rx_buffer_unavailable),
+	FXGMAC_STAT("fatal_bus_error", fatal_bus_error),
+	FXGMAC_STAT("tx_vlan_packets_net", tx_vlan_packets),
+	FXGMAC_STAT("rx_vlan_packets_net", rx_vlan_packets),
+	FXGMAC_STAT("napi_poll_isr", napi_poll_isr),
+	FXGMAC_STAT("napi_poll_txtimer", napi_poll_txtimer),
+	FXGMAC_STAT("alive_cnt_txtimer", cnt_alive_txtimer),
+	FXGMAC_STAT("ephy_poll_timer", ephy_poll_timer_cnt),
+	FXGMAC_STAT("mgmt_int_isr", mgmt_int_isr),
+};
+
+#define FXGMAC_STATS_COUNT ARRAY_SIZE(fxgmac_gstring_stats)
+
+static void fxgmac_ethtool_get_drvinfo(struct net_device *netdev,
+				       struct ethtool_drvinfo *drvinfo)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	u32 ver = pdata->hw_feat.version;
+	u32 sver, devid, userver;
+
+	strscpy(drvinfo->version, pdata->drv_ver, sizeof(drvinfo->version));
+
+	/* S|SVER: MAC Version
+	 * D|DEVID: Indicates the Device family
+	 * U|USERVER: User-defined Version
+	 */
+	sver = FXGMAC_GET_BITS(ver, MAC_VR_SVER_POS, MAC_VR_SVER_LEN);
+	devid = FXGMAC_GET_BITS(ver, MAC_VR_DEVID_POS, MAC_VR_DEVID_LEN);
+	userver = FXGMAC_GET_BITS(ver, MAC_VR_USERVER_POS, MAC_VR_USERVER_LEN);
+
+	snprintf(drvinfo->fw_version, sizeof(drvinfo->fw_version),
+		 "S.D.U: %x.%x.%x", sver, devid, userver);
+}
+
+static u32 fxgmac_ethtool_get_msglevel(struct net_device *netdev)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	return pdata->msg_enable;
+}
+
+static void fxgmac_ethtool_set_msglevel(struct net_device *netdev, u32 msglevel)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	yt_dbg(pdata, "set msglvl from %08x to %08x\n", pdata->msg_enable,
+	       msglevel);
+	pdata->msg_enable = msglevel;
+}
+
+static void fxgmac_ethtool_get_channels(struct net_device *netdev,
+					struct ethtool_channels *channel)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	/* report maximum channels */
+	channel->max_rx = FXGMAC_MAX_DMA_RX_CHANNELS;
+	channel->max_tx = FXGMAC_MAX_DMA_TX_CHANNELS;
+	channel->max_other = 0;
+	channel->max_combined =
+		channel->max_rx + channel->max_tx + channel->max_other;
+
+	yt_dbg(pdata, "channels max rx:%d, tx:%d, other:%d, combined:%d\n",
+	       channel->max_rx, channel->max_tx, channel->max_other,
+	       channel->max_combined);
+
+	channel->rx_count = pdata->rx_q_count;
+	channel->tx_count = FXGMAC_TX_1_Q;
+	channel->other_count = 0;
+	/* record RSS queues */
+	channel->combined_count =
+		channel->rx_count + channel->tx_count + channel->other_count;
+
+	yt_dbg(pdata, "channels count rx:%d, tx:%d, other:%d, combined:%d\n",
+	       channel->rx_count, channel->tx_count, channel->other_count,
+	       channel->combined_count);
+}
+
+static int
+fxgmac_ethtool_get_coalesce(struct net_device *netdev,
+			    struct ethtool_coalesce *ec,
+			    struct kernel_ethtool_coalesce *kernel_coal,
+			    struct netlink_ext_ack *extack)
+
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	memset(ec, 0, sizeof(struct ethtool_coalesce));
+	ec->rx_coalesce_usecs = pdata->rx_usecs;
+	ec->tx_coalesce_usecs = pdata->tx_usecs;
+
+	return 0;
+}
+
+#define FXGMAC_MAX_DMA_RIWT 0xff
+#define FXGMAC_MIN_DMA_RIWT 0x01
+
+static int
+fxgmac_ethtool_set_coalesce(struct net_device *netdev,
+			    struct ethtool_coalesce *ec,
+			    struct kernel_ethtool_coalesce *kernel_coal,
+			    struct netlink_ext_ack *extack)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops;
+	unsigned int rx_frames, rx_riwt, rx_usecs;
+	unsigned int tx_frames;
+
+	/* Check for not supported parameters */
+	if (ec->rx_coalesce_usecs_irq || ec->rx_max_coalesced_frames_irq ||
+	    ec->tx_coalesce_usecs_high || ec->tx_max_coalesced_frames_irq ||
+	    ec->tx_coalesce_usecs_irq || ec->stats_block_coalesce_usecs ||
+	    ec->pkt_rate_low || ec->use_adaptive_rx_coalesce ||
+	    ec->use_adaptive_tx_coalesce || ec->rx_max_coalesced_frames_low ||
+	    ec->rx_coalesce_usecs_low || ec->tx_coalesce_usecs_low ||
+	    ec->tx_max_coalesced_frames_low || ec->pkt_rate_high ||
+	    ec->rx_coalesce_usecs_high || ec->rx_max_coalesced_frames_high ||
+	    ec->tx_max_coalesced_frames_high || ec->rate_sample_interval)
+		return -EOPNOTSUPP;
+
+	rx_usecs = ec->rx_coalesce_usecs;
+	rx_riwt = hw_ops->usec_to_riwt(pdata, rx_usecs);
+	rx_frames = ec->rx_max_coalesced_frames;
+	tx_frames = ec->tx_max_coalesced_frames;
+
+	if (rx_riwt > FXGMAC_MAX_DMA_RIWT || rx_riwt < FXGMAC_MIN_DMA_RIWT ||
+	    rx_frames > pdata->rx_desc_count)
+		return -EINVAL;
+
+	if (tx_frames > pdata->tx_desc_count)
+		return -EINVAL;
+
+	pdata->rx_usecs = rx_usecs;
+	pdata->rx_frames = rx_frames;
+	pdata->rx_riwt = rx_riwt;
+	hw_ops->config_rx_coalesce(pdata);
+
+	pdata->tx_frames = tx_frames;
+	pdata->tx_usecs = ec->tx_coalesce_usecs;
+	hw_ops->set_interrupt_moderation(pdata);
+
+	return 0;
+}
+
+static u32 fxgmac_get_rxfh_key_size(struct net_device *netdev)
+{
+	return FXGMAC_RSS_HASH_KEY_SIZE;
+}
+
+static u32 fxgmac_rss_indir_size(struct net_device *netdev)
+{
+	return FXGMAC_RSS_MAX_TABLE_SIZE;
+}
+
+static void fxgmac_get_reta(struct fxgmac_pdata *pdata, u32 *indir)
+{
+	u16 rss_m = FXGMAC_MAX_DMA_RX_CHANNELS - 1;
+
+	for (u32 i = 0; i < FXGMAC_RSS_MAX_TABLE_SIZE; i++)
+		indir[i] = pdata->rss_table[i] & rss_m;
+}
+
+static int fxgmac_get_rxfh(struct net_device *netdev,
+			   struct ethtool_rxfh_param *rxfh)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	if (rxfh->hfunc) {
+		rxfh->hfunc = ETH_RSS_HASH_TOP;
+		yt_dbg(pdata, "%s, hash function\n", __func__);
+	}
+
+	if (rxfh->indir) {
+		fxgmac_get_reta(pdata, rxfh->indir);
+		yt_dbg(pdata, "%s, indirection tab\n", __func__);
+	}
+
+	if (rxfh->key) {
+		memcpy(rxfh->key, pdata->rss_key,
+		       fxgmac_get_rxfh_key_size(netdev));
+		yt_dbg(pdata, "%s, hash key\n", __func__);
+	}
+
+	return 0;
+}
+
+static int fxgmac_set_rxfh(struct net_device *netdev,
+			   struct ethtool_rxfh_param *rxfh,
+			   struct netlink_ext_ack *ack)
+{
+	u32 i, reta_entries = fxgmac_rss_indir_size(netdev);
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops;
+
+	yt_dbg(pdata, "%s, indir=%lx, key=%lx, func=%02x\n", __func__,
+	       (unsigned long)rxfh->indir, (unsigned long)rxfh->key,
+	       rxfh->hfunc);
+
+	if (rxfh->hfunc)
+		return -EINVAL;
+
+	/* Fill out the redirection table */
+	if (rxfh->indir) {
+		/* double check user input. */
+		for (i = 0; i < reta_entries; i++)
+			if (rxfh->indir[i] >= FXGMAC_MAX_DMA_RX_CHANNELS)
+				return -EINVAL;
+
+		for (i = 0; i < reta_entries; i++)
+			pdata->rss_table[i] = rxfh->indir[i];
+
+		hw_ops->write_rss_lookup_table(pdata);
+	}
+
+	/* Fill out the rss hash key */
+	if (rxfh->key)
+		hw_ops->set_rss_hash_key(pdata, rxfh->key);
+
+	return 0;
+}
+
+static int fxgmac_get_rss_hash_opts(struct fxgmac_pdata *pdata,
+				    struct ethtool_rxnfc *cmd)
+{
+	struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops;
+	u32 reg_opt;
+
+	cmd->data = 0;
+	reg_opt = hw_ops->get_rss_options(pdata);
+	yt_dbg(pdata, "%s, hw=%02x, %02x\n", __func__, reg_opt,
+	       pdata->rss_options);
+
+	if (reg_opt != pdata->rss_options)
+		yt_dbg(pdata, "warning, options are not consistent\n");
+
+	/* Report default options for RSS */
+	switch (cmd->flow_type) {
+	case TCP_V4_FLOW:
+	case UDP_V4_FLOW:
+		if ((cmd->flow_type == TCP_V4_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_TCP4TE)) ||
+		    (cmd->flow_type == UDP_V4_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_UDP4TE))) {
+			cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+		}
+		fallthrough;
+	case SCTP_V4_FLOW:
+	case AH_ESP_V4_FLOW:
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+	case IPV4_FLOW:
+		if ((cmd->flow_type == TCP_V4_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_TCP4TE)) ||
+		    (cmd->flow_type == UDP_V4_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_UDP4TE)) ||
+		    (pdata->rss_options & BIT(FXGMAC_RSS_IP4TE))) {
+			cmd->data |= RXH_IP_SRC | RXH_IP_DST;
+		}
+		break;
+	case TCP_V6_FLOW:
+	case UDP_V6_FLOW:
+		if ((cmd->flow_type == TCP_V6_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_TCP6TE)) ||
+		    (cmd->flow_type == UDP_V6_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_UDP6TE))) {
+			cmd->data |= RXH_L4_B_0_1 | RXH_L4_B_2_3;
+		}
+		fallthrough;
+	case SCTP_V6_FLOW:
+	case AH_ESP_V6_FLOW:
+	case AH_V6_FLOW:
+	case ESP_V6_FLOW:
+	case IPV6_FLOW:
+		if ((cmd->flow_type == TCP_V6_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_TCP6TE)) ||
+		    (cmd->flow_type == UDP_V6_FLOW &&
+		     (pdata->rss_options & FXGMAC_RSS_UDP6TE)) ||
+		    (pdata->rss_options & FXGMAC_RSS_IP6TE)) {
+			cmd->data |= RXH_IP_SRC | RXH_IP_DST;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int fxgmac_get_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd,
+			    u32 *rule_locs)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(dev);
+	int ret = 0;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_GRXRINGS:
+		cmd->data = pdata->rx_q_count;
+		yt_dbg(pdata, "%s, rx ring cnt\n", __func__);
+		break;
+	case ETHTOOL_GRXCLSRLCNT:
+		cmd->rule_cnt = 0;
+		yt_dbg(pdata, "%s, classify rule cnt\n", __func__);
+		break;
+	case ETHTOOL_GRXCLSRULE:
+		yt_dbg(pdata, "%s, classify rules\n", __func__);
+		break;
+	case ETHTOOL_GRXCLSRLALL:
+		cmd->rule_cnt = 0;
+		yt_dbg(pdata, "%s, classify both cnt and rules\n", __func__);
+		break;
+	case ETHTOOL_GRXFH:
+		ret = fxgmac_get_rss_hash_opts(pdata, cmd);
+		yt_dbg(pdata, "%s, hash options\n", __func__);
+		break;
+	default:
+		ret = -EOPNOTSUPP;
+		break;
+	}
+
+	return ret;
+}
+
+#define UDP_RSS_FLAGS (FXGMAC_RSS_UDP4TE | FXGMAC_RSS_UDP6TE)
+
+static int fxgmac_set_rss_hash_opt(struct fxgmac_pdata *pdata,
+				   struct ethtool_rxnfc *nfc)
+{
+	struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops;
+	__u64 data = nfc->data;
+	u32 rssopt = 0;
+
+	yt_dbg(pdata, "%s nfc_data=%llx,cur opt=%x\n", __func__, data,
+	       pdata->rss_options);
+
+	/* For RSS, it does not support anything other than hashing
+	 * to queues on src,dst IPs and L4 ports
+	 */
+	if (data & ~(RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3))
+		return -EINVAL;
+
+	switch (nfc->flow_type) {
+	case TCP_V4_FLOW:
+	case TCP_V6_FLOW:
+		/* default to TCP flow and do nothting */
+		if (!(data &
+		      (RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3)))
+			return -EINVAL;
+		if (nfc->flow_type == TCP_V4_FLOW)
+			rssopt |= BIT(FXGMAC_RSS_IP4TE) | FXGMAC_RSS_TCP4TE;
+		if (nfc->flow_type == TCP_V6_FLOW)
+			rssopt |= FXGMAC_RSS_IP6TE | FXGMAC_RSS_TCP6TE;
+		break;
+	case UDP_V4_FLOW:
+		if (!(data & (RXH_IP_SRC | RXH_IP_DST)))
+			return -EINVAL;
+		rssopt |= BIT(FXGMAC_RSS_IP4TE);
+		switch (data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+		case 0:
+			break;
+		case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+			rssopt |= FXGMAC_RSS_UDP4TE;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case UDP_V6_FLOW:
+		if (!(data & (RXH_IP_SRC | RXH_IP_DST)))
+			return -EINVAL;
+		rssopt |= FXGMAC_RSS_IP6TE;
+
+		switch (data & (RXH_L4_B_0_1 | RXH_L4_B_2_3)) {
+		case 0:
+			break;
+		case (RXH_L4_B_0_1 | RXH_L4_B_2_3):
+			rssopt |= FXGMAC_RSS_UDP6TE;
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case AH_ESP_V4_FLOW:
+	case AH_V4_FLOW:
+	case ESP_V4_FLOW:
+	case SCTP_V4_FLOW:
+	case AH_ESP_V6_FLOW:
+	case AH_V6_FLOW:
+	case ESP_V6_FLOW:
+	case SCTP_V6_FLOW:
+		if (!(data &
+		      (RXH_IP_SRC | RXH_IP_DST | RXH_L4_B_0_1 | RXH_L4_B_2_3)))
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* if options are changed, then update to hw */
+	if (rssopt != pdata->rss_options) {
+		if ((rssopt & UDP_RSS_FLAGS) &&
+		    !(pdata->rss_options & UDP_RSS_FLAGS))
+			yt_dbg(pdata,
+			       "enabling UDP RSS: fragmented packets may arrive out of order to the stack above.");
+
+		yt_dbg(pdata, "rss option changed from %x to %x\n",
+		       pdata->rss_options, rssopt);
+		pdata->rss_options = rssopt;
+		hw_ops->set_rss_options(pdata);
+	}
+
+	return 0;
+}
+
+static int fxgmac_set_rxnfc(struct net_device *dev, struct ethtool_rxnfc *cmd)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(dev);
+	int ret = -EOPNOTSUPP;
+
+	switch (cmd->cmd) {
+	case ETHTOOL_SRXCLSRLINS:
+		yt_dbg(pdata, "%s, rx cls rule insert-n\\a\n", __func__);
+		break;
+	case ETHTOOL_SRXCLSRLDEL:
+		yt_dbg(pdata, "%s, rx cls rule del-n\\a\n", __func__);
+		break;
+	case ETHTOOL_SRXFH:
+		yt_dbg(pdata, "%s, rx rss option\n", __func__);
+		ret = fxgmac_set_rss_hash_opt(pdata, cmd);
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static void fxgmac_get_ringparam(struct net_device *netdev,
+				 struct ethtool_ringparam *ring,
+				 struct kernel_ethtool_ringparam *kernel_ring,
+				 struct netlink_ext_ack *exact)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	ring->rx_max_pending = FXGMAC_RX_DESC_CNT;
+	ring->tx_max_pending = FXGMAC_TX_DESC_CNT;
+	ring->rx_mini_max_pending = 0;
+	ring->rx_jumbo_max_pending = 0;
+	ring->rx_pending = pdata->rx_desc_count;
+	ring->tx_pending = pdata->tx_desc_count;
+	ring->rx_mini_pending = 0;
+	ring->rx_jumbo_pending = 0;
+}
+
+static int fxgmac_set_ringparam(struct net_device *netdev,
+				struct ethtool_ringparam *ring,
+				struct kernel_ethtool_ringparam *kernel_ring,
+				struct netlink_ext_ack *exact)
+
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	int ret;
+
+	if (pdata->dev_state != FXGMAC_DEV_START)
+		return 0;
+
+	mutex_lock(&pdata->mutex);
+	pdata->tx_desc_count = ring->tx_pending;
+	pdata->rx_desc_count = ring->rx_pending;
+
+	fxgmac_stop(pdata);
+	fxgmac_free_tx_data(pdata);
+	fxgmac_free_rx_data(pdata);
+	ret = fxgmac_channels_rings_alloc(pdata);
+	if (ret < 0)
+		goto unlock;
+
+	ret = fxgmac_start(pdata);
+
+unlock:
+	mutex_unlock(&pdata->mutex);
+	return ret;
+}
+
+static int fxgmac_get_regs_len(struct net_device __always_unused *netdev)
+{
+	return FXGMAC_PHY_REG_CNT * sizeof(u32);
+}
+
+static void fxgmac_get_regs(struct net_device *netdev,
+			    struct ethtool_regs *regs, void *p)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	u32 *regs_buff = p;
+
+	memset(p, 0, FXGMAC_PHY_REG_CNT * sizeof(u32));
+	for (u32 i = MII_BMCR; i < FXGMAC_PHY_REG_CNT; i++)
+		regs_buff[i] = phy_read(pdata->phydev, i);
+
+	regs->version = regs_buff[MII_PHYSID1] << 16 | regs_buff[MII_PHYSID2];
+}
+
+static void fxgmac_get_pauseparam(struct net_device *dev,
+				  struct ethtool_pauseparam *data)
+{
+	struct fxgmac_pdata *yt = netdev_priv(dev);
+	bool tx_pause, rx_pause;
+
+	phy_get_pause(yt->phydev, &tx_pause, &rx_pause);
+
+	data->autoneg = yt->phydev->autoneg;
+	data->tx_pause = tx_pause ? 1 : 0;
+	data->rx_pause = rx_pause ? 1 : 0;
+
+	yt_dbg(yt, "%s, rx=%d, tx=%d\n", __func__, data->rx_pause,
+	       data->tx_pause);
+}
+
+static int fxgmac_set_pauseparam(struct net_device *dev,
+				 struct ethtool_pauseparam *data)
+{
+	struct fxgmac_pdata *yt = netdev_priv(dev);
+
+	if (dev->mtu > ETH_DATA_LEN)
+		return -EOPNOTSUPP;
+
+	phy_set_asym_pause(yt->phydev, data->rx_pause, data->tx_pause);
+
+	yt_dbg(yt, "%s, autoneg=%d, rx=%d, tx=%d\n", __func__,
+	       data->autoneg, data->rx_pause, data->tx_pause);
+
+	return 0;
+}
+
+static void fxgmac_ethtool_get_strings(struct net_device *netdev, u32 stringset,
+				       u8 *data)
+{
+	switch (stringset) {
+	case ETH_SS_STATS:
+		for (u32 i = 0; i < FXGMAC_STATS_COUNT; i++) {
+			memcpy(data, fxgmac_gstring_stats[i].stat_string,
+			       strlen(fxgmac_gstring_stats[i].stat_string));
+			data += ETH_GSTRING_LEN;
+		}
+		break;
+	default:
+		WARN_ON(1);
+		break;
+	}
+}
+
+static int fxgmac_ethtool_get_sset_count(struct net_device *netdev,
+					 int stringset)
+{
+	int ret;
+
+	switch (stringset) {
+	case ETH_SS_STATS:
+		ret = FXGMAC_STATS_COUNT;
+		break;
+
+	default:
+		ret = -EOPNOTSUPP;
+	}
+
+	return ret;
+}
+
+static void fxgmac_ethtool_get_ethtool_stats(struct net_device *netdev,
+					     struct ethtool_stats *stats,
+					     u64 *data)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+
+	if (!test_bit(FXGMAC_POWER_STATE_DOWN, &pdata->powerstate))
+		pdata->hw_ops.read_mmc_stats(pdata);
+
+	for (u32 i = 0; i < FXGMAC_STATS_COUNT; i++)
+		*data++ = *(u64 *)((u8 *)pdata +
+				   fxgmac_gstring_stats[i].stat_offset);
+}
+
+static int fxgmac_ethtool_reset(struct net_device *netdev, u32 *flag)
+{
+	struct fxgmac_pdata *pdata = netdev_priv(netdev);
+	int ret = 0;
+	u32 val;
+
+	val = *flag & (ETH_RESET_ALL | ETH_RESET_PHY);
+	if (!val) {
+		yt_err(pdata, "Operation not support.\n");
+		return -EOPNOTSUPP;
+	}
+
+	switch (*flag) {
+	case ETH_RESET_ALL:
+		fxgmac_restart(pdata);
+		*flag = 0;
+		break;
+	case ETH_RESET_PHY:
+		/* Power off and on the phy in order to properly
+		 * configure the MAC timing
+		 */
+		ret = phy_modify(pdata->phydev, MII_BMCR, BMCR_PDOWN,
+				 BMCR_PDOWN);
+		if (ret < 0)
+			return ret;
+		fsleep(9000);
+
+		ret = phy_modify(pdata->phydev, MII_BMCR, BMCR_PDOWN, 0);
+		if (ret < 0)
+			return ret;
+		*flag = 0;
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct ethtool_ops fxgmac_ethtool_ops = {
+	.get_drvinfo			= fxgmac_ethtool_get_drvinfo,
+	.get_link			= ethtool_op_get_link,
+	.get_msglevel			= fxgmac_ethtool_get_msglevel,
+	.set_msglevel			= fxgmac_ethtool_set_msglevel,
+	.get_channels			= fxgmac_ethtool_get_channels,
+	.get_coalesce			= fxgmac_ethtool_get_coalesce,
+	.set_coalesce			= fxgmac_ethtool_set_coalesce,
+	.reset				= fxgmac_ethtool_reset,
+	.supported_coalesce_params	= ETHTOOL_COALESCE_USECS,
+	.get_strings			= fxgmac_ethtool_get_strings,
+	.get_sset_count			= fxgmac_ethtool_get_sset_count,
+	.get_ethtool_stats		= fxgmac_ethtool_get_ethtool_stats,
+	.get_regs_len			= fxgmac_get_regs_len,
+	.get_regs			= fxgmac_get_regs,
+	.get_ringparam			= fxgmac_get_ringparam,
+	.set_ringparam			= fxgmac_set_ringparam,
+	.get_rxnfc			= fxgmac_get_rxnfc,
+	.set_rxnfc			= fxgmac_set_rxnfc,
+	.get_rxfh_indir_size		= fxgmac_rss_indir_size,
+	.get_rxfh_key_size		= fxgmac_get_rxfh_key_size,
+	.get_rxfh			= fxgmac_get_rxfh,
+	.set_rxfh			= fxgmac_set_rxfh,
+	.nway_reset			= phy_ethtool_nway_reset,
+	.get_link_ksettings		= phy_ethtool_get_link_ksettings,
+	.set_link_ksettings		= phy_ethtool_set_link_ksettings,
+	.get_pauseparam			= fxgmac_get_pauseparam,
+	.set_pauseparam			= fxgmac_set_pauseparam,
+};
+
+const struct ethtool_ops *fxgmac_get_ethtool_ops(void)
+{
+	return &fxgmac_ethtool_ops;
+}
-- 
2.34.1