// SPDX-License-Identifier: GPL-2.0
/* Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": [NIC]" fmt

#include <linux/netdevice.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/sctp.h>
#include <linux/ipv6.h>
#include <net/ipv6.h>
#include <net/checksum.h>
#include <net/ip6_checksum.h>
#include <linux/dma-mapping.h>
#include <linux/types.h>
#include <linux/u64_stats_sync.h>

#include "ossl_knl.h"
#include "hinic_hw.h"
#include "hinic_hw_mgmt.h"
#include "hinic_nic_io.h"
#include "hinic_nic_dev.h"
#include "hinic_qp.h"
#include "hinic_tx.h"
#include "hinic_dbg.h"

#define MIN_SKB_LEN		32
#define MAX_PAYLOAD_OFFSET	221

#define NIC_QID(q_id, nic_dev)	((q_id) & ((nic_dev)->num_qps - 1))

#define TXQ_STATS_INC(txq, field)			\
{							\
	u64_stats_update_begin(&(txq)->txq_stats.syncp);	\
	(txq)->txq_stats.field++;				\
	u64_stats_update_end(&(txq)->txq_stats.syncp);	\
}

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->bytes = txq_stats->bytes;
		stats->packets = txq_stats->packets;
		stats->busy = txq_stats->busy;
		stats->wake = txq_stats->wake;
		stats->dropped = txq_stats->dropped;
		stats->big_frags_pkts = txq_stats->big_frags_pkts;
		stats->big_udp_pkts = txq_stats->big_udp_pkts;
	} while (u64_stats_fetch_retry(&txq_stats->syncp, start));
	u64_stats_update_end(&stats->syncp);
}

void hinic_txq_clean_stats(struct hinic_txq_stats *txq_stats)
{
	u64_stats_update_begin(&txq_stats->syncp);
	txq_stats->bytes = 0;
	txq_stats->packets = 0;
	txq_stats->busy = 0;
	txq_stats->wake = 0;
	txq_stats->dropped = 0;
	txq_stats->big_frags_pkts = 0;
	txq_stats->big_udp_pkts = 0;

	txq_stats->ufo_pkt_unsupport = 0;
	txq_stats->ufo_linearize_err = 0;
	txq_stats->ufo_alloc_skb_err = 0;
	txq_stats->skb_pad_err = 0;
	txq_stats->frag_len_overflow = 0;
	txq_stats->offload_cow_skb_err = 0;
	txq_stats->alloc_cpy_frag_err = 0;
	txq_stats->map_cpy_frag_err = 0;
	txq_stats->map_frag_err = 0;
	txq_stats->frag_size_err = 0;
	txq_stats->unknown_tunnel_pkt = 0;
	u64_stats_update_end(&txq_stats->syncp);
}

static void txq_stats_init(struct hinic_txq *txq)
{
	struct hinic_txq_stats *txq_stats = &txq->txq_stats;

	u64_stats_init(&txq_stats->syncp);
	hinic_txq_clean_stats(txq_stats);
}

inline void hinic_set_buf_desc(struct hinic_sq_bufdesc *buf_descs,
			      dma_addr_t addr, u32 len)
{
	buf_descs->hi_addr = cpu_to_be32(upper_32_bits(addr));
	buf_descs->lo_addr = cpu_to_be32(lower_32_bits(addr));
	buf_descs->len  = cpu_to_be32(len);
}

static int tx_map_skb(struct hinic_nic_dev *nic_dev, struct sk_buff *skb,
		      struct hinic_txq *txq, struct hinic_tx_info *tx_info,
		      struct hinic_sq_bufdesc *buf_descs, u16 skb_nr_frags)
{
	struct pci_dev *pdev = nic_dev->pdev;
	struct hinic_dma_len *dma_len = tx_info->dma_len;
	skb_frag_t *frag = NULL;
	u16 base_nr_frags;
	int j, i = 0;
	int node, err = 0;
	u32 nsize, cpy_nsize = 0;
	u8 *vaddr, *cpy_buff = NULL;

	if (unlikely(skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE)) {
		for (i = HINIC_MAX_SKB_NR_FRAGE; i <= skb_nr_frags; i++)
			cpy_nsize +=
				skb_frag_size(&skb_shinfo(skb)->frags[i - 1]);
		if (!cpy_nsize) {
			TXQ_STATS_INC(txq, alloc_cpy_frag_err);
			return -EINVAL;
		}

		node = dev_to_node(&nic_dev->pdev->dev);
		if (node == NUMA_NO_NODE)
			cpy_buff = kzalloc(cpy_nsize,
					   GFP_ATOMIC | __GFP_NOWARN);
		else
			cpy_buff = kzalloc_node(cpy_nsize,
						GFP_ATOMIC | __GFP_NOWARN,
						node);

		if (!cpy_buff) {
			TXQ_STATS_INC(txq, alloc_cpy_frag_err);
			return -ENOMEM;
		}

		tx_info->cpy_buff = cpy_buff;

		for (i = HINIC_MAX_SKB_NR_FRAGE; i <= skb_nr_frags; i++) {
			frag = &skb_shinfo(skb)->frags[i - 1];
			nsize = skb_frag_size(frag);

			vaddr = kmap_local_page(skb_frag_page(frag));
			memcpy(cpy_buff, vaddr + frag->bv_offset, nsize);
			kunmap_local(vaddr);
			cpy_buff += nsize;
		}
	}

	dma_len[0].dma = dma_map_single(&pdev->dev, skb->data,
					skb_headlen(skb), DMA_TO_DEVICE);
	if (dma_mapping_error(&pdev->dev, dma_len[0].dma)) {
		TXQ_STATS_INC(txq, map_frag_err);
		err = -EFAULT;
		goto map_single_err;
	}
	dma_len[0].len = skb_headlen(skb);
	hinic_set_buf_desc(&buf_descs[0], dma_len[0].dma,
			   dma_len[0].len);

	if (skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE)
		base_nr_frags = HINIC_MAX_SKB_NR_FRAGE - 1;
	else
		base_nr_frags = skb_nr_frags;

	for (i = 0; i < base_nr_frags; ) {
		frag = &(skb_shinfo(skb)->frags[i]);
		nsize = skb_frag_size(frag);
		i++;
		dma_len[i].dma = skb_frag_dma_map(&pdev->dev, frag, 0,
						  nsize, DMA_TO_DEVICE);
		if (dma_mapping_error(&pdev->dev, dma_len[i].dma)) {
			TXQ_STATS_INC(txq, map_frag_err);
			i--;
			err = -EFAULT;
			goto frag_map_err;
		}
		dma_len[i].len = nsize;

		hinic_set_buf_desc(&buf_descs[i], dma_len[i].dma,
				   dma_len[i].len);
	}

	if (skb_nr_frags > HINIC_MAX_SKB_NR_FRAGE) {
		dma_len[HINIC_MAX_SKB_NR_FRAGE].dma =
				dma_map_single(&pdev->dev, tx_info->cpy_buff,
					       cpy_nsize, DMA_TO_DEVICE);
		if (dma_mapping_error(&pdev->dev,
				      dma_len[HINIC_MAX_SKB_NR_FRAGE].dma)) {
			TXQ_STATS_INC(txq, map_cpy_frag_err);
			err = -EFAULT;
			goto fusion_map_err;
		}

		dma_len[HINIC_MAX_SKB_NR_FRAGE].len = cpy_nsize;
		hinic_set_buf_desc(&buf_descs[HINIC_MAX_SKB_NR_FRAGE],
				   dma_len[HINIC_MAX_SKB_NR_FRAGE].dma,
				   dma_len[HINIC_MAX_SKB_NR_FRAGE].len);
	}

	return 0;

fusion_map_err:
frag_map_err:
	for (j = 0; j < i;) {
		j++;
		dma_unmap_page(&pdev->dev, dma_len[j].dma,
			       dma_len[j].len, DMA_TO_DEVICE);
	}
	dma_unmap_single(&pdev->dev, dma_len[0].dma, dma_len[0].len,
			 DMA_TO_DEVICE);

map_single_err:
	kfree(tx_info->cpy_buff);
	tx_info->cpy_buff = NULL;

	return err;
}

static inline void tx_unmap_skb(struct hinic_nic_dev *nic_dev,
				struct sk_buff *skb,
				struct hinic_dma_len *dma_len,
				u16 valid_nr_frags)
{
	struct pci_dev *pdev = nic_dev->pdev;
	int i;
	u16 nr_frags = valid_nr_frags;

	if (nr_frags > HINIC_MAX_SKB_NR_FRAGE)
		nr_frags = HINIC_MAX_SKB_NR_FRAGE;

	for (i = 0; i < nr_frags; ) {
		i++;
		dma_unmap_page(&pdev->dev,
			       dma_len[i].dma,
			       dma_len[i].len, DMA_TO_DEVICE);
	}

	dma_unmap_single(&pdev->dev, dma_len[0].dma,
			 dma_len[0].len, DMA_TO_DEVICE);
}

union hinic_ip {
	struct iphdr *v4;
	struct ipv6hdr *v6;
	unsigned char *hdr;
};

union hinic_l4 {
	struct tcphdr *tcp;
	struct udphdr *udp;
	unsigned char *hdr;
};

#define TRANSPORT_OFFSET(l4_hdr, skb)	((u32)((l4_hdr) - (skb)->data))

static void get_inner_l3_l4_type(struct sk_buff *skb, union hinic_ip *ip,
				 union hinic_l4 *l4,
				 enum tx_offload_type offload_type,
				 enum sq_l3_type *l3_type, u8 *l4_proto)
{
	unsigned char *exthdr;

	if (ip->v4->version == 4) {
		*l3_type = (offload_type == TX_OFFLOAD_CSUM) ?
		  IPV4_PKT_NO_CHKSUM_OFFLOAD : IPV4_PKT_WITH_CHKSUM_OFFLOAD;
		*l4_proto = ip->v4->protocol;
	} else if (ip->v4->version == 6) {
		*l3_type = IPV6_PKT;
		exthdr = ip->hdr + sizeof(*ip->v6);
		*l4_proto = ip->v6->nexthdr;
		if (exthdr != l4->hdr) {
			__be16 frag_off = 0;

			ipv6_skip_exthdr(skb, (int)(exthdr - skb->data),
					 l4_proto, &frag_off);
		}
	} else {
		*l3_type = UNKNOWN_L3TYPE;
		*l4_proto = 0;
	}
}

static void get_inner_l4_info(struct sk_buff *skb, union hinic_l4 *l4,
			      enum tx_offload_type offload_type, u8 l4_proto,
			      enum sq_l4offload_type *l4_offload,
			      u32 *l4_len, u32 *offset)
{
	*offset = 0;
	*l4_len = 0;
	*l4_offload = OFFLOAD_DISABLE;

	switch (l4_proto) {
	case IPPROTO_TCP:
		*l4_offload = TCP_OFFLOAD_ENABLE;
		*l4_len = l4->tcp->doff * 4; /* doff in unit of 4B */
		/* To keep same with TSO, payload offset begins from paylaod */
		*offset = *l4_len + TRANSPORT_OFFSET(l4->hdr, skb);
		break;

	case IPPROTO_UDP:
		*l4_offload = UDP_OFFLOAD_ENABLE;
		*l4_len = sizeof(struct udphdr);
		*offset = TRANSPORT_OFFSET(l4->hdr, skb);
		break;

	case IPPROTO_SCTP:
		/* only csum offload support sctp */
		if (offload_type != TX_OFFLOAD_CSUM)
			break;

		*l4_offload = SCTP_OFFLOAD_ENABLE;
		*l4_len = sizeof(struct sctphdr);
		/* To keep same with UFO, payload offset
		 * begins from L4 header
		 */
		*offset = TRANSPORT_OFFSET(l4->hdr, skb);
		break;

	default:
		break;
	}
}

