diff mbox

[net,20/20] net/hinic: Add ethtool and stats

Message ID bd60f835144c37334a53df6a053b7efcc4c26c41.1499865198.git.aviad.krawczyk@huawei.com
State Deferred, archived
Delegated to: David Miller
Headers show

Commit Message

Aviad Krawczyk July 12, 2017, 2:17 p.m. UTC
Add ethtool operations and statistics operations.

Signed-off-by: Aviad Krawczyk <aviad.krawczyk@huawei.com>
Signed-off-by: Zhaochen <zhaochen6@huawei.com>
---
 MAINTAINERS                                    |   7 +
 drivers/net/ethernet/huawei/hinic/hinic_dev.h  |   3 +
 drivers/net/ethernet/huawei/hinic/hinic_main.c | 262 ++++++++++++++++++++++++-
 drivers/net/ethernet/huawei/hinic/hinic_port.c |  29 +++
 drivers/net/ethernet/huawei/hinic/hinic_port.h |  45 +++++
 drivers/net/ethernet/huawei/hinic/hinic_rx.c   |  19 ++
 drivers/net/ethernet/huawei/hinic/hinic_rx.h   |   2 +
 drivers/net/ethernet/huawei/hinic/hinic_tx.c   |  22 +++
 drivers/net/ethernet/huawei/hinic/hinic_tx.h   |   2 +
 9 files changed, 390 insertions(+), 1 deletion(-)

Comments

Andrew Lunn July 12, 2017, 3:43 p.m. UTC | #1
On Wed, Jul 12, 2017 at 10:17:26PM +0800, Aviad Krawczyk wrote:

Hi Avaid

> +
> +static void hinic_tx_timeout(struct net_device *netdev)
> +{
> +	struct hinic_dev *nic_dev = netdev_priv(netdev);
> +
> +	netif_err(nic_dev, drv, netdev, "Tx timeout\n");
> +}
> +
> +#ifdef CONFIG_NET_POLL_CONTROLLER
> +static void hinic_netpoll(struct net_device *netdev)
> +{
> +	struct hinic_dev *nic_dev = netdev_priv(netdev);
> +	struct hinic_hwdev *hwdev = nic_dev->hwdev;
> +	int i, num_qps = hinic_hwdev_num_qps(hwdev);
> +
> +	for (i = 0; i < num_qps; i++) {
> +		struct hinic_txq *txq = &nic_dev->txqs[i];
> +		struct hinic_rxq *rxq = &nic_dev->rxqs[i];
> +
> +		napi_schedule(&txq->napi);
> +		napi_schedule(&rxq->napi);
> +	}
> +}
> +#endif

This has nothing to do with ethtool support. Separate patch please.

     Andrew
Aviad Krawczyk July 13, 2017, 1:16 p.m. UTC | #2
Hi Andrew,

We will separate the patches when we will resubmit.

Aviad

On 7/12/2017 6:43 PM, Andrew Lunn wrote:
> On Wed, Jul 12, 2017 at 10:17:26PM +0800, Aviad Krawczyk wrote:
> 
> Hi Avaid
> 
>> +
>> +static void hinic_tx_timeout(struct net_device *netdev)
>> +{
>> +	struct hinic_dev *nic_dev = netdev_priv(netdev);
>> +
>> +	netif_err(nic_dev, drv, netdev, "Tx timeout\n");
>> +}
>> +
>> +#ifdef CONFIG_NET_POLL_CONTROLLER
>> +static void hinic_netpoll(struct net_device *netdev)
>> +{
>> +	struct hinic_dev *nic_dev = netdev_priv(netdev);
>> +	struct hinic_hwdev *hwdev = nic_dev->hwdev;
>> +	int i, num_qps = hinic_hwdev_num_qps(hwdev);
>> +
>> +	for (i = 0; i < num_qps; i++) {
>> +		struct hinic_txq *txq = &nic_dev->txqs[i];
>> +		struct hinic_rxq *rxq = &nic_dev->rxqs[i];
>> +
>> +		napi_schedule(&txq->napi);
>> +		napi_schedule(&rxq->napi);
>> +	}
>> +}
>> +#endif
> 
> This has nothing to do with ethtool support. Separate patch please.
> 
>      Andrew
> 
> .
>
diff mbox

Patch

