// 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/types.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/etherdevice.h>
#include <linux/netdevice.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/u64_stats_sync.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/sctp.h>
#include <linux/pkt_sched.h>
#include <linux/ipv6.h>

#include "ossl_knl.h"
#include "hinic_hw.h"
#include "hinic_hw_mgmt.h"
#include "hinic_nic_io.h"
#include "hinic_nic_cfg.h"
#include "hinic_nic_dev.h"
#include "hinic_qp.h"
#include "hinic_rx.h"

static void hinic_clear_rss_config_user(struct hinic_nic_dev *nic_dev);

#define HINIC_RX_HDR_SIZE			256
#define HINIC_RX_IPV6_PKT			7
#define HINIC_RX_VXLAN_PKT			0xb

#define RXQ_STATS_INC(rxq, field)			\
{							\
	u64_stats_update_begin(&(rxq)->rxq_stats.syncp);	\
	(rxq)->rxq_stats.field++;				\
	u64_stats_update_end(&(rxq)->rxq_stats.syncp);	\
}

static bool rx_alloc_mapped_page(struct hinic_rxq *rxq,
				 struct hinic_rx_info *rx_info)
{
	struct net_device *netdev = rxq->netdev;
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct pci_dev *pdev = nic_dev->pdev;

	struct page *page = rx_info->page;
	dma_addr_t dma = rx_info->buf_dma_addr;

	if (likely(dma))
		return true;

	/* alloc new page for storage */
	page = dev_alloc_pages(nic_dev->page_order);
	if (unlikely(!page)) {
		RXQ_STATS_INC(rxq, alloc_rx_buf_err);
		return false;
	}

	/* map page for use */
	dma = dma_map_page(&pdev->dev, page, 0, rxq->dma_rx_buff_size,
			   DMA_FROM_DEVICE);

	/* if mapping failed free memory back to system since
	 * there isn't much point in holding memory we can't use
	 */
	if (unlikely(dma_mapping_error(&pdev->dev, dma))) {
		RXQ_STATS_INC(rxq, map_rx_buf_err);
		__free_pages(page, nic_dev->page_order);
		return false;
	}

	rx_info->page = page;
	rx_info->buf_dma_addr = dma;
	rx_info->page_offset = 0;

	return true;
}

static int hinic_rx_fill_wqe(struct hinic_rxq *rxq)
{
	struct net_device *netdev = rxq->netdev;
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_rq_wqe *rq_wqe;
	struct hinic_rx_info *rx_info;
	dma_addr_t dma_addr = 0;
	u16 pi = 0;
	int rq_wqe_len;
	int i;

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

		rq_wqe = hinic_get_rq_wqe(nic_dev->hwdev, rxq->q_id, &pi);
		if (!rq_wqe) {
			nicif_err(nic_dev, drv, netdev, "Failed to get rq wqe, rxq id: %d, wqe id: %d\n",
				  rxq->q_id, i);
			break;
		}

		hinic_prepare_rq_wqe(rq_wqe, pi, dma_addr, rx_info->cqe_dma);

		rq_wqe_len = sizeof(struct hinic_rq_wqe);
		hinic_cpu_to_be32(rq_wqe, rq_wqe_len);
		rx_info->rq_wqe = rq_wqe;
	}

	hinic_return_rq_wqe(nic_dev->hwdev, rxq->q_id, rxq->q_depth);

	return i;
}

static int hinic_rx_fill_buffers(struct hinic_rxq *rxq)
{
	struct net_device *netdev = rxq->netdev;
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct hinic_rq_wqe *rq_wqe;
	struct hinic_rx_info *rx_info;
	dma_addr_t dma_addr;
	int i;
	int free_wqebbs = rxq->delta - 1;

	for (i = 0; i < free_wqebbs; i++) {
		rx_info = &rxq->rx_info[rxq->next_to_update];

		if (unlikely(!rx_alloc_mapped_page(rxq, rx_info)))
			break;

		dma_addr = rx_info->buf_dma_addr + rx_info->page_offset;

		rq_wqe = rx_info->rq_wqe;

		rq_wqe->buf_desc.addr_high =
				cpu_to_be32(upper_32_bits(dma_addr));
		rq_wqe->buf_desc.addr_low =
				cpu_to_be32(lower_32_bits(dma_addr));
		rxq->next_to_update = (rxq->next_to_update + 1) & rxq->q_mask;
	}

	if (likely(i)) {
		/* Write all the wqes before pi update */
		wmb();

		hinic_update_rq_hw_pi(nic_dev->hwdev, rxq->q_id,
				      rxq->next_to_update);
		rxq->delta -= i;
		rxq->next_to_alloc = rxq->next_to_update;
	} else if (free_wqebbs == rxq->q_depth - 1) {
		RXQ_STATS_INC(rxq, rx_buf_empty);
	}

	return i;
}