static int hinic_tx_csum(struct hinic_txq *txq, struct hinic_sq_task *task,
			 u32 *queue_info, struct sk_buff *skb)
{
	union hinic_ip ip;
	union hinic_l4 l4;
	enum sq_l3_type l3_type;
	enum sq_l4offload_type l4_offload;
	u32 network_hdr_len;
	u32 offset, l4_len;
	u8 l4_proto;

	if (skb->ip_summed != CHECKSUM_PARTIAL)
		return 0;

	if (skb->encapsulation) {
		u32 l4_tunnel_len;
		u32 tunnel_type = TUNNEL_UDP_NO_CSUM;

		ip.hdr = skb_network_header(skb);

		if (ip.v4->version == 4) {
			l3_type = IPV4_PKT_NO_CHKSUM_OFFLOAD;
			l4_proto = ip.v4->protocol;
		} else if (ip.v4->version == 6) {
			unsigned char *exthdr;
			__be16 frag_off;

			l3_type = IPV6_PKT;
			exthdr = ip.hdr + sizeof(*ip.v6);
			l4_proto = ip.v6->nexthdr;
			l4.hdr = skb_transport_header(skb);
			if (l4.hdr != exthdr)
				ipv6_skip_exthdr(skb, exthdr - skb->data,
						 &l4_proto, &frag_off);
		} else {
			l3_type = UNKNOWN_L3TYPE;
			l4_proto = IPPROTO_RAW;
		}

		hinic_task_set_outter_l3(task, l3_type,
					 skb_network_header_len(skb));

		switch (l4_proto) {
		case IPPROTO_UDP:
			l4_tunnel_len = skb_inner_network_offset(skb) -
					skb_transport_offset(skb);
			ip.hdr = skb_inner_network_header(skb);
			l4.hdr = skb_inner_transport_header(skb);
			network_hdr_len = skb_inner_network_header_len(skb);
			break;
		case IPPROTO_IPIP:
		case IPPROTO_IPV6:
			tunnel_type = NOT_TUNNEL;
			l4_tunnel_len = 0;

			ip.hdr = skb_inner_network_header(skb);
			l4.hdr = skb_transport_header(skb);
			network_hdr_len = skb_network_header_len(skb);
			break;
		default:
			TXQ_STATS_INC(txq, unknown_tunnel_pkt);
			/* Unsupport tunnel packet, disable csum offload */
			skb_checksum_help(skb);
			return 0;
		}

		hinic_task_set_tunnel_l4(task, tunnel_type, l4_tunnel_len);
	} else {
		ip.hdr = skb_network_header(skb);
		l4.hdr = skb_transport_header(skb);
		network_hdr_len = skb_network_header_len(skb);
	}
	get_inner_l3_l4_type(skb, &ip, &l4, TX_OFFLOAD_CSUM,
			     &l3_type, &l4_proto);

	get_inner_l4_info(skb, &l4, TX_OFFLOAD_CSUM, l4_proto,
			  &l4_offload, &l4_len, &offset);

	hinic_task_set_inner_l3(task, l3_type, network_hdr_len);

	hinic_set_cs_inner_l4(task, queue_info, l4_offload, l4_len, offset);

	return 1;
}

