From c776cbd2596ede6e4b2da554658e8ff36e27d10c Mon Sep 17 00:00:00 2001
From: Vlastimil Babka <vbabka@suse.cz>
Date: Sep 29 2023 12:23:34 +0000
Subject: Merge branch 'users/tbogendoerfer/SLE15-SP4/for-next' into SLE15-SP4


Pull gve updates from Thomas Bogendoerfer.

---

diff --git a/patches.suse/gve-Add-AF_XDP-zero-copy-support-for-GQI-QPL-format.patch b/patches.suse/gve-Add-AF_XDP-zero-copy-support-for-GQI-QPL-format.patch
new file mode 100644
index 0000000..cb10073
--- /dev/null
+++ b/patches.suse/gve-Add-AF_XDP-zero-copy-support-for-GQI-QPL-format.patch
@@ -0,0 +1,533 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Wed, 15 Mar 2023 16:33:12 -0700
+Subject: gve: Add AF_XDP zero-copy support for GQI-QPL format
+Patch-mainline: v6.4-rc1
+Git-commit: fd8e40321a12391e6f554cc637d0c4b6109682a9
+References: bsc#1214479
+
+Adding AF_XDP zero-copy support.
+
+Note: Although these changes support AF_XDP socket in zero-copy
+mode, there is still a copy happening within the driver between
+XSK buffer pool and QPL bounce buffers in GQI-QPL format.
+In GQI-QPL queue format, the driver needs to allocate a fixed size
+memory, the size specified by vNIC device, for RX/TX and register this
+memory as a bounce buffer with the vNIC device when a queue is
+created. The number of pages in the bounce buffer is limited and the
+pages need to be made available to the vNIC by copying the RX data out
+to prevent head-of-line blocking. Therefore, we cannot pass the XSK
+buffer pool to the vNIC.
+
+The number of copies on RX path from the bounce buffer to XSK buffer is 2
+for AF_XDP copy mode (bounce buffer -> allocated page frag -> XSK buffer)
+and 1 for AF_XDP zero-copy mode (bounce buffer -> XSK buffer).
+
+This patch contains the following changes:
+1) Enable and disable XSK buffer pool
+2) Copy XDP packets from QPL bounce buffers to XSK buffer on rx
+3) Copy XDP packets from XSK buffer to QPL bounce buffers and
+   ring the doorbell as part of XDP TX napi poll
+4) ndo_xsk_wakeup callback support
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h         |    7 +
+ drivers/net/ethernet/google/gve/gve_ethtool.c |   14 +-
+ drivers/net/ethernet/google/gve/gve_main.c    |  174 +++++++++++++++++++++++++-
+ drivers/net/ethernet/google/gve/gve_rx.c      |   30 ++++
+ drivers/net/ethernet/google/gve/gve_tx.c      |   58 ++++++++
+ 5 files changed, 274 insertions(+), 9 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -250,6 +250,8 @@ struct gve_rx_ring {
+ 
+ 	/* XDP stuff */
+ 	struct xdp_rxq_info xdp_rxq;
++	struct xdp_rxq_info xsk_rxq;
++	struct xsk_buff_pool *xsk_pool;
+ 	struct page_frag_cache page_cache; /* Page cache to allocate XDP frames */
+ };
+ 
+@@ -277,6 +279,7 @@ struct gve_tx_buffer_state {
+ 	};
+ 	struct {
+ 		u16 size; /* size of xmitted xdp pkt */
++		u8 is_xsk; /* xsk buff */
+ 	} xdp;
+ 	union {
+ 		struct gve_tx_iovec iov[GVE_TX_MAX_IOVEC]; /* segments of this pkt */
+@@ -471,6 +474,10 @@ struct gve_tx_ring {
+ 	dma_addr_t q_resources_bus; /* dma address of the queue resources */
+ 	dma_addr_t complq_bus_dqo; /* dma address of the dqo.compl_ring */
+ 	struct u64_stats_sync statss; /* sync stats for 32bit archs */
++	struct xsk_buff_pool *xsk_pool;
++	u32 xdp_xsk_wakeup;
++	u32 xdp_xsk_done;
++	u64 xdp_xsk_sent;
+ 	u64 xdp_xmit;
+ 	u64 xdp_xmit_errors;
+ } ____cacheline_aligned;
+--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
++++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
+@@ -62,8 +62,8 @@ static const char gve_gstrings_rx_stats[
+ static const char gve_gstrings_tx_stats[][ETH_GSTRING_LEN] = {
+ 	"tx_posted_desc[%u]", "tx_completed_desc[%u]", "tx_consumed_desc[%u]", "tx_bytes[%u]",
+ 	"tx_wake[%u]", "tx_stop[%u]", "tx_event_counter[%u]",
+-	"tx_dma_mapping_error[%u]",
+-	"tx_xdp_xmit[%u]", "tx_xdp_xmit_errors[%u]"
++	"tx_dma_mapping_error[%u]", "tx_xsk_wakeup[%u]",
++	"tx_xsk_done[%u]", "tx_xsk_sent[%u]", "tx_xdp_xmit[%u]", "tx_xdp_xmit_errors[%u]"
+ };
+ 
+ static const char gve_gstrings_adminq_stats[][ETH_GSTRING_LEN] = {
+@@ -381,13 +381,17 @@ gve_get_ethtool_stats(struct net_device
+ 					data[i++] = value;
+ 				}
+ 			}
++			/* XDP xsk counters */
++			data[i++] = tx->xdp_xsk_wakeup;
++			data[i++] = tx->xdp_xsk_done;
+ 			do {
+ 				start = u64_stats_fetch_begin(&priv->tx[ring].statss);
+-				data[i] = tx->xdp_xmit;
+-				data[i + 1] = tx->xdp_xmit_errors;
++				data[i] = tx->xdp_xsk_sent;
++				data[i + 1] = tx->xdp_xmit;
++				data[i + 2] = tx->xdp_xmit_errors;
+ 			} while (u64_stats_fetch_retry(&priv->tx[ring].statss,
+ 						       start));
+-			i += 2; /* XDP tx counters */
++			i += 3; /* XDP tx counters */
+ 		}
+ 	} else {
+ 		i += num_tx_queues * NUM_GVE_TX_CNTS;
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -17,6 +17,7 @@
+ #include <linux/utsname.h>
+ #include <linux/version.h>
+ #include <net/sch_generic.h>
++#include <net/xdp_sock_drv.h>
+ #include "gve.h"
+ #include "gve_dqo.h"
+ #include "gve_adminq.h"
+@@ -1189,6 +1190,7 @@ static int gve_reg_xdp_info(struct gve_p
+ 	struct gve_rx_ring *rx;
+ 	int err = 0;
+ 	int i, j;
++	u32 tx_qid;
+ 
+ 	if (!priv->num_xdp_queues)
+ 		return 0;
+@@ -1205,6 +1207,24 @@ static int gve_reg_xdp_info(struct gve_p
+ 						 MEM_TYPE_PAGE_SHARED, NULL);
+ 		if (err)
+ 			goto err;
++		rx->xsk_pool = xsk_get_pool_from_qid(dev, i);
++		if (rx->xsk_pool) {
++			err = xdp_rxq_info_reg(&rx->xsk_rxq, dev, i,
++					       napi->napi_id);
++			if (err)
++				goto err;
++			err = xdp_rxq_info_reg_mem_model(&rx->xsk_rxq,
++							 MEM_TYPE_XSK_BUFF_POOL, NULL);
++			if (err)
++				goto err;
++			xsk_pool_set_rxq_info(rx->xsk_pool,
++					      &rx->xsk_rxq);
++		}
++	}
++
++	for (i = 0; i < priv->num_xdp_queues; i++) {
++		tx_qid = gve_xdp_tx_queue_id(priv, i);
++		priv->tx[tx_qid].xsk_pool = xsk_get_pool_from_qid(dev, i);
+ 	}
+ 	return 0;
+ 
+@@ -1213,13 +1233,15 @@ err:
+ 		rx = &priv->rx[j];
+ 		if (xdp_rxq_info_is_reg(&rx->xdp_rxq))
+ 			xdp_rxq_info_unreg(&rx->xdp_rxq);
++		if (xdp_rxq_info_is_reg(&rx->xsk_rxq))
++			xdp_rxq_info_unreg(&rx->xsk_rxq);
+ 	}
+ 	return err;
+ }
+ 
+ static void gve_unreg_xdp_info(struct gve_priv *priv)
+ {
+-	int i;
++	int i, tx_qid;
+ 
+ 	if (!priv->num_xdp_queues)
+ 		return;
+@@ -1228,6 +1250,15 @@ static void gve_unreg_xdp_info(struct gv
+ 		struct gve_rx_ring *rx = &priv->rx[i];
+ 
+ 		xdp_rxq_info_unreg(&rx->xdp_rxq);
++		if (rx->xsk_pool) {
++			xdp_rxq_info_unreg(&rx->xsk_rxq);
++			rx->xsk_pool = NULL;
++		}
++	}
++
++	for (i = 0; i < priv->num_xdp_queues; i++) {
++		tx_qid = gve_xdp_tx_queue_id(priv, i);
++		priv->tx[tx_qid].xsk_pool = NULL;
+ 	}
+ }
+ 
+@@ -1470,6 +1501,140 @@ out:
+ 	return err;
+ }
+ 
++static int gve_xsk_pool_enable(struct net_device *dev,
++			       struct xsk_buff_pool *pool,
++			       u16 qid)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++	struct napi_struct *napi;
++	struct gve_rx_ring *rx;
++	int tx_qid;
++	int err;
++
++	if (qid >= priv->rx_cfg.num_queues) {
++		dev_err(&priv->pdev->dev, "xsk pool invalid qid %d", qid);
++		return -EINVAL;
++	}
++	if (xsk_pool_get_rx_frame_size(pool) <
++	     priv->dev->max_mtu + sizeof(struct ethhdr)) {
++		dev_err(&priv->pdev->dev, "xsk pool frame_len too small");
++		return -EINVAL;
++	}
++
++	err = xsk_pool_dma_map(pool, &priv->pdev->dev,
++			       DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING);
++	if (err)
++		return err;
++
++	/* If XDP prog is not installed, return */
++	if (!priv->xdp_prog)
++		return 0;
++
++	rx = &priv->rx[qid];
++	napi = &priv->ntfy_blocks[rx->ntfy_id].napi;
++	err = xdp_rxq_info_reg(&rx->xsk_rxq, dev, qid, napi->napi_id);
++	if (err)
++		goto err;
++
++	err = xdp_rxq_info_reg_mem_model(&rx->xsk_rxq,
++					 MEM_TYPE_XSK_BUFF_POOL, NULL);
++	if (err)
++		goto err;
++
++	xsk_pool_set_rxq_info(pool, &rx->xsk_rxq);
++	rx->xsk_pool = pool;
++
++	tx_qid = gve_xdp_tx_queue_id(priv, qid);
++	priv->tx[tx_qid].xsk_pool = pool;
++
++	return 0;
++err:
++	if (xdp_rxq_info_is_reg(&rx->xsk_rxq))
++		xdp_rxq_info_unreg(&rx->xsk_rxq);
++
++	xsk_pool_dma_unmap(pool,
++			   DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING);
++	return err;
++}
++
++static int gve_xsk_pool_disable(struct net_device *dev,
++				u16 qid)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++	struct napi_struct *napi_rx;
++	struct napi_struct *napi_tx;
++	struct xsk_buff_pool *pool;
++	int tx_qid;
++
++	pool = xsk_get_pool_from_qid(dev, qid);
++	if (!pool)
++		return -EINVAL;
++	if (qid >= priv->rx_cfg.num_queues)
++		return -EINVAL;
++
++	/* If XDP prog is not installed, unmap DMA and return */
++	if (!priv->xdp_prog)
++		goto done;
++
++	tx_qid = gve_xdp_tx_queue_id(priv, qid);
++	if (!netif_running(dev)) {
++		priv->rx[qid].xsk_pool = NULL;
++		xdp_rxq_info_unreg(&priv->rx[qid].xsk_rxq);
++		priv->tx[tx_qid].xsk_pool = NULL;
++		goto done;
++	}
++
++	napi_rx = &priv->ntfy_blocks[priv->rx[qid].ntfy_id].napi;
++	napi_disable(napi_rx); /* make sure current rx poll is done */
++
++	napi_tx = &priv->ntfy_blocks[priv->tx[tx_qid].ntfy_id].napi;
++	napi_disable(napi_tx); /* make sure current tx poll is done */
++
++	priv->rx[qid].xsk_pool = NULL;
++	xdp_rxq_info_unreg(&priv->rx[qid].xsk_rxq);
++	priv->tx[tx_qid].xsk_pool = NULL;
++	smp_mb(); /* Make sure it is visible to the workers on datapath */
++
++	napi_enable(napi_rx);
++	if (gve_rx_work_pending(&priv->rx[qid]))
++		napi_schedule(napi_rx);
++
++	napi_enable(napi_tx);
++	if (gve_tx_clean_pending(priv, &priv->tx[tx_qid]))
++		napi_schedule(napi_tx);
++
++done:
++	xsk_pool_dma_unmap(pool,
++			   DMA_ATTR_SKIP_CPU_SYNC | DMA_ATTR_WEAK_ORDERING);
++	return 0;
++}
++
++static int gve_xsk_wakeup(struct net_device *dev, u32 queue_id, u32 flags)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++	int tx_queue_id = gve_xdp_tx_queue_id(priv, queue_id);
++
++	if (queue_id >= priv->rx_cfg.num_queues || !priv->xdp_prog)
++		return -EINVAL;
++
++	if (flags & XDP_WAKEUP_TX) {
++		struct gve_tx_ring *tx = &priv->tx[tx_queue_id];
++		struct napi_struct *napi =
++			&priv->ntfy_blocks[tx->ntfy_id].napi;
++
++		if (!napi_if_scheduled_mark_missed(napi)) {
++			/* Call local_bh_enable to trigger SoftIRQ processing */
++			local_bh_disable();
++			napi_schedule(napi);
++			local_bh_enable();
++		}
++
++		tx->xdp_xsk_wakeup++;
++	}
++
++	return 0;
++}
++
+ static int verify_xdp_configuration(struct net_device *dev)
+ {
+ 	struct gve_priv *priv = netdev_priv(dev);
+@@ -1513,6 +1678,11 @@ static int gve_xdp(struct net_device *de
+ 	switch (xdp->command) {
+ 	case XDP_SETUP_PROG:
+ 		return gve_set_xdp(priv, xdp->prog, xdp->extack);
++	case XDP_SETUP_XSK_POOL:
++		if (xdp->xsk.pool)
++			return gve_xsk_pool_enable(dev, xdp->xsk.pool, xdp->xsk.queue_id);
++		else
++			return gve_xsk_pool_disable(dev, xdp->xsk.queue_id);
+ 	default:
+ 		return -EINVAL;
+ 	}
+@@ -1714,6 +1884,7 @@ static const struct net_device_ops gve_n
+ 	.ndo_set_features	=	gve_set_features,
+ 	.ndo_bpf		=	gve_xdp,
+ 	.ndo_xdp_xmit		=	gve_xdp_xmit,
++	.ndo_xsk_wakeup		=	gve_xsk_wakeup,
+ };
+ 
+ static void gve_handle_status(struct gve_priv *priv, u32 status)
+@@ -1840,6 +2011,7 @@ static void gve_set_netdev_xdp_features(
+ 		priv->dev->xdp_features = NETDEV_XDP_ACT_BASIC;
+ 		priv->dev->xdp_features |= NETDEV_XDP_ACT_REDIRECT;
+ 		priv->dev->xdp_features |= NETDEV_XDP_ACT_NDO_XMIT;
++		priv->dev->xdp_features |= NETDEV_XDP_ACT_XSK_ZEROCOPY;
+ 	} else {
+ 		priv->dev->xdp_features = 0;
+ 	}
+--- a/drivers/net/ethernet/google/gve/gve_rx.c
++++ b/drivers/net/ethernet/google/gve/gve_rx.c
+@@ -10,6 +10,7 @@
+ #include <linux/etherdevice.h>
+ #include <linux/filter.h>
+ #include <net/xdp.h>
++#include <net/xdp_sock_drv.h>
+ 
+ static void gve_rx_free_buffer(struct device *dev,
+ 			       struct gve_rx_slot_page_info *page_info,
+@@ -593,6 +594,31 @@ static struct sk_buff *gve_rx_skb(struct
+ 	return skb;
+ }
+ 
++static int gve_xsk_pool_redirect(struct net_device *dev,
++				 struct gve_rx_ring *rx,
++				 void *data, int len,
++				 struct bpf_prog *xdp_prog)
++{
++	struct xdp_buff *xdp;
++	int err;
++
++	if (rx->xsk_pool->frame_len < len)
++		return -E2BIG;
++	xdp = xsk_buff_alloc(rx->xsk_pool);
++	if (!xdp) {
++		u64_stats_update_begin(&rx->statss);
++		rx->xdp_alloc_fails++;
++		u64_stats_update_end(&rx->statss);
++		return -ENOMEM;
++	}
++	xdp->data_end = xdp->data + len;
++	memcpy(xdp->data, data, len);
++	err = xdp_do_redirect(dev, xdp, xdp_prog);
++	if (err)
++		xsk_buff_free(xdp);
++	return err;
++}
++
+ static int gve_xdp_redirect(struct net_device *dev, struct gve_rx_ring *rx,
+ 			    struct xdp_buff *orig, struct bpf_prog *xdp_prog)
+ {
+@@ -602,6 +628,10 @@ static int gve_xdp_redirect(struct net_d
+ 	void *frame;
+ 	int err;
+ 
++	if (rx->xsk_pool)
++		return gve_xsk_pool_redirect(dev, rx, orig->data,
++					     len, xdp_prog);
++
+ 	total_len = headroom + SKB_DATA_ALIGN(len) +
+ 		SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
+ 	frame = page_frag_alloc(&rx->page_cache, total_len, GFP_ATOMIC);
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -11,6 +11,7 @@
+ #include <linux/tcp.h>
+ #include <linux/vmalloc.h>
+ #include <linux/skbuff.h>
++#include <net/xdp_sock_drv.h>
+ 
+ static inline void gve_tx_put_doorbell(struct gve_priv *priv,
+ 				       struct gve_queue_resources *q_resources,
+@@ -160,6 +161,7 @@ static int gve_clean_xdp_done(struct gve
+ 	u32 clean_end = tx->done + to_do;
+ 	u64 pkts = 0, bytes = 0;
+ 	size_t space_freed = 0;
++	u32 xsk_complete = 0;
+ 	u32 idx;
+ 
+ 	for (; tx->done < clean_end; tx->done++) {
+@@ -171,6 +173,7 @@ static int gve_clean_xdp_done(struct gve
+ 
+ 		bytes += info->xdp.size;
+ 		pkts++;
++		xsk_complete += info->xdp.is_xsk;
+ 
+ 		info->xdp.size = 0;
+ 		if (info->xdp_frame) {
+@@ -181,6 +184,8 @@ static int gve_clean_xdp_done(struct gve
+ 	}
+ 
+ 	gve_tx_free_fifo(&tx->tx_fifo, space_freed);
++	if (xsk_complete > 0 && tx->xsk_pool)
++		xsk_tx_completed(tx->xsk_pool, xsk_complete);
+ 	u64_stats_update_begin(&tx->statss);
+ 	tx->bytes_done += bytes;
+ 	tx->pkt_done += pkts;
+@@ -718,7 +723,7 @@ netdev_tx_t gve_tx(struct sk_buff *skb,
+ }
+ 
+ static int gve_tx_fill_xdp(struct gve_priv *priv, struct gve_tx_ring *tx,
+-			   void *data, int len, void *frame_p)
++			   void *data, int len, void *frame_p, bool is_xsk)
+ {
+ 	int pad, nfrags, ndescs, iovi, offset;
+ 	struct gve_tx_buffer_state *info;
+@@ -730,6 +735,7 @@ static int gve_tx_fill_xdp(struct gve_pr
+ 	info = &tx->info[reqi & tx->mask];
+ 	info->xdp_frame = frame_p;
+ 	info->xdp.size = len;
++	info->xdp.is_xsk = is_xsk;
+ 
+ 	nfrags = gve_tx_alloc_fifo(&tx->tx_fifo, pad + len,
+ 				   &info->iov[0]);
+@@ -807,7 +813,7 @@ int gve_xdp_xmit_one(struct gve_priv *pr
+ 	if (!gve_can_tx(tx, len + GVE_TX_MAX_HEADER_SIZE - 1))
+ 		return -EBUSY;
+ 
+-	nsegs = gve_tx_fill_xdp(priv, tx, data, len, frame_p);
++	nsegs = gve_tx_fill_xdp(priv, tx, data, len, frame_p, false);
+ 	tx->req += nsegs;
+ 
+ 	return 0;
+@@ -880,11 +886,43 @@ u32 gve_tx_load_event_counter(struct gve
+ 	return be32_to_cpu(counter);
+ }
+ 
++static int gve_xsk_tx(struct gve_priv *priv, struct gve_tx_ring *tx,
++		      int budget)
++{
++	struct xdp_desc desc;
++	int sent = 0, nsegs;
++	void *data;
++
++	spin_lock(&tx->xdp_lock);
++	while (sent < budget) {
++		if (!gve_can_tx(tx, GVE_TX_START_THRESH))
++			goto out;
++
++		if (!xsk_tx_peek_desc(tx->xsk_pool, &desc)) {
++			tx->xdp_xsk_done = tx->xdp_xsk_wakeup;
++			goto out;
++		}
++
++		data = xsk_buff_raw_get_data(tx->xsk_pool, desc.addr);
++		nsegs = gve_tx_fill_xdp(priv, tx, data, desc.len, NULL, true);
++		tx->req += nsegs;
++		sent++;
++	}
++out:
++	if (sent > 0) {
++		gve_tx_put_doorbell(priv, tx->q_resources, tx->req);
++		xsk_tx_release(tx->xsk_pool);
++	}
++	spin_unlock(&tx->xdp_lock);
++	return sent;
++}
++
+ bool gve_xdp_poll(struct gve_notify_block *block, int budget)
+ {
+ 	struct gve_priv *priv = block->priv;
+ 	struct gve_tx_ring *tx = block->tx;
+ 	u32 nic_done;
++	bool repoll;
+ 	u32 to_do;
+ 
+ 	/* If budget is 0, do all the work */
+@@ -895,7 +933,21 @@ bool gve_xdp_poll(struct gve_notify_bloc
+ 	nic_done = gve_tx_load_event_counter(priv, tx);
+ 	to_do = min_t(u32, (nic_done - tx->done), budget);
+ 	gve_clean_xdp_done(priv, tx, to_do);
+-	return nic_done != tx->done;
++	repoll = nic_done != tx->done;
++
++	if (tx->xsk_pool) {
++		int sent = gve_xsk_tx(priv, tx, budget);
++
++		u64_stats_update_begin(&tx->statss);
++		tx->xdp_xsk_sent += sent;
++		u64_stats_update_end(&tx->statss);
++		repoll |= (sent == budget);
++		if (xsk_uses_need_wakeup(tx->xsk_pool))
++			xsk_set_tx_need_wakeup(tx->xsk_pool);
++	}
++
++	/* If we still have work we want to repoll */
++	return repoll;
+ }
+ 
+ bool gve_tx_poll(struct gve_notify_block *block, int budget)
diff --git a/patches.suse/gve-Add-XDP-DROP-and-TX-support-for-GQI-QPL-format.patch b/patches.suse/gve-Add-XDP-DROP-and-TX-support-for-GQI-QPL-format.patch
new file mode 100644
index 0000000..d693fb9
--- /dev/null
+++ b/patches.suse/gve-Add-XDP-DROP-and-TX-support-for-GQI-QPL-format.patch
@@ -0,0 +1,1099 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Wed, 15 Mar 2023 16:33:10 -0700
+Subject: gve: Add XDP DROP and TX support for GQI-QPL format
+Patch-mainline: v6.4-rc1
+Git-commit: 75eaae158b1b7d8d5bde2bafc0bcf778423071d3
+References: bsc#1214479
+
+Add support for XDP PASS, DROP and TX actions.
+
+This patch contains the following changes:
+1) Support installing/uninstalling XDP program
+2) Add dedicated XDP TX queues
+3) Add support for XDP DROP action
+4) Add support for XDP TX action
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h         |   44 ++
+ drivers/net/ethernet/google/gve/gve_ethtool.c |   37 +-
+ drivers/net/ethernet/google/gve/gve_main.c    |  424 ++++++++++++++++++++++++--
+ drivers/net/ethernet/google/gve/gve_rx.c      |   74 ++++
+ drivers/net/ethernet/google/gve/gve_tx.c      |  149 ++++++++-
+ 5 files changed, 689 insertions(+), 39 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -47,6 +47,10 @@
+ 
+ #define GVE_RX_BUFFER_SIZE_DQO 2048
+ 
++#define GVE_XDP_ACTIONS 5
++
++#define GVE_TX_MAX_HEADER_SIZE 182
++
+ #define GVE_GQ_TX_MIN_PKT_DESC_BYTES 182
+ 
+ /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
+@@ -232,7 +236,9 @@ struct gve_rx_ring {
+ 	u64 rx_frag_flip_cnt; /* free-running count of rx segments where page_flip was used */
+ 	u64 rx_frag_copy_cnt; /* free-running count of rx segments copied */
+ 	u64 rx_frag_alloc_cnt; /* free-running count of rx page allocations */
+-
++	u64 xdp_tx_errors;
++	u64 xdp_redirect_errors;
++	u64 xdp_actions[GVE_XDP_ACTIONS];
+ 	u32 q_num; /* queue index */
+ 	u32 ntfy_id; /* notification block index */
+ 	struct gve_queue_resources *q_resources; /* head and tail pointer idx */
+@@ -240,6 +246,9 @@ struct gve_rx_ring {
+ 	struct u64_stats_sync statss; /* sync stats for 32bit archs */
+ 
+ 	struct gve_rx_ctx ctx; /* Info for packet currently being processed in this ring. */
++
++	/* XDP stuff */
++	struct xdp_rxq_info xdp_rxq;
+ };
+ 
+ /* A TX desc ring entry */
+@@ -261,6 +270,9 @@ struct gve_tx_iovec {
+  */
+ struct gve_tx_buffer_state {
+ 	struct sk_buff *skb; /* skb for this pkt */
++	struct {
++		u16 size; /* size of xmitted xdp pkt */
++	} xdp;
+ 	union {
+ 		struct gve_tx_iovec iov[GVE_TX_MAX_IOVEC]; /* segments of this pkt */
+ 		struct {
+@@ -528,9 +540,11 @@ struct gve_priv {
+ 	u16 rx_data_slot_cnt; /* rx buffer length */
+ 	u64 max_registered_pages;
+ 	u64 num_registered_pages; /* num pages registered with NIC */
++	struct bpf_prog *xdp_prog; /* XDP BPF program */
+ 	u32 rx_copybreak; /* copy packets smaller than this */
+ 	u16 default_num_queues; /* default num queues to set up */
+ 
++	u16 num_xdp_queues;
+ 	struct gve_queue_config tx_cfg;
+ 	struct gve_queue_config rx_cfg;
+ 	struct gve_qpl_config qpl_cfg; /* map used QPL ids */
+@@ -787,7 +801,17 @@ static inline u32 gve_num_tx_qpls(struct
+ 	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
+ 		return 0;
+ 
+-	return priv->tx_cfg.num_queues;
++	return priv->tx_cfg.num_queues + priv->num_xdp_queues;
++}
++
++/* Returns the number of XDP tx queue page lists
++ */
++static inline u32 gve_num_xdp_qpls(struct gve_priv *priv)
++{
++	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
++		return 0;
++
++	return priv->num_xdp_queues;
+ }
+ 
+ /* Returns the number of rx queue page lists
+@@ -876,7 +900,17 @@ static inline bool gve_is_gqi(struct gve
+ 
+ static inline u32 gve_num_tx_queues(struct gve_priv *priv)
+ {
+-	return priv->tx_cfg.num_queues;
++	return priv->tx_cfg.num_queues + priv->num_xdp_queues;
++}
++
++static inline u32 gve_xdp_tx_queue_id(struct gve_priv *priv, u32 queue_id)
++{
++	return priv->tx_cfg.num_queues + queue_id;
++}
++
++static inline u32 gve_xdp_tx_start_queue_id(struct gve_priv *priv)
++{
++	return gve_xdp_tx_queue_id(priv, 0);
+ }
+ 
+ /* buffers */
+@@ -887,7 +921,11 @@ void gve_free_page(struct device *dev, s
+ 		   enum dma_data_direction);
+ /* tx handling */
+ netdev_tx_t gve_tx(struct sk_buff *skb, struct net_device *dev);
++int gve_xdp_xmit_one(struct gve_priv *priv, struct gve_tx_ring *tx,
++		     void *data, int len);
++void gve_xdp_tx_flush(struct gve_priv *priv, u32 xdp_qid);
+ bool gve_tx_poll(struct gve_notify_block *block, int budget);
++bool gve_xdp_poll(struct gve_notify_block *block, int budget);
+ int gve_tx_alloc_rings(struct gve_priv *priv, int start_id, int num_rings);
+ void gve_tx_free_rings_gqi(struct gve_priv *priv, int start_id, int num_rings);
+ u32 gve_tx_load_event_counter(struct gve_priv *priv,
+--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
++++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
+@@ -34,6 +34,11 @@ static u32 gve_get_msglevel(struct net_d
+ 	return priv->msg_enable;
+ }
+ 
++/* For the following stats column string names, make sure the order
++ * matches how it is filled in the code. For xdp_aborted, xdp_drop,
++ * xdp_pass, xdp_tx, xdp_redirect, make sure it also matches the order
++ * as declared in enum xdp_action inside file uapi/linux/bpf.h .
++ */
+ static const char gve_gstrings_main_stats[][ETH_GSTRING_LEN] = {
+ 	"rx_packets", "tx_packets", "rx_bytes", "tx_bytes",
+ 	"rx_dropped", "tx_dropped", "tx_timeouts",
+@@ -49,6 +54,9 @@ static const char gve_gstrings_rx_stats[
+ 	"rx_dropped_pkt[%u]", "rx_copybreak_pkt[%u]", "rx_copied_pkt[%u]",
+ 	"rx_queue_drop_cnt[%u]", "rx_no_buffers_posted[%u]",
+ 	"rx_drops_packet_over_mru[%u]", "rx_drops_invalid_checksum[%u]",
++	"rx_xdp_aborted[%u]", "rx_xdp_drop[%u]", "rx_xdp_pass[%u]",
++	"rx_xdp_tx[%u]", "rx_xdp_redirect[%u]",
++	"rx_xdp_tx_errors[%u]", "rx_xdp_redirect_errors[%u]",
+ };
+ 
+ static const char gve_gstrings_tx_stats[][ETH_GSTRING_LEN] = {
+@@ -289,14 +297,25 @@ gve_get_ethtool_stats(struct net_device
+ 			if (skip_nic_stats) {
+ 				/* skip NIC rx stats */
+ 				i += NIC_RX_STATS_REPORT_NUM;
+-				continue;
+-			}
+-			for (j = 0; j < NIC_RX_STATS_REPORT_NUM; j++) {
+-				u64 value =
+-				be64_to_cpu(report_stats[rx_qid_to_stats_idx[ring] + j].value);
++			} else {
++				stats_idx = rx_qid_to_stats_idx[ring];
++				for (j = 0; j < NIC_RX_STATS_REPORT_NUM; j++) {
++					u64 value =
++						be64_to_cpu(report_stats[stats_idx + j].value);
+ 
+-				data[i++] = value;
++					data[i++] = value;
++				}
+ 			}
++			/* XDP rx counters */
++			do {
++				start =	u64_stats_fetch_begin(&priv->rx[ring].statss);
++				for (j = 0; j < GVE_XDP_ACTIONS; j++)
++					data[i + j] = rx->xdp_actions[j];
++				data[i + j++] = rx->xdp_tx_errors;
++				data[i + j++] = rx->xdp_redirect_errors;
++			} while (u64_stats_fetch_retry(&priv->rx[ring].statss,
++						       start));
++			i += GVE_XDP_ACTIONS + 2; /* XDP rx counters */
+ 		}
+ 	} else {
+ 		i += priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS;
+@@ -418,6 +437,12 @@ static int gve_set_channels(struct net_d
+ 	if (!new_rx || !new_tx)
+ 		return -EINVAL;
+ 
++	if (priv->num_xdp_queues &&
++	    (new_tx != new_rx || (2 * new_tx > priv->tx_cfg.max_queues))) {
++		dev_err(&priv->pdev->dev, "XDP load failed: The number of configured RX queues should be equal to the number of configured TX queues and the number of configured RX/TX queues should be less than or equal to half the maximum number of RX/TX queues");
++		return -EINVAL;
++	}
++
+ 	if (!netif_carrier_ok(netdev)) {
+ 		priv->tx_cfg.num_queues = new_tx;
+ 		priv->rx_cfg.num_queues = new_rx;
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -4,8 +4,10 @@
+  * Copyright (C) 2015-2021 Google, Inc.
+  */
+ 
++#include <linux/bpf.h>
+ #include <linux/cpumask.h>
+ #include <linux/etherdevice.h>
++#include <linux/filter.h>
+ #include <linux/interrupt.h>
+ #include <linux/module.h>
+ #include <linux/pci.h>
+@@ -247,8 +249,13 @@ static int gve_napi_poll(struct napi_str
+ 	block = container_of(napi, struct gve_notify_block, napi);
+ 	priv = block->priv;
+ 
+-	if (block->tx)
+-		reschedule |= gve_tx_poll(block, budget);
++	if (block->tx) {
++		if (block->tx->q_num < priv->tx_cfg.num_queues)
++			reschedule |= gve_tx_poll(block, budget);
++		else
++			reschedule |= gve_xdp_poll(block, budget);
++	}
++
+ 	if (block->rx) {
+ 		work_done = gve_rx_poll(block, budget);
+ 		reschedule |= work_done == budget;
+@@ -583,6 +590,28 @@ static void gve_remove_napi(struct gve_p
+ 	netif_napi_del(&block->napi);
+ }
+ 
++static int gve_register_xdp_qpls(struct gve_priv *priv)
++{
++	int start_id;
++	int err;
++	int i;
++
++	start_id = gve_tx_qpl_id(priv, gve_xdp_tx_start_queue_id(priv));
++	for (i = start_id; i < start_id + gve_num_xdp_qpls(priv); i++) {
++		err = gve_adminq_register_page_list(priv, &priv->qpls[i]);
++		if (err) {
++			netif_err(priv, drv, priv->dev,
++				  "failed to register queue page list %d\n",
++				  priv->qpls[i].id);
++			/* This failure will trigger a reset - no need to clean
++			 * up
++			 */
++			return err;
++		}
++	}
++	return 0;
++}
++
+ static int gve_register_qpls(struct gve_priv *priv)
+ {
+ 	int start_id;
+@@ -619,6 +648,26 @@ static int gve_register_qpls(struct gve_
+ 	return 0;
+ }
+ 
++static int gve_unregister_xdp_qpls(struct gve_priv *priv)
++{
++	int start_id;
++	int err;
++	int i;
++
++	start_id = gve_tx_qpl_id(priv, gve_xdp_tx_start_queue_id(priv));
++	for (i = start_id; i < start_id + gve_num_xdp_qpls(priv); i++) {
++		err = gve_adminq_unregister_page_list(priv, priv->qpls[i].id);
++		/* This failure will trigger a reset - no need to clean up */
++		if (err) {
++			netif_err(priv, drv, priv->dev,
++				  "Failed to unregister queue page list %d\n",
++				  priv->qpls[i].id);
++			return err;
++		}
++	}
++	return 0;
++}
++
+ static int gve_unregister_qpls(struct gve_priv *priv)
+ {
+ 	int start_id;
+@@ -651,6 +700,27 @@ static int gve_unregister_qpls(struct gv
+ 	return 0;
+ }
+ 
++static int gve_create_xdp_rings(struct gve_priv *priv)
++{
++	int err;
++
++	err = gve_adminq_create_tx_queues(priv,
++					  gve_xdp_tx_start_queue_id(priv),
++					  priv->num_xdp_queues);
++	if (err) {
++		netif_err(priv, drv, priv->dev, "failed to create %d XDP tx queues\n",
++			  priv->num_xdp_queues);
++		/* This failure will trigger a reset - no need to clean
++		 * up
++		 */
++		return err;
++	}
++	netif_dbg(priv, drv, priv->dev, "created %d XDP tx queues\n",
++		  priv->num_xdp_queues);
++
++	return 0;
++}
++
+ static int gve_create_rings(struct gve_priv *priv)
+ {
+ 	int num_tx_queues = gve_num_tx_queues(priv);
+@@ -700,6 +770,23 @@ static int gve_create_rings(struct gve_p
+ 	return 0;
+ }
+ 
++static void add_napi_init_xdp_sync_stats(struct gve_priv *priv,
++					 int (*napi_poll)(struct napi_struct *napi,
++							  int budget))
++{
++	int start_id = gve_xdp_tx_start_queue_id(priv);
++	int i;
++
++	/* Add xdp tx napi & init sync stats*/
++	for (i = start_id; i < start_id + priv->num_xdp_queues; i++) {
++		int ntfy_idx = gve_tx_idx_to_ntfy(priv, i);
++
++		u64_stats_init(&priv->tx[i].statss);
++		priv->tx[i].ntfy_id = ntfy_idx;
++		gve_add_napi(priv, ntfy_idx, napi_poll);
++	}
++}
++
+ static void add_napi_init_sync_stats(struct gve_priv *priv,
+ 				     int (*napi_poll)(struct napi_struct *napi,
+ 						      int budget))
+@@ -733,6 +820,23 @@ static void gve_tx_free_rings(struct gve
+ 	}
+ }
+ 
++static int gve_alloc_xdp_rings(struct gve_priv *priv)
++{
++	int start_id;
++	int err = 0;
++
++	if (!priv->num_xdp_queues)
++		return 0;
++
++	start_id = gve_xdp_tx_start_queue_id(priv);
++	err = gve_tx_alloc_rings(priv, start_id, priv->num_xdp_queues);
++	if (err)
++		return err;
++	add_napi_init_xdp_sync_stats(priv, gve_napi_poll);
++
++	return 0;
++}
++
+ static int gve_alloc_rings(struct gve_priv *priv)
+ {
+ 	int err;
+@@ -783,6 +887,26 @@ free_tx:
+ 	return err;
+ }
+ 
++static int gve_destroy_xdp_rings(struct gve_priv *priv)
++{
++	int start_id;
++	int err;
++
++	start_id = gve_xdp_tx_start_queue_id(priv);
++	err = gve_adminq_destroy_tx_queues(priv,
++					   start_id,
++					   priv->num_xdp_queues);
++	if (err) {
++		netif_err(priv, drv, priv->dev,
++			  "failed to destroy XDP queues\n");
++		/* This failure will trigger a reset - no need to clean up */
++		return err;
++	}
++	netif_dbg(priv, drv, priv->dev, "destroyed XDP queues\n");
++
++	return 0;
++}
++
+ static int gve_destroy_rings(struct gve_priv *priv)
+ {
+ 	int num_tx_queues = gve_num_tx_queues(priv);
+@@ -815,6 +939,21 @@ static void gve_rx_free_rings(struct gve
+ 		gve_rx_free_rings_dqo(priv);
+ }
+ 
++static void gve_free_xdp_rings(struct gve_priv *priv)
++{
++	int ntfy_idx, start_id;
++	int i;
++
++	start_id = gve_xdp_tx_start_queue_id(priv);
++	if (priv->tx) {
++		for (i = start_id; i <  start_id + priv->num_xdp_queues; i++) {
++			ntfy_idx = gve_tx_idx_to_ntfy(priv, i);
++			gve_remove_napi(priv, ntfy_idx);
++		}
++		gve_tx_free_rings(priv, start_id, priv->num_xdp_queues);
++	}
++}
++
+ static void gve_free_rings(struct gve_priv *priv)
+ {
+ 	int num_tx_queues = gve_num_tx_queues(priv);
+@@ -930,6 +1069,28 @@ free_pages:
+ 	priv->num_registered_pages -= qpl->num_entries;
+ }
+ 
++static int gve_alloc_xdp_qpls(struct gve_priv *priv)
++{
++	int start_id;
++	int i, j;
++	int err;
++
++	start_id = gve_tx_qpl_id(priv, gve_xdp_tx_start_queue_id(priv));
++	for (i = start_id; i < start_id + gve_num_xdp_qpls(priv); i++) {
++		err = gve_alloc_queue_page_list(priv, i,
++						priv->tx_pages_per_qpl);
++		if (err)
++			goto free_qpls;
++	}
++
++	return 0;
++
++free_qpls:
++	for (j = start_id; j <= i; j++)
++		gve_free_queue_page_list(priv, j);
++	return err;
++}
++
+ static int gve_alloc_qpls(struct gve_priv *priv)
+ {
+ 	int max_queues = priv->tx_cfg.max_queues + priv->rx_cfg.max_queues;
+@@ -979,6 +1140,16 @@ free_qpls:
+ 	return err;
+ }
+ 
++static void gve_free_xdp_qpls(struct gve_priv *priv)
++{
++	int start_id;
++	int i;
++
++	start_id = gve_tx_qpl_id(priv, gve_xdp_tx_start_queue_id(priv));
++	for (i = start_id; i < start_id + gve_num_xdp_qpls(priv); i++)
++		gve_free_queue_page_list(priv, i);
++}
++
+ static void gve_free_qpls(struct gve_priv *priv)
+ {
+ 	int max_queues = priv->tx_cfg.max_queues + priv->rx_cfg.max_queues;
+@@ -1012,11 +1183,64 @@ static int gve_reset_recovery(struct gve
+ static void gve_turndown(struct gve_priv *priv);
+ static void gve_turnup(struct gve_priv *priv);
+ 
++static int gve_reg_xdp_info(struct gve_priv *priv, struct net_device *dev)
++{
++	struct napi_struct *napi;
++	struct gve_rx_ring *rx;
++	int err = 0;
++	int i, j;
++
++	if (!priv->num_xdp_queues)
++		return 0;
++
++	for (i = 0; i < priv->rx_cfg.num_queues; i++) {
++		rx = &priv->rx[i];
++		napi = &priv->ntfy_blocks[rx->ntfy_id].napi;
++
++		err = xdp_rxq_info_reg(&rx->xdp_rxq, dev, i,
++				       napi->napi_id);
++		if (err)
++			goto err;
++		err = xdp_rxq_info_reg_mem_model(&rx->xdp_rxq,
++						 MEM_TYPE_PAGE_SHARED, NULL);
++		if (err)
++			goto err;
++	}
++	return 0;
++
++err:
++	for (j = i; j >= 0; j--) {
++		rx = &priv->rx[j];
++		if (xdp_rxq_info_is_reg(&rx->xdp_rxq))
++			xdp_rxq_info_unreg(&rx->xdp_rxq);
++	}
++	return err;
++}
++
++static void gve_unreg_xdp_info(struct gve_priv *priv)
++{
++	int i;
++
++	if (!priv->num_xdp_queues)
++		return;
++
++	for (i = 0; i < priv->rx_cfg.num_queues; i++) {
++		struct gve_rx_ring *rx = &priv->rx[i];
++
++		xdp_rxq_info_unreg(&rx->xdp_rxq);
++	}
++}
++
+ static int gve_open(struct net_device *dev)
+ {
+ 	struct gve_priv *priv = netdev_priv(dev);
+ 	int err;
+ 
++	if (priv->xdp_prog)
++		priv->num_xdp_queues = priv->rx_cfg.num_queues;
++	else
++		priv->num_xdp_queues = 0;
++
+ 	err = gve_alloc_qpls(priv);
+ 	if (err)
+ 		return err;
+@@ -1032,6 +1256,10 @@ static int gve_open(struct net_device *d
+ 	if (err)
+ 		goto free_rings;
+ 
++	err = gve_reg_xdp_info(priv, dev);
++	if (err)
++		goto free_rings;
++
+ 	err = gve_register_qpls(priv);
+ 	if (err)
+ 		goto reset;
+@@ -1096,6 +1324,7 @@ static int gve_close(struct net_device *
+ 	}
+ 	del_timer_sync(&priv->stats_report_timer);
+ 
++	gve_unreg_xdp_info(priv);
+ 	gve_free_rings(priv);
+ 	gve_free_qpls(priv);
+ 	priv->interface_down_cnt++;
+@@ -1112,6 +1341,167 @@ err:
+ 	return gve_reset_recovery(priv, false);
+ }
+ 
++static int gve_remove_xdp_queues(struct gve_priv *priv)
++{
++	int err;
++
++	err = gve_destroy_xdp_rings(priv);
++	if (err)
++		return err;
++
++	err = gve_unregister_xdp_qpls(priv);
++	if (err)
++		return err;
++
++	gve_unreg_xdp_info(priv);
++	gve_free_xdp_rings(priv);
++	gve_free_xdp_qpls(priv);
++	priv->num_xdp_queues = 0;
++	return 0;
++}
++
++static int gve_add_xdp_queues(struct gve_priv *priv)
++{
++	int err;
++
++	priv->num_xdp_queues = priv->tx_cfg.num_queues;
++
++	err = gve_alloc_xdp_qpls(priv);
++	if (err)
++		goto err;
++
++	err = gve_alloc_xdp_rings(priv);
++	if (err)
++		goto free_xdp_qpls;
++
++	err = gve_reg_xdp_info(priv, priv->dev);
++	if (err)
++		goto free_xdp_rings;
++
++	err = gve_register_xdp_qpls(priv);
++	if (err)
++		goto free_xdp_rings;
++
++	err = gve_create_xdp_rings(priv);
++	if (err)
++		goto free_xdp_rings;
++
++	return 0;
++
++free_xdp_rings:
++	gve_free_xdp_rings(priv);
++free_xdp_qpls:
++	gve_free_xdp_qpls(priv);
++err:
++	priv->num_xdp_queues = 0;
++	return err;
++}
++
++static void gve_handle_link_status(struct gve_priv *priv, bool link_status)
++{
++	if (!gve_get_napi_enabled(priv))
++		return;
++
++	if (link_status == netif_carrier_ok(priv->dev))
++		return;
++
++	if (link_status) {
++		netdev_info(priv->dev, "Device link is up.\n");
++		netif_carrier_on(priv->dev);
++	} else {
++		netdev_info(priv->dev, "Device link is down.\n");
++		netif_carrier_off(priv->dev);
++	}
++}
++
++static int gve_set_xdp(struct gve_priv *priv, struct bpf_prog *prog,
++		       struct netlink_ext_ack *extack)
++{
++	struct bpf_prog *old_prog;
++	int err = 0;
++	u32 status;
++
++	old_prog = READ_ONCE(priv->xdp_prog);
++	if (!netif_carrier_ok(priv->dev)) {
++		WRITE_ONCE(priv->xdp_prog, prog);
++		if (old_prog)
++			bpf_prog_put(old_prog);
++		return 0;
++	}
++
++	gve_turndown(priv);
++	if (!old_prog && prog) {
++		// Allocate XDP TX queues if an XDP program is
++		// being installed
++		err = gve_add_xdp_queues(priv);
++		if (err)
++			goto out;
++	} else if (old_prog && !prog) {
++		// Remove XDP TX queues if an XDP program is
++		// being uninstalled
++		err = gve_remove_xdp_queues(priv);
++		if (err)
++			goto out;
++	}
++	WRITE_ONCE(priv->xdp_prog, prog);
++	if (old_prog)
++		bpf_prog_put(old_prog);
++
++out:
++	gve_turnup(priv);
++	status = ioread32be(&priv->reg_bar0->device_status);
++	gve_handle_link_status(priv, GVE_DEVICE_STATUS_LINK_STATUS_MASK & status);
++	return err;
++}
++
++static int verify_xdp_configuration(struct net_device *dev)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++
++	if (dev->features & NETIF_F_LRO) {
++		netdev_warn(dev, "XDP is not supported when LRO is on.\n");
++		return -EOPNOTSUPP;
++	}
++
++	if (priv->queue_format != GVE_GQI_QPL_FORMAT) {
++		netdev_warn(dev, "XDP is not supported in mode %d.\n",
++			    priv->queue_format);
++		return -EOPNOTSUPP;
++	}
++
++	if (dev->mtu > (PAGE_SIZE / 2) - sizeof(struct ethhdr) - GVE_RX_PAD) {
++		netdev_warn(dev, "XDP is not supported for mtu %d.\n",
++			    dev->mtu);
++		return -EOPNOTSUPP;
++	}
++
++	if (priv->rx_cfg.num_queues != priv->tx_cfg.num_queues ||
++	    (2 * priv->tx_cfg.num_queues > priv->tx_cfg.max_queues)) {
++		netdev_warn(dev, "XDP load failed: The number of configured RX queues %d should be equal to the number of configured TX queues %d and the number of configured RX/TX queues should be less than or equal to half the maximum number of RX/TX queues %d",
++			    priv->rx_cfg.num_queues,
++			    priv->tx_cfg.num_queues,
++			    priv->tx_cfg.max_queues);
++		return -EINVAL;
++	}
++	return 0;
++}
++
++static int gve_xdp(struct net_device *dev, struct netdev_bpf *xdp)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++	int err;
++
++	err = verify_xdp_configuration(dev);
++	if (err)
++		return err;
++	switch (xdp->command) {
++	case XDP_SETUP_PROG:
++		return gve_set_xdp(priv, xdp->prog, xdp->extack);
++	default:
++		return -EINVAL;
++	}
++}
++
+ int gve_adjust_queues(struct gve_priv *priv,
+ 		      struct gve_queue_config new_rx_config,
+ 		      struct gve_queue_config new_tx_config)
+@@ -1306,6 +1696,7 @@ static const struct net_device_ops gve_n
+ 	.ndo_get_stats64	=	gve_get_stats,
+ 	.ndo_tx_timeout         =       gve_tx_timeout,
+ 	.ndo_set_features	=	gve_set_features,
++	.ndo_bpf		=	gve_xdp,
+ };
+ 
+ static void gve_handle_status(struct gve_priv *priv, u32 status)
+@@ -1412,23 +1803,6 @@ void gve_handle_report_stats(struct gve_
+ 	}
+ }
+ 
+-static void gve_handle_link_status(struct gve_priv *priv, bool link_status)
+-{
+-	if (!gve_get_napi_enabled(priv))
+-		return;
+-
+-	if (link_status == netif_carrier_ok(priv->dev))
+-		return;
+-
+-	if (link_status) {
+-		netdev_info(priv->dev, "Device link is up.\n");
+-		netif_carrier_on(priv->dev);
+-	} else {
+-		netdev_info(priv->dev, "Device link is down.\n");
+-		netif_carrier_off(priv->dev);
+-	}
+-}
+-
+ /* Handle NIC status register changes, reset requests and report stats */
+ static void gve_service_task(struct work_struct *work)
+ {
+@@ -1442,6 +1816,17 @@ static void gve_service_task(struct work
+ 	gve_handle_link_status(priv, GVE_DEVICE_STATUS_LINK_STATUS_MASK & status);
+ }
+ 
++static void gve_set_netdev_xdp_features(struct gve_priv *priv)
++{
++#if 0 /* not supported in backport */
++	if (priv->queue_format == GVE_GQI_QPL_FORMAT) {
++		priv->dev->xdp_features = NETDEV_XDP_ACT_BASIC;
++	} else {
++		priv->dev->xdp_features = 0;
++	}
++#endif
++}
++
+ static int gve_init_priv(struct gve_priv *priv, bool skip_describe_device)
+ {
+ 	int num_ntfy;
+@@ -1520,6 +1905,7 @@ static int gve_init_priv(struct gve_priv
+ 	}
+ 
+ setup_device:
++	gve_set_netdev_xdp_features(priv);
+ 	err = gve_setup_device_resources(priv);
+ 	if (!err)
+ 		return 0;
+--- a/drivers/net/ethernet/google/gve/gve_rx.c
++++ b/drivers/net/ethernet/google/gve/gve_rx.c
+@@ -8,6 +8,8 @@
+ #include "gve_adminq.h"
+ #include "gve_utils.h"
+ #include <linux/etherdevice.h>
++#include <linux/filter.h>
++#include <net/xdp.h>
+ 
+ static void gve_rx_free_buffer(struct device *dev,
+ 			       struct gve_rx_slot_page_info *page_info,
+@@ -591,6 +593,43 @@ static struct sk_buff *gve_rx_skb(struct
+ 	return skb;
+ }
+ 
++static void gve_xdp_done(struct gve_priv *priv, struct gve_rx_ring *rx,
++			 struct xdp_buff *xdp, struct bpf_prog *xprog,
++			 int xdp_act)
++{
++	struct gve_tx_ring *tx;
++	int tx_qid;
++	int err;
++
++	switch (xdp_act) {
++	case XDP_ABORTED:
++	case XDP_DROP:
++	default:
++		break;
++	case XDP_TX:
++		tx_qid = gve_xdp_tx_queue_id(priv, rx->q_num);
++		tx = &priv->tx[tx_qid];
++		err = gve_xdp_xmit_one(priv, tx, xdp->data,
++				       xdp->data_end - xdp->data);
++
++		if (unlikely(err)) {
++			u64_stats_update_begin(&rx->statss);
++			rx->xdp_tx_errors++;
++			u64_stats_update_end(&rx->statss);
++		}
++		break;
++	case XDP_REDIRECT:
++		u64_stats_update_begin(&rx->statss);
++		rx->xdp_redirect_errors++;
++		u64_stats_update_end(&rx->statss);
++		break;
++	}
++	u64_stats_update_begin(&rx->statss);
++	if ((u32)xdp_act < GVE_XDP_ACTIONS)
++		rx->xdp_actions[xdp_act]++;
++	u64_stats_update_end(&rx->statss);
++}
++
+ #define GVE_PKTCONT_BIT_IS_SET(x) (GVE_RXF_PKT_CONT & (x))
+ static void gve_rx(struct gve_rx_ring *rx, netdev_features_t feat,
+ 		   struct gve_rx_desc *desc, u32 idx,
+@@ -603,9 +642,12 @@ static void gve_rx(struct gve_rx_ring *r
+ 	union gve_rx_data_slot *data_slot;
+ 	struct gve_priv *priv = rx->gve;
+ 	struct sk_buff *skb = NULL;
++	struct bpf_prog *xprog;
++	struct xdp_buff xdp;
+ 	dma_addr_t page_bus;
+ 	void *va;
+ 
++	u16 len = frag_size;
+ 	struct napi_struct *napi = &priv->ntfy_blocks[rx->ntfy_id].napi;
+ 	bool is_first_frag = ctx->frag_cnt == 0;
+ 
+@@ -645,9 +687,35 @@ static void gve_rx(struct gve_rx_ring *r
+ 	dma_sync_single_for_cpu(&priv->pdev->dev, page_bus,
+ 				PAGE_SIZE, DMA_FROM_DEVICE);
+ 	page_info->pad = is_first_frag ? GVE_RX_PAD : 0;
++	len -= page_info->pad;
+ 	frag_size -= page_info->pad;
+ 
+-	skb = gve_rx_skb(priv, rx, page_info, napi, frag_size,
++	xprog = READ_ONCE(priv->xdp_prog);
++	if (xprog && is_only_frag) {
++		void *old_data;
++		int xdp_act;
++
++		xdp_init_buff(&xdp, rx->packet_buffer_size, &rx->xdp_rxq);
++		xdp_prepare_buff(&xdp, page_info->page_address +
++				 page_info->page_offset, GVE_RX_PAD,
++				 len, false);
++		old_data = xdp.data;
++		xdp_act = bpf_prog_run_xdp(xprog, &xdp);
++		if (xdp_act != XDP_PASS) {
++			gve_xdp_done(priv, rx, &xdp, xprog, xdp_act);
++			ctx->total_size += frag_size;
++			goto finish_ok_pkt;
++		}
++
++		page_info->pad += xdp.data - old_data;
++		len = xdp.data_end - xdp.data;
++
++		u64_stats_update_begin(&rx->statss);
++		rx->xdp_actions[XDP_PASS]++;
++		u64_stats_update_end(&rx->statss);
++	}
++
++	skb = gve_rx_skb(priv, rx, page_info, napi, len,
+ 			 data_slot, is_only_frag);
+ 	if (!skb) {
+ 		u64_stats_update_begin(&rx->statss);
+@@ -773,6 +841,7 @@ static bool gve_rx_refill_buffers(struct
+ static int gve_clean_rx_done(struct gve_rx_ring *rx, int budget,
+ 			     netdev_features_t feat)
+ {
++	u64 xdp_txs = rx->xdp_actions[XDP_TX];
+ 	struct gve_rx_ctx *ctx = &rx->ctx;
+ 	struct gve_priv *priv = rx->gve;
+ 	struct gve_rx_cnts cnts = {0};
+@@ -820,6 +889,9 @@ static int gve_clean_rx_done(struct gve_
+ 		u64_stats_update_end(&rx->statss);
+ 	}
+ 
++	if (xdp_txs != rx->xdp_actions[XDP_TX])
++		gve_xdp_tx_flush(priv, rx->q_num);
++
+ 	/* restock ring slots */
+ 	if (!rx->data.raw_addressing) {
+ 		/* In QPL mode buffs are refilled as the desc are processed */
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -19,6 +19,14 @@ static inline void gve_tx_put_doorbell(s
+ 	iowrite32be(val, &priv->db_bar2[be32_to_cpu(q_resources->db_index)]);
+ }
+ 
++void gve_xdp_tx_flush(struct gve_priv *priv, u32 xdp_qid)
++{
++	u32 tx_qid = gve_xdp_tx_queue_id(priv, xdp_qid);
++	struct gve_tx_ring *tx = &priv->tx[tx_qid];
++
++	gve_tx_put_doorbell(priv, tx->q_resources, tx->req);
++}
++
+ /* gvnic can only transmit from a Registered Segment.
+  * We copy skb payloads into the registered segment before writing Tx
+  * descriptors and ringing the Tx doorbell.
+@@ -132,6 +140,50 @@ static void gve_tx_free_fifo(struct gve_
+ 	atomic_add(bytes, &fifo->available);
+ }
+ 
++static size_t gve_tx_clear_buffer_state(struct gve_tx_buffer_state *info)
++{
++	size_t space_freed = 0;
++	int i;
++
++	for (i = 0; i < ARRAY_SIZE(info->iov); i++) {
++		space_freed += info->iov[i].iov_len + info->iov[i].iov_padding;
++		info->iov[i].iov_len = 0;
++		info->iov[i].iov_padding = 0;
++	}
++	return space_freed;
++}
++
++static int gve_clean_xdp_done(struct gve_priv *priv, struct gve_tx_ring *tx,
++			      u32 to_do)
++{
++	struct gve_tx_buffer_state *info;
++	u32 clean_end = tx->done + to_do;
++	u64 pkts = 0, bytes = 0;
++	size_t space_freed = 0;
++	u32 idx;
++
++	for (; tx->done < clean_end; tx->done++) {
++		idx = tx->done & tx->mask;
++		info = &tx->info[idx];
++
++		if (unlikely(!info->xdp.size))
++			continue;
++
++		bytes += info->xdp.size;
++		pkts++;
++
++		info->xdp.size = 0;
++		space_freed += gve_tx_clear_buffer_state(info);
++	}
++
++	gve_tx_free_fifo(&tx->tx_fifo, space_freed);
++	u64_stats_update_begin(&tx->statss);
++	tx->bytes_done += bytes;
++	tx->pkt_done += pkts;
++	u64_stats_update_end(&tx->statss);
++	return pkts;
++}
++
+ static int gve_clean_tx_done(struct gve_priv *priv, struct gve_tx_ring *tx,
+ 			     u32 to_do, bool try_to_wake);
+ 
+@@ -144,8 +196,12 @@ static void gve_tx_free_ring(struct gve_
+ 
+ 	gve_tx_remove_from_block(priv, idx);
+ 	slots = tx->mask + 1;
+-	gve_clean_tx_done(priv, tx, priv->tx_desc_cnt, false);
+-	netdev_tx_reset_queue(tx->netdev_txq);
++	if (tx->q_num < priv->tx_cfg.num_queues) {
++		gve_clean_tx_done(priv, tx, priv->tx_desc_cnt, false);
++		netdev_tx_reset_queue(tx->netdev_txq);
++	} else {
++		gve_clean_xdp_done(priv, tx, priv->tx_desc_cnt);
++	}
+ 
+ 	dma_free_coherent(hdev, sizeof(*tx->q_resources),
+ 			  tx->q_resources, tx->q_resources_bus);
+@@ -213,7 +269,8 @@ static int gve_tx_alloc_ring(struct gve_
+ 
+ 	netif_dbg(priv, drv, priv->dev, "tx[%d]->bus=%lx\n", idx,
+ 		  (unsigned long)tx->bus);
+-	tx->netdev_txq = netdev_get_tx_queue(priv->dev, idx);
++	if (idx < priv->tx_cfg.num_queues)
++		tx->netdev_txq = netdev_get_tx_queue(priv->dev, idx);
+ 	gve_tx_add_to_block(priv, idx);
+ 
+ 	return 0;
+@@ -655,6 +712,65 @@ netdev_tx_t gve_tx(struct sk_buff *skb,
+ 	return NETDEV_TX_OK;
+ }
+ 
++static int gve_tx_fill_xdp(struct gve_priv *priv, struct gve_tx_ring *tx,
++			   void *data, int len)
++{
++	int pad, nfrags, ndescs, iovi, offset;
++	struct gve_tx_buffer_state *info;
++	u32 reqi = tx->req;
++
++	pad = gve_tx_fifo_pad_alloc_one_frag(&tx->tx_fifo, len);
++	if (pad >= GVE_TX_MAX_HEADER_SIZE)
++		pad = 0;
++	info = &tx->info[reqi & tx->mask];
++	info->xdp.size = len;
++
++	nfrags = gve_tx_alloc_fifo(&tx->tx_fifo, pad + len,
++				   &info->iov[0]);
++	iovi = pad > 0;
++	ndescs = nfrags - iovi;
++	offset = 0;
++
++	while (iovi < nfrags) {
++		if (!offset)
++			gve_tx_fill_pkt_desc(&tx->desc[reqi & tx->mask], 0,
++					     CHECKSUM_NONE, false, 0, ndescs,
++					     info->iov[iovi].iov_len,
++					     info->iov[iovi].iov_offset, len);
++		else
++			gve_tx_fill_seg_desc(&tx->desc[reqi & tx->mask],
++					     0, 0, false, false,
++					     info->iov[iovi].iov_len,
++					     info->iov[iovi].iov_offset);
++
++		memcpy(tx->tx_fifo.base + info->iov[iovi].iov_offset,
++		       data + offset, info->iov[iovi].iov_len);
++		gve_dma_sync_for_device(&priv->pdev->dev,
++					tx->tx_fifo.qpl->page_buses,
++					info->iov[iovi].iov_offset,
++					info->iov[iovi].iov_len);
++		offset += info->iov[iovi].iov_len;
++		iovi++;
++		reqi++;
++	}
++
++	return ndescs;
++}
++
++int gve_xdp_xmit_one(struct gve_priv *priv, struct gve_tx_ring *tx,
++		     void *data, int len)
++{
++	int nsegs;
++
++	if (!gve_can_tx(tx, len + GVE_TX_MAX_HEADER_SIZE - 1))
++		return -EBUSY;
++
++	nsegs = gve_tx_fill_xdp(priv, tx, data, len);
++	tx->req += nsegs;
++
++	return 0;
++}
++
+ #define GVE_TX_START_THRESH	PAGE_SIZE
+ 
+ static int gve_clean_tx_done(struct gve_priv *priv, struct gve_tx_ring *tx,
+@@ -664,8 +780,8 @@ static int gve_clean_tx_done(struct gve_
+ 	u64 pkts = 0, bytes = 0;
+ 	size_t space_freed = 0;
+ 	struct sk_buff *skb;
+-	int i, j;
+ 	u32 idx;
++	int j;
+ 
+ 	for (j = 0; j < to_do; j++) {
+ 		idx = tx->done & tx->mask;
+@@ -687,12 +803,7 @@ static int gve_clean_tx_done(struct gve_
+ 			dev_consume_skb_any(skb);
+ 			if (tx->raw_addressing)
+ 				continue;
+-			/* FIFO free */
+-			for (i = 0; i < ARRAY_SIZE(info->iov); i++) {
+-				space_freed += info->iov[i].iov_len + info->iov[i].iov_padding;
+-				info->iov[i].iov_len = 0;
+-				info->iov[i].iov_padding = 0;
+-			}
++			space_freed += gve_tx_clear_buffer_state(info);
+ 		}
+ 	}
+ 
+@@ -727,6 +838,24 @@ u32 gve_tx_load_event_counter(struct gve
+ 	return be32_to_cpu(counter);
+ }
+ 
++bool gve_xdp_poll(struct gve_notify_block *block, int budget)
++{
++	struct gve_priv *priv = block->priv;
++	struct gve_tx_ring *tx = block->tx;
++	u32 nic_done;
++	u32 to_do;
++
++	/* If budget is 0, do all the work */
++	if (budget == 0)
++		budget = INT_MAX;
++
++	/* Find out how much work there is to be done */
++	nic_done = gve_tx_load_event_counter(priv, tx);
++	to_do = min_t(u32, (nic_done - tx->done), budget);
++	gve_clean_xdp_done(priv, tx, to_do);
++	return nic_done != tx->done;
++}
++
+ bool gve_tx_poll(struct gve_notify_block *block, int budget)
+ {
+ 	struct gve_priv *priv = block->priv;
diff --git a/patches.suse/gve-Add-XDP-REDIRECT-support-for-GQI-QPL-format.patch b/patches.suse/gve-Add-XDP-REDIRECT-support-for-GQI-QPL-format.patch
new file mode 100644
index 0000000..4f569d8
--- /dev/null
+++ b/patches.suse/gve-Add-XDP-REDIRECT-support-for-GQI-QPL-format.patch
@@ -0,0 +1,375 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Wed, 15 Mar 2023 16:33:11 -0700
+Subject: gve: Add XDP REDIRECT support for GQI-QPL format
+Patch-mainline: v6.4-rc1
+Git-commit: 39a7f4aa3e4a7947614cf1d5c27abba3300adb1e
+References: bsc#1214479
+
+This patch contains the following changes:
+1) Support for XDP REDIRECT action on rx
+2) ndo_xdp_xmit callback support
+
+In GQI-QPL queue format, the driver needs to allocate a fixed size
+memory, the size specified by vNIC device, for RX/TX and register this
+memory as a bounce buffer with the vNIC device when a queue is created.
+The number of pages in the bounce buffer is limited and the pages need to
+be made available to the vNIC by copying the RX data out to prevent
+head-of-line blocking. The XDP_REDIRECT packets are therefore immediately
+copied to a newly allocated page.
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h         |   15 +++++++-
+ drivers/net/ethernet/google/gve/gve_ethtool.c |   26 +++++++++-----
+ drivers/net/ethernet/google/gve/gve_main.c    |   19 ++++++++++
+ drivers/net/ethernet/google/gve/gve_rx.c      |   47 +++++++++++++++++++++++--
+ drivers/net/ethernet/google/gve/gve_tx.c      |   48 ++++++++++++++++++++++++--
+ 5 files changed, 138 insertions(+), 17 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -238,6 +238,7 @@ struct gve_rx_ring {
+ 	u64 rx_frag_alloc_cnt; /* free-running count of rx page allocations */
+ 	u64 xdp_tx_errors;
+ 	u64 xdp_redirect_errors;
++	u64 xdp_alloc_fails;
+ 	u64 xdp_actions[GVE_XDP_ACTIONS];
+ 	u32 q_num; /* queue index */
+ 	u32 ntfy_id; /* notification block index */
+@@ -249,6 +250,7 @@ struct gve_rx_ring {
+ 
+ 	/* XDP stuff */
+ 	struct xdp_rxq_info xdp_rxq;
++	struct page_frag_cache page_cache; /* Page cache to allocate XDP frames */
+ };
+ 
+ /* A TX desc ring entry */
+@@ -269,7 +271,10 @@ struct gve_tx_iovec {
+  * ring entry but only used for a pkt_desc not a seg_desc
+  */
+ struct gve_tx_buffer_state {
+-	struct sk_buff *skb; /* skb for this pkt */
++	union {
++		struct sk_buff *skb; /* skb for this pkt */
++		struct xdp_frame *xdp_frame; /* xdp_frame */
++	};
+ 	struct {
+ 		u16 size; /* size of xmitted xdp pkt */
+ 	} xdp;
+@@ -387,6 +392,8 @@ struct gve_tx_ring {
+ 		struct {
+ 			/* Spinlock for when cleanup in progress */
+ 			spinlock_t clean_lock;
++			/* Spinlock for XDP tx traffic */
++			spinlock_t xdp_lock;
+ 		};
+ 
+ 		/* DQO fields. */
+@@ -464,6 +471,8 @@ struct gve_tx_ring {
+ 	dma_addr_t q_resources_bus; /* dma address of the queue resources */
+ 	dma_addr_t complq_bus_dqo; /* dma address of the dqo.compl_ring */
+ 	struct u64_stats_sync statss; /* sync stats for 32bit archs */
++	u64 xdp_xmit;
++	u64 xdp_xmit_errors;
+ } ____cacheline_aligned;
+ 
+ /* Wraps the info for one irq including the napi struct and the queues
+@@ -921,8 +930,10 @@ void gve_free_page(struct device *dev, s
+ 		   enum dma_data_direction);
+ /* tx handling */
+ netdev_tx_t gve_tx(struct sk_buff *skb, struct net_device *dev);
++int gve_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames,
++		 u32 flags);
+ int gve_xdp_xmit_one(struct gve_priv *priv, struct gve_tx_ring *tx,
+-		     void *data, int len);
++		     void *data, int len, void *frame_p);
+ void gve_xdp_tx_flush(struct gve_priv *priv, u32 xdp_qid);
+ bool gve_tx_poll(struct gve_notify_block *block, int budget);
+ bool gve_xdp_poll(struct gve_notify_block *block, int budget);
+--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
++++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
+@@ -56,13 +56,14 @@ static const char gve_gstrings_rx_stats[
+ 	"rx_drops_packet_over_mru[%u]", "rx_drops_invalid_checksum[%u]",
+ 	"rx_xdp_aborted[%u]", "rx_xdp_drop[%u]", "rx_xdp_pass[%u]",
+ 	"rx_xdp_tx[%u]", "rx_xdp_redirect[%u]",
+-	"rx_xdp_tx_errors[%u]", "rx_xdp_redirect_errors[%u]",
++	"rx_xdp_tx_errors[%u]", "rx_xdp_redirect_errors[%u]", "rx_xdp_alloc_fails[%u]",
+ };
+ 
+ static const char gve_gstrings_tx_stats[][ETH_GSTRING_LEN] = {
+ 	"tx_posted_desc[%u]", "tx_completed_desc[%u]", "tx_consumed_desc[%u]", "tx_bytes[%u]",
+ 	"tx_wake[%u]", "tx_stop[%u]", "tx_event_counter[%u]",
+ 	"tx_dma_mapping_error[%u]",
++	"tx_xdp_xmit[%u]", "tx_xdp_xmit_errors[%u]"
+ };
+ 
+ static const char gve_gstrings_adminq_stats[][ETH_GSTRING_LEN] = {
+@@ -313,9 +314,10 @@ gve_get_ethtool_stats(struct net_device
+ 					data[i + j] = rx->xdp_actions[j];
+ 				data[i + j++] = rx->xdp_tx_errors;
+ 				data[i + j++] = rx->xdp_redirect_errors;
++				data[i + j++] = rx->xdp_alloc_fails;
+ 			} while (u64_stats_fetch_retry(&priv->rx[ring].statss,
+ 						       start));
+-			i += GVE_XDP_ACTIONS + 2; /* XDP rx counters */
++			i += GVE_XDP_ACTIONS + 3; /* XDP rx counters */
+ 		}
+ 	} else {
+ 		i += priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS;
+@@ -371,13 +373,21 @@ gve_get_ethtool_stats(struct net_device
+ 			if (skip_nic_stats) {
+ 				/* skip NIC tx stats */
+ 				i += NIC_TX_STATS_REPORT_NUM;
+-				continue;
+-			}
+-			for (j = 0; j < NIC_TX_STATS_REPORT_NUM; j++) {
+-				u64 value =
+-				be64_to_cpu(report_stats[tx_qid_to_stats_idx[ring] + j].value);
+-				data[i++] = value;
++			} else {
++				stats_idx = tx_qid_to_stats_idx[ring];
++				for (j = 0; j < NIC_TX_STATS_REPORT_NUM; j++) {
++					u64 value =
++						be64_to_cpu(report_stats[stats_idx + j].value);
++					data[i++] = value;
++				}
+ 			}
++			do {
++				start = u64_stats_fetch_begin(&priv->tx[ring].statss);
++				data[i] = tx->xdp_xmit;
++				data[i + 1] = tx->xdp_xmit_errors;
++			} while (u64_stats_fetch_retry(&priv->tx[ring].statss,
++						       start));
++			i += 2; /* XDP tx counters */
+ 		}
+ 	} else {
+ 		i += num_tx_queues * NUM_GVE_TX_CNTS;
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -1231,6 +1231,21 @@ static void gve_unreg_xdp_info(struct gv
+ 	}
+ }
+ 
++static void gve_drain_page_cache(struct gve_priv *priv)
++{
++	struct page_frag_cache *nc;
++	int i;
++
++	for (i = 0; i < priv->rx_cfg.num_queues; i++) {
++		nc = &priv->rx[i].page_cache;
++		if (nc->va) {
++			__page_frag_cache_drain(virt_to_page(nc->va),
++						nc->pagecnt_bias);
++			nc->va = NULL;
++		}
++	}
++}
++
+ static int gve_open(struct net_device *dev)
+ {
+ 	struct gve_priv *priv = netdev_priv(dev);
+@@ -1314,6 +1329,7 @@ static int gve_close(struct net_device *
+ 	netif_carrier_off(dev);
+ 	if (gve_get_device_rings_ok(priv)) {
+ 		gve_turndown(priv);
++		gve_drain_page_cache(priv);
+ 		err = gve_destroy_rings(priv);
+ 		if (err)
+ 			goto err;
+@@ -1697,6 +1713,7 @@ static const struct net_device_ops gve_n
+ 	.ndo_tx_timeout         =       gve_tx_timeout,
+ 	.ndo_set_features	=	gve_set_features,
+ 	.ndo_bpf		=	gve_xdp,
++	.ndo_xdp_xmit		=	gve_xdp_xmit,
+ };
+ 
+ static void gve_handle_status(struct gve_priv *priv, u32 status)
+@@ -1821,6 +1838,8 @@ static void gve_set_netdev_xdp_features(
+ #if 0 /* not supported in backport */
+ 	if (priv->queue_format == GVE_GQI_QPL_FORMAT) {
+ 		priv->dev->xdp_features = NETDEV_XDP_ACT_BASIC;
++		priv->dev->xdp_features |= NETDEV_XDP_ACT_REDIRECT;
++		priv->dev->xdp_features |= NETDEV_XDP_ACT_NDO_XMIT;
+ 	} else {
+ 		priv->dev->xdp_features = 0;
+ 	}
+--- a/drivers/net/ethernet/google/gve/gve_rx.c
++++ b/drivers/net/ethernet/google/gve/gve_rx.c
+@@ -593,6 +593,35 @@ static struct sk_buff *gve_rx_skb(struct
+ 	return skb;
+ }
+ 
++static int gve_xdp_redirect(struct net_device *dev, struct gve_rx_ring *rx,
++			    struct xdp_buff *orig, struct bpf_prog *xdp_prog)
++{
++	int total_len, len = orig->data_end - orig->data;
++	int headroom = XDP_PACKET_HEADROOM;
++	struct xdp_buff new;
++	void *frame;
++	int err;
++
++	total_len = headroom + SKB_DATA_ALIGN(len) +
++		SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
++	frame = page_frag_alloc(&rx->page_cache, total_len, GFP_ATOMIC);
++	if (!frame) {
++		u64_stats_update_begin(&rx->statss);
++		rx->xdp_alloc_fails++;
++		u64_stats_update_end(&rx->statss);
++		return -ENOMEM;
++	}
++	xdp_init_buff(&new, total_len, &rx->xdp_rxq);
++	xdp_prepare_buff(&new, frame, headroom, len, false);
++	memcpy(new.data, orig->data, len);
++
++	err = xdp_do_redirect(dev, &new, xdp_prog);
++	if (err)
++		page_frag_free(frame);
++
++	return err;
++}
++
+ static void gve_xdp_done(struct gve_priv *priv, struct gve_rx_ring *rx,
+ 			 struct xdp_buff *xdp, struct bpf_prog *xprog,
+ 			 int xdp_act)
+@@ -609,8 +638,10 @@ static void gve_xdp_done(struct gve_priv
+ 	case XDP_TX:
+ 		tx_qid = gve_xdp_tx_queue_id(priv, rx->q_num);
+ 		tx = &priv->tx[tx_qid];
++		spin_lock(&tx->xdp_lock);
+ 		err = gve_xdp_xmit_one(priv, tx, xdp->data,
+-				       xdp->data_end - xdp->data);
++				       xdp->data_end - xdp->data, NULL);
++		spin_unlock(&tx->xdp_lock);
+ 
+ 		if (unlikely(err)) {
+ 			u64_stats_update_begin(&rx->statss);
+@@ -619,9 +650,13 @@ static void gve_xdp_done(struct gve_priv
+ 		}
+ 		break;
+ 	case XDP_REDIRECT:
+-		u64_stats_update_begin(&rx->statss);
+-		rx->xdp_redirect_errors++;
+-		u64_stats_update_end(&rx->statss);
++		err = gve_xdp_redirect(priv->dev, rx, xdp, xprog);
++
++		if (unlikely(err)) {
++			u64_stats_update_begin(&rx->statss);
++			rx->xdp_redirect_errors++;
++			u64_stats_update_end(&rx->statss);
++		}
+ 		break;
+ 	}
+ 	u64_stats_update_begin(&rx->statss);
+@@ -841,6 +876,7 @@ static bool gve_rx_refill_buffers(struct
+ static int gve_clean_rx_done(struct gve_rx_ring *rx, int budget,
+ 			     netdev_features_t feat)
+ {
++	u64 xdp_redirects = rx->xdp_actions[XDP_REDIRECT];
+ 	u64 xdp_txs = rx->xdp_actions[XDP_TX];
+ 	struct gve_rx_ctx *ctx = &rx->ctx;
+ 	struct gve_priv *priv = rx->gve;
+@@ -892,6 +928,9 @@ static int gve_clean_rx_done(struct gve_
+ 	if (xdp_txs != rx->xdp_actions[XDP_TX])
+ 		gve_xdp_tx_flush(priv, rx->q_num);
+ 
++	if (xdp_redirects != rx->xdp_actions[XDP_REDIRECT])
++		xdp_do_flush();
++
+ 	/* restock ring slots */
+ 	if (!rx->data.raw_addressing) {
+ 		/* In QPL mode buffs are refilled as the desc are processed */
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -173,6 +173,10 @@ static int gve_clean_xdp_done(struct gve
+ 		pkts++;
+ 
+ 		info->xdp.size = 0;
++		if (info->xdp_frame) {
++			xdp_return_frame(info->xdp_frame);
++			info->xdp_frame = NULL;
++		}
+ 		space_freed += gve_tx_clear_buffer_state(info);
+ 	}
+ 
+@@ -233,6 +237,7 @@ static int gve_tx_alloc_ring(struct gve_
+ 	/* Make sure everything is zeroed to start */
+ 	memset(tx, 0, sizeof(*tx));
+ 	spin_lock_init(&tx->clean_lock);
++	spin_lock_init(&tx->xdp_lock);
+ 	tx->q_num = idx;
+ 
+ 	tx->mask = slots - 1;
+@@ -713,7 +718,7 @@ netdev_tx_t gve_tx(struct sk_buff *skb,
+ }
+ 
+ static int gve_tx_fill_xdp(struct gve_priv *priv, struct gve_tx_ring *tx,
+-			   void *data, int len)
++			   void *data, int len, void *frame_p)
+ {
+ 	int pad, nfrags, ndescs, iovi, offset;
+ 	struct gve_tx_buffer_state *info;
+@@ -723,6 +728,7 @@ static int gve_tx_fill_xdp(struct gve_pr
+ 	if (pad >= GVE_TX_MAX_HEADER_SIZE)
+ 		pad = 0;
+ 	info = &tx->info[reqi & tx->mask];
++	info->xdp_frame = frame_p;
+ 	info->xdp.size = len;
+ 
+ 	nfrags = gve_tx_alloc_fifo(&tx->tx_fifo, pad + len,
+@@ -757,15 +763,51 @@ static int gve_tx_fill_xdp(struct gve_pr
+ 	return ndescs;
+ }
+ 
++int gve_xdp_xmit(struct net_device *dev, int n, struct xdp_frame **frames,
++		 u32 flags)
++{
++	struct gve_priv *priv = netdev_priv(dev);
++	struct gve_tx_ring *tx;
++	int i, err = 0, qid;
++
++	if (unlikely(flags & ~XDP_XMIT_FLAGS_MASK))
++		return -EINVAL;
++
++	qid = gve_xdp_tx_queue_id(priv,
++				  smp_processor_id() % priv->num_xdp_queues);
++
++	tx = &priv->tx[qid];
++
++	spin_lock(&tx->xdp_lock);
++	for (i = 0; i < n; i++) {
++		err = gve_xdp_xmit_one(priv, tx, frames[i]->data,
++				       frames[i]->len, frames[i]);
++		if (err)
++			break;
++	}
++
++	if (flags & XDP_XMIT_FLUSH)
++		gve_tx_put_doorbell(priv, tx->q_resources, tx->req);
++
++	spin_unlock(&tx->xdp_lock);
++
++	u64_stats_update_begin(&tx->statss);
++	tx->xdp_xmit += n;
++	tx->xdp_xmit_errors += n - i;
++	u64_stats_update_end(&tx->statss);
++
++	return i ? i : err;
++}
++
+ int gve_xdp_xmit_one(struct gve_priv *priv, struct gve_tx_ring *tx,
+-		     void *data, int len)
++		     void *data, int len, void *frame_p)
+ {
+ 	int nsegs;
+ 
+ 	if (!gve_can_tx(tx, len + GVE_TX_MAX_HEADER_SIZE - 1))
+ 		return -EBUSY;
+ 
+-	nsegs = gve_tx_fill_xdp(priv, tx, data, len);
++	nsegs = gve_tx_fill_xdp(priv, tx, data, len, frame_p);
+ 	tx->req += nsegs;
+ 
+ 	return 0;
diff --git a/patches.suse/gve-Changes-to-add-new-TX-queues.patch b/patches.suse/gve-Changes-to-add-new-TX-queues.patch
new file mode 100644
index 0000000..06a494c
--- /dev/null
+++ b/patches.suse/gve-Changes-to-add-new-TX-queues.patch
@@ -0,0 +1,430 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Wed, 15 Mar 2023 16:33:09 -0700
+Subject: gve: Changes to add new TX queues
+Patch-mainline: v6.4-rc1
+Git-commit: 7fc2bf78a430f975e53f2b1e87e99d8f83cafd11
+References: bsc#1214479
+
+Changes to enable adding and removing TX queues without calling
+gve_close() and gve_open().
+
+Made the following changes:
+1) priv->tx, priv->rx and priv->qpls arrays are allocated based on
+   max tx queues and max rx queues
+2) Changed gve_adminq_create_tx_queues(), gve_adminq_destroy_tx_queues(),
+gve_tx_alloc_rings() and gve_tx_free_rings() functions to add/remove a
+subset of TX queues rather than all the TX queues.
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h        |   45 ++++++++++----
+ drivers/net/ethernet/google/gve/gve_adminq.c |    8 +-
+ drivers/net/ethernet/google/gve/gve_adminq.h |    4 -
+ drivers/net/ethernet/google/gve/gve_main.c   |   83 +++++++++++++++++++--------
+ drivers/net/ethernet/google/gve/gve_rx.c     |    2 
+ drivers/net/ethernet/google/gve/gve_tx.c     |   12 +--
+ 6 files changed, 104 insertions(+), 50 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -800,16 +800,35 @@ static inline u32 gve_num_rx_qpls(struct
+ 	return priv->rx_cfg.num_queues;
+ }
+ 
++static inline u32 gve_tx_qpl_id(struct gve_priv *priv, int tx_qid)
++{
++	return tx_qid;
++}
++
++static inline u32 gve_rx_qpl_id(struct gve_priv *priv, int rx_qid)
++{
++	return priv->tx_cfg.max_queues + rx_qid;
++}
++
++static inline u32 gve_tx_start_qpl_id(struct gve_priv *priv)
++{
++	return gve_tx_qpl_id(priv, 0);
++}
++
++static inline u32 gve_rx_start_qpl_id(struct gve_priv *priv)
++{
++	return gve_rx_qpl_id(priv, 0);
++}
++
+ /* Returns a pointer to the next available tx qpl in the list of qpls
+  */
+ static inline
+-struct gve_queue_page_list *gve_assign_tx_qpl(struct gve_priv *priv)
++struct gve_queue_page_list *gve_assign_tx_qpl(struct gve_priv *priv, int tx_qid)
+ {
+-	int id = find_first_zero_bit(priv->qpl_cfg.qpl_id_map,
+-				     priv->qpl_cfg.qpl_map_size);
++	int id = gve_tx_qpl_id(priv, tx_qid);
+ 
+-	/* we are out of tx qpls */
+-	if (id >= gve_num_tx_qpls(priv))
++	/* QPL already in use */
++	if (test_bit(id, priv->qpl_cfg.qpl_id_map))
+ 		return NULL;
+ 
+ 	set_bit(id, priv->qpl_cfg.qpl_id_map);
+@@ -819,14 +838,12 @@ struct gve_queue_page_list *gve_assign_t
+ /* Returns a pointer to the next available rx qpl in the list of qpls
+  */
+ static inline
+-struct gve_queue_page_list *gve_assign_rx_qpl(struct gve_priv *priv)
++struct gve_queue_page_list *gve_assign_rx_qpl(struct gve_priv *priv, int rx_qid)
+ {
+-	int id = find_next_zero_bit(priv->qpl_cfg.qpl_id_map,
+-				    priv->qpl_cfg.qpl_map_size,
+-				    gve_num_tx_qpls(priv));
++	int id = gve_rx_qpl_id(priv, rx_qid);
+ 
+-	/* we are out of rx qpls */
+-	if (id == gve_num_tx_qpls(priv) + gve_num_rx_qpls(priv))
++	/* QPL already in use */
++	if (test_bit(id, priv->qpl_cfg.qpl_id_map))
+ 		return NULL;
+ 
+ 	set_bit(id, priv->qpl_cfg.qpl_id_map);
+@@ -845,7 +862,7 @@ static inline void gve_unassign_qpl(stru
+ static inline enum dma_data_direction gve_qpl_dma_dir(struct gve_priv *priv,
+ 						      int id)
+ {
+-	if (id < gve_num_tx_qpls(priv))
++	if (id < gve_rx_start_qpl_id(priv))
+ 		return DMA_TO_DEVICE;
+ 	else
+ 		return DMA_FROM_DEVICE;
+@@ -871,8 +888,8 @@ void gve_free_page(struct device *dev, s
+ /* tx handling */
+ netdev_tx_t gve_tx(struct sk_buff *skb, struct net_device *dev);
+ bool gve_tx_poll(struct gve_notify_block *block, int budget);
+-int gve_tx_alloc_rings(struct gve_priv *priv);
+-void gve_tx_free_rings_gqi(struct gve_priv *priv);
++int gve_tx_alloc_rings(struct gve_priv *priv, int start_id, int num_rings);
++void gve_tx_free_rings_gqi(struct gve_priv *priv, int start_id, int num_rings);
+ u32 gve_tx_load_event_counter(struct gve_priv *priv,
+ 			      struct gve_tx_ring *tx);
+ bool gve_tx_clean_pending(struct gve_priv *priv, struct gve_tx_ring *tx);
+--- a/drivers/net/ethernet/google/gve/gve_adminq.c
++++ b/drivers/net/ethernet/google/gve/gve_adminq.c
+@@ -516,12 +516,12 @@ static int gve_adminq_create_tx_queue(st
+ 	return gve_adminq_issue_cmd(priv, &cmd);
+ }
+ 
+-int gve_adminq_create_tx_queues(struct gve_priv *priv, u32 num_queues)
++int gve_adminq_create_tx_queues(struct gve_priv *priv, u32 start_id, u32 num_queues)
+ {
+ 	int err;
+ 	int i;
+ 
+-	for (i = 0; i < num_queues; i++) {
++	for (i = start_id; i < start_id + num_queues; i++) {
+ 		err = gve_adminq_create_tx_queue(priv, i);
+ 		if (err)
+ 			return err;
+@@ -604,12 +604,12 @@ static int gve_adminq_destroy_tx_queue(s
+ 	return 0;
+ }
+ 
+-int gve_adminq_destroy_tx_queues(struct gve_priv *priv, u32 num_queues)
++int gve_adminq_destroy_tx_queues(struct gve_priv *priv, u32 start_id, u32 num_queues)
+ {
+ 	int err;
+ 	int i;
+ 
+-	for (i = 0; i < num_queues; i++) {
++	for (i = start_id; i < start_id + num_queues; i++) {
+ 		err = gve_adminq_destroy_tx_queue(priv, i);
+ 		if (err)
+ 			return err;
+--- a/drivers/net/ethernet/google/gve/gve_adminq.h
++++ b/drivers/net/ethernet/google/gve/gve_adminq.h
+@@ -410,8 +410,8 @@ int gve_adminq_configure_device_resource
+ 					  dma_addr_t db_array_bus_addr,
+ 					  u32 num_ntfy_blks);
+ int gve_adminq_deconfigure_device_resources(struct gve_priv *priv);
+-int gve_adminq_create_tx_queues(struct gve_priv *priv, u32 num_queues);
+-int gve_adminq_destroy_tx_queues(struct gve_priv *priv, u32 queue_id);
++int gve_adminq_create_tx_queues(struct gve_priv *priv, u32 start_id, u32 num_queues);
++int gve_adminq_destroy_tx_queues(struct gve_priv *priv, u32 start_id, u32 num_queues);
+ int gve_adminq_create_rx_queues(struct gve_priv *priv, u32 num_queues);
+ int gve_adminq_destroy_rx_queues(struct gve_priv *priv, u32 queue_id);
+ int gve_adminq_register_page_list(struct gve_priv *priv,
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -585,11 +585,26 @@ static void gve_remove_napi(struct gve_p
+ 
+ static int gve_register_qpls(struct gve_priv *priv)
+ {
+-	int num_qpls = gve_num_tx_qpls(priv) + gve_num_rx_qpls(priv);
++	int start_id;
+ 	int err;
+ 	int i;
+ 
+-	for (i = 0; i < num_qpls; i++) {
++	start_id = gve_tx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_tx_qpls(priv); i++) {
++		err = gve_adminq_register_page_list(priv, &priv->qpls[i]);
++		if (err) {
++			netif_err(priv, drv, priv->dev,
++				  "failed to register queue page list %d\n",
++				  priv->qpls[i].id);
++			/* This failure will trigger a reset - no need to clean
++			 * up
++			 */
++			return err;
++		}
++	}
++
++	start_id = gve_rx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_rx_qpls(priv); i++) {
+ 		err = gve_adminq_register_page_list(priv, &priv->qpls[i]);
+ 		if (err) {
+ 			netif_err(priv, drv, priv->dev,
+@@ -606,11 +621,24 @@ static int gve_register_qpls(struct gve_
+ 
+ static int gve_unregister_qpls(struct gve_priv *priv)
+ {
+-	int num_qpls = gve_num_tx_qpls(priv) + gve_num_rx_qpls(priv);
++	int start_id;
+ 	int err;
+ 	int i;
+ 
+-	for (i = 0; i < num_qpls; i++) {
++	start_id = gve_tx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_tx_qpls(priv); i++) {
++		err = gve_adminq_unregister_page_list(priv, priv->qpls[i].id);
++		/* This failure will trigger a reset - no need to clean up */
++		if (err) {
++			netif_err(priv, drv, priv->dev,
++				  "Failed to unregister queue page list %d\n",
++				  priv->qpls[i].id);
++			return err;
++		}
++	}
++
++	start_id = gve_rx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_rx_qpls(priv); i++) {
+ 		err = gve_adminq_unregister_page_list(priv, priv->qpls[i].id);
+ 		/* This failure will trigger a reset - no need to clean up */
+ 		if (err) {
+@@ -629,7 +657,7 @@ static int gve_create_rings(struct gve_p
+ 	int err;
+ 	int i;
+ 
+-	err = gve_adminq_create_tx_queues(priv, num_tx_queues);
++	err = gve_adminq_create_tx_queues(priv, 0, num_tx_queues);
+ 	if (err) {
+ 		netif_err(priv, drv, priv->dev, "failed to create %d tx queues\n",
+ 			  num_tx_queues);
+@@ -696,10 +724,10 @@ static void add_napi_init_sync_stats(str
+ 	}
+ }
+ 
+-static void gve_tx_free_rings(struct gve_priv *priv)
++static void gve_tx_free_rings(struct gve_priv *priv, int start_id, int num_rings)
+ {
+ 	if (gve_is_gqi(priv)) {
+-		gve_tx_free_rings_gqi(priv);
++		gve_tx_free_rings_gqi(priv, start_id, num_rings);
+ 	} else {
+ 		gve_tx_free_rings_dqo(priv);
+ 	}
+@@ -710,20 +738,20 @@ static int gve_alloc_rings(struct gve_pr
+ 	int err;
+ 
+ 	/* Setup tx rings */
+-	priv->tx = kvcalloc(priv->tx_cfg.num_queues, sizeof(*priv->tx),
++	priv->tx = kvcalloc(priv->tx_cfg.max_queues, sizeof(*priv->tx),
+ 			    GFP_KERNEL);
+ 	if (!priv->tx)
+ 		return -ENOMEM;
+ 
+ 	if (gve_is_gqi(priv))
+-		err = gve_tx_alloc_rings(priv);
++		err = gve_tx_alloc_rings(priv, 0, gve_num_tx_queues(priv));
+ 	else
+ 		err = gve_tx_alloc_rings_dqo(priv);
+ 	if (err)
+ 		goto free_tx;
+ 
+ 	/* Setup rx rings */
+-	priv->rx = kvcalloc(priv->rx_cfg.num_queues, sizeof(*priv->rx),
++	priv->rx = kvcalloc(priv->rx_cfg.max_queues, sizeof(*priv->rx),
+ 			    GFP_KERNEL);
+ 	if (!priv->rx) {
+ 		err = -ENOMEM;
+@@ -748,7 +776,7 @@ free_rx:
+ 	kvfree(priv->rx);
+ 	priv->rx = NULL;
+ free_tx_queue:
+-	gve_tx_free_rings(priv);
++	gve_tx_free_rings(priv, 0, gve_num_tx_queues(priv));
+ free_tx:
+ 	kvfree(priv->tx);
+ 	priv->tx = NULL;
+@@ -760,7 +788,7 @@ static int gve_destroy_rings(struct gve_
+ 	int num_tx_queues = gve_num_tx_queues(priv);
+ 	int err;
+ 
+-	err = gve_adminq_destroy_tx_queues(priv, num_tx_queues);
++	err = gve_adminq_destroy_tx_queues(priv, 0, num_tx_queues);
+ 	if (err) {
+ 		netif_err(priv, drv, priv->dev,
+ 			  "failed to destroy tx queues\n");
+@@ -798,7 +826,7 @@ static void gve_free_rings(struct gve_pr
+ 			ntfy_idx = gve_tx_idx_to_ntfy(priv, i);
+ 			gve_remove_napi(priv, ntfy_idx);
+ 		}
+-		gve_tx_free_rings(priv);
++		gve_tx_free_rings(priv, 0, num_tx_queues);
+ 		kvfree(priv->tx);
+ 		priv->tx = NULL;
+ 	}
+@@ -895,40 +923,46 @@ static void gve_free_queue_page_list(str
+ 			      qpl->page_buses[i], gve_qpl_dma_dir(priv, id));
+ 
+ 	kvfree(qpl->page_buses);
++	qpl->page_buses = NULL;
+ free_pages:
+ 	kvfree(qpl->pages);
++	qpl->pages = NULL;
+ 	priv->num_registered_pages -= qpl->num_entries;
+ }
+ 
+ static int gve_alloc_qpls(struct gve_priv *priv)
+ {
+-	int num_qpls = gve_num_tx_qpls(priv) + gve_num_rx_qpls(priv);
++	int max_queues = priv->tx_cfg.max_queues + priv->rx_cfg.max_queues;
++	int start_id;
+ 	int i, j;
+ 	int err;
+ 
+-	if (num_qpls == 0)
++	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
+ 		return 0;
+ 
+-	priv->qpls = kvcalloc(num_qpls, sizeof(*priv->qpls), GFP_KERNEL);
++	priv->qpls = kvcalloc(max_queues, sizeof(*priv->qpls), GFP_KERNEL);
+ 	if (!priv->qpls)
+ 		return -ENOMEM;
+ 
+-	for (i = 0; i < gve_num_tx_qpls(priv); i++) {
++	start_id = gve_tx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_tx_qpls(priv); i++) {
+ 		err = gve_alloc_queue_page_list(priv, i,
+ 						priv->tx_pages_per_qpl);
+ 		if (err)
+ 			goto free_qpls;
+ 	}
+-	for (; i < num_qpls; i++) {
++
++	start_id = gve_rx_start_qpl_id(priv);
++	for (i = start_id; i < start_id + gve_num_rx_qpls(priv); i++) {
+ 		err = gve_alloc_queue_page_list(priv, i,
+ 						priv->rx_data_slot_cnt);
+ 		if (err)
+ 			goto free_qpls;
+ 	}
+ 
+-	priv->qpl_cfg.qpl_map_size = BITS_TO_LONGS(num_qpls) *
++	priv->qpl_cfg.qpl_map_size = BITS_TO_LONGS(max_queues) *
+ 				     sizeof(unsigned long) * BITS_PER_BYTE;
+-	priv->qpl_cfg.qpl_id_map = kvcalloc(BITS_TO_LONGS(num_qpls),
++	priv->qpl_cfg.qpl_id_map = kvcalloc(BITS_TO_LONGS(max_queues),
+ 					    sizeof(unsigned long), GFP_KERNEL);
+ 	if (!priv->qpl_cfg.qpl_id_map) {
+ 		err = -ENOMEM;
+@@ -941,23 +975,26 @@ free_qpls:
+ 	for (j = 0; j <= i; j++)
+ 		gve_free_queue_page_list(priv, j);
+ 	kvfree(priv->qpls);
++	priv->qpls = NULL;
+ 	return err;
+ }
+ 
+ static void gve_free_qpls(struct gve_priv *priv)
+ {
+-	int num_qpls = gve_num_tx_qpls(priv) + gve_num_rx_qpls(priv);
++	int max_queues = priv->tx_cfg.max_queues + priv->rx_cfg.max_queues;
+ 	int i;
+ 
+-	if (num_qpls == 0)
++	if (!priv->qpls)
+ 		return;
+ 
+ 	kvfree(priv->qpl_cfg.qpl_id_map);
++	priv->qpl_cfg.qpl_id_map = NULL;
+ 
+-	for (i = 0; i < num_qpls; i++)
++	for (i = 0; i < max_queues; i++)
+ 		gve_free_queue_page_list(priv, i);
+ 
+ 	kvfree(priv->qpls);
++	priv->qpls = NULL;
+ }
+ 
+ /* Use this to schedule a reset when the device is capable of continuing
+--- a/drivers/net/ethernet/google/gve/gve_rx.c
++++ b/drivers/net/ethernet/google/gve/gve_rx.c
+@@ -124,7 +124,7 @@ static int gve_prefill_rx_pages(struct g
+ 		return -ENOMEM;
+ 
+ 	if (!rx->data.raw_addressing) {
+-		rx->data.qpl = gve_assign_rx_qpl(priv);
++		rx->data.qpl = gve_assign_rx_qpl(priv, rx->q_num);
+ 		if (!rx->data.qpl) {
+ 			kvfree(rx->data.page_info);
+ 			rx->data.page_info = NULL;
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -195,7 +195,7 @@ static int gve_tx_alloc_ring(struct gve_
+ 	tx->raw_addressing = priv->queue_format == GVE_GQI_RDA_FORMAT;
+ 	tx->dev = &priv->pdev->dev;
+ 	if (!tx->raw_addressing) {
+-		tx->tx_fifo.qpl = gve_assign_tx_qpl(priv);
++		tx->tx_fifo.qpl = gve_assign_tx_qpl(priv, idx);
+ 		if (!tx->tx_fifo.qpl)
+ 			goto abort_with_desc;
+ 		/* map Tx FIFO */
+@@ -233,12 +233,12 @@ abort_with_info:
+ 	return -ENOMEM;
+ }
+ 
+-int gve_tx_alloc_rings(struct gve_priv *priv)
++int gve_tx_alloc_rings(struct gve_priv *priv, int start_id, int num_rings)
+ {
+ 	int err = 0;
+ 	int i;
+ 
+-	for (i = 0; i < priv->tx_cfg.num_queues; i++) {
++	for (i = start_id; i < start_id + num_rings; i++) {
+ 		err = gve_tx_alloc_ring(priv, i);
+ 		if (err) {
+ 			netif_err(priv, drv, priv->dev,
+@@ -251,17 +251,17 @@ int gve_tx_alloc_rings(struct gve_priv *
+ 	if (err) {
+ 		int j;
+ 
+-		for (j = 0; j < i; j++)
++		for (j = start_id; j < i; j++)
+ 			gve_tx_free_ring(priv, j);
+ 	}
+ 	return err;
+ }
+ 
+-void gve_tx_free_rings_gqi(struct gve_priv *priv)
++void gve_tx_free_rings_gqi(struct gve_priv *priv, int start_id, int num_rings)
+ {
+ 	int i;
+ 
+-	for (i = 0; i < priv->tx_cfg.num_queues; i++)
++	for (i = start_id; i < start_id + num_rings; i++)
+ 		gve_tx_free_ring(priv, i);
+ }
+ 
diff --git a/patches.suse/gve-Control-path-for-DQO-QPL.patch b/patches.suse/gve-Control-path-for-DQO-QPL.patch
new file mode 100644
index 0000000..06a600a
--- /dev/null
+++ b/patches.suse/gve-Control-path-for-DQO-QPL.patch
@@ -0,0 +1,400 @@
+From: Rushil Gupta <rushilg@google.com>
+Date: Fri, 4 Aug 2023 21:34:41 +0000
+Subject: gve: Control path for DQO-QPL
+Patch-mainline: v6.6-rc1
+Git-commit: 66ce8e6b49df401854f0c98bed50a65e4167825b
+References: bsc#1214479
+
+GVE supports QPL ("queue-page-list") mode where
+all data is communicated through a set of pre-registered
+pages. Adding this mode to DQO descriptor format.
+
+Add checks, abi-changes and device options to support
+QPL mode for DQO in addition to GQI. Also, use
+pages-per-qpl supplied by device-option to control the
+size of the "queue-page-list".
+
+Signed-off-by: Rushil Gupta <rushilg@google.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Signed-off-by: Bailey Forrest <bcf@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h        |   29 +++++++-
+ drivers/net/ethernet/google/gve/gve_adminq.c |   89 +++++++++++++++++++++++----
+ drivers/net/ethernet/google/gve/gve_adminq.h |   10 +++
+ drivers/net/ethernet/google/gve/gve_main.c   |   17 +++--
+ 4 files changed, 127 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -51,6 +51,12 @@
+ 
+ #define GVE_GQ_TX_MIN_PKT_DESC_BYTES 182
+ 
++#define DQO_QPL_DEFAULT_TX_PAGES 512
++#define DQO_QPL_DEFAULT_RX_PAGES 2048
++
++/* Maximum TSO size supported on DQO */
++#define GVE_DQO_TX_MAX	0x3FFFF
++
+ /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
+ struct gve_rx_desc_queue {
+ 	struct gve_rx_desc *desc_ring; /* the descriptor ring */
+@@ -217,6 +223,9 @@ struct gve_rx_ring {
+ 			 * which cannot be reused yet.
+ 			 */
+ 			struct gve_index_list used_buf_states;
++
++			/* qpl assigned to this queue */
++			struct gve_queue_page_list *qpl;
+ 		} dqo;
+ 	};
+ 
+@@ -453,6 +462,12 @@ struct gve_tx_ring {
+ 			s16 num_pending_packets;
+ 
+ 			u32 complq_mask; /* complq size is complq_mask + 1 */
++
++			/* QPL fields */
++			struct {
++				/* qpl assigned to this queue */
++				struct gve_queue_page_list *qpl;
++			};
+ 		} dqo;
+ 	} ____cacheline_aligned;
+ 	struct netdev_queue *netdev_txq;
+@@ -531,6 +546,7 @@ enum gve_queue_format {
+ 	GVE_GQI_RDA_FORMAT		= 0x1,
+ 	GVE_GQI_QPL_FORMAT		= 0x2,
+ 	GVE_DQO_RDA_FORMAT		= 0x3,
++	GVE_DQO_QPL_FORMAT		= 0x4,
+ };
+ 
+ struct gve_priv {
+@@ -550,7 +566,8 @@ struct gve_priv {
+ 	u16 num_event_counters;
+ 	u16 tx_desc_cnt; /* num desc per ring */
+ 	u16 rx_desc_cnt; /* num desc per ring */
+-	u16 tx_pages_per_qpl; /* tx buffer length */
++	u16 tx_pages_per_qpl; /* Suggested number of pages per qpl for TX queues by NIC */
++	u16 rx_pages_per_qpl; /* Suggested number of pages per qpl for RX queues by NIC */
+ 	u16 rx_data_slot_cnt; /* rx buffer length */
+ 	u64 max_registered_pages;
+ 	u64 num_registered_pages; /* num pages registered with NIC */
+@@ -808,11 +825,17 @@ static inline u32 gve_rx_idx_to_ntfy(str
+ 	return (priv->num_ntfy_blks / 2) + queue_idx;
+ }
+ 
++static inline bool gve_is_qpl(struct gve_priv *priv)
++{
++	return priv->queue_format == GVE_GQI_QPL_FORMAT ||
++		priv->queue_format == GVE_DQO_QPL_FORMAT;
++}
++
+ /* Returns the number of tx queue page lists
+  */
+ static inline u32 gve_num_tx_qpls(struct gve_priv *priv)
+ {
+-	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
++	if (!gve_is_qpl(priv))
+ 		return 0;
+ 
+ 	return priv->tx_cfg.num_queues + priv->num_xdp_queues;
+@@ -832,7 +855,7 @@ static inline u32 gve_num_xdp_qpls(struc
+  */
+ static inline u32 gve_num_rx_qpls(struct gve_priv *priv)
+ {
+-	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
++	if (!gve_is_qpl(priv))
+ 		return 0;
+ 
+ 	return priv->rx_cfg.num_queues;
+--- a/drivers/net/ethernet/google/gve/gve_adminq.c
++++ b/drivers/net/ethernet/google/gve/gve_adminq.c
+@@ -39,7 +39,8 @@ void gve_parse_device_option(struct gve_
+ 			     struct gve_device_option_gqi_rda **dev_op_gqi_rda,
+ 			     struct gve_device_option_gqi_qpl **dev_op_gqi_qpl,
+ 			     struct gve_device_option_dqo_rda **dev_op_dqo_rda,
+-			     struct gve_device_option_jumbo_frames **dev_op_jumbo_frames)
++			     struct gve_device_option_jumbo_frames **dev_op_jumbo_frames,
++			     struct gve_device_option_dqo_qpl **dev_op_dqo_qpl)
+ {
+ 	u32 req_feat_mask = be32_to_cpu(option->required_features_mask);
+ 	u16 option_length = be16_to_cpu(option->option_length);
+@@ -112,6 +113,22 @@ void gve_parse_device_option(struct gve_
+ 		}
+ 		*dev_op_dqo_rda = (void *)(option + 1);
+ 		break;
++	case GVE_DEV_OPT_ID_DQO_QPL:
++		if (option_length < sizeof(**dev_op_dqo_qpl) ||
++		    req_feat_mask != GVE_DEV_OPT_REQ_FEAT_MASK_DQO_QPL) {
++			dev_warn(&priv->pdev->dev, GVE_DEVICE_OPTION_ERROR_FMT,
++				 "DQO QPL", (int)sizeof(**dev_op_dqo_qpl),
++				 GVE_DEV_OPT_REQ_FEAT_MASK_DQO_QPL,
++				 option_length, req_feat_mask);
++			break;
++		}
++
++		if (option_length > sizeof(**dev_op_dqo_qpl)) {
++			dev_warn(&priv->pdev->dev,
++				 GVE_DEVICE_OPTION_TOO_BIG_FMT, "DQO QPL");
++		}
++		*dev_op_dqo_qpl = (void *)(option + 1);
++		break;
+ 	case GVE_DEV_OPT_ID_JUMBO_FRAMES:
+ 		if (option_length < sizeof(**dev_op_jumbo_frames) ||
+ 		    req_feat_mask != GVE_DEV_OPT_REQ_FEAT_MASK_JUMBO_FRAMES) {
+@@ -146,7 +163,8 @@ gve_process_device_options(struct gve_pr
+ 			   struct gve_device_option_gqi_rda **dev_op_gqi_rda,
+ 			   struct gve_device_option_gqi_qpl **dev_op_gqi_qpl,
+ 			   struct gve_device_option_dqo_rda **dev_op_dqo_rda,
+-			   struct gve_device_option_jumbo_frames **dev_op_jumbo_frames)
++			   struct gve_device_option_jumbo_frames **dev_op_jumbo_frames,
++			   struct gve_device_option_dqo_qpl **dev_op_dqo_qpl)
+ {
+ 	const int num_options = be16_to_cpu(descriptor->num_device_options);
+ 	struct gve_device_option *dev_opt;
+@@ -166,7 +184,8 @@ gve_process_device_options(struct gve_pr
+ 
+ 		gve_parse_device_option(priv, descriptor, dev_opt,
+ 					dev_op_gqi_rda, dev_op_gqi_qpl,
+-					dev_op_dqo_rda, dev_op_jumbo_frames);
++					dev_op_dqo_rda, dev_op_jumbo_frames,
++					dev_op_dqo_qpl);
+ 		dev_opt = next_opt;
+ 	}
+ 
+@@ -505,12 +524,24 @@ static int gve_adminq_create_tx_queue(st
+ 
+ 		cmd.create_tx_queue.queue_page_list_id = cpu_to_be32(qpl_id);
+ 	} else {
++		u16 comp_ring_size;
++		u32 qpl_id = 0;
++
++		if (priv->queue_format == GVE_DQO_RDA_FORMAT) {
++			qpl_id = GVE_RAW_ADDRESSING_QPL_ID;
++			comp_ring_size =
++				priv->options_dqo_rda.tx_comp_ring_entries;
++		} else {
++			qpl_id = tx->dqo.qpl->id;
++			comp_ring_size = priv->tx_desc_cnt;
++		}
++		cmd.create_tx_queue.queue_page_list_id = cpu_to_be32(qpl_id);
+ 		cmd.create_tx_queue.tx_ring_size =
+ 			cpu_to_be16(priv->tx_desc_cnt);
+ 		cmd.create_tx_queue.tx_comp_ring_addr =
+ 			cpu_to_be64(tx->complq_bus_dqo);
+ 		cmd.create_tx_queue.tx_comp_ring_size =
+-			cpu_to_be16(priv->options_dqo_rda.tx_comp_ring_entries);
++			cpu_to_be16(comp_ring_size);
+ 	}
+ 
+ 	return gve_adminq_issue_cmd(priv, &cmd);
+@@ -555,6 +586,18 @@ static int gve_adminq_create_rx_queue(st
+ 		cmd.create_rx_queue.queue_page_list_id = cpu_to_be32(qpl_id);
+ 		cmd.create_rx_queue.packet_buffer_size = cpu_to_be16(rx->packet_buffer_size);
+ 	} else {
++		u16 rx_buff_ring_entries;
++		u32 qpl_id = 0;
++
++		if (priv->queue_format == GVE_DQO_RDA_FORMAT) {
++			qpl_id = GVE_RAW_ADDRESSING_QPL_ID;
++			rx_buff_ring_entries =
++				priv->options_dqo_rda.rx_buff_ring_entries;
++		} else {
++			qpl_id = rx->dqo.qpl->id;
++			rx_buff_ring_entries = priv->rx_desc_cnt;
++		}
++		cmd.create_rx_queue.queue_page_list_id = cpu_to_be32(qpl_id);
+ 		cmd.create_rx_queue.rx_ring_size =
+ 			cpu_to_be16(priv->rx_desc_cnt);
+ 		cmd.create_rx_queue.rx_desc_ring_addr =
+@@ -564,7 +607,7 @@ static int gve_adminq_create_rx_queue(st
+ 		cmd.create_rx_queue.packet_buffer_size =
+ 			cpu_to_be16(priv->data_buffer_size_dqo);
+ 		cmd.create_rx_queue.rx_buff_ring_size =
+-			cpu_to_be16(priv->options_dqo_rda.rx_buff_ring_entries);
++			cpu_to_be16(rx_buff_ring_entries);
+ 		cmd.create_rx_queue.enable_rsc =
+ 			!!(priv->dev->features & NETIF_F_LRO);
+ 	}
+@@ -675,9 +718,13 @@ gve_set_desc_cnt_dqo(struct gve_priv *pr
+ 		     const struct gve_device_option_dqo_rda *dev_op_dqo_rda)
+ {
+ 	priv->tx_desc_cnt = be16_to_cpu(descriptor->tx_queue_entries);
++	priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries);
++
++	if (priv->queue_format == GVE_DQO_QPL_FORMAT)
++		return 0;
++
+ 	priv->options_dqo_rda.tx_comp_ring_entries =
+ 		be16_to_cpu(dev_op_dqo_rda->tx_comp_ring_entries);
+-	priv->rx_desc_cnt = be16_to_cpu(descriptor->rx_queue_entries);
+ 	priv->options_dqo_rda.rx_buff_ring_entries =
+ 		be16_to_cpu(dev_op_dqo_rda->rx_buff_ring_entries);
+ 
+@@ -687,7 +734,9 @@ gve_set_desc_cnt_dqo(struct gve_priv *pr
+ static void gve_enable_supported_features(struct gve_priv *priv,
+ 					  u32 supported_features_mask,
+ 					  const struct gve_device_option_jumbo_frames
+-						  *dev_op_jumbo_frames)
++					  *dev_op_jumbo_frames,
++					  const struct gve_device_option_dqo_qpl
++					  *dev_op_dqo_qpl)
+ {
+ 	/* Before control reaches this point, the page-size-capped max MTU from
+ 	 * the gve_device_descriptor field has already been stored in
+@@ -699,6 +748,18 @@ static void gve_enable_supported_feature
+ 			 "JUMBO FRAMES device option enabled.\n");
+ 		priv->dev->max_mtu = be16_to_cpu(dev_op_jumbo_frames->max_mtu);
+ 	}
++
++	/* Override pages for qpl for DQO-QPL */
++	if (dev_op_dqo_qpl) {
++		priv->tx_pages_per_qpl =
++			be16_to_cpu(dev_op_dqo_qpl->tx_pages_per_qpl);
++		priv->rx_pages_per_qpl =
++			be16_to_cpu(dev_op_dqo_qpl->rx_pages_per_qpl);
++		if (priv->tx_pages_per_qpl == 0)
++			priv->tx_pages_per_qpl = DQO_QPL_DEFAULT_TX_PAGES;
++		if (priv->rx_pages_per_qpl == 0)
++			priv->rx_pages_per_qpl = DQO_QPL_DEFAULT_RX_PAGES;
++	}
+ }
+ 
+ int gve_adminq_describe_device(struct gve_priv *priv)
+@@ -707,6 +768,7 @@ int gve_adminq_describe_device(struct gv
+ 	struct gve_device_option_gqi_rda *dev_op_gqi_rda = NULL;
+ 	struct gve_device_option_gqi_qpl *dev_op_gqi_qpl = NULL;
+ 	struct gve_device_option_dqo_rda *dev_op_dqo_rda = NULL;
++	struct gve_device_option_dqo_qpl *dev_op_dqo_qpl = NULL;
+ 	struct gve_device_descriptor *descriptor;
+ 	u32 supported_features_mask = 0;
+ 	union gve_adminq_command cmd;
+@@ -733,13 +795,14 @@ int gve_adminq_describe_device(struct gv
+ 
+ 	err = gve_process_device_options(priv, descriptor, &dev_op_gqi_rda,
+ 					 &dev_op_gqi_qpl, &dev_op_dqo_rda,
+-					 &dev_op_jumbo_frames);
++					 &dev_op_jumbo_frames,
++					 &dev_op_dqo_qpl);
+ 	if (err)
+ 		goto free_device_descriptor;
+ 
+ 	/* If the GQI_RAW_ADDRESSING option is not enabled and the queue format
+ 	 * is not set to GqiRda, choose the queue format in a priority order:
+-	 * DqoRda, GqiRda, GqiQpl. Use GqiQpl as default.
++	 * DqoRda, DqoQpl, GqiRda, GqiQpl. Use GqiQpl as default.
+ 	 */
+ 	if (dev_op_dqo_rda) {
+ 		priv->queue_format = GVE_DQO_RDA_FORMAT;
+@@ -747,7 +810,11 @@ int gve_adminq_describe_device(struct gv
+ 			 "Driver is running with DQO RDA queue format.\n");
+ 		supported_features_mask =
+ 			be32_to_cpu(dev_op_dqo_rda->supported_features_mask);
+-	} else if (dev_op_gqi_rda) {
++	} else if (dev_op_dqo_qpl) {
++		priv->queue_format = GVE_DQO_QPL_FORMAT;
++		supported_features_mask =
++			be32_to_cpu(dev_op_dqo_qpl->supported_features_mask);
++	}  else if (dev_op_gqi_rda) {
+ 		priv->queue_format = GVE_GQI_RDA_FORMAT;
+ 		dev_info(&priv->pdev->dev,
+ 			 "Driver is running with GQI RDA queue format.\n");
+@@ -798,7 +865,7 @@ int gve_adminq_describe_device(struct gv
+ 	priv->default_num_queues = be16_to_cpu(descriptor->default_num_queues);
+ 
+ 	gve_enable_supported_features(priv, supported_features_mask,
+-				      dev_op_jumbo_frames);
++				      dev_op_jumbo_frames, dev_op_dqo_qpl);
+ 
+ free_device_descriptor:
+ 	dma_free_coherent(&priv->pdev->dev, PAGE_SIZE, descriptor,
+--- a/drivers/net/ethernet/google/gve/gve_adminq.h
++++ b/drivers/net/ethernet/google/gve/gve_adminq.h
+@@ -109,6 +109,14 @@ struct gve_device_option_dqo_rda {
+ 
+ static_assert(sizeof(struct gve_device_option_dqo_rda) == 8);
+ 
++struct gve_device_option_dqo_qpl {
++	__be32 supported_features_mask;
++	__be16 tx_pages_per_qpl;
++	__be16 rx_pages_per_qpl;
++};
++
++static_assert(sizeof(struct gve_device_option_dqo_qpl) == 8);
++
+ struct gve_device_option_jumbo_frames {
+ 	__be32 supported_features_mask;
+ 	__be16 max_mtu;
+@@ -130,6 +138,7 @@ enum gve_dev_opt_id {
+ 	GVE_DEV_OPT_ID_GQI_RDA = 0x2,
+ 	GVE_DEV_OPT_ID_GQI_QPL = 0x3,
+ 	GVE_DEV_OPT_ID_DQO_RDA = 0x4,
++	GVE_DEV_OPT_ID_DQO_QPL = 0x7,
+ 	GVE_DEV_OPT_ID_JUMBO_FRAMES = 0x8,
+ };
+ 
+@@ -139,6 +148,7 @@ enum gve_dev_opt_req_feat_mask {
+ 	GVE_DEV_OPT_REQ_FEAT_MASK_GQI_QPL = 0x0,
+ 	GVE_DEV_OPT_REQ_FEAT_MASK_DQO_RDA = 0x0,
+ 	GVE_DEV_OPT_REQ_FEAT_MASK_JUMBO_FRAMES = 0x0,
++	GVE_DEV_OPT_REQ_FEAT_MASK_DQO_QPL = 0x0,
+ };
+ 
+ enum gve_sup_feature_mask {
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -493,7 +493,7 @@ static int gve_setup_device_resources(st
+ 		goto abort_with_stats_report;
+ 	}
+ 
+-	if (priv->queue_format == GVE_DQO_RDA_FORMAT) {
++	if (!gve_is_gqi(priv)) {
+ 		priv->ptype_lut_dqo = kvzalloc(sizeof(*priv->ptype_lut_dqo),
+ 					       GFP_KERNEL);
+ 		if (!priv->ptype_lut_dqo) {
+@@ -1083,11 +1083,12 @@ free_qpls:
+ static int gve_alloc_qpls(struct gve_priv *priv)
+ {
+ 	int max_queues = priv->tx_cfg.max_queues + priv->rx_cfg.max_queues;
++	int page_count;
+ 	int start_id;
+ 	int i, j;
+ 	int err;
+ 
+-	if (priv->queue_format != GVE_GQI_QPL_FORMAT)
++	if (!gve_is_qpl(priv))
+ 		return 0;
+ 
+ 	priv->qpls = kvcalloc(max_queues, sizeof(*priv->qpls), GFP_KERNEL);
+@@ -1095,17 +1096,25 @@ static int gve_alloc_qpls(struct gve_pri
+ 		return -ENOMEM;
+ 
+ 	start_id = gve_tx_start_qpl_id(priv);
++	page_count = priv->tx_pages_per_qpl;
+ 	for (i = start_id; i < start_id + gve_num_tx_qpls(priv); i++) {
+ 		err = gve_alloc_queue_page_list(priv, i,
+-						priv->tx_pages_per_qpl);
++						page_count);
+ 		if (err)
+ 			goto free_qpls;
+ 	}
+ 
+ 	start_id = gve_rx_start_qpl_id(priv);
++
++	/* For GQI_QPL number of pages allocated have 1:1 relationship with
++	 * number of descriptors. For DQO, number of pages required are
++	 * more than descriptors (because of out of order completions).
++	 */
++	page_count = priv->queue_format == GVE_GQI_QPL_FORMAT ?
++		priv->rx_data_slot_cnt : priv->rx_pages_per_qpl;
+ 	for (i = start_id; i < start_id + gve_num_rx_qpls(priv); i++) {
+ 		err = gve_alloc_queue_page_list(priv, i,
+-						priv->rx_data_slot_cnt);
++						page_count);
+ 		if (err)
+ 			goto free_qpls;
+ 	}
diff --git a/patches.suse/gve-Fix-gve-interrupt-names.patch b/patches.suse/gve-Fix-gve-interrupt-names.patch
new file mode 100644
index 0000000..868e2af
--- /dev/null
+++ b/patches.suse/gve-Fix-gve-interrupt-names.patch
@@ -0,0 +1,55 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Fri, 3 Feb 2023 13:20:45 -0800
+Subject: gve: Fix gve interrupt names
+Patch-mainline: v6.3-rc1
+Git-commit: 8437114593910e0ac7a333775a9477e69c25cf75
+References: bsc#1214479
+
+IRQs are currently requested before the netdevice is registered
+and a proper name is assigned to the device. Changing interrupt
+name to avoid using the format string in the name.
+
+Interrupt name before change: eth%d-ntfy-block.<blk_id>
+Interrupt name after change: gve-ntfy-blk<blk_id>@pci:<pci_name>
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Acked-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve_main.c |    9 ++++-----
+ 1 file changed, 4 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -327,7 +327,6 @@ static int gve_napi_poll_dqo(struct napi
+ static int gve_alloc_notify_blocks(struct gve_priv *priv)
+ {
+ 	int num_vecs_requested = priv->num_ntfy_blks + 1;
+-	char *name = priv->dev->name;
+ 	unsigned int active_cpus;
+ 	int vecs_enabled;
+ 	int i, j;
+@@ -371,8 +370,8 @@ static int gve_alloc_notify_blocks(struc
+ 	active_cpus = min_t(int, priv->num_ntfy_blks / 2, num_online_cpus());
+ 
+ 	/* Setup Management Vector  - the last vector */
+-	snprintf(priv->mgmt_msix_name, sizeof(priv->mgmt_msix_name), "%s-mgmnt",
+-		 name);
++	snprintf(priv->mgmt_msix_name, sizeof(priv->mgmt_msix_name), "gve-mgmnt@pci:%s",
++		 pci_name(priv->pdev));
+ 	err = request_irq(priv->msix_vectors[priv->mgmt_msix_idx].vector,
+ 			  gve_mgmnt_intr, 0, priv->mgmt_msix_name, priv);
+ 	if (err) {
+@@ -401,8 +400,8 @@ static int gve_alloc_notify_blocks(struc
+ 		struct gve_notify_block *block = &priv->ntfy_blocks[i];
+ 		int msix_idx = i;
+ 
+-		snprintf(block->name, sizeof(block->name), "%s-ntfy-block.%d",
+-			 name, i);
++		snprintf(block->name, sizeof(block->name), "gve-ntfy-blk%d@pci:%s",
++			 i, pci_name(priv->pdev));
+ 		block->priv = priv;
+ 		err = request_irq(priv->msix_vectors[msix_idx].vector,
+ 				  gve_is_gqi(priv) ? gve_intr : gve_intr_dqo,
diff --git a/patches.suse/gve-RX-path-for-DQO-QPL.patch b/patches.suse/gve-RX-path-for-DQO-QPL.patch
new file mode 100644
index 0000000..b2c624b
--- /dev/null
+++ b/patches.suse/gve-RX-path-for-DQO-QPL.patch
@@ -0,0 +1,288 @@
+From: Rushil Gupta <rushilg@google.com>
+Date: Fri, 4 Aug 2023 21:34:43 +0000
+Subject: gve: RX path for DQO-QPL
+Patch-mainline: v6.6-rc1
+Git-commit: e7075ab4fb6b39730dfbfbfa3a5505d678f01d2c
+References: bsc#1214479
+
+The RX path allocates the QPL page pool at queue creation, and
+tries to reuse these pages through page recycling. This patch
+ensures that on refill no non-QPL pages are posted to the device.
+
+When the driver is running low on free buffers, an ondemand
+allocation step kicks in that allocates a non-qpl page for
+SKB business to free up the QPL page in use.
+
+gve_try_recycle_buf was moved to gve_rx_append_frags so that driver does
+not attempt to mark buffer as used if a non-qpl page was allocated
+ondemand.
+
+Signed-off-by: Rushil Gupta <rushilg@google.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Signed-off-by: Bailey Forrest <bcf@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h        |    6 +
+ drivers/net/ethernet/google/gve/gve_rx_dqo.c |  126 +++++++++++++++++++++++----
+ 2 files changed, 114 insertions(+), 18 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -240,6 +240,12 @@ struct gve_rx_ring {
+ 
+ 			/* qpl assigned to this queue */
+ 			struct gve_queue_page_list *qpl;
++
++			/* index into queue page list */
++			u32 next_qpl_page_idx;
++
++			/* track number of used buffers */
++			u16 used_buf_states_cnt;
+ 		} dqo;
+ 	};
+ 
+--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
+@@ -22,11 +22,13 @@ static int gve_buf_ref_cnt(struct gve_rx
+ }
+ 
+ static void gve_free_page_dqo(struct gve_priv *priv,
+-			      struct gve_rx_buf_state_dqo *bs)
++			      struct gve_rx_buf_state_dqo *bs,
++			      bool free_page)
+ {
+ 	page_ref_sub(bs->page_info.page, bs->page_info.pagecnt_bias - 1);
+-	gve_free_page(&priv->pdev->dev, bs->page_info.page, bs->addr,
+-		      DMA_FROM_DEVICE);
++	if (free_page)
++		gve_free_page(&priv->pdev->dev, bs->page_info.page, bs->addr,
++			      DMA_FROM_DEVICE);
+ 	bs->page_info.page = NULL;
+ }
+ 
+@@ -130,12 +132,20 @@ gve_get_recycled_buf_state(struct gve_rx
+ 	 */
+ 	for (i = 0; i < 5; i++) {
+ 		buf_state = gve_dequeue_buf_state(rx, &rx->dqo.used_buf_states);
+-		if (gve_buf_ref_cnt(buf_state) == 0)
++		if (gve_buf_ref_cnt(buf_state) == 0) {
++			rx->dqo.used_buf_states_cnt--;
+ 			return buf_state;
++		}
+ 
+ 		gve_enqueue_buf_state(rx, &rx->dqo.used_buf_states, buf_state);
+ 	}
+ 
++	/* For QPL, we cannot allocate any new buffers and must
++	 * wait for the existing ones to be available.
++	 */
++	if (rx->dqo.qpl)
++		return NULL;
++
+ 	/* If there are no free buf states discard an entry from
+ 	 * `used_buf_states` so it can be used.
+ 	 */
+@@ -144,23 +154,39 @@ gve_get_recycled_buf_state(struct gve_rx
+ 		if (gve_buf_ref_cnt(buf_state) == 0)
+ 			return buf_state;
+ 
+-		gve_free_page_dqo(rx->gve, buf_state);
++		gve_free_page_dqo(rx->gve, buf_state, true);
+ 		gve_free_buf_state(rx, buf_state);
+ 	}
+ 
+ 	return NULL;
+ }
+ 
+-static int gve_alloc_page_dqo(struct gve_priv *priv,
++static int gve_alloc_page_dqo(struct gve_rx_ring *rx,
+ 			      struct gve_rx_buf_state_dqo *buf_state)
+ {
+-	int err;
++	struct gve_priv *priv = rx->gve;
++	u32 idx;
+ 
+-	err = gve_alloc_page(priv, &priv->pdev->dev, &buf_state->page_info.page,
+-			     &buf_state->addr, DMA_FROM_DEVICE, GFP_ATOMIC);
+-	if (err)
+-		return err;
++	if (!rx->dqo.qpl) {
++		int err;
+ 
++		err = gve_alloc_page(priv, &priv->pdev->dev,
++				     &buf_state->page_info.page,
++				     &buf_state->addr,
++				     DMA_FROM_DEVICE, GFP_ATOMIC);
++		if (err)
++			return err;
++	} else {
++		idx = rx->dqo.next_qpl_page_idx;
++		if (idx >= priv->rx_pages_per_qpl) {
++			net_err_ratelimited("%s: Out of QPL pages\n",
++					    priv->dev->name);
++			return -ENOMEM;
++		}
++		buf_state->page_info.page = rx->dqo.qpl->pages[idx];
++		buf_state->addr = rx->dqo.qpl->page_buses[idx];
++		rx->dqo.next_qpl_page_idx++;
++	}
+ 	buf_state->page_info.page_offset = 0;
+ 	buf_state->page_info.page_address =
+ 		page_address(buf_state->page_info.page);
+@@ -195,9 +221,13 @@ static void gve_rx_free_ring_dqo(struct
+ 
+ 	for (i = 0; i < rx->dqo.num_buf_states; i++) {
+ 		struct gve_rx_buf_state_dqo *bs = &rx->dqo.buf_states[i];
+-
++		/* Only free page for RDA. QPL pages are freed in gve_main. */
+ 		if (bs->page_info.page)
+-			gve_free_page_dqo(priv, bs);
++			gve_free_page_dqo(priv, bs, !rx->dqo.qpl);
++	}
++	if (rx->dqo.qpl) {
++		gve_unassign_qpl(priv, rx->dqo.qpl->id);
++		rx->dqo.qpl = NULL;
+ 	}
+ 
+ 	if (rx->dqo.bufq.desc_ring) {
+@@ -229,7 +259,8 @@ static int gve_rx_alloc_ring_dqo(struct
+ 	int i;
+ 
+ 	const u32 buffer_queue_slots =
+-		priv->options_dqo_rda.rx_buff_ring_entries;
++		priv->queue_format == GVE_DQO_RDA_FORMAT ?
++		priv->options_dqo_rda.rx_buff_ring_entries : priv->rx_desc_cnt;
+ 	const u32 completion_queue_slots = priv->rx_desc_cnt;
+ 
+ 	netif_dbg(priv, drv, priv->dev, "allocating rx ring DQO\n");
+@@ -243,7 +274,9 @@ static int gve_rx_alloc_ring_dqo(struct
+ 	rx->ctx.skb_head = NULL;
+ 	rx->ctx.skb_tail = NULL;
+ 
+-	rx->dqo.num_buf_states = min_t(s16, S16_MAX, buffer_queue_slots * 4);
++	rx->dqo.num_buf_states = priv->queue_format == GVE_DQO_RDA_FORMAT ?
++		min_t(s16, S16_MAX, buffer_queue_slots * 4) :
++		priv->rx_pages_per_qpl;
+ 	rx->dqo.buf_states = kvcalloc(rx->dqo.num_buf_states,
+ 				      sizeof(rx->dqo.buf_states[0]),
+ 				      GFP_KERNEL);
+@@ -275,6 +308,13 @@ static int gve_rx_alloc_ring_dqo(struct
+ 	if (!rx->dqo.bufq.desc_ring)
+ 		goto err;
+ 
++	if (priv->queue_format != GVE_DQO_RDA_FORMAT) {
++		rx->dqo.qpl = gve_assign_rx_qpl(priv, rx->q_num);
++		if (!rx->dqo.qpl)
++			goto err;
++		rx->dqo.next_qpl_page_idx = 0;
++	}
++
+ 	rx->q_resources = dma_alloc_coherent(hdev, sizeof(*rx->q_resources),
+ 					     &rx->q_resources_bus, GFP_KERNEL);
+ 	if (!rx->q_resources)
+@@ -352,7 +392,7 @@ void gve_rx_post_buffers_dqo(struct gve_
+ 			if (unlikely(!buf_state))
+ 				break;
+ 
+-			if (unlikely(gve_alloc_page_dqo(priv, buf_state))) {
++			if (unlikely(gve_alloc_page_dqo(rx, buf_state))) {
+ 				u64_stats_update_begin(&rx->statss);
+ 				rx->rx_buf_alloc_fail++;
+ 				u64_stats_update_end(&rx->statss);
+@@ -415,6 +455,7 @@ static void gve_try_recycle_buf(struct g
+ 
+ mark_used:
+ 	gve_enqueue_buf_state(rx, &rx->dqo.used_buf_states, buf_state);
++	rx->dqo.used_buf_states_cnt++;
+ }
+ 
+ static void gve_rx_skb_csum(struct sk_buff *skb,
+@@ -475,6 +516,43 @@ static void gve_rx_free_skb(struct gve_r
+ 	rx->ctx.skb_tail = NULL;
+ }
+ 
++static bool gve_rx_should_trigger_copy_ondemand(struct gve_rx_ring *rx)
++{
++	if (!rx->dqo.qpl)
++		return false;
++	if (rx->dqo.used_buf_states_cnt <
++		     (rx->dqo.num_buf_states -
++		     GVE_DQO_QPL_ONDEMAND_ALLOC_THRESHOLD))
++		return false;
++	return true;
++}
++
++static int gve_rx_copy_ondemand(struct gve_rx_ring *rx,
++				struct gve_rx_buf_state_dqo *buf_state,
++				u16 buf_len)
++{
++	struct page *page = alloc_page(GFP_ATOMIC);
++	int num_frags;
++
++	if (!page)
++		return -ENOMEM;
++
++	memcpy(page_address(page),
++	       buf_state->page_info.page_address +
++	       buf_state->page_info.page_offset,
++	       buf_len);
++	num_frags = skb_shinfo(rx->ctx.skb_tail)->nr_frags;
++	skb_add_rx_frag(rx->ctx.skb_tail, num_frags, page,
++			0, buf_len, PAGE_SIZE);
++
++	u64_stats_update_begin(&rx->statss);
++	rx->rx_frag_alloc_cnt++;
++	u64_stats_update_end(&rx->statss);
++	/* Return unused buffer. */
++	gve_enqueue_buf_state(rx, &rx->dqo.recycled_buf_states, buf_state);
++	return 0;
++}
++
+ /* Chains multi skbs for single rx packet.
+  * Returns 0 if buffer is appended, -1 otherwise.
+  */
+@@ -502,12 +580,20 @@ static int gve_rx_append_frags(struct na
+ 		rx->ctx.skb_head->truesize += priv->data_buffer_size_dqo;
+ 	}
+ 
++	/* Trigger ondemand page allocation if we are running low on buffers */
++	if (gve_rx_should_trigger_copy_ondemand(rx))
++		return gve_rx_copy_ondemand(rx, buf_state, buf_len);
++
+ 	skb_add_rx_frag(rx->ctx.skb_tail, num_frags,
+ 			buf_state->page_info.page,
+ 			buf_state->page_info.page_offset,
+ 			buf_len, priv->data_buffer_size_dqo);
+ 	gve_dec_pagecnt_bias(&buf_state->page_info);
+ 
++	/* Advances buffer page-offset if page is partially used.
++	 * Marks buffer as used if page is full.
++	 */
++	gve_try_recycle_buf(priv, rx, buf_state);
+ 	return 0;
+ }
+ 
+@@ -561,8 +647,6 @@ static int gve_rx_dqo(struct napi_struct
+ 						 priv)) != 0) {
+ 			goto error;
+ 		}
+-
+-		gve_try_recycle_buf(priv, rx, buf_state);
+ 		return 0;
+ 	}
+ 
+@@ -588,6 +672,12 @@ static int gve_rx_dqo(struct napi_struct
+ 		goto error;
+ 	rx->ctx.skb_tail = rx->ctx.skb_head;
+ 
++	if (gve_rx_should_trigger_copy_ondemand(rx)) {
++		if (gve_rx_copy_ondemand(rx, buf_state, buf_len) < 0)
++			goto error;
++		return 0;
++	}
++
+ 	skb_add_rx_frag(rx->ctx.skb_head, 0, buf_state->page_info.page,
+ 			buf_state->page_info.page_offset, buf_len,
+ 			priv->data_buffer_size_dqo);
diff --git a/patches.suse/gve-Tx-path-for-DQO-QPL.patch b/patches.suse/gve-Tx-path-for-DQO-QPL.patch
new file mode 100644
index 0000000..249295e
--- /dev/null
+++ b/patches.suse/gve-Tx-path-for-DQO-QPL.patch
@@ -0,0 +1,680 @@
+From: Rushil Gupta <rushilg@google.com>
+Date: Fri, 4 Aug 2023 21:34:42 +0000
+Subject: gve: Tx path for DQO-QPL
+Patch-mainline: v6.6-rc1
+Git-commit: a6fb8d5a8b6925f1e635818d3dd2d89531d4a058
+References: bsc#1214479
+
+Each QPL page is divided into GVE_TX_BUFS_PER_PAGE_DQO buffers.
+When a packet needs to be transmitted, we break the packet into max
+GVE_TX_BUF_SIZE_DQO sized chunks and transmit each chunk using a TX
+descriptor.
+We allocate the TX buffers from the free list in dqo_tx.
+We store these TX buffer indices in an array in the pending_packet
+structure.
+
+The TX buffers are returned to the free list in dqo_compl after
+receiving packet completion or when removing packets from miss
+completions list.
+
+Signed-off-by: Rushil Gupta <rushilg@google.com>
+Reviewed-by: Willem de Bruijn <willemb@google.com>
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Signed-off-by: Bailey Forrest <bcf@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h        |   77 +++++
+ drivers/net/ethernet/google/gve/gve_tx_dqo.c |  398 +++++++++++++++++++++------
+ 2 files changed, 394 insertions(+), 81 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -57,6 +57,20 @@
+ /* Maximum TSO size supported on DQO */
+ #define GVE_DQO_TX_MAX	0x3FFFF
+ 
++#define GVE_TX_BUF_SHIFT_DQO 11
++
++/* 2K buffers for DQO-QPL */
++#define GVE_TX_BUF_SIZE_DQO BIT(GVE_TX_BUF_SHIFT_DQO)
++#define GVE_TX_BUFS_PER_PAGE_DQO (PAGE_SIZE >> GVE_TX_BUF_SHIFT_DQO)
++#define GVE_MAX_TX_BUFS_PER_PKT (DIV_ROUND_UP(GVE_DQO_TX_MAX, GVE_TX_BUF_SIZE_DQO))
++
++/* If number of free/recyclable buffers are less than this threshold; driver
++ * allocs and uses a non-qpl page on the receive path of DQO QPL to free
++ * up buffers.
++ * Value is set big enough to post at least 3 64K LRO packet via 2K buffer to NIC.
++ */
++#define GVE_DQO_QPL_ONDEMAND_ALLOC_THRESHOLD 96
++
+ /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
+ struct gve_rx_desc_queue {
+ 	struct gve_rx_desc *desc_ring; /* the descriptor ring */
+@@ -337,8 +351,14 @@ struct gve_tx_pending_packet_dqo {
+ 	 * All others correspond to `skb`'s frags and should be unmapped with
+ 	 * `dma_unmap_page`.
+ 	 */
+-	DEFINE_DMA_UNMAP_ADDR(dma[MAX_SKB_FRAGS + 1]);
+-	DEFINE_DMA_UNMAP_LEN(len[MAX_SKB_FRAGS + 1]);
++	union {
++		struct {
++			DEFINE_DMA_UNMAP_ADDR(dma[MAX_SKB_FRAGS + 1]);
++			DEFINE_DMA_UNMAP_LEN(len[MAX_SKB_FRAGS + 1]);
++		};
++		s16 tx_qpl_buf_ids[GVE_MAX_TX_BUFS_PER_PKT];
++	};
++
+ 	u16 num_bufs;
+ 
+ 	/* Linked list index to next element in the list, or -1 if none */
+@@ -393,6 +413,32 @@ struct gve_tx_ring {
+ 			 * set.
+ 			 */
+ 			u32 last_re_idx;
++
++			/* free running number of packet buf descriptors posted */
++			u16 posted_packet_desc_cnt;
++			/* free running number of packet buf descriptors completed */
++			u16 completed_packet_desc_cnt;
++
++			/* QPL fields */
++			struct {
++			       /* Linked list of gve_tx_buf_dqo. Index into
++				* tx_qpl_buf_next, or -1 if empty.
++				*
++				* This is a consumer list owned by the TX path. When it
++				* runs out, the producer list is stolen from the
++				* completion handling path
++				* (dqo_compl.free_tx_qpl_buf_head).
++				*/
++				s16 free_tx_qpl_buf_head;
++
++			       /* Free running count of the number of QPL tx buffers
++				* allocated
++				*/
++				u32 alloc_tx_qpl_buf_cnt;
++
++				/* Cached value of `dqo_compl.free_tx_qpl_buf_cnt` */
++				u32 free_tx_qpl_buf_cnt;
++			};
+ 		} dqo_tx;
+ 	};
+ 
+@@ -436,6 +482,24 @@ struct gve_tx_ring {
+ 			 * reached a specified timeout.
+ 			 */
+ 			struct gve_index_list timed_out_completions;
++
++			/* QPL fields */
++			struct {
++				/* Linked list of gve_tx_buf_dqo. Index into
++				 * tx_qpl_buf_next, or -1 if empty.
++				 *
++				 * This is the producer list, owned by the completion
++				 * handling path. When the consumer list
++				 * (dqo_tx.free_tx_qpl_buf_head) is runs out, this list
++				 * will be stolen.
++				 */
++				atomic_t free_tx_qpl_buf_head;
++
++				/* Free running count of the number of tx buffers
++				 * freed
++				 */
++				atomic_t free_tx_qpl_buf_cnt;
++			};
+ 		} dqo_compl;
+ 	} ____cacheline_aligned;
+ 	u64 pkt_done; /* free-running - total packets completed */
+@@ -467,6 +531,15 @@ struct gve_tx_ring {
+ 			struct {
+ 				/* qpl assigned to this queue */
+ 				struct gve_queue_page_list *qpl;
++
++				/* Each QPL page is divided into TX bounce buffers
++				 * of size GVE_TX_BUF_SIZE_DQO. tx_qpl_buf_next is
++				 * an array to manage linked lists of TX buffers.
++				 * An entry j at index i implies that j'th buffer
++				 * is next on the list after i
++				 */
++				s16 *tx_qpl_buf_next;
++				u32 num_tx_qpl_bufs;
+ 			};
+ 		} dqo;
+ 	} ____cacheline_aligned;
+--- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c
+@@ -12,6 +12,89 @@
+ #include <linux/slab.h>
+ #include <linux/skbuff.h>
+ 
++/* Returns true if tx_bufs are available. */
++static bool gve_has_free_tx_qpl_bufs(struct gve_tx_ring *tx, int count)
++{
++	int num_avail;
++
++	if (!tx->dqo.qpl)
++		return true;
++
++	num_avail = tx->dqo.num_tx_qpl_bufs -
++		(tx->dqo_tx.alloc_tx_qpl_buf_cnt -
++		 tx->dqo_tx.free_tx_qpl_buf_cnt);
++
++	if (count <= num_avail)
++		return true;
++
++	/* Update cached value from dqo_compl. */
++	tx->dqo_tx.free_tx_qpl_buf_cnt =
++		atomic_read_acquire(&tx->dqo_compl.free_tx_qpl_buf_cnt);
++
++	num_avail = tx->dqo.num_tx_qpl_bufs -
++		(tx->dqo_tx.alloc_tx_qpl_buf_cnt -
++		 tx->dqo_tx.free_tx_qpl_buf_cnt);
++
++	return count <= num_avail;
++}
++
++static s16
++gve_alloc_tx_qpl_buf(struct gve_tx_ring *tx)
++{
++	s16 index;
++
++	index = tx->dqo_tx.free_tx_qpl_buf_head;
++
++	/* No TX buffers available, try to steal the list from the
++	 * completion handler.
++	 */
++	if (unlikely(index == -1)) {
++		tx->dqo_tx.free_tx_qpl_buf_head =
++			atomic_xchg(&tx->dqo_compl.free_tx_qpl_buf_head, -1);
++		index = tx->dqo_tx.free_tx_qpl_buf_head;
++
++		if (unlikely(index == -1))
++			return index;
++	}
++
++	/* Remove TX buf from free list */
++	tx->dqo_tx.free_tx_qpl_buf_head = tx->dqo.tx_qpl_buf_next[index];
++
++	return index;
++}
++
++static void
++gve_free_tx_qpl_bufs(struct gve_tx_ring *tx,
++		     struct gve_tx_pending_packet_dqo *pkt)
++{
++	s16 index;
++	int i;
++
++	if (!pkt->num_bufs)
++		return;
++
++	index = pkt->tx_qpl_buf_ids[0];
++	/* Create a linked list of buffers to be added to the free list */
++	for (i = 1; i < pkt->num_bufs; i++) {
++		tx->dqo.tx_qpl_buf_next[index] = pkt->tx_qpl_buf_ids[i];
++		index = pkt->tx_qpl_buf_ids[i];
++	}
++
++	while (true) {
++		s16 old_head = atomic_read_acquire(&tx->dqo_compl.free_tx_qpl_buf_head);
++
++		tx->dqo.tx_qpl_buf_next[index] = old_head;
++		if (atomic_cmpxchg(&tx->dqo_compl.free_tx_qpl_buf_head,
++				   old_head,
++				   pkt->tx_qpl_buf_ids[0]) == old_head) {
++			break;
++		}
++	}
++
++	atomic_add(pkt->num_bufs, &tx->dqo_compl.free_tx_qpl_buf_cnt);
++	pkt->num_bufs = 0;
++}
++
+ /* Returns true if a gve_tx_pending_packet_dqo object is available. */
+ static bool gve_has_pending_packet(struct gve_tx_ring *tx)
+ {
+@@ -135,9 +218,40 @@ static void gve_tx_free_ring_dqo(struct
+ 	kvfree(tx->dqo.pending_packets);
+ 	tx->dqo.pending_packets = NULL;
+ 
++	kvfree(tx->dqo.tx_qpl_buf_next);
++	tx->dqo.tx_qpl_buf_next = NULL;
++
++	if (tx->dqo.qpl) {
++		gve_unassign_qpl(priv, tx->dqo.qpl->id);
++		tx->dqo.qpl = NULL;
++	}
++
+ 	netif_dbg(priv, drv, priv->dev, "freed tx queue %d\n", idx);
+ }
+ 
++static int gve_tx_qpl_buf_init(struct gve_tx_ring *tx)
++{
++	int num_tx_qpl_bufs = GVE_TX_BUFS_PER_PAGE_DQO *
++		tx->dqo.qpl->num_entries;
++	int i;
++
++	tx->dqo.tx_qpl_buf_next = kvcalloc(num_tx_qpl_bufs,
++					   sizeof(tx->dqo.tx_qpl_buf_next[0]),
++					   GFP_KERNEL);
++	if (!tx->dqo.tx_qpl_buf_next)
++		return -ENOMEM;
++
++	tx->dqo.num_tx_qpl_bufs = num_tx_qpl_bufs;
++
++	/* Generate free TX buf list */
++	for (i = 0; i < num_tx_qpl_bufs - 1; i++)
++		tx->dqo.tx_qpl_buf_next[i] = i + 1;
++	tx->dqo.tx_qpl_buf_next[num_tx_qpl_bufs - 1] = -1;
++
++	atomic_set_release(&tx->dqo_compl.free_tx_qpl_buf_head, -1);
++	return 0;
++}
++
+ static int gve_tx_alloc_ring_dqo(struct gve_priv *priv, int idx)
+ {
+ 	struct gve_tx_ring *tx = &priv->tx[idx];
+@@ -154,7 +268,9 @@ static int gve_tx_alloc_ring_dqo(struct
+ 
+ 	/* Queue sizes must be a power of 2 */
+ 	tx->mask = priv->tx_desc_cnt - 1;
+-	tx->dqo.complq_mask = priv->options_dqo_rda.tx_comp_ring_entries - 1;
++	tx->dqo.complq_mask = priv->queue_format == GVE_DQO_RDA_FORMAT ?
++		priv->options_dqo_rda.tx_comp_ring_entries - 1 :
++		tx->mask;
+ 
+ 	/* The max number of pending packets determines the maximum number of
+ 	 * descriptors which maybe written to the completion queue.
+@@ -210,6 +326,15 @@ static int gve_tx_alloc_ring_dqo(struct
+ 	if (!tx->q_resources)
+ 		goto err;
+ 
++	if (gve_is_qpl(priv)) {
++		tx->dqo.qpl = gve_assign_tx_qpl(priv, idx);
++		if (!tx->dqo.qpl)
++			goto err;
++
++		if (gve_tx_qpl_buf_init(tx))
++			goto err;
++	}
++
+ 	gve_tx_add_to_block(priv, idx);
+ 
+ 	return 0;
+@@ -266,20 +391,27 @@ static u32 num_avail_tx_slots(const stru
+ 	return tx->mask - num_used;
+ }
+ 
++static bool gve_has_avail_slots_tx_dqo(struct gve_tx_ring *tx,
++				       int desc_count, int buf_count)
++{
++	return gve_has_pending_packet(tx) &&
++		   num_avail_tx_slots(tx) >= desc_count &&
++		   gve_has_free_tx_qpl_bufs(tx, buf_count);
++}
++
+ /* Stops the queue if available descriptors is less than 'count'.
+  * Return: 0 if stop is not required.
+  */
+-static int gve_maybe_stop_tx_dqo(struct gve_tx_ring *tx, int count)
++static int gve_maybe_stop_tx_dqo(struct gve_tx_ring *tx,
++				 int desc_count, int buf_count)
+ {
+-	if (likely(gve_has_pending_packet(tx) &&
+-		   num_avail_tx_slots(tx) >= count))
++	if (likely(gve_has_avail_slots_tx_dqo(tx, desc_count, buf_count)))
+ 		return 0;
+ 
+ 	/* Update cached TX head pointer */
+ 	tx->dqo_tx.head = atomic_read_acquire(&tx->dqo_compl.hw_tx_head);
+ 
+-	if (likely(gve_has_pending_packet(tx) &&
+-		   num_avail_tx_slots(tx) >= count))
++	if (likely(gve_has_avail_slots_tx_dqo(tx, desc_count, buf_count)))
+ 		return 0;
+ 
+ 	/* No space, so stop the queue */
+@@ -294,8 +426,7 @@ static int gve_maybe_stop_tx_dqo(struct
+ 	 */
+ 	tx->dqo_tx.head = atomic_read_acquire(&tx->dqo_compl.hw_tx_head);
+ 
+-	if (likely(!gve_has_pending_packet(tx) ||
+-		   num_avail_tx_slots(tx) < count))
++	if (likely(!gve_has_avail_slots_tx_dqo(tx, desc_count, buf_count)))
+ 		return -EBUSY;
+ 
+ 	netif_tx_start_queue(tx->netdev_txq);
+@@ -443,44 +574,16 @@ gve_tx_fill_general_ctx_desc(struct gve_
+ 	};
+ }
+ 
+-/* Returns 0 on success, or < 0 on error.
+- *
+- * Before this function is called, the caller must ensure
+- * gve_has_pending_packet(tx) returns true.
+- */
+ static int gve_tx_add_skb_no_copy_dqo(struct gve_tx_ring *tx,
+-				      struct sk_buff *skb)
++				      struct sk_buff *skb,
++				      struct gve_tx_pending_packet_dqo *pkt,
++				      s16 completion_tag,
++				      u32 *desc_idx,
++				      bool is_gso)
+ {
+ 	const struct skb_shared_info *shinfo = skb_shinfo(skb);
+-	const bool is_gso = skb_is_gso(skb);
+-	u32 desc_idx = tx->dqo_tx.tail;
+-
+-	struct gve_tx_pending_packet_dqo *pkt;
+-	struct gve_tx_metadata_dqo metadata;
+-	s16 completion_tag;
+ 	int i;
+ 
+-	pkt = gve_alloc_pending_packet(tx);
+-	pkt->skb = skb;
+-	pkt->num_bufs = 0;
+-	completion_tag = pkt - tx->dqo.pending_packets;
+-
+-	gve_extract_tx_metadata_dqo(skb, &metadata);
+-	if (is_gso) {
+-		int header_len = gve_prep_tso(skb);
+-
+-		if (unlikely(header_len < 0))
+-			goto err;
+-
+-		gve_tx_fill_tso_ctx_desc(&tx->dqo.tx_ring[desc_idx].tso_ctx,
+-					 skb, &metadata, header_len);
+-		desc_idx = (desc_idx + 1) & tx->mask;
+-	}
+-
+-	gve_tx_fill_general_ctx_desc(&tx->dqo.tx_ring[desc_idx].general_ctx,
+-				     &metadata);
+-	desc_idx = (desc_idx + 1) & tx->mask;
+-
+ 	/* Note: HW requires that the size of a non-TSO packet be within the
+ 	 * range of [17, 9728].
+ 	 *
+@@ -489,6 +592,7 @@ static int gve_tx_add_skb_no_copy_dqo(st
+ 	 * - Hypervisor won't allow MTU larger than 9216.
+ 	 */
+ 
++	pkt->num_bufs = 0;
+ 	/* Map the linear portion of skb */
+ 	{
+ 		u32 len = skb_headlen(skb);
+@@ -502,7 +606,7 @@ static int gve_tx_add_skb_no_copy_dqo(st
+ 		dma_unmap_addr_set(pkt, dma[pkt->num_bufs], addr);
+ 		++pkt->num_bufs;
+ 
+-		gve_tx_fill_pkt_desc_dqo(tx, &desc_idx, skb, len, addr,
++		gve_tx_fill_pkt_desc_dqo(tx, desc_idx, skb, len, addr,
+ 					 completion_tag,
+ 					 /*eop=*/shinfo->nr_frags == 0, is_gso);
+ 	}
+@@ -521,10 +625,139 @@ static int gve_tx_add_skb_no_copy_dqo(st
+ 		dma_unmap_addr_set(pkt, dma[pkt->num_bufs], addr);
+ 		++pkt->num_bufs;
+ 
+-		gve_tx_fill_pkt_desc_dqo(tx, &desc_idx, skb, len, addr,
++		gve_tx_fill_pkt_desc_dqo(tx, desc_idx, skb, len, addr,
+ 					 completion_tag, is_eop, is_gso);
+ 	}
+ 
++	return 0;
++err:
++	for (i = 0; i < pkt->num_bufs; i++) {
++		if (i == 0) {
++			dma_unmap_single(tx->dev,
++					 dma_unmap_addr(pkt, dma[i]),
++					 dma_unmap_len(pkt, len[i]),
++					 DMA_TO_DEVICE);
++		} else {
++			dma_unmap_page(tx->dev,
++				       dma_unmap_addr(pkt, dma[i]),
++				       dma_unmap_len(pkt, len[i]),
++				       DMA_TO_DEVICE);
++		}
++	}
++	pkt->num_bufs = 0;
++	return -1;
++}
++
++/* Tx buffer i corresponds to
++ * qpl_page_id = i / GVE_TX_BUFS_PER_PAGE_DQO
++ * qpl_page_offset = (i % GVE_TX_BUFS_PER_PAGE_DQO) * GVE_TX_BUF_SIZE_DQO
++ */
++static void gve_tx_buf_get_addr(struct gve_tx_ring *tx,
++				s16 index,
++				void **va, dma_addr_t *dma_addr)
++{
++	int page_id = index >> (PAGE_SHIFT - GVE_TX_BUF_SHIFT_DQO);
++	int offset = (index & (GVE_TX_BUFS_PER_PAGE_DQO - 1)) << GVE_TX_BUF_SHIFT_DQO;
++
++	*va = page_address(tx->dqo.qpl->pages[page_id]) + offset;
++	*dma_addr = tx->dqo.qpl->page_buses[page_id] + offset;
++}
++
++static int gve_tx_add_skb_copy_dqo(struct gve_tx_ring *tx,
++				   struct sk_buff *skb,
++				   struct gve_tx_pending_packet_dqo *pkt,
++				   s16 completion_tag,
++				   u32 *desc_idx,
++				   bool is_gso)
++{
++	u32 copy_offset = 0;
++	dma_addr_t dma_addr;
++	u32 copy_len;
++	s16 index;
++	void *va;
++
++	/* Break the packet into buffer size chunks */
++	pkt->num_bufs = 0;
++	while (copy_offset < skb->len) {
++		index = gve_alloc_tx_qpl_buf(tx);
++		if (unlikely(index == -1))
++			goto err;
++
++		gve_tx_buf_get_addr(tx, index, &va, &dma_addr);
++		copy_len = min_t(u32, GVE_TX_BUF_SIZE_DQO,
++				 skb->len - copy_offset);
++		skb_copy_bits(skb, copy_offset, va, copy_len);
++
++		copy_offset += copy_len;
++		dma_sync_single_for_device(tx->dev, dma_addr,
++					   copy_len, DMA_TO_DEVICE);
++		gve_tx_fill_pkt_desc_dqo(tx, desc_idx, skb,
++					 copy_len,
++					 dma_addr,
++					 completion_tag,
++					 copy_offset == skb->len,
++					 is_gso);
++
++		pkt->tx_qpl_buf_ids[pkt->num_bufs] = index;
++		++tx->dqo_tx.alloc_tx_qpl_buf_cnt;
++		++pkt->num_bufs;
++	}
++
++	return 0;
++err:
++	/* Should not be here if gve_has_free_tx_qpl_bufs() check is correct */
++	gve_free_tx_qpl_bufs(tx, pkt);
++	return -ENOMEM;
++}
++
++/* Returns 0 on success, or < 0 on error.
++ *
++ * Before this function is called, the caller must ensure
++ * gve_has_pending_packet(tx) returns true.
++ */
++static int gve_tx_add_skb_dqo(struct gve_tx_ring *tx,
++			      struct sk_buff *skb)
++{
++	const bool is_gso = skb_is_gso(skb);
++	u32 desc_idx = tx->dqo_tx.tail;
++	struct gve_tx_pending_packet_dqo *pkt;
++	struct gve_tx_metadata_dqo metadata;
++	s16 completion_tag;
++
++	pkt = gve_alloc_pending_packet(tx);
++	pkt->skb = skb;
++	completion_tag = pkt - tx->dqo.pending_packets;
++
++	gve_extract_tx_metadata_dqo(skb, &metadata);
++	if (is_gso) {
++		int header_len = gve_prep_tso(skb);
++
++		if (unlikely(header_len < 0))
++			goto err;
++
++		gve_tx_fill_tso_ctx_desc(&tx->dqo.tx_ring[desc_idx].tso_ctx,
++					 skb, &metadata, header_len);
++		desc_idx = (desc_idx + 1) & tx->mask;
++	}
++
++	gve_tx_fill_general_ctx_desc(&tx->dqo.tx_ring[desc_idx].general_ctx,
++				     &metadata);
++	desc_idx = (desc_idx + 1) & tx->mask;
++
++	if (tx->dqo.qpl) {
++		if (gve_tx_add_skb_copy_dqo(tx, skb, pkt,
++					    completion_tag,
++					    &desc_idx, is_gso))
++			goto err;
++	}  else {
++		if (gve_tx_add_skb_no_copy_dqo(tx, skb, pkt,
++					       completion_tag,
++					       &desc_idx, is_gso))
++			goto err;
++	}
++
++	tx->dqo_tx.posted_packet_desc_cnt += pkt->num_bufs;
++
+ 	/* Commit the changes to our state */
+ 	tx->dqo_tx.tail = desc_idx;
+ 
+@@ -546,22 +779,7 @@ static int gve_tx_add_skb_no_copy_dqo(st
+ 	return 0;
+ 
+ err:
+-	for (i = 0; i < pkt->num_bufs; i++) {
+-		if (i == 0) {
+-			dma_unmap_single(tx->dev,
+-					 dma_unmap_addr(pkt, dma[i]),
+-					 dma_unmap_len(pkt, len[i]),
+-					 DMA_TO_DEVICE);
+-		} else {
+-			dma_unmap_page(tx->dev,
+-				       dma_unmap_addr(pkt, dma[i]),
+-				       dma_unmap_len(pkt, len[i]),
+-				       DMA_TO_DEVICE);
+-		}
+-	}
+-
+ 	pkt->skb = NULL;
+-	pkt->num_bufs = 0;
+ 	gve_free_pending_packet(tx, pkt);
+ 
+ 	return -1;
+@@ -635,37 +853,51 @@ static int gve_try_tx_skb(struct gve_pri
+ 	int num_buffer_descs;
+ 	int total_num_descs;
+ 
+-	if (skb_is_gso(skb)) {
+-		/* If TSO doesn't meet HW requirements, attempt to linearize the
+-		 * packet.
+-		 */
+-		if (unlikely(!gve_can_send_tso(skb) &&
+-			     skb_linearize(skb) < 0)) {
+-			net_err_ratelimited("%s: Failed to transmit TSO packet\n",
+-					    priv->dev->name);
+-			goto drop;
+-		}
++	if (tx->dqo.qpl) {
++		if (skb_is_gso(skb))
+ 
+-		num_buffer_descs = gve_num_buffer_descs_needed(skb);
++		/* We do not need to verify the number of buffers used per
++		 * packet or per segment in case of TSO as with 2K size buffers
++		 * none of the TX packet rules would be violated.
++		 *
++		 * gve_can_send_tso() checks that each TCP segment of gso_size is
++		 * not distributed over more than 9 SKB frags..
++		 */
++		num_buffer_descs = DIV_ROUND_UP(skb->len, GVE_TX_BUF_SIZE_DQO);
+ 	} else {
+-		num_buffer_descs = gve_num_buffer_descs_needed(skb);
+-
+-		if (unlikely(num_buffer_descs > GVE_TX_MAX_DATA_DESCS)) {
+-			if (unlikely(skb_linearize(skb) < 0))
++		if (skb_is_gso(skb)) {
++			/* If TSO doesn't meet HW requirements, attempt to linearize the
++			 * packet.
++			 */
++			if (unlikely(!gve_can_send_tso(skb) &&
++				     skb_linearize(skb) < 0)) {
++				net_err_ratelimited("%s: Failed to transmit TSO packet\n",
++						    priv->dev->name);
+ 				goto drop;
++			}
++
++			num_buffer_descs = gve_num_buffer_descs_needed(skb);
++		} else {
++			num_buffer_descs = gve_num_buffer_descs_needed(skb);
+ 
+-			num_buffer_descs = 1;
++			if (unlikely(num_buffer_descs > GVE_TX_MAX_DATA_DESCS)) {
++				if (unlikely(skb_linearize(skb) < 0))
++					goto drop;
++
++				num_buffer_descs = 1;
++			}
+ 		}
+ 	}
+ 
+ 	/* Metadata + (optional TSO) + data descriptors. */
+ 	total_num_descs = 1 + skb_is_gso(skb) + num_buffer_descs;
+ 	if (unlikely(gve_maybe_stop_tx_dqo(tx, total_num_descs +
+-			GVE_TX_MIN_DESC_PREVENT_CACHE_OVERLAP))) {
++			GVE_TX_MIN_DESC_PREVENT_CACHE_OVERLAP,
++			num_buffer_descs))) {
+ 		return -1;
+ 	}
+ 
+-	if (unlikely(gve_tx_add_skb_no_copy_dqo(tx, skb) < 0))
++	if (unlikely(gve_tx_add_skb_dqo(tx, skb) < 0))
+ 		goto drop;
+ 
+ 	netdev_tx_sent_queue(tx->netdev_txq, skb->len);
+@@ -813,7 +1045,11 @@ static void gve_handle_packet_completion
+ 			return;
+ 		}
+ 	}
+-	gve_unmap_packet(tx->dev, pending_packet);
++	tx->dqo_tx.completed_packet_desc_cnt += pending_packet->num_bufs;
++	if (tx->dqo.qpl)
++		gve_free_tx_qpl_bufs(tx, pending_packet);
++	else
++		gve_unmap_packet(tx->dev, pending_packet);
+ 
+ 	*bytes += pending_packet->skb->len;
+ 	(*pkts)++;
+@@ -871,12 +1107,16 @@ static void remove_miss_completions(stru
+ 
+ 		remove_from_list(tx, &tx->dqo_compl.miss_completions,
+ 				 pending_packet);
+-		/* Unmap buffers and free skb but do not unallocate packet i.e.
++		/* Unmap/free TX buffers and free skb but do not unallocate packet i.e.
+ 		 * the completion tag is not freed to ensure that the driver
+ 		 * can take appropriate action if a corresponding valid
+ 		 * completion is received later.
+ 		 */
+-		gve_unmap_packet(tx->dev, pending_packet);
++		if (tx->dqo.qpl)
++			gve_free_tx_qpl_bufs(tx, pending_packet);
++		else
++			gve_unmap_packet(tx->dev, pending_packet);
++
+ 		/* This indicates the packet was dropped. */
+ 		dev_kfree_skb_any(pending_packet->skb);
+ 		pending_packet->skb = NULL;
diff --git a/patches.suse/gve-Unify-duplicate-GQ-min-pkt-desc-size-constants.patch b/patches.suse/gve-Unify-duplicate-GQ-min-pkt-desc-size-constants.patch
new file mode 100644
index 0000000..029941b
--- /dev/null
+++ b/patches.suse/gve-Unify-duplicate-GQ-min-pkt-desc-size-constants.patch
@@ -0,0 +1,50 @@
+From: Shailend Chand <shailend@google.com>
+Date: Fri, 7 Apr 2023 11:48:30 -0700
+Subject: gve: Unify duplicate GQ min pkt desc size constants
+Patch-mainline: v6.4-rc1
+Git-commit: 4de00f0acc722f43046dca06fe1336597d1250ab
+References: bsc#1214479
+
+The two constants accomplish the same thing.
+
+Signed-off-by: Shailend Chand <shailend@google.com>
+Reviewed-by: Jakub Kicinski <kuba@kernel.org>
+Link: https://lore.kernel.org/r/20230407184830.309398-1-shailend@google.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h    |    2 --
+ drivers/net/ethernet/google/gve/gve_tx.c |    4 ++--
+ 2 files changed, 2 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -49,8 +49,6 @@
+ 
+ #define GVE_XDP_ACTIONS 5
+ 
+-#define GVE_TX_MAX_HEADER_SIZE 182
+-
+ #define GVE_GQ_TX_MIN_PKT_DESC_BYTES 182
+ 
+ /* Each slot in the desc ring has a 1:1 mapping to a slot in the data ring */
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -730,7 +730,7 @@ static int gve_tx_fill_xdp(struct gve_pr
+ 	u32 reqi = tx->req;
+ 
+ 	pad = gve_tx_fifo_pad_alloc_one_frag(&tx->tx_fifo, len);
+-	if (pad >= GVE_TX_MAX_HEADER_SIZE)
++	if (pad >= GVE_GQ_TX_MIN_PKT_DESC_BYTES)
+ 		pad = 0;
+ 	info = &tx->info[reqi & tx->mask];
+ 	info->xdp_frame = frame_p;
+@@ -810,7 +810,7 @@ int gve_xdp_xmit_one(struct gve_priv *pr
+ {
+ 	int nsegs;
+ 
+-	if (!gve_can_tx(tx, len + GVE_TX_MAX_HEADER_SIZE - 1))
++	if (!gve_can_tx(tx, len + GVE_GQ_TX_MIN_PKT_DESC_BYTES - 1))
+ 		return -EBUSY;
+ 
+ 	nsegs = gve_tx_fill_xdp(priv, tx, data, len, frame_p, false);
diff --git a/patches.suse/gve-XDP-support-GQI-QPL-helper-function-changes.patch b/patches.suse/gve-XDP-support-GQI-QPL-helper-function-changes.patch
new file mode 100644
index 0000000..e006b07
--- /dev/null
+++ b/patches.suse/gve-XDP-support-GQI-QPL-helper-function-changes.patch
@@ -0,0 +1,445 @@
+From: Praveen Kaligineedi <pkaligineedi@google.com>
+Date: Wed, 15 Mar 2023 16:33:08 -0700
+Subject: gve: XDP support GQI-QPL: helper function changes
+Patch-mainline: v6.4-rc1
+Git-commit: 2e80aeae9f807ac7e967dea7633abea5829c6531
+References: bsc#1214479
+
+This patch adds/modifies helper functions needed to add XDP
+support.
+
+Signed-off-by: Praveen Kaligineedi <pkaligineedi@google.com>
+Reviewed-by: Jeroen de Borst <jeroendb@google.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve.h         |    5 +++
+ drivers/net/ethernet/google/gve/gve_ethtool.c |   26 ++++++++++-----
+ drivers/net/ethernet/google/gve/gve_main.c    |   27 +++++++++-------
+ drivers/net/ethernet/google/gve/gve_rx.c      |    2 -
+ drivers/net/ethernet/google/gve/gve_rx_dqo.c  |    2 -
+ drivers/net/ethernet/google/gve/gve_tx.c      |   43 +++++++++++++++-----------
+ drivers/net/ethernet/google/gve/gve_utils.c   |    6 +--
+ drivers/net/ethernet/google/gve/gve_utils.h   |    3 -
+ 8 files changed, 70 insertions(+), 44 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve.h
++++ b/drivers/net/ethernet/google/gve/gve.h
+@@ -857,6 +857,11 @@ static inline bool gve_is_gqi(struct gve
+ 		priv->queue_format == GVE_GQI_QPL_FORMAT;
+ }
+ 
++static inline u32 gve_num_tx_queues(struct gve_priv *priv)
++{
++	return priv->tx_cfg.num_queues;
++}
++
+ /* buffers */
+ int gve_alloc_page(struct gve_priv *priv, struct device *dev,
+ 		   struct page **page, dma_addr_t *dma,
+--- a/drivers/net/ethernet/google/gve/gve_ethtool.c
++++ b/drivers/net/ethernet/google/gve/gve_ethtool.c
+@@ -81,8 +81,10 @@ static void gve_get_strings(struct net_d
+ {
+ 	struct gve_priv *priv = netdev_priv(netdev);
+ 	char *s = (char *)data;
++	int num_tx_queues;
+ 	int i, j;
+ 
++	num_tx_queues = gve_num_tx_queues(priv);
+ 	switch (stringset) {
+ 	case ETH_SS_STATS:
+ 		memcpy(s, *gve_gstrings_main_stats,
+@@ -97,7 +99,7 @@ static void gve_get_strings(struct net_d
+ 			}
+ 		}
+ 
+-		for (i = 0; i < priv->tx_cfg.num_queues; i++) {
++		for (i = 0; i < num_tx_queues; i++) {
+ 			for (j = 0; j < NUM_GVE_TX_CNTS; j++) {
+ 				snprintf(s, ETH_GSTRING_LEN,
+ 					 gve_gstrings_tx_stats[j], i);
+@@ -124,12 +126,14 @@ static void gve_get_strings(struct net_d
+ static int gve_get_sset_count(struct net_device *netdev, int sset)
+ {
+ 	struct gve_priv *priv = netdev_priv(netdev);
++	int num_tx_queues;
+ 
++	num_tx_queues = gve_num_tx_queues(priv);
+ 	switch (sset) {
+ 	case ETH_SS_STATS:
+ 		return GVE_MAIN_STATS_LEN + GVE_ADMINQ_STATS_LEN +
+ 		       (priv->rx_cfg.num_queues * NUM_GVE_RX_CNTS) +
+-		       (priv->tx_cfg.num_queues * NUM_GVE_TX_CNTS);
++		       (num_tx_queues * NUM_GVE_TX_CNTS);
+ 	case ETH_SS_PRIV_FLAGS:
+ 		return GVE_PRIV_FLAGS_STR_LEN;
+ 	default:
+@@ -153,18 +157,20 @@ gve_get_ethtool_stats(struct net_device
+ 	struct gve_priv *priv;
+ 	bool skip_nic_stats;
+ 	unsigned int start;
++	int num_tx_queues;
+ 	int ring;
+ 	int i, j;
+ 
+ 	ASSERT_RTNL();
+ 
+ 	priv = netdev_priv(netdev);
++	num_tx_queues = gve_num_tx_queues(priv);
+ 	report_stats = priv->stats_report->stats;
+ 	rx_qid_to_stats_idx = kmalloc_array(priv->rx_cfg.num_queues,
+ 					    sizeof(int), GFP_KERNEL);
+ 	if (!rx_qid_to_stats_idx)
+ 		return;
+-	tx_qid_to_stats_idx = kmalloc_array(priv->tx_cfg.num_queues,
++	tx_qid_to_stats_idx = kmalloc_array(num_tx_queues,
+ 					    sizeof(int), GFP_KERNEL);
+ 	if (!tx_qid_to_stats_idx) {
+ 		kfree(rx_qid_to_stats_idx);
+@@ -195,7 +201,7 @@ gve_get_ethtool_stats(struct net_device
+ 		}
+ 	}
+ 	for (tx_pkts = 0, tx_bytes = 0, tx_dropped = 0, ring = 0;
+-	     ring < priv->tx_cfg.num_queues; ring++) {
++	     ring < num_tx_queues; ring++) {
+ 		if (priv->tx) {
+ 			do {
+ 				start =
+@@ -232,7 +238,7 @@ gve_get_ethtool_stats(struct net_device
+ 	i = GVE_MAIN_STATS_LEN;
+ 
+ 	/* For rx cross-reporting stats, start from nic rx stats in report */
+-	base_stats_idx = GVE_TX_STATS_REPORT_NUM * priv->tx_cfg.num_queues +
++	base_stats_idx = GVE_TX_STATS_REPORT_NUM * num_tx_queues +
+ 		GVE_RX_STATS_REPORT_NUM * priv->rx_cfg.num_queues;
+ 	max_stats_idx = NIC_RX_STATS_REPORT_NUM * priv->rx_cfg.num_queues +
+ 		base_stats_idx;
+@@ -298,7 +304,7 @@ gve_get_ethtool_stats(struct net_device
+ 
+ 	/* For tx cross-reporting stats, start from nic tx stats in report */
+ 	base_stats_idx = max_stats_idx;
+-	max_stats_idx = NIC_TX_STATS_REPORT_NUM * priv->tx_cfg.num_queues +
++	max_stats_idx = NIC_TX_STATS_REPORT_NUM * num_tx_queues +
+ 		max_stats_idx;
+ 	/* Preprocess the stats report for tx, map queue id to start index */
+ 	skip_nic_stats = false;
+@@ -316,7 +322,7 @@ gve_get_ethtool_stats(struct net_device
+ 	}
+ 	/* walk TX rings */
+ 	if (priv->tx) {
+-		for (ring = 0; ring < priv->tx_cfg.num_queues; ring++) {
++		for (ring = 0; ring < num_tx_queues; ring++) {
+ 			struct gve_tx_ring *tx = &priv->tx[ring];
+ 
+ 			if (gve_is_gqi(priv)) {
+@@ -355,7 +361,7 @@ gve_get_ethtool_stats(struct net_device
+ 			}
+ 		}
+ 	} else {
+-		i += priv->tx_cfg.num_queues * NUM_GVE_TX_CNTS;
++		i += num_tx_queues * NUM_GVE_TX_CNTS;
+ 	}
+ 
+ 	kfree(rx_qid_to_stats_idx);
+@@ -500,7 +506,9 @@ static int gve_set_priv_flags(struct net
+ {
+ 	struct gve_priv *priv = netdev_priv(netdev);
+ 	u64 ori_flags, new_flags;
++	int num_tx_queues;
+ 
++	num_tx_queues = gve_num_tx_queues(priv);
+ 	ori_flags = READ_ONCE(priv->ethtool_flags);
+ 	new_flags = ori_flags;
+ 
+@@ -520,7 +528,7 @@ static int gve_set_priv_flags(struct net
+ 	/* delete report stats timer. */
+ 	if (!(flags & BIT(0)) && (ori_flags & BIT(0))) {
+ 		int tx_stats_num = GVE_TX_STATS_REPORT_NUM *
+-			priv->tx_cfg.num_queues;
++			num_tx_queues;
+ 		int rx_stats_num = GVE_RX_STATS_REPORT_NUM *
+ 			priv->rx_cfg.num_queues;
+ 
+--- a/drivers/net/ethernet/google/gve/gve_main.c
++++ b/drivers/net/ethernet/google/gve/gve_main.c
+@@ -90,8 +90,10 @@ static void gve_get_stats(struct net_dev
+ 	struct gve_priv *priv = netdev_priv(dev);
+ 	unsigned int start;
+ 	u64 packets, bytes;
++	int num_tx_queues;
+ 	int ring;
+ 
++	num_tx_queues = gve_num_tx_queues(priv);
+ 	if (priv->rx) {
+ 		for (ring = 0; ring < priv->rx_cfg.num_queues; ring++) {
+ 			do {
+@@ -106,7 +108,7 @@ static void gve_get_stats(struct net_dev
+ 		}
+ 	}
+ 	if (priv->tx) {
+-		for (ring = 0; ring < priv->tx_cfg.num_queues; ring++) {
++		for (ring = 0; ring < num_tx_queues; ring++) {
+ 			do {
+ 				start =
+ 				  u64_stats_fetch_begin(&priv->tx[ring].statss);
+@@ -180,7 +182,7 @@ static int gve_alloc_stats_report(struct
+ 	int tx_stats_num, rx_stats_num;
+ 
+ 	tx_stats_num = (GVE_TX_STATS_REPORT_NUM + NIC_TX_STATS_REPORT_NUM) *
+-		       priv->tx_cfg.num_queues;
++		       gve_num_tx_queues(priv);
+ 	rx_stats_num = (GVE_RX_STATS_REPORT_NUM + NIC_RX_STATS_REPORT_NUM) *
+ 		       priv->rx_cfg.num_queues;
+ 	priv->stats_report_len = struct_size(priv->stats_report, stats,
+@@ -623,20 +625,21 @@ static int gve_unregister_qpls(struct gv
+ 
+ static int gve_create_rings(struct gve_priv *priv)
+ {
++	int num_tx_queues = gve_num_tx_queues(priv);
+ 	int err;
+ 	int i;
+ 
+-	err = gve_adminq_create_tx_queues(priv, priv->tx_cfg.num_queues);
++	err = gve_adminq_create_tx_queues(priv, num_tx_queues);
+ 	if (err) {
+ 		netif_err(priv, drv, priv->dev, "failed to create %d tx queues\n",
+-			  priv->tx_cfg.num_queues);
++			  num_tx_queues);
+ 		/* This failure will trigger a reset - no need to clean
+ 		 * up
+ 		 */
+ 		return err;
+ 	}
+ 	netif_dbg(priv, drv, priv->dev, "created %d tx queues\n",
+-		  priv->tx_cfg.num_queues);
++		  num_tx_queues);
+ 
+ 	err = gve_adminq_create_rx_queues(priv, priv->rx_cfg.num_queues);
+ 	if (err) {
+@@ -676,7 +679,7 @@ static void add_napi_init_sync_stats(str
+ 	int i;
+ 
+ 	/* Add tx napi & init sync stats*/
+-	for (i = 0; i < priv->tx_cfg.num_queues; i++) {
++	for (i = 0; i < gve_num_tx_queues(priv); i++) {
+ 		int ntfy_idx = gve_tx_idx_to_ntfy(priv, i);
+ 
+ 		u64_stats_init(&priv->tx[i].statss);
+@@ -754,9 +757,10 @@ free_tx:
+ 
+ static int gve_destroy_rings(struct gve_priv *priv)
+ {
++	int num_tx_queues = gve_num_tx_queues(priv);
+ 	int err;
+ 
+-	err = gve_adminq_destroy_tx_queues(priv, priv->tx_cfg.num_queues);
++	err = gve_adminq_destroy_tx_queues(priv, num_tx_queues);
+ 	if (err) {
+ 		netif_err(priv, drv, priv->dev,
+ 			  "failed to destroy tx queues\n");
+@@ -785,11 +789,12 @@ static void gve_rx_free_rings(struct gve
+ 
+ static void gve_free_rings(struct gve_priv *priv)
+ {
++	int num_tx_queues = gve_num_tx_queues(priv);
+ 	int ntfy_idx;
+ 	int i;
+ 
+ 	if (priv->tx) {
+-		for (i = 0; i < priv->tx_cfg.num_queues; i++) {
++		for (i = 0; i < num_tx_queues; i++) {
+ 			ntfy_idx = gve_tx_idx_to_ntfy(priv, i);
+ 			gve_remove_napi(priv, ntfy_idx);
+ 		}
+@@ -1119,7 +1124,7 @@ static void gve_turndown(struct gve_priv
+ 		return;
+ 
+ 	/* Disable napi to prevent more work from coming in */
+-	for (idx = 0; idx < priv->tx_cfg.num_queues; idx++) {
++	for (idx = 0; idx < gve_num_tx_queues(priv); idx++) {
+ 		int ntfy_idx = gve_tx_idx_to_ntfy(priv, idx);
+ 		struct gve_notify_block *block = &priv->ntfy_blocks[ntfy_idx];
+ 
+@@ -1147,7 +1152,7 @@ static void gve_turnup(struct gve_priv *
+ 	netif_tx_start_all_queues(priv->dev);
+ 
+ 	/* Enable napi and unmask interrupts for all queues */
+-	for (idx = 0; idx < priv->tx_cfg.num_queues; idx++) {
++	for (idx = 0; idx < gve_num_tx_queues(priv); idx++) {
+ 		int ntfy_idx = gve_tx_idx_to_ntfy(priv, idx);
+ 		struct gve_notify_block *block = &priv->ntfy_blocks[ntfy_idx];
+ 
+@@ -1307,7 +1312,7 @@ void gve_handle_report_stats(struct gve_
+ 	be64_add_cpu(&priv->stats_report->written_count, 1);
+ 	/* tx stats */
+ 	if (priv->tx) {
+-		for (idx = 0; idx < priv->tx_cfg.num_queues; idx++) {
++		for (idx = 0; idx < gve_num_tx_queues(priv); idx++) {
+ 			u32 last_completion = 0;
+ 			u32 tx_frames = 0;
+ 
+--- a/drivers/net/ethernet/google/gve/gve_rx.c
++++ b/drivers/net/ethernet/google/gve/gve_rx.c
+@@ -556,7 +556,7 @@ static struct sk_buff *gve_rx_skb(struct
+ 
+ 	if (len <= priv->rx_copybreak && is_only_frag)  {
+ 		/* Just copy small packets */
+-		skb = gve_rx_copy(netdev, napi, page_info, len, GVE_RX_PAD);
++		skb = gve_rx_copy(netdev, napi, page_info, len);
+ 		if (skb) {
+ 			u64_stats_update_begin(&rx->statss);
+ 			rx->rx_copied_pkt++;
+--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
+@@ -568,7 +568,7 @@ static int gve_rx_dqo(struct napi_struct
+ 
+ 	if (eop && buf_len <= priv->rx_copybreak) {
+ 		rx->ctx.skb_head = gve_rx_copy(priv->dev, napi,
+-					       &buf_state->page_info, buf_len, 0);
++					       &buf_state->page_info, buf_len);
+ 		if (unlikely(!rx->ctx.skb_head))
+ 			goto error;
+ 		rx->ctx.skb_tail = rx->ctx.skb_head;
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -374,18 +374,18 @@ static int gve_maybe_stop_tx(struct gve_
+ }
+ 
+ static void gve_tx_fill_pkt_desc(union gve_tx_desc *pkt_desc,
+-				 struct sk_buff *skb, bool is_gso,
++				 u16 csum_offset, u8 ip_summed, bool is_gso,
+ 				 int l4_hdr_offset, u32 desc_cnt,
+-				 u16 hlen, u64 addr)
++				 u16 hlen, u64 addr, u16 pkt_len)
+ {
+ 	/* l4_hdr_offset and csum_offset are in units of 16-bit words */
+ 	if (is_gso) {
+ 		pkt_desc->pkt.type_flags = GVE_TXD_TSO | GVE_TXF_L4CSUM;
+-		pkt_desc->pkt.l4_csum_offset = skb->csum_offset >> 1;
++		pkt_desc->pkt.l4_csum_offset = csum_offset >> 1;
+ 		pkt_desc->pkt.l4_hdr_offset = l4_hdr_offset >> 1;
+-	} else if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
++	} else if (likely(ip_summed == CHECKSUM_PARTIAL)) {
+ 		pkt_desc->pkt.type_flags = GVE_TXD_STD | GVE_TXF_L4CSUM;
+-		pkt_desc->pkt.l4_csum_offset = skb->csum_offset >> 1;
++		pkt_desc->pkt.l4_csum_offset = csum_offset >> 1;
+ 		pkt_desc->pkt.l4_hdr_offset = l4_hdr_offset >> 1;
+ 	} else {
+ 		pkt_desc->pkt.type_flags = GVE_TXD_STD;
+@@ -393,7 +393,7 @@ static void gve_tx_fill_pkt_desc(union g
+ 		pkt_desc->pkt.l4_hdr_offset = 0;
+ 	}
+ 	pkt_desc->pkt.desc_cnt = desc_cnt;
+-	pkt_desc->pkt.len = cpu_to_be16(skb->len);
++	pkt_desc->pkt.len = cpu_to_be16(pkt_len);
+ 	pkt_desc->pkt.seg_len = cpu_to_be16(hlen);
+ 	pkt_desc->pkt.seg_addr = cpu_to_be64(addr);
+ }
+@@ -412,15 +412,16 @@ static void gve_tx_fill_mtd_desc(union g
+ }
+ 
+ static void gve_tx_fill_seg_desc(union gve_tx_desc *seg_desc,
+-				 struct sk_buff *skb, bool is_gso,
++				 u16 l3_offset, u16 gso_size,
++				 bool is_gso_v6, bool is_gso,
+ 				 u16 len, u64 addr)
+ {
+ 	seg_desc->seg.type_flags = GVE_TXD_SEG;
+ 	if (is_gso) {
+-		if (skb_is_gso_v6(skb))
++		if (is_gso_v6)
+ 			seg_desc->seg.type_flags |= GVE_TXSF_IPV6;
+-		seg_desc->seg.l3_offset = skb_network_offset(skb) >> 1;
+-		seg_desc->seg.mss = cpu_to_be16(skb_shinfo(skb)->gso_size);
++		seg_desc->seg.l3_offset = l3_offset >> 1;
++		seg_desc->seg.mss = cpu_to_be16(gso_size);
+ 	}
+ 	seg_desc->seg.seg_len = cpu_to_be16(len);
+ 	seg_desc->seg.seg_addr = cpu_to_be64(addr);
+@@ -471,9 +472,10 @@ static int gve_tx_add_skb_copy(struct gv
+ 	payload_nfrags = gve_tx_alloc_fifo(&tx->tx_fifo, skb->len - hlen,
+ 					   &info->iov[payload_iov]);
+ 
+-	gve_tx_fill_pkt_desc(pkt_desc, skb, is_gso, l4_hdr_offset,
++	gve_tx_fill_pkt_desc(pkt_desc, skb->csum_offset, skb->ip_summed,
++			     is_gso, l4_hdr_offset,
+ 			     1 + mtd_desc_nr + payload_nfrags, hlen,
+-			     info->iov[hdr_nfrags - 1].iov_offset);
++			     info->iov[hdr_nfrags - 1].iov_offset, skb->len);
+ 
+ 	skb_copy_bits(skb, 0,
+ 		      tx->tx_fifo.base + info->iov[hdr_nfrags - 1].iov_offset,
+@@ -492,7 +494,9 @@ static int gve_tx_add_skb_copy(struct gv
+ 		next_idx = (tx->req + 1 + mtd_desc_nr + i - payload_iov) & tx->mask;
+ 		seg_desc = &tx->desc[next_idx];
+ 
+-		gve_tx_fill_seg_desc(seg_desc, skb, is_gso,
++		gve_tx_fill_seg_desc(seg_desc, skb_network_offset(skb),
++				     skb_shinfo(skb)->gso_size,
++				     skb_is_gso_v6(skb), is_gso,
+ 				     info->iov[i].iov_len,
+ 				     info->iov[i].iov_offset);
+ 
+@@ -550,8 +554,9 @@ static int gve_tx_add_skb_no_copy(struct
+ 	if (mtd_desc_nr)
+ 		num_descriptors++;
+ 
+-	gve_tx_fill_pkt_desc(pkt_desc, skb, is_gso, l4_hdr_offset,
+-			     num_descriptors, hlen, addr);
++	gve_tx_fill_pkt_desc(pkt_desc, skb->csum_offset, skb->ip_summed,
++			     is_gso, l4_hdr_offset,
++			     num_descriptors, hlen, addr, skb->len);
+ 
+ 	if (mtd_desc_nr) {
+ 		idx = (idx + 1) & tx->mask;
+@@ -567,7 +572,9 @@ static int gve_tx_add_skb_no_copy(struct
+ 		addr += hlen;
+ 		idx = (idx + 1) & tx->mask;
+ 		seg_desc = &tx->desc[idx];
+-		gve_tx_fill_seg_desc(seg_desc, skb, is_gso, len, addr);
++		gve_tx_fill_seg_desc(seg_desc, skb_network_offset(skb),
++				     skb_shinfo(skb)->gso_size,
++				     skb_is_gso_v6(skb), is_gso, len, addr);
+ 	}
+ 
+ 	for (i = 0; i < shinfo->nr_frags; i++) {
+@@ -585,7 +592,9 @@ static int gve_tx_add_skb_no_copy(struct
+ 		dma_unmap_len_set(&tx->info[idx], len, len);
+ 		dma_unmap_addr_set(&tx->info[idx], dma, addr);
+ 
+-		gve_tx_fill_seg_desc(seg_desc, skb, is_gso, len, addr);
++		gve_tx_fill_seg_desc(seg_desc, skb_network_offset(skb),
++				     skb_shinfo(skb)->gso_size,
++				     skb_is_gso_v6(skb), is_gso, len, addr);
+ 	}
+ 
+ 	return num_descriptors;
+--- a/drivers/net/ethernet/google/gve/gve_utils.c
++++ b/drivers/net/ethernet/google/gve/gve_utils.c
+@@ -49,10 +49,10 @@ void gve_rx_add_to_block(struct gve_priv
+ }
+ 
+ struct sk_buff *gve_rx_copy(struct net_device *dev, struct napi_struct *napi,
+-			    struct gve_rx_slot_page_info *page_info, u16 len,
+-			    u16 padding)
++			    struct gve_rx_slot_page_info *page_info, u16 len)
+ {
+-	void *va = page_info->page_address + padding + page_info->page_offset;
++	void *va = page_info->page_address + page_info->page_offset +
++		page_info->pad;
+ 	struct sk_buff *skb;
+ 
+ 	skb = napi_alloc_skb(napi, len);
+--- a/drivers/net/ethernet/google/gve/gve_utils.h
++++ b/drivers/net/ethernet/google/gve/gve_utils.h
+@@ -18,8 +18,7 @@ void gve_rx_remove_from_block(struct gve
+ void gve_rx_add_to_block(struct gve_priv *priv, int queue_idx);
+ 
+ struct sk_buff *gve_rx_copy(struct net_device *dev, struct napi_struct *napi,
+-			    struct gve_rx_slot_page_info *page_info, u16 len,
+-			    u16 pad);
++			    struct gve_rx_slot_page_info *page_info, u16 len);
+ 
+ /* Decrement pagecnt_bias. Set it back to INT_MAX if it reached zero. */
+ void gve_dec_pagecnt_bias(struct gve_rx_slot_page_info *page_info);
diff --git a/patches.suse/gve-fix-frag_list-chaining.patch b/patches.suse/gve-fix-frag_list-chaining.patch
new file mode 100644
index 0000000..5d25622
--- /dev/null
+++ b/patches.suse/gve-fix-frag_list-chaining.patch
@@ -0,0 +1,51 @@
+From: Eric Dumazet <edumazet@google.com>
+Date: Thu, 31 Aug 2023 21:38:12 +0000
+Subject: gve: fix frag_list chaining
+Patch-mainline: v6.6-rc1
+Git-commit: 817c7cd2043a83a3d8147f40eea1505ac7300b62
+References: bsc#1214479
+
+gve_rx_append_frags() is able to build skbs chained with frag_list,
+like GRO engine.
+
+Problem is that shinfo->frag_list should only be used
+for the head of the chain.
+
+All other links should use skb->next pointer.
+
+Otherwise, built skbs are not valid and can cause crashes.
+
+Equivalent code in GRO (skb_gro_receive()) is:
+
+    if (NAPI_GRO_CB(p)->last == p)
+        skb_shinfo(p)->frag_list = skb;
+    else
+        NAPI_GRO_CB(p)->last->next = skb;
+    NAPI_GRO_CB(p)->last = skb;
+
+Fixes: 9b8dd5e5ea48 ("gve: DQO: Add RX path")
+Signed-off-by: Eric Dumazet <edumazet@google.com>
+Cc: Bailey Forrest <bcf@google.com>
+Cc: Willem de Bruijn <willemb@google.com>
+Cc: Catherine Sullivan <csully@google.com>
+Reviewed-by: David Ahern <dsahern@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve_rx_dqo.c |    5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c
++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c
+@@ -570,7 +570,10 @@ static int gve_rx_append_frags(struct na
+ 		if (!skb)
+ 			return -1;
+ 
+-		skb_shinfo(rx->ctx.skb_tail)->frag_list = skb;
++		if (rx->ctx.skb_tail == rx->ctx.skb_head)
++			skb_shinfo(rx->ctx.skb_head)->frag_list = skb;
++		else
++			rx->ctx.skb_tail->next = skb;
+ 		rx->ctx.skb_tail = skb;
+ 		num_frags = 0;
+ 	}
diff --git a/patches.suse/gve-trivial-spell-fix-Recive-to-Receive.patch b/patches.suse/gve-trivial-spell-fix-Recive-to-Receive.patch
new file mode 100644
index 0000000..143212c
--- /dev/null
+++ b/patches.suse/gve-trivial-spell-fix-Recive-to-Receive.patch
@@ -0,0 +1,32 @@
+From: Jesper Dangaard Brouer <brouer@redhat.com>
+Date: Thu, 13 Jul 2023 17:54:37 +0200
+Subject: gve: trivial spell fix Recive to Receive
+Patch-mainline: v6.6-rc1
+Git-commit: 68af900072c157c0cdce0256968edd15067e1e5a
+References: bsc#1214479
+
+Spotted this trivial spell mistake while casually reading
+the google GVE driver code.
+
+Signed-off-by: Jesper Dangaard Brouer <hawk@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve_desc.h |    4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_desc.h
++++ b/drivers/net/ethernet/google/gve/gve_desc.h
+@@ -105,10 +105,10 @@ union gve_rx_data_slot {
+ 	__be64 addr;
+ };
+ 
+-/* GVE Recive Packet Descriptor Seq No */
++/* GVE Receive Packet Descriptor Seq No */
+ #define GVE_SEQNO(x) (be16_to_cpu(x) & 0x7)
+ 
+-/* GVE Recive Packet Descriptor Flags */
++/* GVE Receive Packet Descriptor Flags */
+ #define GVE_RXFLG(x)	cpu_to_be16(1 << (3 + (x)))
+ #define	GVE_RXF_FRAG		GVE_RXFLG(3)	/* IP Fragment			*/
+ #define	GVE_RXF_IPV4		GVE_RXFLG(4)	/* IPv4				*/
diff --git a/patches.suse/gve-use-vmalloc_array-and-vcalloc.patch b/patches.suse/gve-use-vmalloc_array-and-vcalloc.patch
new file mode 100644
index 0000000..b7e95f9
--- /dev/null
+++ b/patches.suse/gve-use-vmalloc_array-and-vcalloc.patch
@@ -0,0 +1,71 @@
+From: Julia Lawall <Julia.Lawall@inria.fr>
+Date: Tue, 27 Jun 2023 16:43:19 +0200
+Subject: gve: use vmalloc_array and vcalloc
+Patch-mainline: v6.5-rc1
+Git-commit: a13de901e8d590a7d26a6d4a1c4c7e9eebbb6ca6
+References: bsc#1214479
+
+Use vmalloc_array and vcalloc to protect against
+multiplication overflows.
+
+The changes were done using the following Coccinelle
+semantic patch:
+
+// <smpl>
+@initialize:ocaml@
+@@
+
+let rename alloc =
+  match alloc with
+    "vmalloc" -> "vmalloc_array"
+  | "vzalloc" -> "vcalloc"
+  | _ -> failwith "unknown"
+
+@@
+    size_t e1,e2;
+    constant C1, C2;
+    expression E1, E2, COUNT, x1, x2, x3;
+    typedef u8;
+    typedef __u8;
+    type t = {u8,__u8,char,unsigned char};
+    identifier alloc = {vmalloc,vzalloc};
+    fresh identifier realloc = script:ocaml(alloc) { rename alloc };
+@@
+
+(
+      alloc(x1*x2*x3)
+|
+      alloc(C1 * C2)
+|
+      alloc((sizeof(t)) * (COUNT), ...)
+|
+-     alloc((e1) * (e2))
++     realloc(e1, e2)
+|
+-     alloc((e1) * (COUNT))
++     realloc(COUNT, e1)
+|
+-     alloc((E1) * (E2))
++     realloc(E1, E2)
+)
+// </smpl>
+
+Signed-off-by: Julia Lawall <Julia.Lawall@inria.fr>
+Link: https://lore.kernel.org/r/20230627144339.144478-5-Julia.Lawall@inria.fr
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Acked-by: Thomas Bogendoerfer <tbogendoerfer@suse.de>
+---
+ drivers/net/ethernet/google/gve/gve_tx.c |    2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/google/gve/gve_tx.c
++++ b/drivers/net/ethernet/google/gve/gve_tx.c
+@@ -248,7 +248,7 @@ static int gve_tx_alloc_ring(struct gve_
+ 	tx->mask = slots - 1;
+ 
+ 	/* alloc metadata */
+-	tx->info = vzalloc(sizeof(*tx->info) * slots);
++	tx->info = vcalloc(slots, sizeof(*tx->info));
+ 	if (!tx->info)
+ 		return -ENOMEM;
+ 
diff --git a/series.conf b/series.conf
index 1d2f376..da04dbf 100644
--- a/series.conf
+++ b/series.conf
@@ -19461,6 +19461,7 @@
 	patches.suse/act_mirred-use-the-backlog-for-nested-calls-to-mirre.patch
 	patches.suse/virtio-net-Maintain-reverse-cleanup-order.patch
 	patches.suse/net-mlx5-Enhance-debug-print-in-page-allocation-fail.patch
+	patches.suse/gve-Fix-gve-interrupt-names.patch
 	patches.suse/net-add-sock_init_data_uid.patch
 	patches.suse/tun-tun_chr_open-correctly-initialize-socket-uid.patch
 	patches.suse/tap-tap_open-correctly-initialize-socket-uid.patch
@@ -20547,6 +20548,11 @@
 	patches.suse/wifi-rtw88-mac-Return-the-original-error-from-rtw_ma.patch
 	patches.suse/wifi-mac80211-adjust-scan-cancel-comment-check.patch
 	patches.suse/net-mana-Add-new-MANA-VF-performance-counters-for-ea.patch
+	patches.suse/gve-XDP-support-GQI-QPL-helper-function-changes.patch
+	patches.suse/gve-Changes-to-add-new-TX-queues.patch
+	patches.suse/gve-Add-XDP-DROP-and-TX-support-for-GQI-QPL-format.patch
+	patches.suse/gve-Add-XDP-REDIRECT-support-for-GQI-QPL-format.patch
+	patches.suse/gve-Add-AF_XDP-zero-copy-support-for-GQI-QPL-format.patch
 	patches.suse/wifi-brcmfmac-slab-out-of-bounds-read-in-brcmf_get_a.patch
 	patches.suse/wifi-ath6kl-minor-fix-for-allocation-size.patch
 	patches.suse/wifi-ath-Silence-memcpy-run-time-false-positive-warn.patch
@@ -20559,6 +20565,7 @@
 	patches.suse/wifi-rtlwifi-fix-incorrect-error-codes-in-rtl_debugf-5dbe1f8eb8c5.patch
 	patches.suse/wifi-rtw89-fix-potential-race-condition-between-napi.patch
 	patches.suse/wifi-rt2x00-Fix-memory-leak-when-handling-surveys.patch
+	patches.suse/gve-Unify-duplicate-GQ-min-pkt-desc-size-constants.patch
 	patches.suse/selftests-xsk-Disable-IPv6-on-VETH1.patch
 	patches.suse/net-mana-Use-napi_build_skb-in-RX-path.patch
 	patches.suse/net-mana-Refactor-RX-buffer-allocation-code-to-prepa.patch
@@ -21296,6 +21303,7 @@
 	patches.suse/wifi-ath9k-don-t-allow-to-overwrite-ENDPOINT0-attrib.patch
 	patches.suse/wifi-ath9k-Fix-possible-stall-on-ath9k_txq_list_has_.patch
 	patches.suse/wifi-ath9k-convert-msecs-to-jiffies-where-needed.patch
+	patches.suse/gve-use-vmalloc_array-and-vcalloc.patch
 	patches.suse/can-isotp-isotp_sendmsg-fix-return-error-fix-on-TX-p.patch
 	patches.suse/igc-Fix-race-condition-in-PTP-tx-code.patch
 	patches.suse/igc-Check-if-hardware-TX-timestamping-is-enabled-ear.patch
@@ -22046,6 +22054,7 @@
 	patches.suse/crypto-caam-fix-unchecked-return-value-error.patch
 	patches.suse/hwrng-iproc-rng200-Implement-suspend-and-resume-call.patch
 	patches.suse/bpf-Clear-the-probe_addr-for-uprobe.patch
+	patches.suse/gve-trivial-spell-fix-Recive-to-Receive.patch
 	patches.suse/net-mana-Batch-ringing-RX-queue-doorbell-on-receivin.patch
 	patches.suse/net-mana-Use-the-correct-WQE-count-for-ringing-RQ-do.patch
 	patches.suse/can-gs_usb-gs_usb_receive_bulk_callback-count-RX-ove.patch
@@ -22057,6 +22066,9 @@
 	patches.suse/wifi-mt76-testmode-add-nla_policy-for-MT76_TM_ATTR_T.patch
 	patches.suse/wifi-mt76-mt7915-fix-power-limits-while-chan_switch.patch
 	patches.suse/wifi-mwifiex-fix-error-recovery-in-PCIE-buffer-descr.patch
+	patches.suse/gve-Control-path-for-DQO-QPL.patch
+	patches.suse/gve-Tx-path-for-DQO-QPL.patch
+	patches.suse/gve-RX-path-for-DQO-QPL.patch
 	patches.suse/net-mana-Add-page-pool-for-RX-buffers.patch
 	patches.suse/Bluetooth-nokia-fix-value-check-in-nokia_bluetooth_s.patch
 	patches.suse/Bluetooth-Remove-unused-declaration-amp_read_loc_inf.patch
@@ -22304,6 +22316,7 @@
 	patches.suse/KVM-x86-mmu-Include-mmu.h-in-spte.h.patch
 	patches.suse/i3c-master-svc-fix-probe-failure-when-no-i3c-device-.patch
 	patches.suse/pwm-lpc32xx-Remove-handling-of-PWM-channels.patch
+	patches.suse/gve-fix-frag_list-chaining.patch
 	patches.suse/net-phy-micrel-Correct-bit-assignments-for-phy_devic.patch
 	patches.suse/netfilter-nftables-exthdr-fix-4-byte-stack-OOB-write.patch
 	patches.suse/drm-i915-mark-requests-for-GuC-virtual-engines-to-av.patch