void hinic_rx_free_buffers(struct hinic_rxq *rxq)
{
	u16 i;
	struct hinic_nic_dev *nic_dev = netdev_priv(rxq->netdev);
	struct hinic_rx_info *rx_info;

	/* Free all the Rx ring sk_buffs */
	for (i = 0; i < rxq->q_depth; i++) {
		rx_info = &rxq->rx_info[i];

		if (rx_info->buf_dma_addr) {
			dma_unmap_page(rxq->dev, rx_info->buf_dma_addr,
				       rxq->dma_rx_buff_size,
				       DMA_FROM_DEVICE);
			rx_info->buf_dma_addr = 0;
		}

		if (rx_info->page) {
			__free_pages(rx_info->page, nic_dev->page_order);
			rx_info->page = NULL;
		}
	}
}

static void hinic_reuse_rx_page(struct hinic_rxq *rxq,
				struct hinic_rx_info *old_rx_info)
{
	struct hinic_rx_info *new_rx_info;
	u16 nta = rxq->next_to_alloc;

	new_rx_info = &rxq->rx_info[nta];

	/* update, and store next to alloc */
	nta++;
	rxq->next_to_alloc = (nta < rxq->q_depth) ? nta : 0;

	new_rx_info->page = old_rx_info->page;
	new_rx_info->page_offset = old_rx_info->page_offset;
	new_rx_info->buf_dma_addr = old_rx_info->buf_dma_addr;

	/* sync the buffer for use by the device */
	dma_sync_single_range_for_device(rxq->dev, new_rx_info->buf_dma_addr,
					 new_rx_info->page_offset,
					 rxq->buf_len,
					 DMA_FROM_DEVICE);
}

static bool hinic_add_rx_frag(struct hinic_rxq *rxq,
			      struct hinic_rx_info *rx_info,
			      struct sk_buff *skb, u32 size)
{
	struct page *page;
	u8 *va;

	page = rx_info->page;
	va = (u8 *)page_address(page) + rx_info->page_offset;
	prefetch(va);
#if L1_CACHE_BYTES < 128
	prefetch(va + L1_CACHE_BYTES);
#endif

	dma_sync_single_range_for_cpu(rxq->dev,
				      rx_info->buf_dma_addr,
				      rx_info->page_offset,
				      rxq->buf_len,
				      DMA_FROM_DEVICE);

	if (size <= HINIC_RX_HDR_SIZE && !skb_is_nonlinear(skb)) {
		memcpy(__skb_put(skb, size), va,
		       ALIGN(size, sizeof(long))); /*lint !e666*/

		/* page is not reserved, we can reuse buffer as-is */
		if (likely(page_to_nid(page) == numa_node_id()))
			return true;

		/* this page cannot be reused so discard it */
		put_page(page);
		return false;
	}

	skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page,
			(int)rx_info->page_offset, (int)size, rxq->buf_len);

	/* avoid re-using remote pages */
	if (unlikely(page_to_nid(page) != numa_node_id()))
		return false;

	/* if we are only owner of page we can reuse it */
	if (unlikely(page_count(page) != 1))
		return false;

	/* flip page offset to other buffer */
	rx_info->page_offset ^= rxq->buf_len;

	page_ref_inc(page);

	return true;
}

static void __packaging_skb(struct hinic_rxq *rxq, struct sk_buff *head_skb,
			    u8 sge_num, u32 pkt_len)
{
	struct hinic_rx_info *rx_info;
	struct sk_buff *skb;
	u8 frag_num = 0;
	u32 size;
	u16 sw_ci;

	sw_ci = ((u32)rxq->cons_idx) & rxq->q_mask;
	skb = head_skb;
	while (sge_num) {
		rx_info = &rxq->rx_info[sw_ci];
		sw_ci = (sw_ci + 1) & rxq->q_mask;
		if (unlikely(pkt_len > rxq->buf_len)) {
			size = rxq->buf_len;
			pkt_len -= rxq->buf_len;
		} else {
			size = pkt_len;
		}

		if (unlikely(frag_num == MAX_SKB_FRAGS)) {
			frag_num = 0;
			if (skb == head_skb)
				skb = skb_shinfo(skb)->frag_list;
			else
				skb = skb->next;
		}

		if (unlikely(skb != head_skb)) {
			head_skb->len += size;
			head_skb->data_len += size;
			head_skb->truesize += rxq->buf_len;
		}

		if (likely(hinic_add_rx_frag(rxq, rx_info, skb, size))) {
			hinic_reuse_rx_page(rxq, rx_info);
		} else {
			/* we are not reusing the buffer so unmap it */
			dma_unmap_page(rxq->dev, rx_info->buf_dma_addr,
				       rxq->dma_rx_buff_size, DMA_FROM_DEVICE);
		}
		/* clear contents of buffer_info */
		rx_info->buf_dma_addr = 0;
		rx_info->page = NULL;
		sge_num--;
		frag_num++;
	}
}