static __sum16 csum_magic(union hinic_ip *ip, unsigned short proto)
{
	return (ip->v4->version == 4) ?
		csum_tcpudp_magic(ip->v4->saddr, ip->v4->daddr, 0, proto, 0) :
		csum_ipv6_magic(&ip->v6->saddr, &ip->v6->daddr, 0, proto, 0);
}

static int hinic_tso(struct hinic_sq_task *task, u32 *queue_info,
		     struct sk_buff *skb)
{
	union hinic_ip ip;
	union hinic_l4 l4;
	enum sq_l3_type l3_type;
	enum sq_l4offload_type l4_offload;
	u32 network_hdr_len;
	u32 offset, l4_len;
	u32 ip_identify = 0;
	u8 l4_proto;
	int err;

	if (!skb_is_gso(skb))
		return 0;

	err = skb_cow_head(skb, 0);
	if (err < 0)
		return err;

	if (skb->encapsulation) {
		u32 l4_tunnel_len;
		u32 tunnel_type = 0;
		u32 gso_type = skb_shinfo(skb)->gso_type;

		ip.hdr = skb_network_header(skb);
		l4.hdr = skb_transport_header(skb);
		network_hdr_len = skb_inner_network_header_len(skb);

		if (ip.v4->version == 4)
			l3_type = IPV4_PKT_WITH_CHKSUM_OFFLOAD;
		else if (ip.v4->version == 6)
			l3_type = IPV6_PKT;
		else
			l3_type = 0;

		hinic_task_set_outter_l3(task, l3_type,
					 skb_network_header_len(skb));

		if (gso_type & SKB_GSO_UDP_TUNNEL_CSUM) {
			l4.udp->check = ~csum_magic(&ip, IPPROTO_UDP);
			tunnel_type = TUNNEL_UDP_CSUM;
		} else if (gso_type & SKB_GSO_UDP_TUNNEL) {
			tunnel_type = TUNNEL_UDP_NO_CSUM;
		}

		l4_tunnel_len = skb_inner_network_offset(skb) -
				skb_transport_offset(skb);
		hinic_task_set_tunnel_l4(task, tunnel_type, l4_tunnel_len);

		ip.hdr = skb_inner_network_header(skb);
		l4.hdr = skb_inner_transport_header(skb);
	} else {
		ip.hdr = skb_network_header(skb);
		l4.hdr = skb_transport_header(skb);
		network_hdr_len = skb_network_header_len(skb);
	}

