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