static struct sk_buff *hinic_fetch_rx_buffer(struct hinic_rxq *rxq, u32 pkt_len)
{
	struct sk_buff *head_skb, *cur_skb, *skb = NULL;
	struct net_device *netdev = rxq->netdev;
	u8 sge_num, skb_num;
	u16 wqebb_cnt = 0;

	head_skb = netdev_alloc_skb_ip_align(netdev, HINIC_RX_HDR_SIZE);
	if (unlikely(!head_skb))
		return NULL;

	sge_num = (u8)(pkt_len >> rxq->rx_buff_shift) +
			((pkt_len & (rxq->buf_len - 1)) ? 1 : 0);
	if (likely(sge_num <= MAX_SKB_FRAGS))
		skb_num = 1;
	else
		skb_num = (sge_num / MAX_SKB_FRAGS) +
			((sge_num % MAX_SKB_FRAGS) ? 1 : 0);

	while (unlikely(skb_num > 1)) {
		cur_skb = netdev_alloc_skb_ip_align(netdev, HINIC_RX_HDR_SIZE);
		if (unlikely(!cur_skb))
			goto alloc_skb_fail;

		if (!skb) {
			skb_shinfo(head_skb)->frag_list = cur_skb;
			skb = cur_skb;
		} else {
			skb->next = cur_skb;
			skb = cur_skb;
		}

		skb_num--;
	}

	prefetchw(head_skb->data);
	wqebb_cnt = sge_num;

	__packaging_skb(rxq, head_skb, sge_num, pkt_len);

	rxq->cons_idx += wqebb_cnt;
	rxq->delta += wqebb_cnt;

	return head_skb;

alloc_skb_fail:
	dev_kfree_skb_any(head_skb);
	return NULL;
}

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->bytes = rxq_stats->bytes;
		stats->packets = rxq_stats->packets;
		stats->errors = rxq_stats->csum_errors +
				rxq_stats->other_errors;
		stats->csum_errors = rxq_stats->csum_errors;
		stats->other_errors = rxq_stats->other_errors;
		stats->dropped = rxq_stats->dropped;
		stats->rx_buf_empty = rxq_stats->rx_buf_empty;
	} while (u64_stats_fetch_retry(&rxq_stats->syncp, start));
	u64_stats_update_end(&stats->syncp);
}

void hinic_rxq_clean_stats(struct hinic_rxq_stats *rxq_stats)
{
	u64_stats_update_begin(&rxq_stats->syncp);
	rxq_stats->bytes = 0;
	rxq_stats->packets = 0;
	rxq_stats->errors = 0;
	rxq_stats->csum_errors = 0;
	rxq_stats->other_errors = 0;
	rxq_stats->dropped = 0;

	rxq_stats->alloc_skb_err = 0;
	rxq_stats->alloc_rx_buf_err = 0;
	rxq_stats->map_rx_buf_err = 0;
	rxq_stats->rx_buf_empty = 0;
	u64_stats_update_end(&rxq_stats->syncp);
}

static void rxq_stats_init(struct hinic_rxq *rxq)
{
	struct hinic_rxq_stats *rxq_stats = &rxq->rxq_stats;

	u64_stats_init(&rxq_stats->syncp);
	hinic_rxq_clean_stats(rxq_stats);
}

static void hinic_pull_tail(struct sk_buff *skb)
{
	skb_frag_t *frag = &skb_shinfo(skb)->frags[0];
	unsigned char *va;

	/* it is valid to use page_address instead of kmap since we are
	 * working with pages allocated out of the lomem pool per
	 * alloc_page(GFP_ATOMIC)
	 */
	va = skb_frag_address(frag);

	/* align pull length to size of long to optimize memcpy performance */
	skb_copy_to_linear_data(skb, va, HINIC_RX_HDR_SIZE);

	/* update all of the pointers */
	skb_frag_size_sub(frag, HINIC_RX_HDR_SIZE);
	frag->bv_offset += HINIC_RX_HDR_SIZE;
	skb->data_len -= HINIC_RX_HDR_SIZE;
	skb->tail += HINIC_RX_HDR_SIZE;
}

static void hinic_rx_csum(struct hinic_rxq *rxq, u32 status,
			  struct sk_buff *skb)
{
	struct net_device *netdev = rxq->netdev;
	u32 csum_err;