diff --git a/MAINTAINERS b/MAINTAINERS
index 4f4057c..5c27965 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -6238,6 +6238,13 @@  L:	linux-input@vger.kernel.org
 S:	Maintained
 F:	drivers/input/touchscreen/htcpen.c
 
+HUAWEI ETHERNET DRIVER
+M:      Aviad Krawczyk <aviad.krawczyk@huawei.com>
+L:      netdev@vger.kernel.org
+S:      Supported
+F:      Documentation/networking/hinic.txt
+F:      drivers/net/ethernet/huawei/*
+
 HUGETLB FILESYSTEM
 M:	Nadia Yvette Chambers <nyc@holomorphy.com>
 S:	Maintained
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_dev.h b/drivers/net/ethernet/huawei/hinic/hinic_dev.h
index f59c90d..08918a8 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_dev.h
+++ b/drivers/net/ethernet/huawei/hinic/hinic_dev.h
@@ -57,6 +57,9 @@  struct hinic_dev {
 
 	struct hinic_txq		*txqs;
 	struct hinic_rxq		*rxqs;
+
+	struct hinic_txq_stats		tx_stats;
+	struct hinic_rxq_stats		rx_stats;
 };
 
 #endif
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_main.c b/drivers/net/ethernet/huawei/hinic/hinic_main.c
index fac0249..21f53fc 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_main.c
+++ b/drivers/net/ethernet/huawei/hinic/hinic_main.c
@@ -70,6 +70,179 @@ 
 
 static int change_mac_addr(struct net_device *netdev, const u8 *addr);
 
+static int hinic_get_link_ksettings(struct net_device *netdev,
+				    struct ethtool_link_ksettings
+				    *link_ksettings)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic_port_cap port_cap;
+	enum hinic_autoneg_cap autoneg_cap;
+	enum hinic_autoneg_state autoneg_state;
+	enum hinic_port_link_state link_state;
+	int err;
+
+	ethtool_link_ksettings_zero_link_mode(link_ksettings, advertising);
+	ethtool_link_ksettings_add_link_mode(link_ksettings, supported,
+					     Autoneg);
+
+	link_ksettings->base.speed = SPEED_UNKNOWN;
+	link_ksettings->base.autoneg = AUTONEG_DISABLE;
+	link_ksettings->base.duplex = DUPLEX_UNKNOWN;
+
+	err = hinic_port_get_cap(nic_dev, &port_cap);
+	if (err) {
+		netif_err(nic_dev, drv, netdev, "Failed to get port capabilities\n");
+		return err;
+	}
+
+	err = hinic_port_link_state(nic_dev, &link_state);
+	if (err) {
+		netif_err(nic_dev, drv, netdev, "Failed to get port link state\n");
+		return err;
+	}
+
+	if (link_state != HINIC_LINK_STATE_UP) {
+		netif_info(nic_dev, drv, netdev, "No link\n");
+		return err;
+	}
+
+	switch (port_cap.speed) {
+	case HINIC_SPEED_10MB_LINK:
+		link_ksettings->base.speed = SPEED_10;
+		break;
+
+	case HINIC_SPEED_100MB_LINK:
+		link_ksettings->base.speed = SPEED_100;
+		break;
+
+	case HINIC_SPEED_1000MB_LINK:
+		link_ksettings->base.speed = SPEED_1000;
+		break;
+
+	case HINIC_SPEED_10GB_LINK:
+		link_ksettings->base.speed = SPEED_10000;
+		break;
+
+	case HINIC_SPEED_25GB_LINK:
+		link_ksettings->base.speed = SPEED_25000;
+		break;
+
+	case HINIC_SPEED_40GB_LINK:
+		link_ksettings->base.speed = SPEED_40000;
+		break;
+
+	case HINIC_SPEED_100GB_LINK:
+		link_ksettings->base.speed = SPEED_100000;
+		break;
+
+	default:
+		link_ksettings->base.speed = SPEED_UNKNOWN;
+		break;
+	}
+
+	autoneg_cap = port_cap.autoneg_cap;
+	autoneg_state = port_cap.autoneg_state;
+
+	if (!!(autoneg_cap & HINIC_AUTONEG_SUPPORTED))
+		ethtool_link_ksettings_add_link_mode(link_ksettings,
+						     advertising, Autoneg);
+
+	link_ksettings->base.autoneg = (autoneg_state == HINIC_AUTONEG_ACTIVE) ?
+				       AUTONEG_ENABLE : AUTONEG_DISABLE;
+	link_ksettings->base.duplex = (port_cap.duplex == HINIC_DUPLEX_FULL) ?
+				      DUPLEX_FULL : DUPLEX_HALF;
+
+	return 0;
+}
+
+static void hinic_get_drvinfo(struct net_device *netdev,
+			      struct ethtool_drvinfo *info)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic_hwdev *hwdev = nic_dev->hwdev;
+	struct hinic_hwif *hwif = hwdev->hwif;
+	struct pci_dev *pdev = hwif->pdev;
+
+	strlcpy(info->driver, HINIC_DRV_NAME, sizeof(info->driver));
+	strlcpy(info->version, HINIC_DRV_VERSION, sizeof(info->driver));
+	strlcpy(info->bus_info, pci_name(pdev), sizeof(info->bus_info));
+}
+
+static void hinic_get_ringparam(struct net_device *netdev,
+				struct ethtool_ringparam *ring)
+{
+	ring->rx_max_pending = HINIC_RQ_DEPTH;
+	ring->tx_max_pending = HINIC_SQ_DEPTH;
+	ring->rx_pending = HINIC_RQ_DEPTH;
+	ring->tx_pending = HINIC_SQ_DEPTH;
+}
+
+static void hinic_get_channels(struct net_device *netdev,
+			       struct ethtool_channels *channels)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic_hwdev *hwdev = nic_dev->hwdev;
+
+	channels->max_rx = hwdev->nic_cap.max_qps;
+	channels->max_tx = hwdev->nic_cap.max_qps;
+	channels->max_other = 0;
+	channels->max_combined = 0;
+	channels->rx_count = hinic_hwdev_num_qps(hwdev);
+	channels->tx_count = hinic_hwdev_num_qps(hwdev);
+	channels->other_count = 0;
+	channels->combined_count = 0;
+}
+
+static const struct ethtool_ops hinic_ethtool_ops = {
+	.get_link_ksettings = hinic_get_link_ksettings,
+	.get_drvinfo = hinic_get_drvinfo,
+	.get_link = ethtool_op_get_link,
+	.get_ringparam = hinic_get_ringparam,
+	.get_channels = hinic_get_channels,
+};
+
+static void update_nic_stats(struct hinic_dev *nic_dev)
+{
+	struct hinic_rxq_stats *nic_rx_stats = &nic_dev->rx_stats;
+	struct hinic_txq_stats *nic_tx_stats = &nic_dev->tx_stats;
+	struct hinic_hwdev *hwdev = nic_dev->hwdev;
+	int i, num_qps = hinic_hwdev_num_qps(hwdev);
+	struct hinic_rxq_stats rx_stats;
+	struct hinic_txq_stats tx_stats;
+
+	u64_stats_init(&tx_stats.syncp);
+	u64_stats_init(&rx_stats.syncp);
+
+	for (i = 0; i < num_qps; i++) {
+		struct hinic_rxq *rxq = &nic_dev->rxqs[i];
+
+		hinic_rxq_get_stats(rxq, &rx_stats);
+
+		u64_stats_update_begin(&nic_rx_stats->syncp);
+		nic_rx_stats->bytes += rx_stats.bytes;
+		nic_rx_stats->pkts += rx_stats.pkts;
+		u64_stats_update_end(&nic_rx_stats->syncp);
+
+		hinic_rxq_clean_stats(rxq);
+	}
+
+	for (i = 0; i < num_qps; i++) {
+		struct hinic_txq *txq = &nic_dev->txqs[i];
+
+		hinic_txq_get_stats(txq, &tx_stats);
+
+		u64_stats_update_begin(&nic_tx_stats->syncp);
+		nic_tx_stats->bytes += tx_stats.bytes;
+		nic_tx_stats->pkts += tx_stats.pkts;
+		nic_tx_stats->tx_busy += tx_stats.tx_busy;
+		nic_tx_stats->tx_wake += tx_stats.tx_wake;
+		nic_tx_stats->tx_dropped += tx_stats.tx_dropped;
+		u64_stats_update_end(&nic_tx_stats->syncp);
+
+		hinic_txq_clean_stats(txq);
+	}
+}
+
 /**
  * create_txqs - Create the Logical Tx Queues of specific NIC device
  * @nic_dev: the specific NIC device
@@ -300,6 +473,8 @@  static int hinic_close(struct net_device *netdev)
 	netif_carrier_off(netdev);
 	netif_tx_disable(netdev);
 
+	update_nic_stats(nic_dev);
+
 	up(&nic_dev->mgmt_lock);
 
 	err = hinic_port_set_func_state(nic_dev, HINIC_FUNC_PORT_DISABLE);
@@ -580,6 +755,77 @@  static void hinic_set_rx_mode(struct net_device *netdev)
 	queue_work(nic_dev->workq, &rx_mode_work->work);
 }
 
+static u16 hinic_select_queue(struct net_device *netdev, struct sk_buff *skb,
+			      void *accel_priv,
+			      select_queue_fallback_t fallback)
+{
+	u16 qid;
+
+	if (skb_rx_queue_recorded(skb))
+		qid = skb_get_rx_queue(skb);
+	else
+		qid = fallback(netdev, skb);
+
+	return qid;
+}
+
+static void hinic_get_stats64(struct net_device *netdev,
+			      struct rtnl_link_stats64 *stats)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic_rxq_stats *nic_rx_stats = &nic_dev->rx_stats;
+	struct hinic_txq_stats *nic_tx_stats = &nic_dev->tx_stats;
+
+	stats->rx_bytes = nic_rx_stats->bytes;
+	stats->rx_packets = nic_rx_stats->pkts;
+
+	stats->tx_bytes = nic_tx_stats->bytes;
+	stats->tx_packets = nic_tx_stats->pkts;
+	stats->tx_errors = nic_tx_stats->tx_dropped;
+
+	down(&nic_dev->mgmt_lock);
+
+	if (!(nic_dev->flags & HINIC_INTF_UP)) {
+		up(&nic_dev->mgmt_lock);
+		return;
+	}
+
+	update_nic_stats(nic_dev);
+
+	up(&nic_dev->mgmt_lock);
+
+	stats->rx_bytes = nic_rx_stats->bytes;
+	stats->rx_packets = nic_rx_stats->pkts;
+
+	stats->tx_bytes = nic_tx_stats->bytes;
+	stats->tx_packets = nic_tx_stats->pkts;
+	stats->tx_errors = nic_tx_stats->tx_dropped;
+}
+
+static void hinic_tx_timeout(struct net_device *netdev)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+
+	netif_err(nic_dev, drv, netdev, "Tx timeout\n");
+}
+
+#ifdef CONFIG_NET_POLL_CONTROLLER
+static void hinic_netpoll(struct net_device *netdev)
+{
+	struct hinic_dev *nic_dev = netdev_priv(netdev);
+	struct hinic_hwdev *hwdev = nic_dev->hwdev;
+	int i, num_qps = hinic_hwdev_num_qps(hwdev);
+
+	for (i = 0; i < num_qps; i++) {
+		struct hinic_txq *txq = &nic_dev->txqs[i];
+		struct hinic_rxq *rxq = &nic_dev->rxqs[i];
+
+		napi_schedule(&txq->napi);
+		napi_schedule(&rxq->napi);
+	}
+}
+#endif
+
 static const struct net_device_ops hinic_netdev_ops = {
 	.ndo_open = hinic_open,
 	.ndo_stop = hinic_close,
@@ -590,7 +836,12 @@  static void hinic_set_rx_mode(struct net_device *netdev)
 	.ndo_vlan_rx_kill_vid = hinic_vlan_rx_kill_vid,
 	.ndo_set_rx_mode = hinic_set_rx_mode,
 	.ndo_start_xmit = hinic_xmit_frame,
-	/* more operations should be filled */
+	.ndo_select_queue = hinic_select_queue,
+	.ndo_get_stats64 = hinic_get_stats64,
+	.ndo_tx_timeout = hinic_tx_timeout,
+#ifdef CONFIG_NET_POLL_CONTROLLER
+	.ndo_poll_controller = hinic_netpoll,
+#endif
 };
 
 static void netdev_features_init(struct net_device *netdev)