	get_inner_l3_l4_type(skb, &ip, &l4, TX_OFFLOAD_TSO,
			     &l3_type, &l4_proto);

	if (l4_proto == IPPROTO_TCP)
		l4.tcp->check = ~csum_magic(&ip, IPPROTO_TCP);

	get_inner_l4_info(skb, &l4, TX_OFFLOAD_TSO, l4_proto,
			  &l4_offload, &l4_len, &offset);

	hinic_task_set_inner_l3(task, l3_type, network_hdr_len);

	hinic_set_tso_inner_l4(task, queue_info, l4_offload, l4_len,
			       offset, ip_identify, skb_shinfo(skb)->gso_size);

	return 1;
}

static enum tx_offload_type hinic_tx_offload(struct hinic_txq *txq,
					     struct sk_buff *skb,
					     struct hinic_sq_task *task,
					     u32 *queue_info, u8 avd_flag)
{
	enum tx_offload_type offload = 0;
	int tso_cs_en;
	u16 vlan_tag;

	task->pkt_info0 = 0;
	task->pkt_info1 = 0;
	task->pkt_info2 = 0;

	tso_cs_en = hinic_tso(task, queue_info, skb);
	if (tso_cs_en < 0) {
		offload = TX_OFFLOAD_INVALID;
		return offload;
	} else if (tso_cs_en) {
		offload |= TX_OFFLOAD_TSO;
	} else {
		tso_cs_en = hinic_tx_csum(txq, task, queue_info, skb);
		if (tso_cs_en)
			offload |= TX_OFFLOAD_CSUM;
	}

	if (unlikely(skb_vlan_tag_present(skb))) {
		vlan_tag = skb_vlan_tag_get(skb);
		hinic_set_vlan_tx_offload(task, queue_info, vlan_tag,
					  vlan_tag >> VLAN_PRIO_SHIFT);
		offload |= TX_OFFLOAD_VLAN;
	}

	if (unlikely(SQ_CTRL_QUEUE_INFO_GET(*queue_info, PLDOFF) >
		     MAX_PAYLOAD_OFFSET)) {
		offload = TX_OFFLOAD_INVALID;
		return offload;
	}

	if (avd_flag == HINIC_TX_UFO_AVD)
		task->pkt_info0 |= SQ_TASK_INFO0_SET(1, UFO_AVD);

	if (offload) {
		hinic_task_set_tx_offload_valid(task, skb_network_offset(skb));
		task->pkt_info0 = be32_to_cpu(task->pkt_info0);
		task->pkt_info1 = be32_to_cpu(task->pkt_info1);
		task->pkt_info2 = be32_to_cpu(task->pkt_info2);
	}

	return offload;
}