	csum_err = HINIC_GET_RX_CSUM_ERR(status);

	if (unlikely(csum_err == HINIC_RX_CSUM_IPSU_OTHER_ERR))
		rxq->rxq_stats.other_errors++;

	if (!(netdev->features & NETIF_F_RXCSUM))
		return;

	if (!csum_err) {
		skb->ip_summed = CHECKSUM_UNNECESSARY;
	} else {
		/* pkt type is recognized by HW, and csum is err */
		if (!(csum_err & (HINIC_RX_CSUM_HW_CHECK_NONE |
		    HINIC_RX_CSUM_IPSU_OTHER_ERR)))
			rxq->rxq_stats.csum_errors++;

		skb->ip_summed = CHECKSUM_NONE;
	}
}

static void hinic_rx_gro(struct hinic_rxq *rxq, u32 offload_type,
			 struct sk_buff *skb)
{
	struct net_device *netdev = rxq->netdev;
	bool l2_tunnel;

	if (!(netdev->features & NETIF_F_GRO))
		return;

	l2_tunnel = HINIC_GET_RX_PKT_TYPE(offload_type) == HINIC_RX_VXLAN_PKT ?
			1 : 0;

	if (l2_tunnel && skb->ip_summed == CHECKSUM_UNNECESSARY)
		/* If we checked the outer header let the stack know */
		skb->csum_level = 1;
}

static void hinic_copy_lp_data(struct hinic_nic_dev *nic_dev,
			       struct sk_buff *skb)
{
	struct net_device *netdev = nic_dev->netdev;
	u8 *lb_buf = nic_dev->lb_test_rx_buf;
	void *frag_data;
	int lb_len = nic_dev->lb_pkt_len;
	int pkt_offset, frag_len, i;

	if (nic_dev->lb_test_rx_idx == LP_PKT_CNT) {
		nic_dev->lb_test_rx_idx = 0;
		nicif_warn(nic_dev, rx_err, netdev, "Loopback test warning, recive too more test pkt\n");
	}

	if (skb->len != nic_dev->lb_pkt_len) {
		nicif_warn(nic_dev, rx_err, netdev, "Wrong packet length\n");
		nic_dev->lb_test_rx_idx++;
		return;
	}

	pkt_offset = nic_dev->lb_test_rx_idx * lb_len;
	frag_len = (int)skb_headlen(skb);
	memcpy((lb_buf + pkt_offset), skb->data, frag_len);
	pkt_offset += frag_len;
	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
		frag_data = skb_frag_address(&skb_shinfo(skb)->frags[i]);
		frag_len = (int)skb_frag_size(&skb_shinfo(skb)->frags[i]);
		memcpy((lb_buf + pkt_offset), frag_data, frag_len);
		pkt_offset += frag_len;
	}
	nic_dev->lb_test_rx_idx++;
}

int recv_one_pkt(struct hinic_rxq *rxq, struct hinic_rq_cqe *rx_cqe,
		 u32 pkt_len, u32 vlan_len, u32 status)
{
	struct sk_buff *skb;
	struct net_device *netdev = rxq->netdev;
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u32 offload_type;

	skb = hinic_fetch_rx_buffer(rxq, pkt_len);
	if (unlikely(!skb)) {
		RXQ_STATS_INC(rxq, alloc_skb_err);
		return -ENOMEM;
	}

	/* place header in linear portion of buffer */
	if (skb_is_nonlinear(skb))
		hinic_pull_tail(skb);

	hinic_rx_csum(rxq, status, skb);

	offload_type = be32_to_cpu(rx_cqe->offload_type);
	hinic_rx_gro(rxq, offload_type, skb);

	if ((netdev->features & NETIF_F_HW_VLAN_CTAG_RX) &&
	    HINIC_GET_RX_VLAN_OFFLOAD_EN(offload_type)) {
		u16 vid = HINIC_GET_RX_VLAN_TAG(vlan_len);

		/* if the packet is a vlan pkt, the vid may be 0 */
		__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vid);
	}

	if (unlikely(test_bit(HINIC_LP_TEST, &nic_dev->flags)))
		hinic_copy_lp_data(nic_dev, skb);

	skb_record_rx_queue(skb, rxq->q_id);
	skb->protocol = eth_type_trans(skb, netdev);

	if (skb_has_frag_list(skb)) {
		napi_gro_flush(&rxq->irq_cfg->napi, false);
		netif_receive_skb(skb);
	} else {
		napi_gro_receive(&rxq->irq_cfg->napi, skb);
	}

	return 0;
}