@@ -665,6 +916,8 @@  static int nic_dev_init(struct pci_dev *pdev)
 	struct hinic_dev *nic_dev;
 	struct net_device *netdev;
 	struct hinic_hwdev *hwdev;
+	struct hinic_txq_stats *tx_stats;
+	struct hinic_rxq_stats *rx_stats;
 	struct hinic_rx_mode_work *rx_mode_work;
 	int err, num_qps;
 
@@ -689,6 +942,7 @@  static int nic_dev_init(struct pci_dev *pdev)
 	}
 
 	netdev->netdev_ops = &hinic_netdev_ops;
+	netdev->ethtool_ops = &hinic_ethtool_ops;
 
 	nic_dev = (struct hinic_dev *)netdev_priv(netdev);
 	nic_dev->hwdev = hwdev;
@@ -702,6 +956,12 @@  static int nic_dev_init(struct pci_dev *pdev)
 
 	sema_init(&nic_dev->mgmt_lock, 1);
 
+	tx_stats = &nic_dev->tx_stats;
+	rx_stats = &nic_dev->rx_stats;
+
+	u64_stats_init(&tx_stats->syncp);
+	u64_stats_init(&rx_stats->syncp);
+
 	nic_dev->vlan_bitmap = kzalloc(VLAN_BITMAP_SIZE(nic_dev), GFP_KERNEL);
 	if (!nic_dev->vlan_bitmap) {
 		err = -ENOMEM;
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_port.c b/drivers/net/ethernet/huawei/hinic/hinic_port.c
index 2c97ece..8da5762 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_port.c
+++ b/drivers/net/ethernet/huawei/hinic/hinic_port.c
@@ -372,3 +372,32 @@  int hinic_port_set_func_state(struct hinic_dev *nic_dev,
 
 	return 0;
 }
+
+/**
+ * hinic_port_get_cap - get port capabilities
+ * @nic_dev: nic device
+ * @port_cap: returned port capabilities
+ *
+ * Return 0 - Success, negative - Failure
+ **/
+int hinic_port_get_cap(struct hinic_dev *nic_dev,
+		       struct hinic_port_cap *port_cap)
+{
+	struct hinic_hwdev *hwdev = nic_dev->hwdev;
+	struct hinic_hwif *hwif = hwdev->hwif;
+	struct pci_dev *pdev = hwif->pdev;
+	u16 out_size;
+	int err;
+
+	port_cap->func_idx = HINIC_HWIF_GLOB_IDX(hwif);
+
+	err = hinic_port_msg_cmd(hwdev, HINIC_PORT_CMD_GET_CAP,
+				 port_cap, sizeof(*port_cap),
+				 port_cap, &out_size);
+	if (err || (out_size != sizeof(*port_cap)) || port_cap->status) {
+		dev_err(&pdev->dev, "Failed to get port capabilities\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_port.h b/drivers/net/ethernet/huawei/hinic/hinic_port.h
index 754bac5..15095ab 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_port.h
+++ b/drivers/net/ethernet/huawei/hinic/hinic_port.h
@@ -45,6 +45,33 @@  enum hinic_func_port_state {
 	HINIC_FUNC_PORT_ENABLE = 2,
 };
 
+enum hinic_autoneg_cap {
+	HINIC_AUTONEG_UNSUPPORTED,
+	HINIC_AUTONEG_SUPPORTED,
+};
+
+enum hinic_autoneg_state {
+	HINIC_AUTONEG_DISABLED,
+	HINIC_AUTONEG_ACTIVE,
+};
+
+enum hinic_duplex {
+	HINIC_DUPLEX_HALF,
+	HINIC_DUPLEX_FULL,
+};
+
+enum hinic_speed {
+	HINIC_SPEED_10MB_LINK = 0,
+	HINIC_SPEED_100MB_LINK,
+	HINIC_SPEED_1000MB_LINK,
+	HINIC_SPEED_10GB_LINK,
+	HINIC_SPEED_25GB_LINK,
+	HINIC_SPEED_40GB_LINK,
+	HINIC_SPEED_100GB_LINK,
+
+	HINIC_SPEED_UNKNOWN = 0xFF,
+};
+
 struct hinic_port_mac_cmd {
 	u8		status;
 	u8		version;
@@ -125,6 +152,21 @@  struct hinic_port_func_state_cmd {
 	u8	rsvd2[3];
 };
 
+struct hinic_port_cap {
+	u8	status;
+	u8	version;
+	u8	rsvd0[6];
+
+	u16	func_idx;
+	u16	rsvd1;
+	u8	port_type;
+	u8	autoneg_cap;
+	u8	autoneg_state;
+	u8	duplex;
+	u8	speed;
+	u8	resv2[3];
+};
+
 int hinic_port_add_mac(struct hinic_dev *nic_dev, const u8 *addr,
 		       u16 vlan_id);
 
@@ -150,4 +192,7 @@  int hinic_port_set_state(struct hinic_dev *nic_dev,
 int hinic_port_set_func_state(struct hinic_dev *nic_dev,
 			      enum hinic_func_port_state state);
 
+int hinic_port_get_cap(struct hinic_dev *nic_dev,
+		       struct hinic_port_cap *port_cap);
+
 #endif
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_rx.c b/drivers/net/ethernet/huawei/hinic/hinic_rx.c
index 85eb989..7396d57 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_rx.c
+++ b/drivers/net/ethernet/huawei/hinic/hinic_rx.c
@@ -59,6 +59,25 @@  void hinic_rxq_clean_stats(struct hinic_rxq *rxq)
 }
 
 /**
+ * hinic_rxq_get_stats - get statistics of Rx Queue
+ * @rxq: Logical Rx Queue
+ * @stats: return updated stats here
+ **/
+void hinic_rxq_get_stats(struct hinic_rxq *rxq, struct hinic_rxq_stats *stats)
+{
+	struct hinic_rxq_stats *rxq_stats = &rxq->rxq_stats;
+	unsigned int start;
+
+	u64_stats_update_begin(&stats->syncp);
+	do {
+		start = u64_stats_fetch_begin(&rxq_stats->syncp);
+		stats->pkts = rxq_stats->pkts;
+		stats->bytes = rxq_stats->bytes;
+	} while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
+	u64_stats_update_end(&stats->syncp);
+}
+
+/**
  * rxq_stats_init - Initialize the statistics of specific queue
  * @rxq: Logical Rx Queue
  **/
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_rx.h b/drivers/net/ethernet/huawei/hinic/hinic_rx.h
index 17c4842..6215528 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_rx.h
+++ b/drivers/net/ethernet/huawei/hinic/hinic_rx.h
@@ -45,6 +45,8 @@  struct hinic_rxq {
 
 void hinic_rxq_clean_stats(struct hinic_rxq *rxq);
 
+void hinic_rxq_get_stats(struct hinic_rxq *rxq, struct hinic_rxq_stats *stats);
+
 int hinic_init_rxq(struct hinic_rxq *rxq, struct hinic_rq *rq,
 		   struct net_device *netdev);
 
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_tx.c b/drivers/net/ethernet/huawei/hinic/hinic_tx.c
index 62051c4..7642150 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_tx.c
+++ b/drivers/net/ethernet/huawei/hinic/hinic_tx.c
@@ -68,6 +68,28 @@  void hinic_txq_clean_stats(struct hinic_txq *txq)
 }
 
 /**
+ * hinic_txq_get_stats - get statistics of Tx Queue
+ * @txq: Logical Tx Queue
+ * @stats: return updated stats here
+ **/
+void hinic_txq_get_stats(struct hinic_txq *txq, struct hinic_txq_stats *stats)
+{
+	struct hinic_txq_stats *txq_stats = &txq->txq_stats;
+	unsigned int start;
+
+	u64_stats_update_begin(&stats->syncp);
+	do {
+		start = u64_stats_fetch_begin(&txq_stats->syncp);
+		stats->pkts = txq_stats->pkts;
+		stats->bytes = txq_stats->bytes;
+		stats->tx_busy = txq_stats->tx_busy;
+		stats->tx_wake = txq_stats->tx_wake;
+		stats->tx_dropped = txq_stats->tx_dropped;
+	} while (u64_stats_fetch_retry(&txq_stats->syncp, start));
+	u64_stats_update_end(&stats->syncp);
+}
+
+/**
  * txq_stats_init - Initialize the statistics of specific queue
  * @txq: Logical Tx Queue
  **/
diff --git a/drivers/net/ethernet/huawei/hinic/hinic_tx.h b/drivers/net/ethernet/huawei/hinic/hinic_tx.h
index 9b5a665..b5d580c 100644
--- a/drivers/net/ethernet/huawei/hinic/hinic_tx.h
+++ b/drivers/net/ethernet/huawei/hinic/hinic_tx.h
@@ -50,6 +50,8 @@  struct hinic_txq {
 
 void hinic_txq_clean_stats(struct hinic_txq *txq);
 
+void hinic_txq_get_stats(struct hinic_txq *txq, struct hinic_txq_stats *stats);
+
 netdev_tx_t hinic_xmit_frame(struct sk_buff *skb, struct net_device *netdev);
 
 int hinic_init_txq(struct hinic_txq *txq, struct hinic_sq *sq,