static inline void __get_pkt_stats(struct hinic_tx_info *tx_info,
				   struct sk_buff *skb)
{
	u32 ihs, hdr_len;

	if (skb_is_gso(skb)) {
#if (defined(HAVE_SKB_INNER_TRANSPORT_HEADER) && \
	defined(HAVE_SK_BUFF_ENCAPSULATION))
		if (skb->encapsulation) {
#ifdef HAVE_SKB_INNER_TRANSPORT_OFFSET
			ihs = skb_inner_transport_offset(skb) +
			      inner_tcp_hdrlen(skb);
#else
			ihs = (skb_inner_transport_header(skb) - skb->data) +
			      inner_tcp_hdrlen(skb);
#endif
		} else {
#endif
			ihs = skb_transport_offset(skb) + tcp_hdrlen(skb);
#if (defined(HAVE_SKB_INNER_TRANSPORT_HEADER) && \
	defined(HAVE_SK_BUFF_ENCAPSULATION))
		}
#endif
		hdr_len = (skb_shinfo(skb)->gso_segs - 1) * ihs;
		tx_info->num_bytes = skb->len + (u64)hdr_len;

	} else {
		tx_info->num_bytes = skb->len > ETH_ZLEN ? skb->len : ETH_ZLEN;
	}

	tx_info->num_pkts = 1;
}

inline u8 hinic_get_vlan_pri(struct sk_buff *skb)
{
	u16 vlan_tci = 0;
	int err;

	err = vlan_get_tag(skb, &vlan_tci);
	if (err)
		return 0;

	return (vlan_tci & VLAN_PRIO_MASK) >> VLAN_PRIO_SHIFT;
}

static void *__try_to_get_wqe(struct net_device *netdev, u16 q_id,
			      int wqebb_cnt, u16 *pi, u8 *owner)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	void *wqe = NULL;

	netif_stop_subqueue(netdev, q_id);
	/* We need to check again in a case another CPU has just
	 * made room available.
	 */
	if (unlikely(hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >=
					      wqebb_cnt)) {
		netif_start_subqueue(netdev, q_id);
		/* there have enough wqebbs after queue is wake up */
		wqe = hinic_get_sq_wqe(nic_dev->hwdev, q_id,
				       wqebb_cnt, pi, owner);
	}

	return wqe;
}

#define HINIC_FRAG_STATUS_OK		0
#define HINIC_FRAG_STATUS_IGNORE	1