void rx_pass_super_cqe(struct hinic_rxq *rxq, u32 index, u32 pkt_num,
		       struct hinic_rq_cqe *cqe)
{
	u8 sge_num = 0;
	u32 pkt_len;

	while (index < pkt_num) {
		pkt_len = hinic_get_pkt_len_for_super_cqe
				(cqe, index == (pkt_num - 1));
		sge_num += (u8)(pkt_len >> rxq->rx_buff_shift) +
				((pkt_len & (rxq->buf_len - 1)) ? 1 : 0);
		index++;
	}

	rxq->cons_idx += sge_num;
	rxq->delta += sge_num;
}

static inline int __recv_supper_cqe(struct hinic_rxq *rxq,
				    struct hinic_rq_cqe *rx_cqe, u32 pkt_info,
				    u32 vlan_len, u32 status, int *pkts,
				    u64 *rx_bytes, u32 *dropped)
{
	u32 pkt_len;
	int i, pkt_num = 0;

	pkt_num = HINIC_GET_RQ_CQE_PKT_NUM(pkt_info);
	i = 0;
	while (i < pkt_num) {
		pkt_len = ((i == (pkt_num - 1)) ?
		    RQ_CQE_PKT_LEN_GET(pkt_info, LAST_LEN) :
		    RQ_CQE_PKT_LEN_GET(pkt_info, FIRST_LEN));
		if (unlikely(recv_one_pkt(rxq, rx_cqe, pkt_len,
					  vlan_len, status))) {
			if (i) {
				rx_pass_super_cqe(rxq, i,
						  pkt_num,
						  rx_cqe);
				*dropped += (pkt_num - i);
			}
			break;
		}

		*rx_bytes += pkt_len;
		(*pkts)++;
		i++;
	}

	if (!i)
		return -EFAULT;

	return 0;
}

#define LRO_PKT_HDR_LEN_IPV4		66
#define LRO_PKT_HDR_LEN_IPV6		86
#define LRO_PKT_HDR_LEN(cqe)		\
	(HINIC_GET_RX_PKT_TYPE(be32_to_cpu((cqe)->offload_type)) == \
	 HINIC_RX_IPV6_PKT ? LRO_PKT_HDR_LEN_IPV6 : LRO_PKT_HDR_LEN_IPV4)

int hinic_rx_poll(struct hinic_rxq *rxq, int budget)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(rxq->netdev);
	u32 status, pkt_len, vlan_len, pkt_info, dropped = 0;
	struct hinic_rq_cqe *rx_cqe;
	u64 rx_bytes = 0;
	u16 sw_ci, num_lro;
	int pkts = 0, nr_pkts = 0;
	u16 num_wqe = 0;

	while (likely(pkts < budget)) {
		sw_ci = ((u32)rxq->cons_idx) & rxq->q_mask;
		rx_cqe = rxq->rx_info[sw_ci].cqe;
		status = be32_to_cpu(rx_cqe->status);

		if (!HINIC_GET_RX_DONE(status))
			break;

		/* make sure we read rx_done before packet length */
		rmb();

		vlan_len = be32_to_cpu(rx_cqe->vlan_len);
		pkt_info = be32_to_cpu(rx_cqe->pkt_info);
		pkt_len = HINIC_GET_RX_PKT_LEN(vlan_len);

		if (unlikely(HINIC_GET_SUPER_CQE_EN(pkt_info))) {
			if (unlikely(__recv_supper_cqe(rxq, rx_cqe, pkt_info,
						       vlan_len, status, &pkts,
						       &rx_bytes, &dropped)))
				break;
			nr_pkts += (int)HINIC_GET_RQ_CQE_PKT_NUM(pkt_info);
		} else {
			if (recv_one_pkt(rxq, rx_cqe, pkt_len,
					 vlan_len, status))
				break;
			rx_bytes += pkt_len;
			pkts++;
			nr_pkts++;

			num_lro = HINIC_GET_RX_NUM_LRO(status);
			if (num_lro) {
				rx_bytes += ((num_lro - 1) *
					     LRO_PKT_HDR_LEN(rx_cqe));

				num_wqe +=
				(u16)(pkt_len >> rxq->rx_buff_shift) +
				((pkt_len & (rxq->buf_len - 1)) ? 1 : 0);
			}
		}

		rx_cqe->status = 0;

		if (num_wqe >= nic_dev->lro_replenish_thld)
			break;
	}

	if (rxq->delta >= HINIC_RX_BUFFER_WRITE)
		hinic_rx_fill_buffers(rxq);

	u64_stats_update_begin(&rxq->rxq_stats.syncp);
	rxq->rxq_stats.packets += nr_pkts;
	rxq->rxq_stats.bytes += rx_bytes;
	rxq->rxq_stats.dropped += dropped;
	u64_stats_update_end(&rxq->rxq_stats.syncp);
	return pkts;
}

