summaryrefslogblamecommitdiffstats
path: root/drivers/net/ethernet/qlogic/qed/qed_ll2.c
blob: 8e5cb7605b0fa92d8d778e2c16cae49cdc2570db (plain) (tree)
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968





































                                                                               
                    

                         
                     





































































































                                                                            




                                                                            




































































































































                                                                                                                                                                 
                           















                                                                               

                                                                      
 
























                                                                              










                                                                          
                           







































                                                                               












                                                                               









                                                      















































                                                                                  




























































                                                                                          



                                                                                












                                                                                
                                                                            




















                                                                               
                                                                      
 































































































































































































































































































































































































































                                                                                        
 



                                                                          
         







                                                  
 



















                                                                             









































                                                                         

                                                                              










                                                                            
                                                            

















                                                                     




                                                            
















                                                                    

























                                                                              
                                                            

























































































































































































                                                                                   
                                                      








                                                                            




                                                                            
                                                     






                                                            















































































































                                                                               

                                                             
















                                                                              

                                                             










































































































                                                                                
                                                                             
















                                                                           
                                                                   
































































                                                                                                                                          
                                                                        


























                                                                                          
                                                             
                                                                            




                                                                             
                                               

                                      
                                  





























                                                                                

                                                                     








                                                      




                                                                         
                                                             
                                                                         
                                                     







































































                                                                               


                                                                      





























                                                                                

                                                           













































































































































                                                                             
                                                   


                                         
                          






























                                                                          
                               















                                                                
                                         

















                                                                          
                                                                              


























                                                                              










                                                                              
















































                                                                                



                                                                  





















































                                                                              

                                                                          
                                                                  













                                                                                    
                                             























































                                                                                
/* QLogic qed NIC Driver
 *
 * Copyright (c) 2015 QLogic Corporation
 *
 * This software is available under the terms of the GNU General Public License
 * (GPL) Version 2, available from the file COPYING in the main directory of
 * this source tree.
 */

#include <linux/types.h>
#include <asm/byteorder.h>
#include <linux/dma-mapping.h>
#include <linux/if_vlan.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/slab.h>
#include <linux/stddef.h>
#include <linux/version.h>
#include <linux/workqueue.h>
#include <net/ipv6.h>
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/etherdevice.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/qed/qed_ll2_if.h>
#include "qed.h"
#include "qed_cxt.h"
#include "qed_dev_api.h"
#include "qed_hsi.h"
#include "qed_hw.h"
#include "qed_int.h"
#include "qed_ll2.h"
#include "qed_mcp.h"
#include "qed_ooo.h"
#include "qed_reg_addr.h"
#include "qed_sp.h"
#include "qed_roce.h"

#define QED_LL2_RX_REGISTERED(ll2)	((ll2)->rx_queue.b_cb_registred)
#define QED_LL2_TX_REGISTERED(ll2)	((ll2)->tx_queue.b_cb_registred)

#define QED_LL2_TX_SIZE (256)
#define QED_LL2_RX_SIZE (4096)

struct qed_cb_ll2_info {
	int rx_cnt;
	u32 rx_size;
	u8 handle;
	bool frags_mapped;

	/* Lock protecting LL2 buffer lists in sleepless context */
	spinlock_t lock;
	struct list_head list;

	const struct qed_ll2_cb_ops *cbs;
	void *cb_cookie;
};

struct qed_ll2_buffer {
	struct list_head list;
	void *data;
	dma_addr_t phys_addr;
};

static void qed_ll2b_complete_tx_packet(struct qed_hwfn *p_hwfn,
					u8 connection_handle,
					void *cookie,
					dma_addr_t first_frag_addr,
					bool b_last_fragment,
					bool b_last_packet)
{
	struct qed_dev *cdev = p_hwfn->cdev;
	struct sk_buff *skb = cookie;

	/* All we need to do is release the mapping */
	dma_unmap_single(&p_hwfn->cdev->pdev->dev, first_frag_addr,
			 skb_headlen(skb), DMA_TO_DEVICE);

	if (cdev->ll2->cbs && cdev->ll2->cbs->tx_cb)
		cdev->ll2->cbs->tx_cb(cdev->ll2->cb_cookie, skb,
				      b_last_fragment);

	if (cdev->ll2->frags_mapped)
		/* Case where mapped frags were received, need to
		 * free skb with nr_frags marked as 0
		 */
		skb_shinfo(skb)->nr_frags = 0;

	dev_kfree_skb_any(skb);
}

static int qed_ll2_alloc_buffer(struct qed_dev *cdev,
				u8 **data, dma_addr_t *phys_addr)
{
	*data = kmalloc(cdev->ll2->rx_size, GFP_ATOMIC);
	if (!(*data)) {
		DP_INFO(cdev, "Failed to allocate LL2 buffer data\n");
		return -ENOMEM;
	}

	*phys_addr = dma_map_single(&cdev->pdev->dev,
				    ((*data) + NET_SKB_PAD),
				    cdev->ll2->rx_size, DMA_FROM_DEVICE);
	if (dma_mapping_error(&cdev->pdev->dev, *phys_addr)) {
		DP_INFO(cdev, "Failed to map LL2 buffer data\n");
		kfree((*data));
		return -ENOMEM;
	}

	return 0;
}

static int qed_ll2_dealloc_buffer(struct qed_dev *cdev,
				 struct qed_ll2_buffer *buffer)
{
	spin_lock_bh(&cdev->ll2->lock);

	dma_unmap_single(&cdev->pdev->dev, buffer->phys_addr,
			 cdev->ll2->rx_size, DMA_FROM_DEVICE);
	kfree(buffer->data);
	list_del(&buffer->list);

	cdev->ll2->rx_cnt--;
	if (!cdev->ll2->rx_cnt)
		DP_INFO(cdev, "All LL2 entries were removed\n");

	spin_unlock_bh(&cdev->ll2->lock);

	return 0;
}

static void qed_ll2_kill_buffers(struct qed_dev *cdev)
{
	struct qed_ll2_buffer *buffer, *tmp_buffer;

	list_for_each_entry_safe(buffer, tmp_buffer, &cdev->ll2->list, list)
		qed_ll2_dealloc_buffer(cdev, buffer);
}

static void qed_ll2b_complete_rx_packet(struct qed_hwfn *p_hwfn,
					u8 connection_handle,
					struct qed_ll2_rx_packet *p_pkt,
					struct core_rx_fast_path_cqe *p_cqe,
					bool b_last_packet)
{
	u16 packet_length = le16_to_cpu(p_cqe->packet_length);
	struct qed_ll2_buffer *buffer = p_pkt->cookie;
	struct qed_dev *cdev = p_hwfn->cdev;
	u16 vlan = le16_to_cpu(p_cqe->vlan);
	u32 opaque_data_0, opaque_data_1;
	u8 pad = p_cqe->placement_offset;
	dma_addr_t new_phys_addr;
	struct sk_buff *skb;
	bool reuse = false;
	int rc = -EINVAL;
	u8 *new_data;

	opaque_data_0 = le32_to_cpu(p_cqe->opaque_data.data[0]);
	opaque_data_1 = le32_to_cpu(p_cqe->opaque_data.data[1]);

	DP_VERBOSE(p_hwfn,
		   (NETIF_MSG_RX_STATUS | QED_MSG_STORAGE | NETIF_MSG_PKTDATA),
		   "Got an LL2 Rx completion: [Buffer at phys 0x%llx, offset 0x%02x] Length 0x%04x Parse_flags 0x%04x vlan 0x%04x Opaque data [0x%08x:0x%08x]\n",
		   (u64)p_pkt->rx_buf_addr, pad, packet_length,
		   le16_to_cpu(p_cqe->parse_flags.flags), vlan,
		   opaque_data_0, opaque_data_1);

	if ((cdev->dp_module & NETIF_MSG_PKTDATA) && buffer->data) {
		print_hex_dump(KERN_INFO, "",
			       DUMP_PREFIX_OFFSET, 16, 1,
			       buffer->data, packet_length, false);
	}

	/* Determine if data is valid */
	if (packet_length < ETH_HLEN)
		reuse = true;

	/* Allocate a replacement for buffer; Reuse upon failure */
	if (!reuse)
		rc = qed_ll2_alloc_buffer(p_hwfn->cdev, &new_data,
					  &new_phys_addr);

	/* If need to reuse or there's no replacement buffer, repost this */
	if (rc)
		goto out_post;

	skb = build_skb(buffer->data, 0);
	if (!skb) {
		rc = -ENOMEM;
		goto out_post;
	}

	pad += NET_SKB_PAD;
	skb_reserve(skb, pad);
	skb_put(skb, packet_length);
	skb_checksum_none_assert(skb);

	/* Get parital ethernet information instead of eth_type_trans(),
	 * Since we don't have an associated net_device.
	 */
	skb_reset_mac_header(skb);
	skb->protocol = eth_hdr(skb)->h_proto;

	/* Pass SKB onward */
	if (cdev->ll2->cbs && cdev->ll2->cbs->rx_cb) {
		if (vlan)
			__vlan_hwaccel_put_tag(skb, htons(ETH_P_8021Q), vlan);
		cdev->ll2->cbs->rx_cb(cdev->ll2->cb_cookie, skb,
				      opaque_data_0, opaque_data_1);
	}

	/* Update Buffer information and update FW producer */
	buffer->data = new_data;
	buffer->phys_addr = new_phys_addr;

out_post:
	rc = qed_ll2_post_rx_buffer(QED_LEADING_HWFN(cdev), cdev->ll2->handle,
				    buffer->phys_addr, 0,  buffer, 1);

	if (rc)
		qed_ll2_dealloc_buffer(cdev, buffer);
}

static struct qed_ll2_info *__qed_ll2_handle_sanity(struct qed_hwfn *p_hwfn,
						    u8 connection_handle,
						    bool b_lock,
						    bool b_only_active)
{
	struct qed_ll2_info *p_ll2_conn, *p_ret = NULL;

	if (connection_handle >= QED_MAX_NUM_OF_LL2_CONNECTIONS)
		return NULL;

	if (!p_hwfn->p_ll2_info)
		return NULL;

	p_ll2_conn = &p_hwfn->p_ll2_info[connection_handle];

	if (b_only_active) {
		if (b_lock)
			mutex_lock(&p_ll2_conn->mutex);
		if (p_ll2_conn->b_active)
			p_ret = p_ll2_conn;
		if (b_lock)
			mutex_unlock(&p_ll2_conn->mutex);
	} else {
		p_ret = p_ll2_conn;
	}

	return p_ret;
}

static struct qed_ll2_info *qed_ll2_handle_sanity(struct qed_hwfn *p_hwfn,
						  u8 connection_handle)
{
	return __qed_ll2_handle_sanity(p_hwfn, connection_handle, false, true);
}

static struct qed_ll2_info *qed_ll2_handle_sanity_lock(struct qed_hwfn *p_hwfn,
						       u8 connection_handle)
{
	return __qed_ll2_handle_sanity(p_hwfn, connection_handle, true, true);
}

static struct qed_ll2_info *qed_ll2_handle_sanity_inactive(struct qed_hwfn
							   *p_hwfn,
							   u8 connection_handle)
{
	return __qed_ll2_handle_sanity(p_hwfn, connection_handle, false, false);
}