static netdev_tx_t hinic_send_one_skb(struct sk_buff *skb,
				      struct net_device *netdev,
				      struct hinic_txq *txq,
				      u8 *flag, u8 avd_flag)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_tx_info *tx_info;
	struct hinic_sq_wqe *wqe = NULL;
	enum tx_offload_type offload = 0;
	u16 q_id = txq->q_id;
	u32 queue_info = 0;
	u8 owner = 0;
	u16 pi = 0;
	int err, wqebb_cnt;
	u16 num_sge = 0;
	u16 original_nr_frags;
	u16 new_nr_frags;
	u16 i;
	int frag_err = HINIC_FRAG_STATUS_OK;

	/* skb->dev will not initialized when calling netdev_alloc_skb_ip_align
	 * and parameter of length is largger then PAGE_SIZE(under redhat7.3),
	 * but skb->dev will be used in vlan_get_tag or somewhere
	 */
	if (unlikely(!skb->dev))
		skb->dev = netdev;

	if (unlikely(skb->len < MIN_SKB_LEN)) {
		if (skb_pad(skb, (int)(MIN_SKB_LEN - skb->len))) {
			TXQ_STATS_INC(txq, skb_pad_err);
			goto tx_skb_pad_err;
		}

		skb->len = MIN_SKB_LEN;
	}

	original_nr_frags = skb_shinfo(skb)->nr_frags;
	new_nr_frags = original_nr_frags;

	/* If size of lastest frags are all zero, should ignore this frags.
	 * If size of some frag in the middle is zero, should drop this skb.
	 */
	for (i = 0; i < original_nr_frags; i++) {
		if ((skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
		    frag_err == HINIC_FRAG_STATUS_OK)
			continue;

		if ((!skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
		    frag_err == HINIC_FRAG_STATUS_OK) {
			frag_err = HINIC_FRAG_STATUS_IGNORE;
			new_nr_frags = i + 1;
			continue;
		}

		if ((!skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
		    frag_err == HINIC_FRAG_STATUS_IGNORE)
			continue;

		if ((skb_frag_size(&skb_shinfo(skb)->frags[i])) &&
		    frag_err == HINIC_FRAG_STATUS_IGNORE) {
			TXQ_STATS_INC(txq, frag_size_err);
			goto tx_drop_pkts;
		}
	}

	num_sge = new_nr_frags + 1;

	/* if skb->len is more than 65536B but num_sge is 1,
	 * driver will drop it
	 */
	if (unlikely(skb->len > HINIC_GSO_MAX_SIZE && num_sge == 1)) {
		TXQ_STATS_INC(txq, frag_len_overflow);
		goto tx_drop_pkts;
	}

	/* if sge number more than 17, driver will set 17 sges */
	if (unlikely(num_sge > HINIC_MAX_SQ_SGE)) {
		TXQ_STATS_INC(txq, big_frags_pkts);
		num_sge = HINIC_MAX_SQ_SGE;
	}

	wqebb_cnt = HINIC_SQ_WQEBB_CNT(num_sge);
	if (likely(hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >=
			wqebb_cnt)) {
		if (likely(wqebb_cnt == 1)) {
			hinic_update_sq_pi(nic_dev->hwdev, q_id,
					   wqebb_cnt, &pi, &owner);
			wqe = txq->tx_info[pi].wqe;
		} else {
			wqe = hinic_get_sq_wqe(nic_dev->hwdev, q_id,
					       wqebb_cnt, &pi, &owner);
		}

	} else {
		wqe = __try_to_get_wqe(netdev, q_id, wqebb_cnt, &pi, &owner);
		if (likely(!wqe)) {
			TXQ_STATS_INC(txq, busy);
			return NETDEV_TX_BUSY;
		}
	}

	tx_info = &txq->tx_info[pi];
	tx_info->skb = skb;
	tx_info->wqebb_cnt = wqebb_cnt;
	tx_info->valid_nr_frags = new_nr_frags;

	__get_pkt_stats(tx_info, skb);

	offload = hinic_tx_offload(txq, skb, &wqe->task, &queue_info, avd_flag);
	if (unlikely(offload == TX_OFFLOAD_INVALID)) {
		hinic_return_sq_wqe(nic_dev->hwdev, q_id, wqebb_cnt, owner);
		TXQ_STATS_INC(txq, offload_cow_skb_err);
		goto tx_drop_pkts;
	}

	err = tx_map_skb(nic_dev, skb, txq, tx_info, wqe->buf_descs,
			 new_nr_frags);
	if (err) {
		hinic_return_sq_wqe(nic_dev->hwdev, q_id, wqebb_cnt, owner);
		goto tx_drop_pkts;
	}

	hinic_prepare_sq_ctrl(&wqe->ctrl, queue_info, num_sge, owner);

	hinic_send_sq_wqe(nic_dev->hwdev, q_id, wqe, wqebb_cnt,
			  nic_dev->sq_cos_mapping[hinic_get_vlan_pri(skb)]);

	return NETDEV_TX_OK;

tx_drop_pkts:
	dev_kfree_skb_any(skb);

tx_skb_pad_err:
	TXQ_STATS_INC(txq, dropped);

	*flag = HINIC_TX_DROPPED;
	return NETDEV_TX_OK;
}

netdev_tx_t hinic_lb_xmit_frame(struct sk_buff *skb,
				struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u16 q_id = skb_get_queue_mapping(skb);
	struct hinic_txq *txq;
	u8 flag = 0;

	if (unlikely(!nic_dev->heart_status)) {
		dev_kfree_skb_any(skb);
		HINIC_NIC_STATS_INC(nic_dev, tx_carrier_off_drop);
		return NETDEV_TX_OK;
	}

	txq = &nic_dev->txqs[q_id];

	return hinic_send_one_skb(skb, netdev, txq, &flag, HINIC_TX_NON_AVD);
}

netdev_tx_t hinic_xmit_frame(struct sk_buff *skb, struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u16 q_id = skb_get_queue_mapping(skb);
	struct hinic_txq *txq;
	u8 flag = 0;

	if (unlikely(!netif_carrier_ok(netdev) ||
		     !nic_dev->heart_status)) {
		dev_kfree_skb_any(skb);
		HINIC_NIC_STATS_INC(nic_dev, tx_carrier_off_drop);
		return NETDEV_TX_OK;
	}

	if (unlikely(q_id >= nic_dev->num_qps)) {
		txq = &nic_dev->txqs[0];
		HINIC_NIC_STATS_INC(nic_dev, tx_invalid_qid);
		goto tx_drop_pkts;
	}
	txq = &nic_dev->txqs[q_id];

	return hinic_send_one_skb(skb, netdev, txq, &flag, HINIC_TX_NON_AVD);

tx_drop_pkts:
	dev_kfree_skb_any(skb);
	u64_stats_update_begin(&txq->txq_stats.syncp);
	txq->txq_stats.dropped++;
	u64_stats_update_end(&txq->txq_stats.syncp);

	return NETDEV_TX_OK;
}

static inline void tx_free_skb(struct hinic_nic_dev *nic_dev,
			       struct sk_buff *skb,
			       struct hinic_tx_info *tx_info)
{
	tx_unmap_skb(nic_dev, skb, tx_info->dma_len, tx_info->valid_nr_frags);

	kfree(tx_info->cpy_buff);
	tx_info->cpy_buff = NULL;
	dev_kfree_skb_any(skb);
}

static void free_all_tx_skbs(struct hinic_txq *txq)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
	struct hinic_tx_info *tx_info;
	u16 ci;
	int free_wqebbs = hinic_get_sq_free_wqebbs(nic_dev->hwdev,
						   txq->q_id) + 1;

	while (free_wqebbs < txq->q_depth) {
		ci = hinic_get_sq_local_ci(nic_dev->hwdev, txq->q_id);

		tx_info = &txq->tx_info[ci];

		tx_free_skb(nic_dev, tx_info->skb, tx_info);

		hinic_update_sq_local_ci(nic_dev->hwdev, txq->q_id,
					 tx_info->wqebb_cnt);

		free_wqebbs += tx_info->wqebb_cnt;
	}
}

int hinic_tx_poll(struct hinic_txq *txq, int budget)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
	struct sk_buff *skb;
	struct hinic_tx_info *tx_info;
	u64 tx_bytes = 0, wake = 0;
	int pkts = 0, nr_pkts = 0, wqebb_cnt = 0;
	u16 hw_ci, sw_ci = 0, q_id = txq->q_id;

	hw_ci = hinic_get_sq_hw_ci(nic_dev->hwdev, q_id);
	dma_rmb();
	sw_ci = hinic_get_sq_local_ci(nic_dev->hwdev, q_id);

	do {
		tx_info = &txq->tx_info[sw_ci];

		/* Whether all of the wqebb of this wqe is completed */
		if (hw_ci == sw_ci || ((hw_ci - sw_ci) &
			txq->q_mask) < tx_info->wqebb_cnt) {
			break;
		}

		sw_ci = (u16)(sw_ci + tx_info->wqebb_cnt) & txq->q_mask;
		prefetch(&txq->tx_info[sw_ci]);

		wqebb_cnt += tx_info->wqebb_cnt;

		skb = tx_info->skb;
		tx_bytes += tx_info->num_bytes;
		nr_pkts += tx_info->num_pkts;
		pkts++;

		tx_free_skb(nic_dev, skb, tx_info);

	} while (likely(pkts < budget));

	hinic_update_sq_local_ci(nic_dev->hwdev, q_id, wqebb_cnt);

	if (unlikely(__netif_subqueue_stopped(nic_dev->netdev, q_id) &&
		     hinic_get_sq_free_wqebbs(nic_dev->hwdev, q_id) >= 1 &&
		     test_bit(HINIC_INTF_UP, &nic_dev->flags))) {
		struct netdev_queue *netdev_txq =
				netdev_get_tx_queue(txq->netdev, q_id);

		__netif_tx_lock(netdev_txq, smp_processor_id());
		/* To avoid re-waking subqueue with xmit_frame */
		if (__netif_subqueue_stopped(nic_dev->netdev, q_id)) {
			netif_wake_subqueue(nic_dev->netdev, q_id);
			wake++;
		}
		__netif_tx_unlock(netdev_txq);
	}

	u64_stats_update_begin(&txq->txq_stats.syncp);
	txq->txq_stats.bytes += tx_bytes;
	txq->txq_stats.packets += nr_pkts;
	txq->txq_stats.wake += wake;
	u64_stats_update_end(&txq->txq_stats.syncp);

	return pkts;
}