static int rx_alloc_cqe(struct hinic_rxq *rxq)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(rxq->netdev);
	struct pci_dev *pdev = nic_dev->pdev;
	struct hinic_rx_info *rx_info;
	struct hinic_rq_cqe *cqe_va;
	dma_addr_t cqe_pa;
	u32 cqe_mem_size;
	int idx;

	cqe_mem_size = sizeof(*rx_info->cqe) * rxq->q_depth;
	rxq->cqe_start_vaddr = dma_alloc_coherent(&pdev->dev, cqe_mem_size,
						  &rxq->cqe_start_paddr,
						  GFP_KERNEL);
	if (!rxq->cqe_start_vaddr) {
		nicif_err(nic_dev, drv, rxq->netdev, "Failed to allocate cqe dma\n");
		return -ENOMEM;
	}

	cqe_va = (struct hinic_rq_cqe *)rxq->cqe_start_vaddr;
	cqe_pa = rxq->cqe_start_paddr;

	for (idx = 0; idx < rxq->q_depth; idx++) {
		rx_info = &rxq->rx_info[idx];
		rx_info->cqe = cqe_va;
		rx_info->cqe_dma = cqe_pa;

		cqe_va++;
		cqe_pa += sizeof(*rx_info->cqe);
	}

	hinic_rq_cqe_addr_set(nic_dev->hwdev, rxq->q_id, rxq->cqe_start_paddr);
	return 0;
}

static void rx_free_cqe(struct hinic_rxq *rxq)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(rxq->netdev);
	struct pci_dev *pdev = nic_dev->pdev;
	u32 cqe_mem_size;

	cqe_mem_size = sizeof(struct hinic_rq_cqe) * rxq->q_depth;

	dma_free_coherent(&pdev->dev, cqe_mem_size,
			  rxq->cqe_start_vaddr, rxq->cqe_start_paddr);
}

static int hinic_setup_rx_resources(struct hinic_rxq *rxq,
				    struct net_device *netdev,
				    struct irq_info *entry)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(rxq->netdev);
	u64 rx_info_sz;
	int err, pkts;

	rxq->irq_id = entry->irq_id;
	rxq->msix_entry_idx = entry->msix_entry_idx;
	rxq->next_to_alloc = 0;
	rxq->next_to_update = 0;
	rxq->delta = rxq->q_depth;
	rxq->q_mask = rxq->q_depth - 1;
	rxq->cons_idx = 0;

	rx_info_sz = rxq->q_depth * sizeof(*rxq->rx_info);
	if (!rx_info_sz) {
		nicif_err(nic_dev, drv, netdev, "Cannot allocate zero size rx info\n");
		return -EINVAL;
	}

	rxq->rx_info = kzalloc(rx_info_sz, GFP_KERNEL);
	if (!rxq->rx_info)
		return -ENOMEM;

	err = rx_alloc_cqe(rxq);
	if (err) {
		nicif_err(nic_dev, drv, netdev, "Failed to allocate Rx cqe\n");
		goto rx_cqe_err;
	}

	pkts = hinic_rx_fill_wqe(rxq);
	if (pkts != rxq->q_depth) {
		nicif_err(nic_dev, drv, netdev, "Failed to fill rx wqe\n");
		err = -ENOMEM;
		goto rx_pkts_err;
	}
	pkts = hinic_rx_fill_buffers(rxq);
	if (!pkts) {
		nicif_err(nic_dev, drv, netdev, "Failed to allocate Rx buffer\n");
		err = -ENOMEM;
		goto rx_pkts_err;
	}

	return 0;

rx_pkts_err:
	rx_free_cqe(rxq);

rx_cqe_err:
	kfree(rxq->rx_info);

	return err;
}

static void hinic_free_rx_resources(struct hinic_rxq *rxq)
{
	hinic_rx_free_buffers(rxq);
	rx_free_cqe(rxq);
	kfree(rxq->rx_info);
}

int hinic_setup_all_rx_resources(struct net_device *netdev,
				 struct irq_info *msix_entries)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u16 i, q_id;
	int err;

	for (q_id = 0; q_id < nic_dev->num_qps; q_id++) {
		err = hinic_setup_rx_resources(&nic_dev->rxqs[q_id],
					       nic_dev->netdev,
					       &msix_entries[q_id]);
		if (err) {
			nicif_err(nic_dev, drv, netdev, "Failed to set up rxq resource\n");
			goto init_rxq_err;
		}
	}

	return 0;