static void qed_ll2_txq_flush(struct qed_hwfn *p_hwfn, u8 connection_handle)
{
	bool b_last_packet = false, b_last_frag = false;
	struct qed_ll2_tx_packet *p_pkt = NULL;
	struct qed_ll2_info *p_ll2_conn;
	struct qed_ll2_tx_queue *p_tx;
	dma_addr_t tx_frag;

	p_ll2_conn = qed_ll2_handle_sanity_inactive(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return;

	p_tx = &p_ll2_conn->tx_queue;

	while (!list_empty(&p_tx->active_descq)) {
		p_pkt = list_first_entry(&p_tx->active_descq,
					 struct qed_ll2_tx_packet, list_entry);
		if (!p_pkt)
			break;

		list_del(&p_pkt->list_entry);
		b_last_packet = list_empty(&p_tx->active_descq);
		list_add_tail(&p_pkt->list_entry, &p_tx->free_descq);
		if (p_ll2_conn->conn_type == QED_LL2_TYPE_ISCSI_OOO) {
			struct qed_ooo_buffer *p_buffer;

			p_buffer = (struct qed_ooo_buffer *)p_pkt->cookie;
			qed_ooo_put_free_buffer(p_hwfn, p_hwfn->p_ooo_info,
						p_buffer);
		} else {
			p_tx->cur_completing_packet = *p_pkt;
			p_tx->cur_completing_bd_idx = 1;
			b_last_frag =
				p_tx->cur_completing_bd_idx == p_pkt->bd_used;
			tx_frag = p_pkt->bds_set[0].tx_frag;
			if (p_ll2_conn->gsi_enable)
				qed_ll2b_release_tx_gsi_packet(p_hwfn,
							       p_ll2_conn->
							       my_id,
							       p_pkt->cookie,
							       tx_frag,
							       b_last_frag,
							       b_last_packet);
			else
				qed_ll2b_complete_tx_packet(p_hwfn,
							    p_ll2_conn->my_id,
							    p_pkt->cookie,
							    tx_frag,
							    b_last_frag,
							    b_last_packet);
		}
	}
}

static int qed_ll2_txq_completion(struct qed_hwfn *p_hwfn, void *p_cookie)
{
	struct qed_ll2_info *p_ll2_conn = p_cookie;
	struct qed_ll2_tx_queue *p_tx = &p_ll2_conn->tx_queue;
	u16 new_idx = 0, num_bds = 0, num_bds_in_packet = 0;
	struct qed_ll2_tx_packet *p_pkt;
	bool b_last_frag = false;
	unsigned long flags;
	dma_addr_t tx_frag;
	int rc = -EINVAL;

	spin_lock_irqsave(&p_tx->lock, flags);
	if (p_tx->b_completing_packet) {
		rc = -EBUSY;
		goto out;
	}

	new_idx = le16_to_cpu(*p_tx->p_fw_cons);
	num_bds = ((s16)new_idx - (s16)p_tx->bds_idx);
	while (num_bds) {
		if (list_empty(&p_tx->active_descq))
			goto out;

		p_pkt = list_first_entry(&p_tx->active_descq,
					 struct qed_ll2_tx_packet, list_entry);
		if (!p_pkt)
			goto out;

		p_tx->b_completing_packet = true;
		p_tx->cur_completing_packet = *p_pkt;
		num_bds_in_packet = p_pkt->bd_used;
		list_del(&p_pkt->list_entry);

		if (num_bds < num_bds_in_packet) {
			DP_NOTICE(p_hwfn,
				  "Rest of BDs does not cover whole packet\n");
			goto out;
		}

		num_bds -= num_bds_in_packet;
		p_tx->bds_idx += num_bds_in_packet;
		while (num_bds_in_packet--)
			qed_chain_consume(&p_tx->txq_chain);

		p_tx->cur_completing_bd_idx = 1;
		b_last_frag = p_tx->cur_completing_bd_idx == p_pkt->bd_used;
		list_add_tail(&p_pkt->list_entry, &p_tx->free_descq);

		spin_unlock_irqrestore(&p_tx->lock, flags);
		tx_frag = p_pkt->bds_set[0].tx_frag;
		if (p_ll2_conn->gsi_enable)
			qed_ll2b_complete_tx_gsi_packet(p_hwfn,
							p_ll2_conn->my_id,
							p_pkt->cookie,
							tx_frag,
							b_last_frag, !num_bds);
		else
			qed_ll2b_complete_tx_packet(p_hwfn,
						    p_ll2_conn->my_id,
						    p_pkt->cookie,
						    tx_frag,
						    b_last_frag, !num_bds);
		spin_lock_irqsave(&p_tx->lock, flags);
	}

	p_tx->b_completing_packet = false;
	rc = 0;
out:
	spin_unlock_irqrestore(&p_tx->lock, flags);
	return rc;
}

static int
qed_ll2_rxq_completion_gsi(struct qed_hwfn *p_hwfn,
			   struct qed_ll2_info *p_ll2_info,
			   union core_rx_cqe_union *p_cqe,
			   unsigned long lock_flags, bool b_last_cqe)
{
	struct qed_ll2_rx_queue *p_rx = &p_ll2_info->rx_queue;
	struct qed_ll2_rx_packet *p_pkt = NULL;
	u16 packet_length, parse_flags, vlan;
	u32 src_mac_addrhi;
	u16 src_mac_addrlo;

	if (!list_empty(&p_rx->active_descq))
		p_pkt = list_first_entry(&p_rx->active_descq,
					 struct qed_ll2_rx_packet, list_entry);
	if (!p_pkt) {
		DP_NOTICE(p_hwfn,
			  "GSI Rx completion but active_descq is empty\n");
		return -EIO;
	}

	list_del(&p_pkt->list_entry);
	parse_flags = le16_to_cpu(p_cqe->rx_cqe_gsi.parse_flags.flags);
	packet_length = le16_to_cpu(p_cqe->rx_cqe_gsi.data_length);
	vlan = le16_to_cpu(p_cqe->rx_cqe_gsi.vlan);
	src_mac_addrhi = le32_to_cpu(p_cqe->rx_cqe_gsi.src_mac_addrhi);
	src_mac_addrlo = le16_to_cpu(p_cqe->rx_cqe_gsi.src_mac_addrlo);
	if (qed_chain_consume(&p_rx->rxq_chain) != p_pkt->rxq_bd)
		DP_NOTICE(p_hwfn,
			  "Mismatch between active_descq and the LL2 Rx chain\n");
	list_add_tail(&p_pkt->list_entry, &p_rx->free_descq);

	spin_unlock_irqrestore(&p_rx->lock, lock_flags);
	qed_ll2b_complete_rx_gsi_packet(p_hwfn,
					p_ll2_info->my_id,
					p_pkt->cookie,
					p_pkt->rx_buf_addr,
					packet_length,
					p_cqe->rx_cqe_gsi.data_length_error,
					parse_flags,
					vlan,
					src_mac_addrhi,
					src_mac_addrlo, b_last_cqe);
	spin_lock_irqsave(&p_rx->lock, lock_flags);

	return 0;
}

static int qed_ll2_rxq_completion_reg(struct qed_hwfn *p_hwfn,
				      struct qed_ll2_info *p_ll2_conn,
				      union core_rx_cqe_union *p_cqe,
				      unsigned long lock_flags,
				      bool b_last_cqe)
{
	struct qed_ll2_rx_queue *p_rx = &p_ll2_conn->rx_queue;
	struct qed_ll2_rx_packet *p_pkt = NULL;

	if (!list_empty(&p_rx->active_descq))
		p_pkt = list_first_entry(&p_rx->active_descq,
					 struct qed_ll2_rx_packet, list_entry);
	if (!p_pkt) {
		DP_NOTICE(p_hwfn,
			  "LL2 Rx completion but active_descq is empty\n");
		return -EIO;
	}
	list_del(&p_pkt->list_entry);

	if (qed_chain_consume(&p_rx->rxq_chain) != p_pkt->rxq_bd)
		DP_NOTICE(p_hwfn,
			  "Mismatch between active_descq and the LL2 Rx chain\n");
	list_add_tail(&p_pkt->list_entry, &p_rx->free_descq);

	spin_unlock_irqrestore(&p_rx->lock, lock_flags);
	qed_ll2b_complete_rx_packet(p_hwfn, p_ll2_conn->my_id,
				    p_pkt, &p_cqe->rx_cqe_fp, b_last_cqe);
	spin_lock_irqsave(&p_rx->lock, lock_flags);

	return 0;
}

static int qed_ll2_rxq_completion(struct qed_hwfn *p_hwfn, void *cookie)
{
	struct qed_ll2_info *p_ll2_conn = cookie;
	struct qed_ll2_rx_queue *p_rx = &p_ll2_conn->rx_queue;
	union core_rx_cqe_union *cqe = NULL;
	u16 cq_new_idx = 0, cq_old_idx = 0;
	unsigned long flags = 0;
	int rc = 0;

	spin_lock_irqsave(&p_rx->lock, flags);
	cq_new_idx = le16_to_cpu(*p_rx->p_fw_cons);
	cq_old_idx = qed_chain_get_cons_idx(&p_rx->rcq_chain);

	while (cq_new_idx != cq_old_idx) {
		bool b_last_cqe = (cq_new_idx == cq_old_idx);

		cqe = qed_chain_consume(&p_rx->rcq_chain);
		cq_old_idx = qed_chain_get_cons_idx(&p_rx->rcq_chain);

		DP_VERBOSE(p_hwfn,
			   QED_MSG_LL2,
			   "LL2 [sw. cons %04x, fw. at %04x] - Got Packet of type %02x\n",
			   cq_old_idx, cq_new_idx, cqe->rx_cqe_sp.type);

		switch (cqe->rx_cqe_sp.type) {
		case CORE_RX_CQE_TYPE_SLOW_PATH:
			DP_NOTICE(p_hwfn, "LL2 - unexpected Rx CQE slowpath\n");
			rc = -EINVAL;
			break;
		case CORE_RX_CQE_TYPE_GSI_OFFLOAD:
			rc = qed_ll2_rxq_completion_gsi(p_hwfn, p_ll2_conn,
							cqe, flags, b_last_cqe);
			break;
		case CORE_RX_CQE_TYPE_REGULAR:
			rc = qed_ll2_rxq_completion_reg(p_hwfn, p_ll2_conn,
							cqe, flags, b_last_cqe);
			break;
		default:
			rc = -EIO;
		}
	}

	spin_unlock_irqrestore(&p_rx->lock, flags);
	return rc;
}

static void qed_ll2_rxq_flush(struct qed_hwfn *p_hwfn, u8 connection_handle)
{
	struct qed_ll2_info *p_ll2_conn = NULL;
	struct qed_ll2_rx_packet *p_pkt = NULL;
	struct qed_ll2_rx_queue *p_rx;

	p_ll2_conn = qed_ll2_handle_sanity_inactive(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return;

	p_rx = &p_ll2_conn->rx_queue;

	while (!list_empty(&p_rx->active_descq)) {
		dma_addr_t rx_buf_addr;
		void *cookie;
		bool b_last;

		p_pkt = list_first_entry(&p_rx->active_descq,
					 struct qed_ll2_rx_packet, list_entry);
		if (!p_pkt)
			break;

		list_move_tail(&p_pkt->list_entry, &p_rx->free_descq);

		if (p_ll2_conn->conn_type == QED_LL2_TYPE_ISCSI_OOO) {
			struct qed_ooo_buffer *p_buffer;

			p_buffer = (struct qed_ooo_buffer *)p_pkt->cookie;
			qed_ooo_put_free_buffer(p_hwfn, p_hwfn->p_ooo_info,
						p_buffer);
		} else {
			rx_buf_addr = p_pkt->rx_buf_addr;
			cookie = p_pkt->cookie;

			b_last = list_empty(&p_rx->active_descq);
		}
	}
}

#if IS_ENABLED(CONFIG_QED_ISCSI)
static u8 qed_ll2_convert_rx_parse_to_tx_flags(u16 parse_flags)
{
	u8 bd_flags = 0;

	if (GET_FIELD(parse_flags, PARSING_AND_ERR_FLAGS_TAG8021QEXIST))
		SET_FIELD(bd_flags, CORE_TX_BD_FLAGS_VLAN_INSERTION, 1);

	return bd_flags;
}

static int qed_ll2_lb_rxq_handler(struct qed_hwfn *p_hwfn,
				  struct qed_ll2_info *p_ll2_conn)
{
	struct qed_ll2_rx_queue *p_rx = &p_ll2_conn->rx_queue;
	u16 packet_length = 0, parse_flags = 0, vlan = 0;
	struct qed_ll2_rx_packet *p_pkt = NULL;
	u32 num_ooo_add_to_peninsula = 0, cid;
	union core_rx_cqe_union *cqe = NULL;
	u16 cq_new_idx = 0, cq_old_idx = 0;
	struct qed_ooo_buffer *p_buffer;
	struct ooo_opaque *iscsi_ooo;
	u8 placement_offset = 0;
	u8 cqe_type;

	cq_new_idx = le16_to_cpu(*p_rx->p_fw_cons);
	cq_old_idx = qed_chain_get_cons_idx(&p_rx->rcq_chain);
	if (cq_new_idx == cq_old_idx)
		return 0;

	while (cq_new_idx != cq_old_idx) {
		struct core_rx_fast_path_cqe *p_cqe_fp;

		cqe = qed_chain_consume(&p_rx->rcq_chain);
		cq_old_idx = qed_chain_get_cons_idx(&p_rx->rcq_chain);
		cqe_type = cqe->rx_cqe_sp.type;

		if (cqe_type != CORE_RX_CQE_TYPE_REGULAR) {
			DP_NOTICE(p_hwfn,
				  "Got a non-regular LB LL2 completion [type 0x%02x]\n",
				  cqe_type);
			return -EINVAL;
		}
		p_cqe_fp = &cqe->rx_cqe_fp;

		placement_offset = p_cqe_fp->placement_offset;
		parse_flags = le16_to_cpu(p_cqe_fp->parse_flags.flags);
		packet_length = le16_to_cpu(p_cqe_fp->packet_length);
		vlan = le16_to_cpu(p_cqe_fp->vlan);
		iscsi_ooo = (struct ooo_opaque *)&p_cqe_fp->opaque_data;
		qed_ooo_save_history_entry(p_hwfn, p_hwfn->p_ooo_info,
					   iscsi_ooo);
		cid = le32_to_cpu(iscsi_ooo->cid);

		/* Process delete isle first */
		if (iscsi_ooo->drop_size)
			qed_ooo_delete_isles(p_hwfn, p_hwfn->p_ooo_info, cid,
					     iscsi_ooo->drop_isle,
					     iscsi_ooo->drop_size);

		if (iscsi_ooo->ooo_opcode == TCP_EVENT_NOP)
			continue;

		/* Now process create/add/join isles */
		if (list_empty(&p_rx->active_descq)) {
			DP_NOTICE(p_hwfn,
				  "LL2 OOO RX chain has no submitted buffers\n"
				  );
			return -EIO;
		}

		p_pkt = list_first_entry(&p_rx->active_descq,
					 struct qed_ll2_rx_packet, list_entry);

		if ((iscsi_ooo->ooo_opcode == TCP_EVENT_ADD_NEW_ISLE) ||
		    (iscsi_ooo->ooo_opcode == TCP_EVENT_ADD_ISLE_RIGHT) ||
		    (iscsi_ooo->ooo_opcode == TCP_EVENT_ADD_ISLE_LEFT) ||
		    (iscsi_ooo->ooo_opcode == TCP_EVENT_ADD_PEN) ||
		    (iscsi_ooo->ooo_opcode == TCP_EVENT_JOIN)) {
			if (!p_pkt) {
				DP_NOTICE(p_hwfn,
					  "LL2 OOO RX packet is not valid\n");
				return -EIO;
			}
			list_del(&p_pkt->list_entry);
			p_buffer = (struct qed_ooo_buffer *)p_pkt->cookie;
			p_buffer->packet_length = packet_length;
			p_buffer->parse_flags = parse_flags;
			p_buffer->vlan = vlan;
			p_buffer->placement_offset = placement_offset;
			qed_chain_consume(&p_rx->rxq_chain);
			list_add_tail(&p_pkt->list_entry, &p_rx->free_descq);

			switch (iscsi_ooo->ooo_opcode) {
			case TCP_EVENT_ADD_NEW_ISLE:
				qed_ooo_add_new_isle(p_hwfn,
						     p_hwfn->p_ooo_info,
						     cid,
						     iscsi_ooo->ooo_isle,
						     p_buffer);
				break;
			case TCP_EVENT_ADD_ISLE_RIGHT:
				qed_ooo_add_new_buffer(p_hwfn,
						       p_hwfn->p_ooo_info,
						       cid,
						       iscsi_ooo->ooo_isle,
						       p_buffer,
						       QED_OOO_RIGHT_BUF);
				break;
			case TCP_EVENT_ADD_ISLE_LEFT:
				qed_ooo_add_new_buffer(p_hwfn,
						       p_hwfn->p_ooo_info,
						       cid,
						       iscsi_ooo->ooo_isle,
						       p_buffer,
						       QED_OOO_LEFT_BUF);
				break;
			case TCP_EVENT_JOIN:
				qed_ooo_add_new_buffer(p_hwfn,
						       p_hwfn->p_ooo_info,
						       cid,
						       iscsi_ooo->ooo_isle +
						       1,
						       p_buffer,
						       QED_OOO_LEFT_BUF);
				qed_ooo_join_isles(p_hwfn,
						   p_hwfn->p_ooo_info,
						   cid, iscsi_ooo->ooo_isle);
				break;
			case TCP_EVENT_ADD_PEN:
				num_ooo_add_to_peninsula++;
				qed_ooo_put_ready_buffer(p_hwfn,
							 p_hwfn->p_ooo_info,
							 p_buffer, true);
				break;
			}
		} else {
			DP_NOTICE(p_hwfn,
				  "Unexpected event (%d) TX OOO completion\n",
				  iscsi_ooo->ooo_opcode);
		}
	}

	return 0;
}

static void
qed_ooo_submit_tx_buffers(struct qed_hwfn *p_hwfn,
			  struct qed_ll2_info *p_ll2_conn)
{
	struct qed_ooo_buffer *p_buffer;
	int rc;
	u16 l4_hdr_offset_w;
	dma_addr_t first_frag;
	u16 parse_flags;
	u8 bd_flags;

	/* Submit Tx buffers here */
	while ((p_buffer = qed_ooo_get_ready_buffer(p_hwfn,
						    p_hwfn->p_ooo_info))) {
		l4_hdr_offset_w = 0;
		bd_flags = 0;

		first_frag = p_buffer->rx_buffer_phys_addr +
			     p_buffer->placement_offset;
		parse_flags = p_buffer->parse_flags;
		bd_flags = qed_ll2_convert_rx_parse_to_tx_flags(parse_flags);
		SET_FIELD(bd_flags, CORE_TX_BD_FLAGS_FORCE_VLAN_MODE, 1);
		SET_FIELD(bd_flags, CORE_TX_BD_FLAGS_L4_PROTOCOL, 1);

		rc = qed_ll2_prepare_tx_packet(p_hwfn, p_ll2_conn->my_id, 1,
					       p_buffer->vlan, bd_flags,
					       l4_hdr_offset_w,
					       p_ll2_conn->tx_dest, 0,
					       first_frag,
					       p_buffer->packet_length,
					       p_buffer, true);
		if (rc) {
			qed_ooo_put_ready_buffer(p_hwfn, p_hwfn->p_ooo_info,
						 p_buffer, false);
			break;
		}
	}
}

static void
qed_ooo_submit_rx_buffers(struct qed_hwfn *p_hwfn,
			  struct qed_ll2_info *p_ll2_conn)
{
	struct qed_ooo_buffer *p_buffer;
	int rc;

	while ((p_buffer = qed_ooo_get_free_buffer(p_hwfn,
						   p_hwfn->p_ooo_info))) {
		rc = qed_ll2_post_rx_buffer(p_hwfn,
					    p_ll2_conn->my_id,
					    p_buffer->rx_buffer_phys_addr,
					    0, p_buffer, true);
		if (rc) {
			qed_ooo_put_free_buffer(p_hwfn,
						p_hwfn->p_ooo_info, p_buffer);
			break;
		}
	}
}

static int qed_ll2_lb_rxq_completion(struct qed_hwfn *p_hwfn, void *p_cookie)
{
	struct qed_ll2_info *p_ll2_conn = (struct qed_ll2_info *)p_cookie;
	int rc;

	rc = qed_ll2_lb_rxq_handler(p_hwfn, p_ll2_conn);
	if (rc)
		return rc;

	qed_ooo_submit_rx_buffers(p_hwfn, p_ll2_conn);
	qed_ooo_submit_tx_buffers(p_hwfn, p_ll2_conn);

	return 0;
}

static int qed_ll2_lb_txq_completion(struct qed_hwfn *p_hwfn, void *p_cookie)
{
	struct qed_ll2_info *p_ll2_conn = (struct qed_ll2_info *)p_cookie;
	struct qed_ll2_tx_queue *p_tx = &p_ll2_conn->tx_queue;
	struct qed_ll2_tx_packet *p_pkt = NULL;
	struct qed_ooo_buffer *p_buffer;
	bool b_dont_submit_rx = false;
	u16 new_idx = 0, num_bds = 0;
	int rc;

	new_idx = le16_to_cpu(*p_tx->p_fw_cons);
	num_bds = ((s16)new_idx - (s16)p_tx->bds_idx);

	if (!num_bds)
		return 0;

	while (num_bds) {
		if (list_empty(&p_tx->active_descq))
			return -EINVAL;

		p_pkt = list_first_entry(&p_tx->active_descq,
					 struct qed_ll2_tx_packet, list_entry);
		if (!p_pkt)
			return -EINVAL;

		if (p_pkt->bd_used != 1) {
			DP_NOTICE(p_hwfn,
				  "Unexpectedly many BDs(%d) in TX OOO completion\n",
				  p_pkt->bd_used);
			return -EINVAL;
		}

		list_del(&p_pkt->list_entry);

		num_bds--;
		p_tx->bds_idx++;
		qed_chain_consume(&p_tx->txq_chain);

		p_buffer = (struct qed_ooo_buffer *)p_pkt->cookie;
		list_add_tail(&p_pkt->list_entry, &p_tx->free_descq);

		if (b_dont_submit_rx) {
			qed_ooo_put_free_buffer(p_hwfn, p_hwfn->p_ooo_info,
						p_buffer);
			continue;
		}

		rc = qed_ll2_post_rx_buffer(p_hwfn, p_ll2_conn->my_id,
					    p_buffer->rx_buffer_phys_addr, 0,
					    p_buffer, true);
		if (rc != 0) {
			qed_ooo_put_free_buffer(p_hwfn,
						p_hwfn->p_ooo_info, p_buffer);
			b_dont_submit_rx = true;
		}
	}

	qed_ooo_submit_tx_buffers(p_hwfn, p_ll2_conn);

	return 0;
}

static int
qed_ll2_acquire_connection_ooo(struct qed_hwfn *p_hwfn,
			       struct qed_ll2_info *p_ll2_info,
			       u16 rx_num_ooo_buffers, u16 mtu)
{
	struct qed_ooo_buffer *p_buf = NULL;
	void *p_virt;
	u16 buf_idx;
	int rc = 0;

	if (p_ll2_info->conn_type != QED_LL2_TYPE_ISCSI_OOO)
		return rc;

	if (!rx_num_ooo_buffers)
		return -EINVAL;

	for (buf_idx = 0; buf_idx < rx_num_ooo_buffers; buf_idx++) {
		p_buf = kzalloc(sizeof(*p_buf), GFP_KERNEL);
		if (!p_buf) {
			rc = -ENOMEM;
			goto out;
		}

		p_buf->rx_buffer_size = mtu + 26 + ETH_CACHE_LINE_SIZE;
		p_buf->rx_buffer_size = (p_buf->rx_buffer_size +
					 ETH_CACHE_LINE_SIZE - 1) &
					~(ETH_CACHE_LINE_SIZE - 1);
		p_virt = dma_alloc_coherent(&p_hwfn->cdev->pdev->dev,
					    p_buf->rx_buffer_size,
					    &p_buf->rx_buffer_phys_addr,
					    GFP_KERNEL);
		if (!p_virt) {
			kfree(p_buf);
			rc = -ENOMEM;
			goto out;
		}

		p_buf->rx_buffer_virt_addr = p_virt;
		qed_ooo_put_free_buffer(p_hwfn, p_hwfn->p_ooo_info, p_buf);
	}

	DP_VERBOSE(p_hwfn, QED_MSG_LL2,
		   "Allocated [%04x] LL2 OOO buffers [each of size 0x%08x]\n",
		   rx_num_ooo_buffers, p_buf->rx_buffer_size);

out:
	return rc;
}

static void
qed_ll2_establish_connection_ooo(struct qed_hwfn *p_hwfn,
				 struct qed_ll2_info *p_ll2_conn)
{
	if (p_ll2_conn->conn_type != QED_LL2_TYPE_ISCSI_OOO)
		return;

	qed_ooo_release_all_isles(p_hwfn, p_hwfn->p_ooo_info);
	qed_ooo_submit_rx_buffers(p_hwfn, p_ll2_conn);
}

static void qed_ll2_release_connection_ooo(struct qed_hwfn *p_hwfn,
					   struct qed_ll2_info *p_ll2_conn)
{
	struct qed_ooo_buffer *p_buffer;

	if (p_ll2_conn->conn_type != QED_LL2_TYPE_ISCSI_OOO)
		return;

	qed_ooo_release_all_isles(p_hwfn, p_hwfn->p_ooo_info);
	while ((p_buffer = qed_ooo_get_free_buffer(p_hwfn,
						   p_hwfn->p_ooo_info))) {
		dma_free_coherent(&p_hwfn->cdev->pdev->dev,
				  p_buffer->rx_buffer_size,
				  p_buffer->rx_buffer_virt_addr,
				  p_buffer->rx_buffer_phys_addr);
		kfree(p_buffer);
	}
}

static void qed_ll2_stop_ooo(struct qed_dev *cdev)
{
	struct qed_hwfn *hwfn = QED_LEADING_HWFN(cdev);
	u8 *handle = &hwfn->pf_params.iscsi_pf_params.ll2_ooo_queue_id;

	DP_VERBOSE(cdev, QED_MSG_STORAGE, "Stopping LL2 OOO queue [%02x]\n",
		   *handle);

	qed_ll2_terminate_connection(hwfn, *handle);
	qed_ll2_release_connection(hwfn, *handle);
	*handle = QED_LL2_UNUSED_HANDLE;
}

static int qed_ll2_start_ooo(struct qed_dev *cdev,
			     struct qed_ll2_params *params)
{
	struct qed_hwfn *hwfn = QED_LEADING_HWFN(cdev);
	u8 *handle = &hwfn->pf_params.iscsi_pf_params.ll2_ooo_queue_id;
	struct qed_ll2_info *ll2_info;
	int rc;

	ll2_info = kzalloc(sizeof(*ll2_info), GFP_KERNEL);
	if (!ll2_info)
		return -ENOMEM;
	ll2_info->conn_type = QED_LL2_TYPE_ISCSI_OOO;
	ll2_info->mtu = params->mtu;
	ll2_info->rx_drop_ttl0_flg = params->drop_ttl0_packets;
	ll2_info->rx_vlan_removal_en = params->rx_vlan_stripping;
	ll2_info->tx_tc = OOO_LB_TC;
	ll2_info->tx_dest = CORE_TX_DEST_LB;

	rc = qed_ll2_acquire_connection(hwfn, ll2_info,
					QED_LL2_RX_SIZE, QED_LL2_TX_SIZE,
					handle);
	kfree(ll2_info);
	if (rc) {
		DP_INFO(cdev, "Failed to acquire LL2 OOO connection\n");
		goto out;
	}

	rc = qed_ll2_establish_connection(hwfn, *handle);
	if (rc) {
		DP_INFO(cdev, "Failed to establist LL2 OOO connection\n");
		goto fail;
	}

	return 0;

fail:
	qed_ll2_release_connection(hwfn, *handle);
out:
	*handle = QED_LL2_UNUSED_HANDLE;
	return rc;
}
#else /* IS_ENABLED(CONFIG_QED_ISCSI) */
static int qed_ll2_lb_rxq_completion(struct qed_hwfn *p_hwfn,
				     void *p_cookie) { return -EINVAL; }
static int qed_ll2_lb_txq_completion(struct qed_hwfn *p_hwfn,
				     void *p_cookie) { return -EINVAL; }
static inline int
qed_ll2_acquire_connection_ooo(struct qed_hwfn *p_hwfn,
			       struct qed_ll2_info *p_ll2_info,
			       u16 rx_num_ooo_buffers, u16 mtu) { return 0; }
static inline void
qed_ll2_establish_connection_ooo(struct qed_hwfn *p_hwfn,
				 struct qed_ll2_info *p_ll2_conn) { return; }
static inline void
qed_ll2_release_connection_ooo(struct qed_hwfn *p_hwfn,
			       struct qed_ll2_info *p_ll2_conn) { return; }
static inline void qed_ll2_stop_ooo(struct qed_dev *cdev) { return; }
static inline int qed_ll2_start_ooo(struct qed_dev *cdev,
				    struct qed_ll2_params *params)
				    { return -EINVAL; }
#endif /* IS_ENABLED(CONFIG_QED_ISCSI) */

static int qed_sp_ll2_rx_queue_start(struct qed_hwfn *p_hwfn,
				     struct qed_ll2_info *p_ll2_conn,
				     u8 action_on_error)
{
	enum qed_ll2_conn_type conn_type = p_ll2_conn->conn_type;
	struct qed_ll2_rx_queue *p_rx = &p_ll2_conn->rx_queue;
	struct core_rx_start_ramrod_data *p_ramrod = NULL;
	struct qed_spq_entry *p_ent = NULL;
	struct qed_sp_init_data init_data;
	u16 cqe_pbl_size;
	int rc = 0;

	/* Get SPQ entry */
	memset(&init_data, 0, sizeof(init_data));
	init_data.cid = p_ll2_conn->cid;
	init_data.opaque_fid = p_hwfn->hw_info.opaque_fid;
	init_data.comp_mode = QED_SPQ_MODE_EBLOCK;

	rc = qed_sp_init_request(p_hwfn, &p_ent,
				 CORE_RAMROD_RX_QUEUE_START,
				 PROTOCOLID_CORE, &init_data);
	if (rc)
		return rc;

	p_ramrod = &p_ent->ramrod.core_rx_queue_start;

	p_ramrod->sb_id = cpu_to_le16(qed_int_get_sp_sb_id(p_hwfn));
	p_ramrod->sb_index = p_rx->rx_sb_index;
	p_ramrod->complete_event_flg = 1;

	p_ramrod->mtu = cpu_to_le16(p_ll2_conn->mtu);
	DMA_REGPAIR_LE(p_ramrod->bd_base,
		       p_rx->rxq_chain.p_phys_addr);
	cqe_pbl_size = (u16)qed_chain_get_page_cnt(&p_rx->rcq_chain);
	p_ramrod->num_of_pbl_pages = cpu_to_le16(cqe_pbl_size);
	DMA_REGPAIR_LE(p_ramrod->cqe_pbl_addr,
		       qed_chain_get_pbl_phys(&p_rx->rcq_chain));

	p_ramrod->drop_ttl0_flg = p_ll2_conn->rx_drop_ttl0_flg;
	p_ramrod->inner_vlan_removal_en = p_ll2_conn->rx_vlan_removal_en;
	p_ramrod->queue_id = p_ll2_conn->queue_id;
	p_ramrod->main_func_queue = (conn_type == QED_LL2_TYPE_ISCSI_OOO) ? 0
									  : 1;

	if ((IS_MF_DEFAULT(p_hwfn) || IS_MF_SI(p_hwfn)) &&
	    p_ramrod->main_func_queue && (conn_type != QED_LL2_TYPE_ROCE)) {
		p_ramrod->mf_si_bcast_accept_all = 1;
		p_ramrod->mf_si_mcast_accept_all = 1;
	} else {
		p_ramrod->mf_si_bcast_accept_all = 0;
		p_ramrod->mf_si_mcast_accept_all = 0;
	}

	p_ramrod->action_on_error.error_type = action_on_error;
	p_ramrod->gsi_offload_flag = p_ll2_conn->gsi_enable;
	return qed_spq_post(p_hwfn, p_ent, NULL);
}

static int qed_sp_ll2_tx_queue_start(struct qed_hwfn *p_hwfn,
				     struct qed_ll2_info *p_ll2_conn)
{
	enum qed_ll2_conn_type conn_type = p_ll2_conn->conn_type;
	struct qed_ll2_tx_queue *p_tx = &p_ll2_conn->tx_queue;
	struct core_tx_start_ramrod_data *p_ramrod = NULL;
	struct qed_spq_entry *p_ent = NULL;
	struct qed_sp_init_data init_data;
	union qed_qm_pq_params pq_params;
	u16 pq_id = 0, pbl_size;
	int rc = -EINVAL;

	if (!QED_LL2_TX_REGISTERED(p_ll2_conn))
		return 0;

	if (p_ll2_conn->conn_type == QED_LL2_TYPE_ISCSI_OOO)
		p_ll2_conn->tx_stats_en = 0;
	else
		p_ll2_conn->tx_stats_en = 1;

	/* Get SPQ entry */
	memset(&init_data, 0, sizeof(init_data));
	init_data.cid = p_ll2_conn->cid;
	init_data.opaque_fid = p_hwfn->hw_info.opaque_fid;
	init_data.comp_mode = QED_SPQ_MODE_EBLOCK;

	rc = qed_sp_init_request(p_hwfn, &p_ent,
				 CORE_RAMROD_TX_QUEUE_START,
				 PROTOCOLID_CORE, &init_data);
	if (rc)
		return rc;

	p_ramrod = &p_ent->ramrod.core_tx_queue_start;

	p_ramrod->sb_id = cpu_to_le16(qed_int_get_sp_sb_id(p_hwfn));
	p_ramrod->sb_index = p_tx->tx_sb_index;
	p_ramrod->mtu = cpu_to_le16(p_ll2_conn->mtu);
	p_ramrod->stats_en = p_ll2_conn->tx_stats_en;
	p_ramrod->stats_id = p_ll2_conn->tx_stats_id;

	DMA_REGPAIR_LE(p_ramrod->pbl_base_addr,
		       qed_chain_get_pbl_phys(&p_tx->txq_chain));
	pbl_size = qed_chain_get_page_cnt(&p_tx->txq_chain);
	p_ramrod->pbl_size = cpu_to_le16(pbl_size);

	memset(&pq_params, 0, sizeof(pq_params));
	pq_params.core.tc = p_ll2_conn->tx_tc;
	pq_id = qed_get_qm_pq(p_hwfn, PROTOCOLID_CORE, &pq_params);
	p_ramrod->qm_pq_id = cpu_to_le16(pq_id);

	switch (conn_type) {
	case QED_LL2_TYPE_ISCSI:
	case QED_LL2_TYPE_ISCSI_OOO:
		p_ramrod->conn_type = PROTOCOLID_ISCSI;
		break;
	case QED_LL2_TYPE_ROCE:
		p_ramrod->conn_type = PROTOCOLID_ROCE;
		break;
	default:
		p_ramrod->conn_type = PROTOCOLID_ETH;
		DP_NOTICE(p_hwfn, "Unknown connection type: %d\n", conn_type);
	}

	p_ramrod->gsi_offload_flag = p_ll2_conn->gsi_enable;
	return qed_spq_post(p_hwfn, p_ent, NULL);
}

static int qed_sp_ll2_rx_queue_stop(struct qed_hwfn *p_hwfn,
				    struct qed_ll2_info *p_ll2_conn)
{
	struct core_rx_stop_ramrod_data *p_ramrod = NULL;
	struct qed_spq_entry *p_ent = NULL;
	struct qed_sp_init_data init_data;
	int rc = -EINVAL;

	/* Get SPQ entry */
	memset(&init_data, 0, sizeof(init_data));
	init_data.cid = p_ll2_conn->cid;
	init_data.opaque_fid = p_hwfn->hw_info.opaque_fid;
	init_data.comp_mode = QED_SPQ_MODE_EBLOCK;

	rc = qed_sp_init_request(p_hwfn, &p_ent,
				 CORE_RAMROD_RX_QUEUE_STOP,
				 PROTOCOLID_CORE, &init_data);
	if (rc)
		return rc;

	p_ramrod = &p_ent->ramrod.core_rx_queue_stop;

	p_ramrod->complete_event_flg = 1;
	p_ramrod->queue_id = p_ll2_conn->queue_id;

	return qed_spq_post(p_hwfn, p_ent, NULL);
}

static int qed_sp_ll2_tx_queue_stop(struct qed_hwfn *p_hwfn,
				    struct qed_ll2_info *p_ll2_conn)
{
	struct qed_spq_entry *p_ent = NULL;
	struct qed_sp_init_data init_data;
	int rc = -EINVAL;

	/* Get SPQ entry */
	memset(&init_data, 0, sizeof(init_data));
	init_data.cid = p_ll2_conn->cid;
	init_data.opaque_fid = p_hwfn->hw_info.opaque_fid;
	init_data.comp_mode = QED_SPQ_MODE_EBLOCK;

	rc = qed_sp_init_request(p_hwfn, &p_ent,
				 CORE_RAMROD_TX_QUEUE_STOP,
				 PROTOCOLID_CORE, &init_data);
	if (rc)
		return rc;

	return qed_spq_post(p_hwfn, p_ent, NULL);
}

static int
qed_ll2_acquire_connection_rx(struct qed_hwfn *p_hwfn,
			      struct qed_ll2_info *p_ll2_info, u16 rx_num_desc)
{
	struct qed_ll2_rx_packet *p_descq;
	u32 capacity;
	int rc = 0;

	if (!rx_num_desc)
		goto out;

	rc = qed_chain_alloc(p_hwfn->cdev,
			     QED_CHAIN_USE_TO_CONSUME_PRODUCE,
			     QED_CHAIN_MODE_NEXT_PTR,
			     QED_CHAIN_CNT_TYPE_U16,
			     rx_num_desc,
			     sizeof(struct core_rx_bd),
			     &p_ll2_info->rx_queue.rxq_chain);
	if (rc) {
		DP_NOTICE(p_hwfn, "Failed to allocate ll2 rxq chain\n");
		goto out;
	}

	capacity = qed_chain_get_capacity(&p_ll2_info->rx_queue.rxq_chain);
	p_descq = kcalloc(capacity, sizeof(struct qed_ll2_rx_packet),
			  GFP_KERNEL);
	if (!p_descq) {
		rc = -ENOMEM;
		DP_NOTICE(p_hwfn, "Failed to allocate ll2 Rx desc\n");
		goto out;
	}
	p_ll2_info->rx_queue.descq_array = p_descq;

	rc = qed_chain_alloc(p_hwfn->cdev,
			     QED_CHAIN_USE_TO_CONSUME_PRODUCE,
			     QED_CHAIN_MODE_PBL,
			     QED_CHAIN_CNT_TYPE_U16,
			     rx_num_desc,
			     sizeof(struct core_rx_fast_path_cqe),
			     &p_ll2_info->rx_queue.rcq_chain);
	if (rc) {
		DP_NOTICE(p_hwfn, "Failed to allocate ll2 rcq chain\n");
		goto out;
	}

	DP_VERBOSE(p_hwfn, QED_MSG_LL2,
		   "Allocated LL2 Rxq [Type %08x] with 0x%08x buffers\n",
		   p_ll2_info->conn_type, rx_num_desc);

out:
	return rc;
}

static int qed_ll2_acquire_connection_tx(struct qed_hwfn *p_hwfn,
					 struct qed_ll2_info *p_ll2_info,
					 u16 tx_num_desc)
{
	struct qed_ll2_tx_packet *p_descq;
	u32 capacity;
	int rc = 0;

	if (!tx_num_desc)
		goto out;

	rc = qed_chain_alloc(p_hwfn->cdev,
			     QED_CHAIN_USE_TO_CONSUME_PRODUCE,
			     QED_CHAIN_MODE_PBL,
			     QED_CHAIN_CNT_TYPE_U16,
			     tx_num_desc,
			     sizeof(struct core_tx_bd),
			     &p_ll2_info->tx_queue.txq_chain);
	if (rc)
		goto out;

	capacity = qed_chain_get_capacity(&p_ll2_info->tx_queue.txq_chain);
	p_descq = kcalloc(capacity, sizeof(struct qed_ll2_tx_packet),
			  GFP_KERNEL);
	if (!p_descq) {
		rc = -ENOMEM;
		goto out;
	}
	p_ll2_info->tx_queue.descq_array = p_descq;

	DP_VERBOSE(p_hwfn, QED_MSG_LL2,
		   "Allocated LL2 Txq [Type %08x] with 0x%08x buffers\n",
		   p_ll2_info->conn_type, tx_num_desc);

out:
	if (rc)
		DP_NOTICE(p_hwfn,
			  "Can't allocate memory for Tx LL2 with 0x%08x buffers\n",
			  tx_num_desc);
	return rc;
}

int qed_ll2_acquire_connection(struct qed_hwfn *p_hwfn,
			       struct qed_ll2_info *p_params,
			       u16 rx_num_desc,
			       u16 tx_num_desc,
			       u8 *p_connection_handle)
{
	qed_int_comp_cb_t comp_rx_cb, comp_tx_cb;
	struct qed_ll2_info *p_ll2_info = NULL;
	int rc;
	u8 i;

	if (!p_connection_handle || !p_hwfn->p_ll2_info)
		return -EINVAL;

	/* Find a free connection to be used */
	for (i = 0; (i < QED_MAX_NUM_OF_LL2_CONNECTIONS); i++) {
		mutex_lock(&p_hwfn->p_ll2_info[i].mutex);
		if (p_hwfn->p_ll2_info[i].b_active) {
			mutex_unlock(&p_hwfn->p_ll2_info[i].mutex);
			continue;
		}

		p_hwfn->p_ll2_info[i].b_active = true;
		p_ll2_info = &p_hwfn->p_ll2_info[i];
		mutex_unlock(&p_hwfn->p_ll2_info[i].mutex);
		break;
	}
	if (!p_ll2_info)
		return -EBUSY;

	p_ll2_info->conn_type = p_params->conn_type;
	p_ll2_info->mtu = p_params->mtu;
	p_ll2_info->rx_drop_ttl0_flg = p_params->rx_drop_ttl0_flg;
	p_ll2_info->rx_vlan_removal_en = p_params->rx_vlan_removal_en;
	p_ll2_info->tx_tc = p_params->tx_tc;
	p_ll2_info->tx_dest = p_params->tx_dest;
	p_ll2_info->ai_err_packet_too_big = p_params->ai_err_packet_too_big;
	p_ll2_info->ai_err_no_buf = p_params->ai_err_no_buf;
	p_ll2_info->gsi_enable = p_params->gsi_enable;

	rc = qed_ll2_acquire_connection_rx(p_hwfn, p_ll2_info, rx_num_desc);
	if (rc)
		goto q_allocate_fail;

	rc = qed_ll2_acquire_connection_tx(p_hwfn, p_ll2_info, tx_num_desc);
	if (rc)
		goto q_allocate_fail;

	rc = qed_ll2_acquire_connection_ooo(p_hwfn, p_ll2_info,
					    rx_num_desc * 2, p_params->mtu);
	if (rc)
		goto q_allocate_fail;

	/* Register callbacks for the Rx/Tx queues */
	if (p_params->conn_type == QED_LL2_TYPE_ISCSI_OOO) {
		comp_rx_cb = qed_ll2_lb_rxq_completion;
		comp_tx_cb = qed_ll2_lb_txq_completion;
	} else {
		comp_rx_cb = qed_ll2_rxq_completion;
		comp_tx_cb = qed_ll2_txq_completion;
	}

	if (rx_num_desc) {
		qed_int_register_cb(p_hwfn, comp_rx_cb,
				    &p_hwfn->p_ll2_info[i],
				    &p_ll2_info->rx_queue.rx_sb_index,
				    &p_ll2_info->rx_queue.p_fw_cons);
		p_ll2_info->rx_queue.b_cb_registred = true;
	}

	if (tx_num_desc) {
		qed_int_register_cb(p_hwfn,
				    comp_tx_cb,
				    &p_hwfn->p_ll2_info[i],
				    &p_ll2_info->tx_queue.tx_sb_index,
				    &p_ll2_info->tx_queue.p_fw_cons);
		p_ll2_info->tx_queue.b_cb_registred = true;
	}

	*p_connection_handle = i;
	return rc;

q_allocate_fail:
	qed_ll2_release_connection(p_hwfn, i);
	return -ENOMEM;
}

static int qed_ll2_establish_connection_rx(struct qed_hwfn *p_hwfn,
					   struct qed_ll2_info *p_ll2_conn)
{
	u8 action_on_error = 0;

	if (!QED_LL2_RX_REGISTERED(p_ll2_conn))
		return 0;

	DIRECT_REG_WR(p_ll2_conn->rx_queue.set_prod_addr, 0x0);

	SET_FIELD(action_on_error,
		  CORE_RX_ACTION_ON_ERROR_PACKET_TOO_BIG,
		  p_ll2_conn->ai_err_packet_too_big);
	SET_FIELD(action_on_error,
		  CORE_RX_ACTION_ON_ERROR_NO_BUFF, p_ll2_conn->ai_err_no_buf);

	return qed_sp_ll2_rx_queue_start(p_hwfn, p_ll2_conn, action_on_error);
}

int qed_ll2_establish_connection(struct qed_hwfn *p_hwfn, u8 connection_handle)
{
	struct qed_ll2_info *p_ll2_conn;
	struct qed_ll2_rx_queue *p_rx;
	struct qed_ll2_tx_queue *p_tx;
	int rc = -EINVAL;
	u32 i, capacity;
	u8 qid;

	p_ll2_conn = qed_ll2_handle_sanity_lock(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return -EINVAL;
	p_rx = &p_ll2_conn->rx_queue;
	p_tx = &p_ll2_conn->tx_queue;

	qed_chain_reset(&p_rx->rxq_chain);
	qed_chain_reset(&p_rx->rcq_chain);
	INIT_LIST_HEAD(&p_rx->active_descq);
	INIT_LIST_HEAD(&p_rx->free_descq);
	INIT_LIST_HEAD(&p_rx->posting_descq);
	spin_lock_init(&p_rx->lock);
	capacity = qed_chain_get_capacity(&p_rx->rxq_chain);
	for (i = 0; i < capacity; i++)
		list_add_tail(&p_rx->descq_array[i].list_entry,
			      &p_rx->free_descq);
	*p_rx->p_fw_cons = 0;

	qed_chain_reset(&p_tx->txq_chain);
	INIT_LIST_HEAD(&p_tx->active_descq);
	INIT_LIST_HEAD(&p_tx->free_descq);
	INIT_LIST_HEAD(&p_tx->sending_descq);
	spin_lock_init(&p_tx->lock);
	capacity = qed_chain_get_capacity(&p_tx->txq_chain);
	for (i = 0; i < capacity; i++)
		list_add_tail(&p_tx->descq_array[i].list_entry,
			      &p_tx->free_descq);
	p_tx->cur_completing_bd_idx = 0;
	p_tx->bds_idx = 0;
	p_tx->b_completing_packet = false;
	p_tx->cur_send_packet = NULL;
	p_tx->cur_send_frag_num = 0;
	p_tx->cur_completing_frag_num = 0;
	*p_tx->p_fw_cons = 0;

	qed_cxt_acquire_cid(p_hwfn, PROTOCOLID_CORE, &p_ll2_conn->cid);

	qid = p_hwfn->hw_info.resc_start[QED_LL2_QUEUE] + connection_handle;
	p_ll2_conn->queue_id = qid;
	p_ll2_conn->tx_stats_id = qid;
	p_rx->set_prod_addr = (u8 __iomem *)p_hwfn->regview +
					    GTT_BAR0_MAP_REG_TSDM_RAM +
					    TSTORM_LL2_RX_PRODS_OFFSET(qid);
	p_tx->doorbell_addr = (u8 __iomem *)p_hwfn->doorbells +
					    qed_db_addr(p_ll2_conn->cid,
							DQ_DEMS_LEGACY);

	rc = qed_ll2_establish_connection_rx(p_hwfn, p_ll2_conn);
	if (rc)
		return rc;

	rc = qed_sp_ll2_tx_queue_start(p_hwfn, p_ll2_conn);
	if (rc)
		return rc;

	if (p_hwfn->hw_info.personality != QED_PCI_ETH_ROCE)
		qed_wr(p_hwfn, p_hwfn->p_main_ptt, PRS_REG_USE_LIGHT_L2, 1);

	qed_ll2_establish_connection_ooo(p_hwfn, p_ll2_conn);

	return rc;
}

static void qed_ll2_post_rx_buffer_notify_fw(struct qed_hwfn *p_hwfn,
					     struct qed_ll2_rx_queue *p_rx,
					     struct qed_ll2_rx_packet *p_curp)
{
	struct qed_ll2_rx_packet *p_posting_packet = NULL;
	struct core_ll2_rx_prod rx_prod = { 0, 0, 0 };
	bool b_notify_fw = false;
	u16 bd_prod, cq_prod;

	/* This handles the flushing of already posted buffers */
	while (!list_empty(&p_rx->posting_descq)) {
		p_posting_packet = list_first_entry(&p_rx->posting_descq,
						    struct qed_ll2_rx_packet,
						    list_entry);
		list_move_tail(&p_posting_packet->list_entry,
			       &p_rx->active_descq);
		b_notify_fw = true;
	}

	/* This handles the supplied packet [if there is one] */
	if (p_curp) {
		list_add_tail(&p_curp->list_entry, &p_rx->active_descq);
		b_notify_fw = true;
	}

	if (!b_notify_fw)
		return;

	bd_prod = qed_chain_get_prod_idx(&p_rx->rxq_chain);
	cq_prod = qed_chain_get_prod_idx(&p_rx->rcq_chain);
	rx_prod.bd_prod = cpu_to_le16(bd_prod);
	rx_prod.cqe_prod = cpu_to_le16(cq_prod);
	DIRECT_REG_WR(p_rx->set_prod_addr, *((u32 *)&rx_prod));
}

int qed_ll2_post_rx_buffer(struct qed_hwfn *p_hwfn,
			   u8 connection_handle,
			   dma_addr_t addr,
			   u16 buf_len, void *cookie, u8 notify_fw)
{
	struct core_rx_bd_with_buff_len *p_curb = NULL;
	struct qed_ll2_rx_packet *p_curp = NULL;
	struct qed_ll2_info *p_ll2_conn;
	struct qed_ll2_rx_queue *p_rx;
	unsigned long flags;
	void *p_data;
	int rc = 0;

	p_ll2_conn = qed_ll2_handle_sanity(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return -EINVAL;
	p_rx = &p_ll2_conn->rx_queue;

	spin_lock_irqsave(&p_rx->lock, flags);
	if (!list_empty(&p_rx->free_descq))
		p_curp = list_first_entry(&p_rx->free_descq,
					  struct qed_ll2_rx_packet, list_entry);
	if (p_curp) {
		if (qed_chain_get_elem_left(&p_rx->rxq_chain) &&
		    qed_chain_get_elem_left(&p_rx->rcq_chain)) {
			p_data = qed_chain_produce(&p_rx->rxq_chain);
			p_curb = (struct core_rx_bd_with_buff_len *)p_data;
			qed_chain_produce(&p_rx->rcq_chain);
		}
	}

	/* If we're lacking entires, let's try to flush buffers to FW */
	if (!p_curp || !p_curb) {
		rc = -EBUSY;
		p_curp = NULL;
		goto out_notify;
	}

	/* We have an Rx packet we can fill */
	DMA_REGPAIR_LE(p_curb->addr, addr);
	p_curb->buff_length = cpu_to_le16(buf_len);
	p_curp->rx_buf_addr = addr;
	p_curp->cookie = cookie;
	p_curp->rxq_bd = p_curb;
	p_curp->buf_length = buf_len;
	list_del(&p_curp->list_entry);

	/* Check if we only want to enqueue this packet without informing FW */
	if (!notify_fw) {
		list_add_tail(&p_curp->list_entry, &p_rx->posting_descq);
		goto out;
	}

out_notify:
	qed_ll2_post_rx_buffer_notify_fw(p_hwfn, p_rx, p_curp);
out:
	spin_unlock_irqrestore(&p_rx->lock, flags);
	return rc;
}

static void qed_ll2_prepare_tx_packet_set(struct qed_hwfn *p_hwfn,
					  struct qed_ll2_tx_queue *p_tx,
					  struct qed_ll2_tx_packet *p_curp,
					  u8 num_of_bds,
					  dma_addr_t first_frag,
					  u16 first_frag_len, void *p_cookie,
					  u8 notify_fw)
{
	list_del(&p_curp->list_entry);
	p_curp->cookie = p_cookie;
	p_curp->bd_used = num_of_bds;
	p_curp->notify_fw = notify_fw;
	p_tx->cur_send_packet = p_curp;
	p_tx->cur_send_frag_num = 0;

	p_curp->bds_set[p_tx->cur_send_frag_num].tx_frag = first_frag;
	p_curp->bds_set[p_tx->cur_send_frag_num].frag_len = first_frag_len;
	p_tx->cur_send_frag_num++;
}

static void qed_ll2_prepare_tx_packet_set_bd(struct qed_hwfn *p_hwfn,
					     struct qed_ll2_info *p_ll2,
					     struct qed_ll2_tx_packet *p_curp,
					     u8 num_of_bds,
					     enum core_tx_dest tx_dest,
					     u16 vlan,
					     u8 bd_flags,
					     u16 l4_hdr_offset_w,
					     enum core_roce_flavor_type type,
					     dma_addr_t first_frag,
					     u16 first_frag_len)
{
	struct qed_chain *p_tx_chain = &p_ll2->tx_queue.txq_chain;
	u16 prod_idx = qed_chain_get_prod_idx(p_tx_chain);
	struct core_tx_bd *start_bd = NULL;
	u16 frag_idx;

	start_bd = (struct core_tx_bd *)qed_chain_produce(p_tx_chain);
	start_bd->nw_vlan_or_lb_echo = cpu_to_le16(vlan);
	SET_FIELD(start_bd->bitfield1, CORE_TX_BD_L4_HDR_OFFSET_W,
		  cpu_to_le16(l4_hdr_offset_w));
	SET_FIELD(start_bd->bitfield1, CORE_TX_BD_TX_DST, tx_dest);
	start_bd->bd_flags.as_bitfield = bd_flags;
	start_bd->bd_flags.as_bitfield |= CORE_TX_BD_FLAGS_START_BD_MASK <<
	    CORE_TX_BD_FLAGS_START_BD_SHIFT;
	SET_FIELD(start_bd->bitfield0, CORE_TX_BD_NBDS, num_of_bds);
	SET_FIELD(start_bd->bitfield0, CORE_TX_BD_ROCE_FLAV, type);
	DMA_REGPAIR_LE(start_bd->addr, first_frag);
	start_bd->nbytes = cpu_to_le16(first_frag_len);

	DP_VERBOSE(p_hwfn,
		   (NETIF_MSG_TX_QUEUED | QED_MSG_LL2),
		   "LL2 [q 0x%02x cid 0x%08x type 0x%08x] Tx Producer at [0x%04x] - set with a %04x bytes %02x BDs buffer at %08x:%08x\n",
		   p_ll2->queue_id,
		   p_ll2->cid,
		   p_ll2->conn_type,
		   prod_idx,
		   first_frag_len,
		   num_of_bds,
		   le32_to_cpu(start_bd->addr.hi),
		   le32_to_cpu(start_bd->addr.lo));

	if (p_ll2->tx_queue.cur_send_frag_num == num_of_bds)
		return;

	/* Need to provide the packet with additional BDs for frags */
	for (frag_idx = p_ll2->tx_queue.cur_send_frag_num;
	     frag_idx < num_of_bds; frag_idx++) {
		struct core_tx_bd **p_bd = &p_curp->bds_set[frag_idx].txq_bd;

		*p_bd = (struct core_tx_bd *)qed_chain_produce(p_tx_chain);
		(*p_bd)->bd_flags.as_bitfield = 0;
		(*p_bd)->bitfield1 = 0;
		(*p_bd)->bitfield0 = 0;
		p_curp->bds_set[frag_idx].tx_frag = 0;
		p_curp->bds_set[frag_idx].frag_len = 0;
	}
}

/* This should be called while the Txq spinlock is being held */
static void qed_ll2_tx_packet_notify(struct qed_hwfn *p_hwfn,
				     struct qed_ll2_info *p_ll2_conn)
{
	bool b_notify = p_ll2_conn->tx_queue.cur_send_packet->notify_fw;
	struct qed_ll2_tx_queue *p_tx = &p_ll2_conn->tx_queue;
	struct qed_ll2_tx_packet *p_pkt = NULL;
	struct core_db_data db_msg = { 0, 0, 0 };
	u16 bd_prod;

	/* If there are missing BDs, don't do anything now */
	if (p_ll2_conn->tx_queue.cur_send_frag_num !=
	    p_ll2_conn->tx_queue.cur_send_packet->bd_used)
		return;

	/* Push the current packet to the list and clean after it */
	list_add_tail(&p_ll2_conn->tx_queue.cur_send_packet->list_entry,
		      &p_ll2_conn->tx_queue.sending_descq);
	p_ll2_conn->tx_queue.cur_send_packet = NULL;
	p_ll2_conn->tx_queue.cur_send_frag_num = 0;

	/* Notify FW of packet only if requested to */
	if (!b_notify)
		return;

	bd_prod = qed_chain_get_prod_idx(&p_ll2_conn->tx_queue.txq_chain);

	while (!list_empty(&p_tx->sending_descq)) {
		p_pkt = list_first_entry(&p_tx->sending_descq,
					 struct qed_ll2_tx_packet, list_entry);
		if (!p_pkt)
			break;

		list_move_tail(&p_pkt->list_entry, &p_tx->active_descq);
	}

	SET_FIELD(db_msg.params, CORE_DB_DATA_DEST, DB_DEST_XCM);
	SET_FIELD(db_msg.params, CORE_DB_DATA_AGG_CMD, DB_AGG_CMD_SET);
	SET_FIELD(db_msg.params, CORE_DB_DATA_AGG_VAL_SEL,
		  DQ_XCM_CORE_TX_BD_PROD_CMD);
	db_msg.agg_flags = DQ_XCM_CORE_DQ_CF_CMD;
	db_msg.spq_prod = cpu_to_le16(bd_prod);

	/* Make sure the BDs data is updated before ringing the doorbell */
	wmb();

	DIRECT_REG_WR(p_tx->doorbell_addr, *((u32 *)&db_msg));

	DP_VERBOSE(p_hwfn,
		   (NETIF_MSG_TX_QUEUED | QED_MSG_LL2),
		   "LL2 [q 0x%02x cid 0x%08x type 0x%08x] Doorbelled [producer 0x%04x]\n",
		   p_ll2_conn->queue_id,
		   p_ll2_conn->cid, p_ll2_conn->conn_type, db_msg.spq_prod);
}

int qed_ll2_prepare_tx_packet(struct qed_hwfn *p_hwfn,
			      u8 connection_handle,
			      u8 num_of_bds,
			      u16 vlan,
			      u8 bd_flags,
			      u16 l4_hdr_offset_w,
			      enum qed_ll2_tx_dest e_tx_dest,
			      enum qed_ll2_roce_flavor_type qed_roce_flavor,
			      dma_addr_t first_frag,
			      u16 first_frag_len, void *cookie, u8 notify_fw)
{
	struct qed_ll2_tx_packet *p_curp = NULL;
	struct qed_ll2_info *p_ll2_conn = NULL;
	enum core_roce_flavor_type roce_flavor;
	struct qed_ll2_tx_queue *p_tx;
	struct qed_chain *p_tx_chain;
	enum core_tx_dest tx_dest;
	unsigned long flags;
	int rc = 0;

	p_ll2_conn = qed_ll2_handle_sanity(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return -EINVAL;
	p_tx = &p_ll2_conn->tx_queue;
	p_tx_chain = &p_tx->txq_chain;

	if (num_of_bds > CORE_LL2_TX_MAX_BDS_PER_PACKET)
		return -EIO;

	spin_lock_irqsave(&p_tx->lock, flags);
	if (p_tx->cur_send_packet) {
		rc = -EEXIST;
		goto out;
	}

	/* Get entry, but only if we have tx elements for it */
	if (!list_empty(&p_tx->free_descq))
		p_curp = list_first_entry(&p_tx->free_descq,
					  struct qed_ll2_tx_packet, list_entry);
	if (p_curp && qed_chain_get_elem_left(p_tx_chain) < num_of_bds)
		p_curp = NULL;

	if (!p_curp) {
		rc = -EBUSY;
		goto out;
	}

	tx_dest = e_tx_dest == QED_LL2_TX_DEST_NW ? CORE_TX_DEST_NW :
						    CORE_TX_DEST_LB;
	if (qed_roce_flavor == QED_LL2_ROCE) {
		roce_flavor = CORE_ROCE;
	} else if (qed_roce_flavor == QED_LL2_RROCE) {
		roce_flavor = CORE_RROCE;
	} else {
		rc = -EINVAL;
		goto out;
	}

	/* Prepare packet and BD, and perhaps send a doorbell to FW */
	qed_ll2_prepare_tx_packet_set(p_hwfn, p_tx, p_curp,
				      num_of_bds, first_frag,
				      first_frag_len, cookie, notify_fw);
	qed_ll2_prepare_tx_packet_set_bd(p_hwfn, p_ll2_conn, p_curp,
					 num_of_bds, tx_dest,
					 vlan, bd_flags, l4_hdr_offset_w,
					 roce_flavor,
					 first_frag, first_frag_len);

	qed_ll2_tx_packet_notify(p_hwfn, p_ll2_conn);

out:
	spin_unlock_irqrestore(&p_tx->lock, flags);
	return rc;
}

int qed_ll2_set_fragment_of_tx_packet(struct qed_hwfn *p_hwfn,
				      u8 connection_handle,
				      dma_addr_t addr, u16 nbytes)
{
	struct qed_ll2_tx_packet *p_cur_send_packet = NULL;
	struct qed_ll2_info *p_ll2_conn = NULL;
	u16 cur_send_frag_num = 0;
	struct core_tx_bd *p_bd;
	unsigned long flags;

	p_ll2_conn = qed_ll2_handle_sanity(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return -EINVAL;

	if (!p_ll2_conn->tx_queue.cur_send_packet)
		return -EINVAL;

	p_cur_send_packet = p_ll2_conn->tx_queue.cur_send_packet;
	cur_send_frag_num = p_ll2_conn->tx_queue.cur_send_frag_num;

	if (cur_send_frag_num >= p_cur_send_packet->bd_used)
		return -EINVAL;

	/* Fill the BD information, and possibly notify FW */
	p_bd = p_cur_send_packet->bds_set[cur_send_frag_num].txq_bd;
	DMA_REGPAIR_LE(p_bd->addr, addr);
	p_bd->nbytes = cpu_to_le16(nbytes);
	p_cur_send_packet->bds_set[cur_send_frag_num].tx_frag = addr;
	p_cur_send_packet->bds_set[cur_send_frag_num].frag_len = nbytes;

	p_ll2_conn->tx_queue.cur_send_frag_num++;

	spin_lock_irqsave(&p_ll2_conn->tx_queue.lock, flags);
	qed_ll2_tx_packet_notify(p_hwfn, p_ll2_conn);
	spin_unlock_irqrestore(&p_ll2_conn->tx_queue.lock, flags);

	return 0;
}

int qed_ll2_terminate_connection(struct qed_hwfn *p_hwfn, u8 connection_handle)
{
	struct qed_ll2_info *p_ll2_conn = NULL;
	int rc = -EINVAL;

	p_ll2_conn = qed_ll2_handle_sanity_lock(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return -EINVAL;

	/* Stop Tx & Rx of connection, if needed */
	if (QED_LL2_TX_REGISTERED(p_ll2_conn)) {
		rc = qed_sp_ll2_tx_queue_stop(p_hwfn, p_ll2_conn);
		if (rc)
			return rc;
		qed_ll2_txq_flush(p_hwfn, connection_handle);
	}

	if (QED_LL2_RX_REGISTERED(p_ll2_conn)) {
		rc = qed_sp_ll2_rx_queue_stop(p_hwfn, p_ll2_conn);
		if (rc)
			return rc;
		qed_ll2_rxq_flush(p_hwfn, connection_handle);
	}

	if (p_ll2_conn->conn_type == QED_LL2_TYPE_ISCSI_OOO)
		qed_ooo_release_all_isles(p_hwfn, p_hwfn->p_ooo_info);

	return rc;
}

void qed_ll2_release_connection(struct qed_hwfn *p_hwfn, u8 connection_handle)
{
	struct qed_ll2_info *p_ll2_conn = NULL;

	p_ll2_conn = qed_ll2_handle_sanity(p_hwfn, connection_handle);
	if (!p_ll2_conn)
		return;

	if (QED_LL2_RX_REGISTERED(p_ll2_conn)) {
		p_ll2_conn->rx_queue.b_cb_registred = false;
		qed_int_unregister_cb(p_hwfn, p_ll2_conn->rx_queue.rx_sb_index);
	}

	if (QED_LL2_TX_REGISTERED(p_ll2_conn)) {
		p_ll2_conn->tx_queue.b_cb_registred = false;
		qed_int_unregister_cb(p_hwfn, p_ll2_conn->tx_queue.tx_sb_index);
	}

	kfree(p_ll2_conn->tx_queue.descq_array);
	qed_chain_free(p_hwfn->cdev, &p_ll2_conn->tx_queue.txq_chain);

	kfree(p_ll2_conn->rx_queue.descq_array);
	qed_chain_free(p_hwfn->cdev, &p_ll2_conn->rx_queue.rxq_chain);
	qed_chain_free(p_hwfn->cdev, &p_ll2_conn->rx_queue.rcq_chain);

	qed_cxt_release_cid(p_hwfn, p_ll2_conn->cid);

	qed_ll2_release_connection_ooo(p_hwfn, p_ll2_conn);

	mutex_lock(&p_ll2_conn->mutex);
	p_ll2_conn->b_active = false;
	mutex_unlock(&p_ll2_conn->mutex);
}

struct qed_ll2_info *qed_ll2_alloc(struct qed_hwfn *p_hwfn)
{
	struct qed_ll2_info *p_ll2_connections;
	u8 i;

	/* Allocate LL2's set struct */
	p_ll2_connections = kcalloc(QED_MAX_NUM_OF_LL2_CONNECTIONS,
				    sizeof(struct qed_ll2_info), GFP_KERNEL);
	if (!p_ll2_connections) {
		DP_NOTICE(p_hwfn, "Failed to allocate `struct qed_ll2'\n");
		return NULL;
	}

	for (i = 0; i < QED_MAX_NUM_OF_LL2_CONNECTIONS; i++)
		p_ll2_connections[i].my_id = i;

	return p_ll2_connections;
}

void qed_ll2_setup(struct qed_hwfn *p_hwfn,
		   struct qed_ll2_info *p_ll2_connections)
{
	int i;

	for (i = 0; i < QED_MAX_NUM_OF_LL2_CONNECTIONS; i++)
		mutex_init(&p_ll2_connections[i].mutex);
}

void qed_ll2_free(struct qed_hwfn *p_hwfn,
		  struct qed_ll2_info *p_ll2_connections)
{
	kfree(p_ll2_connections);
}

static void _qed_ll2_get_tstats(struct qed_hwfn *p_hwfn,
				struct qed_ptt *p_ptt,
				struct qed_ll2_info *p_ll2_conn,
				struct qed_ll2_stats *p_stats)
{
	struct core_ll2_tstorm_per_queue_stat tstats;
	u8 qid = p_ll2_conn->queue_id;
	u32 tstats_addr;

	memset(&tstats, 0, sizeof(tstats));
	tstats_addr = BAR0_MAP_REG_TSDM_RAM +
		      CORE_LL2_TSTORM_PER_QUEUE_STAT_OFFSET(qid);
	qed_memcpy_from(p_hwfn, p_ptt, &tstats, tstats_addr, sizeof(tstats));

	p_stats->packet_too_big_discard =
			HILO_64_REGPAIR(tstats.packet_too_big_discard);
	p_stats->no_buff_discard = HILO_64_REGPAIR(tstats.no_buff_discard);
}

static void _qed_ll2_get_ustats(struct qed_hwfn *p_hwfn,
				struct qed_ptt *p_ptt,
				struct qed_ll2_info *p_ll2_conn,
				struct qed_ll2_stats *p_stats)
{
	struct core_ll2_ustorm_per_queue_stat ustats;
	u8 qid = p_ll2_conn->queue_id;
	u32 ustats_addr;

	memset(&ustats, 0, sizeof(ustats));
	ustats_addr = BAR0_MAP_REG_USDM_RAM +
		      CORE_LL2_USTORM_PER_QUEUE_STAT_OFFSET(qid);
	qed_memcpy_from(p_hwfn, p_ptt, &ustats, ustats_addr, sizeof(ustats));

	p_stats->rcv_ucast_bytes = HILO_64_REGPAIR(ustats.rcv_ucast_bytes);
	p_stats->rcv_mcast_bytes = HILO_64_REGPAIR(ustats.rcv_mcast_bytes);
	p_stats->rcv_bcast_bytes = HILO_64_REGPAIR(ustats.rcv_bcast_bytes);
	p_stats->rcv_ucast_pkts = HILO_64_REGPAIR(ustats.rcv_ucast_pkts);
	p_stats->rcv_mcast_pkts = HILO_64_REGPAIR(ustats.rcv_mcast_pkts);
	p_stats->rcv_bcast_pkts = HILO_64_REGPAIR(ustats.rcv_bcast_pkts);
}

static void _qed_ll2_get_pstats(struct qed_hwfn *p_hwfn,
				struct qed_ptt *p_ptt,
				struct qed_ll2_info *p_ll2_conn,
				struct qed_ll2_stats *p_stats)
{
	struct core_ll2_pstorm_per_queue_stat pstats;
	u8 stats_id = p_ll2_conn->tx_stats_id;
	u32 pstats_addr;

	memset(&pstats, 0, sizeof(pstats));
	pstats_addr = BAR0_MAP_REG_PSDM_RAM +
		      CORE_LL2_PSTORM_PER_QUEUE_STAT_OFFSET(stats_id);
	qed_memcpy_from(p_hwfn, p_ptt, &pstats, pstats_addr, sizeof(pstats));

	p_stats->sent_ucast_bytes = HILO_64_REGPAIR(pstats.sent_ucast_bytes);
	p_stats->sent_mcast_bytes = HILO_64_REGPAIR(pstats.sent_mcast_bytes);
	p_stats->sent_bcast_bytes = HILO_64_REGPAIR(pstats.sent_bcast_bytes);
	p_stats->sent_ucast_pkts = HILO_64_REGPAIR(pstats.sent_ucast_pkts);
	p_stats->sent_mcast_pkts = HILO_64_REGPAIR(pstats.sent_mcast_pkts);
	p_stats->sent_bcast_pkts = HILO_64_REGPAIR(pstats.sent_bcast_pkts);
}

int qed_ll2_get_stats(struct qed_hwfn *p_hwfn,
		      u8 connection_handle, struct qed_ll2_stats *p_stats)
{
	struct qed_ll2_info *p_ll2_conn = NULL;
	struct qed_ptt *p_ptt;

	memset(p_stats, 0, sizeof(*p_stats));

	if ((connection_handle >= QED_MAX_NUM_OF_LL2_CONNECTIONS) ||
	    !p_hwfn->p_ll2_info)
		return -EINVAL;

	p_ll2_conn = &p_hwfn->p_ll2_info[connection_handle];

	p_ptt = qed_ptt_acquire(p_hwfn);
	if (!p_ptt) {
		DP_ERR(p_hwfn, "Failed to acquire ptt\n");
		return -EINVAL;
	}

	_qed_ll2_get_tstats(p_hwfn, p_ptt, p_ll2_conn, p_stats);
	_qed_ll2_get_ustats(p_hwfn, p_ptt, p_ll2_conn, p_stats);
	if (p_ll2_conn->tx_stats_en)
		_qed_ll2_get_pstats(p_hwfn, p_ptt, p_ll2_conn, p_stats);

	qed_ptt_release(p_hwfn, p_ptt);
	return 0;
}

static void qed_ll2_register_cb_ops(struct qed_dev *cdev,
				    const struct qed_ll2_cb_ops *ops,
				    void *cookie)
{
	cdev->ll2->cbs = ops;
	cdev->ll2->cb_cookie = cookie;
}

static int qed_ll2_start(struct qed_dev *cdev, struct qed_ll2_params *params)
{
	struct qed_ll2_info ll2_info;
	struct qed_ll2_buffer *buffer, *tmp_buffer;
	enum qed_ll2_conn_type conn_type;
	struct qed_ptt *p_ptt;
	int rc, i;
	u8 gsi_enable = 1;

	/* Initialize LL2 locks & lists */
	INIT_LIST_HEAD(&cdev->ll2->list);
	spin_lock_init(&cdev->ll2->lock);
	cdev->ll2->rx_size = NET_SKB_PAD + ETH_HLEN +
			     L1_CACHE_BYTES + params->mtu;
	cdev->ll2->frags_mapped = params->frags_mapped;

	/*Allocate memory for LL2 */
	DP_INFO(cdev, "Allocating LL2 buffers of size %08x bytes\n",
		cdev->ll2->rx_size);
	for (i = 0; i < QED_LL2_RX_SIZE; i++) {
		buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
		if (!buffer) {
			DP_INFO(cdev, "Failed to allocate LL2 buffers\n");
			goto fail;
		}

		rc = qed_ll2_alloc_buffer(cdev, (u8 **)&buffer->data,
					  &buffer->phys_addr);
		if (rc) {
			kfree(buffer);
			goto fail;
		}

		list_add_tail(&buffer->list, &cdev->ll2->list);
	}

	switch (QED_LEADING_HWFN(cdev)->hw_info.personality) {
	case QED_PCI_ISCSI:
		conn_type = QED_LL2_TYPE_ISCSI;
		gsi_enable = 0;
		break;
	case QED_PCI_ETH_ROCE:
		conn_type = QED_LL2_TYPE_ROCE;
		break;
	default:
		conn_type = QED_LL2_TYPE_TEST;
	}

	/* Prepare the temporary ll2 information */
	memset(&ll2_info, 0, sizeof(ll2_info));
	ll2_info.conn_type = conn_type;
	ll2_info.mtu = params->mtu;
	ll2_info.rx_drop_ttl0_flg = params->drop_ttl0_packets;
	ll2_info.rx_vlan_removal_en = params->rx_vlan_stripping;
	ll2_info.tx_tc = 0;
	ll2_info.tx_dest = CORE_TX_DEST_NW;
	ll2_info.gsi_enable = gsi_enable;

	rc = qed_ll2_acquire_connection(QED_LEADING_HWFN(cdev), &ll2_info,
					QED_LL2_RX_SIZE, QED_LL2_TX_SIZE,
					&cdev->ll2->handle);
	if (rc) {
		DP_INFO(cdev, "Failed to acquire LL2 connection\n");
		goto fail;
	}

	rc = qed_ll2_establish_connection(QED_LEADING_HWFN(cdev),
					  cdev->ll2->handle);
	if (rc) {
		DP_INFO(cdev, "Failed to establish LL2 connection\n");
		goto release_fail;
	}

	/* Post all Rx buffers to FW */
	spin_lock_bh(&cdev->ll2->lock);
	list_for_each_entry_safe(buffer, tmp_buffer, &cdev->ll2->list, list) {
		rc = qed_ll2_post_rx_buffer(QED_LEADING_HWFN(cdev),
					    cdev->ll2->handle,
					    buffer->phys_addr, 0, buffer, 1);
		if (rc) {
			DP_INFO(cdev,
				"Failed to post an Rx buffer; Deleting it\n");
			dma_unmap_single(&cdev->pdev->dev, buffer->phys_addr,
					 cdev->ll2->rx_size, DMA_FROM_DEVICE);
			kfree(buffer->data);
			list_del(&buffer->list);
			kfree(buffer);
		} else {
			cdev->ll2->rx_cnt++;
		}
	}
	spin_unlock_bh(&cdev->ll2->lock);

	if (!cdev->ll2->rx_cnt) {
		DP_INFO(cdev, "Failed passing even a single Rx buffer\n");
		goto release_terminate;
	}

	if (!is_valid_ether_addr(params->ll2_mac_address)) {
		DP_INFO(cdev, "Invalid Ethernet address\n");
		goto release_terminate;
	}

	if (cdev->hwfns[0].hw_info.personality == QED_PCI_ISCSI &&
	    cdev->hwfns[0].pf_params.iscsi_pf_params.ooo_enable) {
		DP_VERBOSE(cdev, QED_MSG_STORAGE, "Starting OOO LL2 queue\n");
		rc = qed_ll2_start_ooo(cdev, params);
		if (rc) {
			DP_INFO(cdev,
				"Failed to initialize the OOO LL2 queue\n");
			goto release_terminate;
		}
	}

	p_ptt = qed_ptt_acquire(QED_LEADING_HWFN(cdev));
	if (!p_ptt) {
		DP_INFO(cdev, "Failed to acquire PTT\n");
		goto release_terminate;
	}

	rc = qed_llh_add_mac_filter(QED_LEADING_HWFN(cdev), p_ptt,
				    params->ll2_mac_address);
	qed_ptt_release(QED_LEADING_HWFN(cdev), p_ptt);
	if (rc) {
		DP_ERR(cdev, "Failed to allocate LLH filter\n");
		goto release_terminate_all;
	}

	ether_addr_copy(cdev->ll2_mac_address, params->ll2_mac_address);

	return 0;

release_terminate_all:

release_terminate:
	qed_ll2_terminate_connection(QED_LEADING_HWFN(cdev), cdev->ll2->handle);
release_fail:
	qed_ll2_release_connection(QED_LEADING_HWFN(cdev), cdev->ll2->handle);
fail:
	qed_ll2_kill_buffers(cdev);
	cdev->ll2->handle = QED_LL2_UNUSED_HANDLE;
	return -EINVAL;
}

static int qed_ll2_stop(struct qed_dev *cdev)
{
	struct qed_ptt *p_ptt;
	int rc;

	if (cdev->ll2->handle == QED_LL2_UNUSED_HANDLE)
		return 0;

	p_ptt = qed_ptt_acquire(QED_LEADING_HWFN(cdev));
	if (!p_ptt) {
		DP_INFO(cdev, "Failed to acquire PTT\n");
		goto fail;
	}

	qed_llh_remove_mac_filter(QED_LEADING_HWFN(cdev), p_ptt,
				  cdev->ll2_mac_address);
	qed_ptt_release(QED_LEADING_HWFN(cdev), p_ptt);
	eth_zero_addr(cdev->ll2_mac_address);

	if (cdev->hwfns[0].hw_info.personality == QED_PCI_ISCSI &&
	    cdev->hwfns[0].pf_params.iscsi_pf_params.ooo_enable)
		qed_ll2_stop_ooo(cdev);

	rc = qed_ll2_terminate_connection(QED_LEADING_HWFN(cdev),
					  cdev->ll2->handle);
	if (rc)
		DP_INFO(cdev, "Failed to terminate LL2 connection\n");

	qed_ll2_kill_buffers(cdev);

	qed_ll2_release_connection(QED_LEADING_HWFN(cdev), cdev->ll2->handle);
	cdev->ll2->handle = QED_LL2_UNUSED_HANDLE;

	return rc;
fail:
	return -EINVAL;
}

static int qed_ll2_start_xmit(struct qed_dev *cdev, struct sk_buff *skb)
{
	const skb_frag_t *frag;
	int rc = -EINVAL, i;
	dma_addr_t mapping;
	u16 vlan = 0;
	u8 flags = 0;

	if (unlikely(skb->ip_summed != CHECKSUM_NONE)) {
		DP_INFO(cdev, "Cannot transmit a checksumed packet\n");
		return -EINVAL;
	}

	if (1 + skb_shinfo(skb)->nr_frags > CORE_LL2_TX_MAX_BDS_PER_PACKET) {
		DP_ERR(cdev, "Cannot transmit a packet with %d fragments\n",
		       1 + skb_shinfo(skb)->nr_frags);
		return -EINVAL;
	}

	mapping = dma_map_single(&cdev->pdev->dev, skb->data,
				 skb->len, DMA_TO_DEVICE);
	if (unlikely(dma_mapping_error(&cdev->pdev->dev, mapping))) {
		DP_NOTICE(cdev, "SKB mapping failed\n");
		return -EINVAL;
	}

	/* Request HW to calculate IP csum */
	if (!((vlan_get_protocol(skb) == htons(ETH_P_IPV6)) &&
	      ipv6_hdr(skb)->nexthdr == NEXTHDR_IPV6))
		flags |= BIT(CORE_TX_BD_FLAGS_IP_CSUM_SHIFT);

	if (skb_vlan_tag_present(skb)) {
		vlan = skb_vlan_tag_get(skb);
		flags |= BIT(CORE_TX_BD_FLAGS_VLAN_INSERTION_SHIFT);
	}

	rc = qed_ll2_prepare_tx_packet(QED_LEADING_HWFN(cdev),
				       cdev->ll2->handle,
				       1 + skb_shinfo(skb)->nr_frags,
				       vlan, flags, 0, QED_LL2_TX_DEST_NW,
				       0 /* RoCE FLAVOR */,
				       mapping, skb->len, skb, 1);
	if (rc)
		goto err;

	for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
		frag = &skb_shinfo(skb)->frags[i];
		if (!cdev->ll2->frags_mapped) {
			mapping = skb_frag_dma_map(&cdev->pdev->dev, frag, 0,
						   skb_frag_size(frag),
						   DMA_TO_DEVICE);

			if (unlikely(dma_mapping_error(&cdev->pdev->dev,
						       mapping))) {
				DP_NOTICE(cdev,
					  "Unable to map frag - dropping packet\n");
				rc = -ENOMEM;
				goto err;
			}
		} else {
			mapping = page_to_phys(skb_frag_page(frag)) |
			    frag->page_offset;
		}

		rc = qed_ll2_set_fragment_of_tx_packet(QED_LEADING_HWFN(cdev),
						       cdev->ll2->handle,
						       mapping,
						       skb_frag_size(frag));

		/* if failed not much to do here, partial packet has been posted
		 * we can't free memory, will need to wait for completion.
		 */
		if (rc)
			goto err2;
	}

	return 0;

err:
	dma_unmap_single(&cdev->pdev->dev, mapping, skb->len, DMA_TO_DEVICE);

err2:
	return rc;
}

static int qed_ll2_stats(struct qed_dev *cdev, struct qed_ll2_stats *stats)
{
	if (!cdev->ll2)
		return -EINVAL;

	return qed_ll2_get_stats(QED_LEADING_HWFN(cdev),
				 cdev->ll2->handle, stats);
}

const struct qed_ll2_ops qed_ll2_ops_pass = {
	.start = &qed_ll2_start,
	.stop = &qed_ll2_stop,
	.start_xmit = &qed_ll2_start_xmit,
	.register_cb_ops = &qed_ll2_register_cb_ops,
	.get_stats = &qed_ll2_stats,
};

int qed_ll2_alloc_if(struct qed_dev *cdev)
{
	cdev->ll2 = kzalloc(sizeof(*cdev->ll2), GFP_KERNEL);
	return cdev->ll2 ? 0 : -ENOMEM;
}

void qed_ll2_dealloc_if(struct qed_dev *cdev)
{
	kfree(cdev->ll2);
	cdev->ll2 = NULL;
}