int hinic_setup_tx_wqe(struct hinic_txq *txq)
{
	struct net_device *netdev = txq->netdev;
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_sq_wqe *wqe;
	struct hinic_tx_info *tx_info;
	u16 pi = 0;
	int i;
	u8 owner = 0;

	for (i = 0; i < txq->q_depth; i++) {
		tx_info = &txq->tx_info[i];

		wqe = hinic_get_sq_wqe(nic_dev->hwdev, txq->q_id,
				       1, &pi, &owner);
		if (!wqe) {
			nicif_err(nic_dev, drv, netdev, "Failed to get SQ wqe\n");
			break;
		}

		tx_info->wqe = wqe;
	}

	hinic_return_sq_wqe(nic_dev->hwdev, txq->q_id, txq->q_depth, owner);

	return i;
}

int hinic_setup_all_tx_resources(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_txq *txq;
	u64 tx_info_sz;
	u16 i, q_id;
	int err;

	for (q_id = 0; q_id < nic_dev->num_qps; q_id++) {
		txq = &nic_dev->txqs[q_id];
		tx_info_sz = txq->q_depth * sizeof(*txq->tx_info);
		if (!tx_info_sz) {
			nicif_err(nic_dev, drv, netdev, "Cannot allocate zero size txq%d info\n",
				  q_id);
			err = -EINVAL;
			goto init_txq_err;
		}

		txq->tx_info = kzalloc(tx_info_sz, GFP_KERNEL);
		if (!txq->tx_info) {
			err = -ENOMEM;
			goto init_txq_err;
		}

		err = hinic_setup_tx_wqe(txq);
		if (err != txq->q_depth) {
			nicif_err(nic_dev, drv, netdev, "Failed to setup Tx: %d wqe\n",
				  q_id);
			q_id++;
			goto init_txq_err;
		}
	}

	return 0;

init_txq_err:
	for (i = 0; i < q_id; i++) {
		txq = &nic_dev->txqs[i];
		kfree(txq->tx_info);
	}

	return err;
}