init_rxq_err:
	for (i = 0; i < q_id; i++)
		hinic_free_rx_resources(&nic_dev->rxqs[i]);

	return err;
}

void hinic_free_all_rx_resources(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u16 q_id;

	for (q_id = 0; q_id < nic_dev->num_qps; q_id++)
		hinic_free_rx_resources(&nic_dev->rxqs[q_id]);
}

int hinic_alloc_rxqs(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	struct pci_dev *pdev = nic_dev->pdev;
	struct hinic_rxq *rxq;
	u16 num_rxqs = nic_dev->max_qps;
	u16 q_id;
	u64 rxq_size;

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

	nic_dev->rxqs = kzalloc(rxq_size, GFP_KERNEL);
	if (!nic_dev->rxqs)
		return -ENOMEM;

	for (q_id = 0; q_id < num_rxqs; q_id++) {
		rxq = &nic_dev->rxqs[q_id];
		rxq->netdev = netdev;
		rxq->dev = &pdev->dev;
		rxq->q_id = q_id;
		rxq->buf_len = nic_dev->rx_buff_len;
		rxq->rx_buff_shift = ilog2(nic_dev->rx_buff_len);
		rxq->dma_rx_buff_size = RX_BUFF_NUM_PER_PAGE *
					nic_dev->rx_buff_len;
		rxq->q_depth = nic_dev->rq_depth;
		rxq->q_mask = nic_dev->rq_depth - 1;

		rxq_stats_init(rxq);
	}

	return 0;
}

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

	hinic_clear_rss_config_user(nic_dev);
	kfree(nic_dev->rxqs);
}

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

	nic_dev->rss_hash_engine = HINIC_RSS_HASH_ENGINE_TYPE_XOR;

	nic_dev->rss_type.tcp_ipv6_ext = 1;
	nic_dev->rss_type.ipv6_ext = 1;
	nic_dev->rss_type.tcp_ipv6 = 1;
	nic_dev->rss_type.ipv6 = 1;
	nic_dev->rss_type.tcp_ipv4 = 1;
	nic_dev->rss_type.ipv4 = 1;
	nic_dev->rss_type.udp_ipv6 = 1;
	nic_dev->rss_type.udp_ipv4 = 1;
}

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

	if (!nic_dev->rss_indir_user)
		return;

	nicif_info(nic_dev, drv, netdev,
		   "Discard user configured Rx flow hash indirection\n");

	kfree(nic_dev->rss_indir_user);
	nic_dev->rss_indir_user = NULL;
}

static void hinic_maybe_reconfig_rss_indir(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int i;

	if (!nic_dev->rss_indir_user)
		return;

	if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags))
		goto discard_user_rss_indir;

	for (i = 0; i < HINIC_RSS_INDIR_SIZE; i++) {
		if (nic_dev->rss_indir_user[i] >= nic_dev->num_qps)
			goto discard_user_rss_indir;
	}

	return;

discard_user_rss_indir:
	hinic_set_default_rss_indir(netdev);
}

static void hinic_clear_rss_config_user(struct hinic_nic_dev *nic_dev)
{
	kfree(nic_dev->rss_hkey_user);

	nic_dev->rss_hkey_user_be = NULL;
	nic_dev->rss_hkey_user    = NULL;

	kfree(nic_dev->rss_indir_user);
	nic_dev->rss_indir_user = NULL;
}

static void hinic_fillout_indir_tbl(struct hinic_nic_dev *nic_dev,
				    u8 num_tcs, u32 *indir)
{
	u16 num_rss, tc_group_size;
	int i;

	if (num_tcs)
		tc_group_size = HINIC_RSS_INDIR_SIZE / num_tcs;
	else
		tc_group_size = HINIC_RSS_INDIR_SIZE;

	num_rss = nic_dev->num_rss;
	for (i = 0; i < HINIC_RSS_INDIR_SIZE; i++)
		indir[i] = (i / tc_group_size) * num_rss + i % num_rss;
}

static void hinic_rss_deinit(struct hinic_nic_dev *nic_dev)
{
	u8 prio_tc[HINIC_DCB_UP_MAX] = {0};

	hinic_rss_cfg(nic_dev->hwdev, 0, nic_dev->rss_tmpl_idx, 0, prio_tc);
}