void hinic_free_all_tx_resources(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_txq *txq;
	u16 q_id;

	for (q_id = 0; q_id < nic_dev->num_qps; q_id++) {
		txq = &nic_dev->txqs[q_id];
		free_all_tx_skbs(txq);
		kfree(txq->tx_info);
	}
}

void hinic_set_sq_default_cos(struct net_device *netdev, u8 cos_id)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int up;

	for (up = HINIC_DCB_UP_MAX - 1; up >= 0; up--)
		nic_dev->sq_cos_mapping[up] = nic_dev->default_cos_id;
}

int hinic_sq_cos_mapping(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_dcb_state dcb_state = {0};
	u8 default_cos = 0;
	int err;

	if (HINIC_FUNC_IS_VF(nic_dev->hwdev)) {
		err = hinic_get_pf_dcb_state(nic_dev->hwdev, &dcb_state);
		if (err) {
			hinic_info(nic_dev, drv, "Failed to get vf default cos\n");
			return err;
		}

		default_cos = dcb_state.default_cos;
		nic_dev->default_cos_id = default_cos;
		hinic_set_sq_default_cos(nic_dev->netdev, default_cos);
	} else {
		default_cos = nic_dev->default_cos_id;
		if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags))
			memcpy(nic_dev->sq_cos_mapping, nic_dev->up_cos,
			       sizeof(nic_dev->sq_cos_mapping));
		else
			hinic_set_sq_default_cos(nic_dev->netdev, default_cos);

		dcb_state.dcb_on = !!test_bit(HINIC_DCB_ENABLE,
					      &nic_dev->flags);
		dcb_state.default_cos = default_cos;
		memcpy(dcb_state.up_cos, nic_dev->sq_cos_mapping,
		       sizeof(dcb_state.up_cos));

		err = hinic_set_dcb_state(nic_dev->hwdev, &dcb_state);
		if (err)
			hinic_info(nic_dev, drv, "Failed to set vf default cos\n");
	}

	return err;
}

int hinic_alloc_txqs(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct pci_dev *pdev = nic_dev->pdev;
	struct hinic_txq *txq;
	u16 q_id, num_txqs = nic_dev->max_qps;
	u64 txq_size;

	txq_size = num_txqs * sizeof(*nic_dev->txqs);
	if (!txq_size) {
		nic_err(&pdev->dev, "Cannot allocate zero size txqs\n");
		return -EINVAL;
	}

	nic_dev->txqs = kzalloc(txq_size, GFP_KERNEL);

	if (!nic_dev->txqs) {
		nic_err(&pdev->dev, "Failed to allocate txqs\n");
		return -ENOMEM;
	}

	for (q_id = 0; q_id < num_txqs; q_id++) {
		txq = &nic_dev->txqs[q_id];
		txq->netdev = netdev;
		txq->q_id = q_id;
		txq->q_depth = nic_dev->sq_depth;
		txq->q_mask = nic_dev->sq_depth - 1;

		txq_stats_init(txq);
	}

	return 0;
}

void hinic_free_txqs(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);

	kfree(nic_dev->txqs);
}

/* should stop transmit any packets before calling this function */
#define HINIC_FLUSH_QUEUE_TIMEOUT	1000

static bool hinic_get_hw_handle_status(void *hwdev, u16 q_id)
{
	u16 sw_pi = 0, hw_ci = 0;

	sw_pi = hinic_dbg_get_sq_pi(hwdev, q_id);
	hw_ci = hinic_get_sq_hw_ci(hwdev, q_id);

	return sw_pi == hw_ci;
}

int hinic_stop_sq(struct hinic_txq *txq)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(txq->netdev);
	unsigned long timeout;
	int err;

	timeout = msecs_to_jiffies(HINIC_FLUSH_QUEUE_TIMEOUT) + jiffies;
	do {
		if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
			return 0;

		usleep_range(900, 1000);
	} while (time_before(jiffies, timeout));

	/* force hardware to drop packets */
	timeout = msecs_to_jiffies(HINIC_FLUSH_QUEUE_TIMEOUT) + jiffies;
	do {
		if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
			return 0;

		err = hinic_force_drop_tx_pkt(nic_dev->hwdev);
		if (err)
			break;

		usleep_range(9900, 10000);
	} while (time_before(jiffies, timeout));

	/* Avoid msleep takes too long and get a fake result */
	if (hinic_get_hw_handle_status(nic_dev->hwdev, txq->q_id))
		return 0;

	return -EFAULT;
}

void hinic_flush_txqs(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u16 qid;
	int err;

	for (qid = 0; qid < nic_dev->num_qps; qid++) {
		err = hinic_stop_sq(&nic_dev->txqs[qid]);
		if (err)
			nicif_err(nic_dev, drv, netdev,
				  "Failed to stop sq%d\n", qid);
	}
} /*lint -e766*/