int hinic_set_hw_rss_parameters(struct net_device *netdev, u8 rss_en, u8 num_tc,
				u8 *prio_tc)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 tmpl_idx = 0xFF;
	u8 default_rss_key[HINIC_RSS_KEY_SIZE] = {
			 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
			 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
			 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
			 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
			 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa};
	u32 *indir_tbl;
	u8 *hkey;
	int err;

	tmpl_idx = nic_dev->rss_tmpl_idx;

	/* RSS key */
	if (nic_dev->rss_hkey_user)
		hkey = nic_dev->rss_hkey_user;
	else
		hkey = default_rss_key;
	err = hinic_rss_set_template_tbl(nic_dev->hwdev, tmpl_idx, hkey);
	if (err)
		return err;

	hinic_maybe_reconfig_rss_indir(netdev);
	indir_tbl = kzalloc(sizeof(u32) * HINIC_RSS_INDIR_SIZE, GFP_KERNEL);
	if (!indir_tbl) {
		nicif_err(nic_dev, drv, netdev, "Failed to allocate set hw rss indir_tbl\n");
		return -ENOMEM;
	}

	if (nic_dev->rss_indir_user)
		memcpy(indir_tbl, nic_dev->rss_indir_user,
		       sizeof(u32) * HINIC_RSS_INDIR_SIZE);
	else
		hinic_fillout_indir_tbl(nic_dev, num_tc, indir_tbl);

	err = hinic_rss_set_indir_tbl(nic_dev->hwdev, tmpl_idx, indir_tbl);
	if (err)
		goto out;

	err = hinic_set_rss_type(nic_dev->hwdev, tmpl_idx, nic_dev->rss_type);
	if (err)
		goto out;

	err = hinic_rss_set_hash_engine(nic_dev->hwdev, tmpl_idx,
					nic_dev->rss_hash_engine);
	if (err)
		goto out;

	err = hinic_rss_cfg(nic_dev->hwdev, rss_en, tmpl_idx, num_tc, prio_tc);
	if (err)
		goto out;

	kfree(indir_tbl);
	return 0;

out:
	kfree(indir_tbl);
	return err;
}

static int hinic_rss_init(struct hinic_nic_dev *nic_dev)
{
	struct net_device *netdev = nic_dev->netdev;
	u32 *indir_tbl;
	u8 cos, num_tc = 0;
	u8 prio_tc[HINIC_DCB_UP_MAX] = {0};
	int err;

	if (test_bit(HINIC_DCB_ENABLE, &nic_dev->flags)) {
		num_tc = nic_dev->max_cos;
		for (cos = 0; cos < HINIC_DCB_COS_MAX; cos++) {
			if (cos < HINIC_DCB_COS_MAX - nic_dev->max_cos)
				prio_tc[cos] = nic_dev->max_cos - 1;
			else
				prio_tc[cos] = (HINIC_DCB_COS_MAX - 1) - cos;
		}
	} else {
		num_tc = 0;
	}

	indir_tbl = kzalloc(sizeof(u32) * HINIC_RSS_INDIR_SIZE, GFP_KERNEL);
	if (!indir_tbl)
		return -ENOMEM;

	if (nic_dev->rss_indir_user)
		memcpy(indir_tbl, nic_dev->rss_indir_user,
		       sizeof(u32) * HINIC_RSS_INDIR_SIZE);
	else
		hinic_fillout_indir_tbl(nic_dev, num_tc, indir_tbl);
	err = hinic_set_hw_rss_parameters(netdev, 1, num_tc, prio_tc);
	if (err) {
		kfree(indir_tbl);
		return err;
	}

	kfree(indir_tbl);
	return 0;
}

int hinic_update_hw_tc_map(struct net_device *netdev, u8 num_tc, u8 *prio_tc)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	u8 tmpl_idx = nic_dev->rss_tmpl_idx;

	/* RSS must be enable when dcb is enabled */
	return hinic_rss_cfg(nic_dev->hwdev, 1, tmpl_idx, num_tc, prio_tc);
}

int hinic_rx_configure(struct net_device *netdev)
{
	struct hinic_nic_dev *nic_dev = netdev_priv(netdev);
	int err;

	if (test_bit(HINIC_RSS_ENABLE, &nic_dev->flags)) {
		err = hinic_rss_init(nic_dev);
		if (err) {
			nicif_err(nic_dev, drv, netdev, "Failed to init rss\n");
			return -EFAULT;
		}
	}

	err = hinic_dcb_set_rq_iq_mapping(nic_dev->hwdev,
					  hinic_func_max_qnum(nic_dev->hwdev),
					  NULL);
	if (err) {
		nicif_err(nic_dev, drv, netdev, "Failed to set rq_iq mapping\n");
		goto set_rq_cos_mapping_err;
	}

	return 0;

set_rq_cos_mapping_err:
	if (test_bit(HINIC_RSS_ENABLE, &nic_dev->flags))
		hinic_rss_deinit(nic_dev);

	return err;
}

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

	if (test_bit(HINIC_RSS_ENABLE, &nic_dev->flags))
		hinic_rss_deinit(nic_dev);
}
