diff --git a/1106-gve-Update-QPL-page-registration-logic.patch b/1106-gve-Update-QPL-page-registration-logic.patch new file mode 100644 index 000000000..807482a90 --- /dev/null +++ b/1106-gve-Update-QPL-page-registration-logic.patch @@ -0,0 +1,323 @@ +From 07993df560917357610e0625a9a2e7531c3211fc Mon Sep 17 00:00:00 2001 +From: Matt Olson +Date: Wed, 25 Feb 2026 10:23:41 -0800 +Subject: [PATCH AlmaLinux 10] gve: Update QPL page registration logic + +For DQO, change QPL page registration logic to be more flexible to honor +the "max_registered_pages" parameter from the gVNIC device. + +Previously the number of RX pages per QPL was hardcoded to twice the +ring size, and the number of TX pages per QPL was dictated by the device +in the DQO-QPL device option. Now [in DQO-QPL mode], the driver will +ignore the "tx_pages_per_qpl" parameter indicated in the DQO-QPL device +option and instead allocate up to (tx_queue_length / 2) pages per TX QPL +and up to (rx_queue_length * 2) pages per RX QPL while keeping the total +number of pages under the "max_registered_pages". + +Merge DQO and GQI QPL page calculation logic into a unified +gve_update_num_qpl_pages function. Add rx_pages_per_qpl to the priv +struct for consumption by both DQO and GQI. + +Signed-off-by: Matt Olson +Signed-off-by: Max Yuan +Reviewed-by: Jordan Rhee +Reviewed-by: Harshitha Ramamurthy +Reviewed-by: Willem de Bruijn +Reviewed-by: Praveen Kaligineedi +Signed-off-by: Joshua Washington +Link: https://patch.msgid.link/20260225182342.1049816-2-joshwash@google.com +Signed-off-by: Jakub Kicinski + +[ AlmaLinux: backported to the 6.12 gve driver. The upstream commit targets + the post-refactor gve driver; the equivalent changes were applied to the + older driver shipped in RHEL/AlmaLinux 10: + - rx_alloc_cfg->qcfg_rx->num_queues -> rx_alloc_cfg->qcfg->num_queues + (the rx alloc-rings cfg uses the shared gve_queue_config field name). + - The gve_alloc_qpl_page_dqo() bound check lives in gve_rx_dqo.c here + rather than in the not-yet-present gve_buffer_mgmt_dqo.c. + The QPL page-sizing logic is otherwise identical to upstream. ] +Signed-off-by: Andrew Lukoshko +--- +diff --git a/drivers/net/ethernet/google/gve/gve.h b/drivers/net/ethernet/google/gve/gve.h +index adc2b9a..65bd511 100644 +--- a/drivers/net/ethernet/google/gve/gve.h ++++ b/drivers/net/ethernet/google/gve/gve.h +@@ -71,8 +71,6 @@ + + #define GVE_DEFAULT_HEADER_BUFFER_SIZE 128 + +-#define DQO_QPL_DEFAULT_TX_PAGES 512 +- + /* Maximum TSO size supported on DQO */ + #define GVE_DQO_TX_MAX 0x3FFFF + +@@ -646,6 +644,7 @@ struct gve_ptype_lut { + /* Parameters for allocating resources for tx queues */ + struct gve_tx_alloc_rings_cfg { + struct gve_queue_config *qcfg; ++ u16 pages_per_qpl; + + u16 ring_size; + u16 start_idx; +@@ -661,6 +660,7 @@ struct gve_rx_alloc_rings_cfg { + /* tx config is also needed to determine QPL ids */ + struct gve_queue_config *qcfg; + struct gve_queue_config *qcfg_tx; ++ u16 pages_per_qpl; + + u16 ring_size; + u16 packet_buffer_size; +@@ -738,7 +738,8 @@ struct gve_priv { + u16 min_rx_desc_cnt; + bool modify_ring_size_enabled; + bool default_min_ring_size; +- u16 tx_pages_per_qpl; /* Suggested number of pages per qpl for TX queues by NIC */ ++ u16 tx_pages_per_qpl; ++ u16 rx_pages_per_qpl; + u64 max_registered_pages; + u64 num_registered_pages; /* num pages registered with NIC */ + struct bpf_prog *xdp_prog; /* XDP BPF program */ +@@ -1071,14 +1072,6 @@ static inline u32 gve_rx_start_qpl_id(const struct gve_queue_config *tx_cfg) + return gve_get_rx_qpl_id(tx_cfg, 0); + } + +-static inline u32 gve_get_rx_pages_per_qpl_dqo(u32 rx_desc_cnt) +-{ +- /* For DQO, page count should be more than ring size for +- * out-of-order completions. Set it to two times of ring size. +- */ +- return 2 * rx_desc_cnt; +-} +- + /* Returns the correct dma direction for tx and rx qpls */ + static inline enum dma_data_direction gve_qpl_dma_dir(struct gve_priv *priv, + int id) +@@ -1178,6 +1171,9 @@ int gve_reset(struct gve_priv *priv, bool attempt_teardown); + void gve_get_curr_alloc_cfgs(struct gve_priv *priv, + struct gve_tx_alloc_rings_cfg *tx_alloc_cfg, + struct gve_rx_alloc_rings_cfg *rx_alloc_cfg); ++void gve_update_num_qpl_pages(struct gve_priv *priv, ++ struct gve_rx_alloc_rings_cfg *rx_alloc_cfg, ++ struct gve_tx_alloc_rings_cfg *tx_alloc_cfg); + int gve_adjust_config(struct gve_priv *priv, + struct gve_tx_alloc_rings_cfg *tx_alloc_cfg, + struct gve_rx_alloc_rings_cfg *rx_alloc_cfg); +diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c +index e44e8b1..7335b48 100644 +--- a/drivers/net/ethernet/google/gve/gve_adminq.c ++++ b/drivers/net/ethernet/google/gve/gve_adminq.c +@@ -911,14 +911,6 @@ static void gve_enable_supported_features(struct gve_priv *priv, + 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); +- if (priv->tx_pages_per_qpl == 0) +- priv->tx_pages_per_qpl = DQO_QPL_DEFAULT_TX_PAGES; +- } +- + if (dev_op_buffer_sizes && + (supported_features_mask & GVE_SUP_BUFFER_SIZES_MASK)) { + priv->max_rx_buffer_size = +diff --git a/drivers/net/ethernet/google/gve/gve_main.c b/drivers/net/ethernet/google/gve/gve_main.c +index 72e9ce9..a065891 100644 +--- a/drivers/net/ethernet/google/gve/gve_main.c ++++ b/drivers/net/ethernet/google/gve/gve_main.c +@@ -10,6 +10,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -926,6 +927,7 @@ static void gve_tx_get_curr_alloc_cfg(struct gve_priv *priv, + cfg->qcfg = &priv->tx_cfg; + cfg->raw_addressing = !gve_is_qpl(priv); + cfg->ring_size = priv->tx_desc_cnt; ++ cfg->pages_per_qpl = priv->tx_pages_per_qpl; + cfg->start_idx = 0; + cfg->num_rings = gve_num_tx_queues(priv); + cfg->tx = priv->tx; +@@ -981,12 +983,48 @@ static int gve_alloc_xdp_rings(struct gve_priv *priv) + return 0; + } + ++void gve_update_num_qpl_pages(struct gve_priv *priv, ++ struct gve_rx_alloc_rings_cfg *rx_alloc_cfg, ++ struct gve_tx_alloc_rings_cfg *tx_alloc_cfg) ++{ ++ u64 ideal_tx_pages, ideal_rx_pages; ++ u16 tx_num_queues, rx_num_queues; ++ u64 max_pages, tx_pages; ++ ++ if (priv->queue_format == GVE_GQI_QPL_FORMAT) { ++ rx_alloc_cfg->pages_per_qpl = rx_alloc_cfg->ring_size; ++ } else if (priv->queue_format == GVE_DQO_QPL_FORMAT) { ++ /* ++ * We want 2 pages per RX descriptor and half a page per TX ++ * descriptor, which means the fraction ideal_tx_pages / ++ * (ideal_tx_pages + ideal_rx_pages) of the pages we allocate ++ * should be for TX. Shrink proportionally as necessary to avoid ++ * allocating more than max_registered_pages total pages. ++ */ ++ tx_num_queues = tx_alloc_cfg->qcfg->num_queues; ++ rx_num_queues = rx_alloc_cfg->qcfg->num_queues; ++ ++ ideal_tx_pages = tx_alloc_cfg->ring_size * tx_num_queues / 2; ++ ideal_rx_pages = rx_alloc_cfg->ring_size * rx_num_queues * 2; ++ max_pages = min(priv->max_registered_pages, ++ ideal_tx_pages + ideal_rx_pages); ++ ++ tx_pages = div64_u64(max_pages * ideal_tx_pages, ++ ideal_tx_pages + ideal_rx_pages); ++ tx_alloc_cfg->pages_per_qpl = div_u64(tx_pages, tx_num_queues); ++ rx_alloc_cfg->pages_per_qpl = div_u64(max_pages - tx_pages, ++ rx_num_queues); ++ } ++} ++ + static int gve_queues_mem_alloc(struct gve_priv *priv, + struct gve_tx_alloc_rings_cfg *tx_alloc_cfg, + struct gve_rx_alloc_rings_cfg *rx_alloc_cfg) + { + int err; + ++ gve_update_num_qpl_pages(priv, rx_alloc_cfg, tx_alloc_cfg); ++ + if (gve_is_gqi(priv)) + err = gve_tx_alloc_rings_gqi(priv, tx_alloc_cfg); + else +@@ -1281,6 +1319,7 @@ static void gve_rx_get_curr_alloc_cfg(struct gve_priv *priv, + cfg->raw_addressing = !gve_is_qpl(priv); + cfg->enable_header_split = priv->header_split_enabled; + cfg->ring_size = priv->rx_desc_cnt; ++ cfg->pages_per_qpl = priv->rx_pages_per_qpl; + cfg->packet_buffer_size = gve_is_gqi(priv) ? + GVE_DEFAULT_RX_BUFFER_SIZE : + priv->data_buffer_size_dqo; +@@ -1360,6 +1399,8 @@ static int gve_queues_start(struct gve_priv *priv, + priv->rx_cfg = *rx_alloc_cfg->qcfg; + priv->tx_desc_cnt = tx_alloc_cfg->ring_size; + priv->rx_desc_cnt = rx_alloc_cfg->ring_size; ++ priv->tx_pages_per_qpl = tx_alloc_cfg->pages_per_qpl; ++ priv->rx_pages_per_qpl = rx_alloc_cfg->pages_per_qpl; + + if (priv->xdp_prog) + priv->num_xdp_queues = priv->rx_cfg.num_queues; +diff --git a/drivers/net/ethernet/google/gve/gve_rx.c b/drivers/net/ethernet/google/gve/gve_rx.c +index acb73d4..77077ca 100644 +--- a/drivers/net/ethernet/google/gve/gve_rx.c ++++ b/drivers/net/ethernet/google/gve/gve_rx.c +@@ -272,7 +272,6 @@ int gve_rx_alloc_ring_gqi(struct gve_priv *priv, + struct device *hdev = &priv->pdev->dev; + u32 slots = cfg->ring_size; + int filled_pages; +- int qpl_page_cnt; + u32 qpl_id = 0; + size_t bytes; + int err; +@@ -308,10 +307,8 @@ int gve_rx_alloc_ring_gqi(struct gve_priv *priv, + + if (!rx->data.raw_addressing) { + qpl_id = gve_get_rx_qpl_id(cfg->qcfg_tx, rx->q_num); +- qpl_page_cnt = cfg->ring_size; +- + rx->data.qpl = gve_alloc_queue_page_list(priv, qpl_id, +- qpl_page_cnt); ++ cfg->pages_per_qpl); + if (!rx->data.qpl) { + err = -ENOMEM; + goto abort_with_copy_pool; +diff --git a/drivers/net/ethernet/google/gve/gve_rx_dqo.c b/drivers/net/ethernet/google/gve/gve_rx_dqo.c +index 1154c1d..1cbaf23 100644 +--- a/drivers/net/ethernet/google/gve/gve_rx_dqo.c ++++ b/drivers/net/ethernet/google/gve/gve_rx_dqo.c +@@ -178,7 +178,7 @@ static int gve_alloc_page_dqo(struct gve_rx_ring *rx, + return err; + } else { + idx = rx->dqo.next_qpl_page_idx; +- if (idx >= gve_get_rx_pages_per_qpl_dqo(priv->rx_desc_cnt)) { ++ if (idx >= priv->rx_pages_per_qpl) { + net_err_ratelimited("%s: Out of QPL pages\n", + priv->dev->name); + return -ENOMEM; +@@ -382,7 +382,6 @@ int gve_rx_alloc_ring_dqo(struct gve_priv *priv, + int idx) + { + struct device *hdev = &priv->pdev->dev; +- int qpl_page_cnt; + size_t size; + u32 qpl_id; + +@@ -397,7 +396,7 @@ int gve_rx_alloc_ring_dqo(struct gve_priv *priv, + + rx->dqo.num_buf_states = cfg->raw_addressing ? + min_t(s16, S16_MAX, buffer_queue_slots * 4) : +- gve_get_rx_pages_per_qpl_dqo(cfg->ring_size); ++ cfg->pages_per_qpl; + rx->dqo.buf_states = kvcalloc(rx->dqo.num_buf_states, + sizeof(rx->dqo.buf_states[0]), + GFP_KERNEL); +@@ -426,10 +425,8 @@ int gve_rx_alloc_ring_dqo(struct gve_priv *priv, + + if (!cfg->raw_addressing) { + qpl_id = gve_get_rx_qpl_id(cfg->qcfg_tx, rx->q_num); +- qpl_page_cnt = gve_get_rx_pages_per_qpl_dqo(cfg->ring_size); +- + rx->dqo.qpl = gve_alloc_queue_page_list(priv, qpl_id, +- qpl_page_cnt); ++ cfg->pages_per_qpl); + if (!rx->dqo.qpl) + goto err; + rx->dqo.next_qpl_page_idx = 0; +diff --git a/drivers/net/ethernet/google/gve/gve_tx.c b/drivers/net/ethernet/google/gve/gve_tx.c +index e7fb7d6..d471507 100644 +--- a/drivers/net/ethernet/google/gve/gve_tx.c ++++ b/drivers/net/ethernet/google/gve/gve_tx.c +@@ -261,7 +261,6 @@ static int gve_tx_alloc_ring_gqi(struct gve_priv *priv, + int idx) + { + struct device *hdev = &priv->pdev->dev; +- int qpl_page_cnt; + u32 qpl_id = 0; + size_t bytes; + +@@ -288,10 +287,8 @@ static int gve_tx_alloc_ring_gqi(struct gve_priv *priv, + tx->dev = hdev; + if (!tx->raw_addressing) { + qpl_id = gve_tx_qpl_id(priv, tx->q_num); +- qpl_page_cnt = priv->tx_pages_per_qpl; +- + tx->tx_fifo.qpl = gve_alloc_queue_page_list(priv, qpl_id, +- qpl_page_cnt); ++ cfg->pages_per_qpl); + if (!tx->tx_fifo.qpl) + goto abort_with_desc; + +diff --git a/drivers/net/ethernet/google/gve/gve_tx_dqo.c b/drivers/net/ethernet/google/gve/gve_tx_dqo.c +index f879426..9118256 100644 +--- a/drivers/net/ethernet/google/gve/gve_tx_dqo.c ++++ b/drivers/net/ethernet/google/gve/gve_tx_dqo.c +@@ -287,7 +287,6 @@ static int gve_tx_alloc_ring_dqo(struct gve_priv *priv, + { + struct device *hdev = &priv->pdev->dev; + int num_pending_packets; +- int qpl_page_cnt; + size_t bytes; + u32 qpl_id; + int i; +@@ -357,10 +356,8 @@ static int gve_tx_alloc_ring_dqo(struct gve_priv *priv, + + if (!cfg->raw_addressing) { + qpl_id = gve_tx_qpl_id(priv, tx->q_num); +- qpl_page_cnt = priv->tx_pages_per_qpl; +- + tx->dqo.qpl = gve_alloc_queue_page_list(priv, qpl_id, +- qpl_page_cnt); ++ cfg->pages_per_qpl); + if (!tx->dqo.qpl) + goto err; + diff --git a/1107-gve-Enable-reading-max-ring-size-in-DQO-QPL-mode.patch b/1107-gve-Enable-reading-max-ring-size-in-DQO-QPL-mode.patch new file mode 100644 index 000000000..a5ca09b8f --- /dev/null +++ b/1107-gve-Enable-reading-max-ring-size-in-DQO-QPL-mode.patch @@ -0,0 +1,53 @@ +From a2f19184014f309165d2d4cfb41088b75c1121a4 Mon Sep 17 00:00:00 2001 +From: Matt Olson +Date: Wed, 25 Feb 2026 10:23:42 -0800 +Subject: [PATCH AlmaLinux 10] gve: Enable reading max ring size from the device in DQO-QPL mode + +The gVNIC device indicates a device option (MODIFY_RING) to the driver, +which presents a range of ring sizes from which the user is allowed to +select. But in DQO-QPL queue format, the driver ignores the "max" of +this range and instead allows the user to configure the ring size in the +range [min, default]. This was done because increasing the ring size +could result in the number of registered pages being higher than the max +allowed by the device. + +In order to support large ring sizes, stop ignoring the "max" of the +range presented in the MODIFY_RING option. + +Signed-off-by: Matt Olson +Signed-off-by: Max Yuan +Reviewed-by: Jordan Rhee +Reviewed-by: Harshitha Ramamurthy +Reviewed-by: Praveen Kaligineedi +Signed-off-by: Joshua Washington +Link: https://patch.msgid.link/20260225182342.1049816-3-joshwash@google.com +Signed-off-by: Jakub Kicinski + +[ AlmaLinux: applied unmodified on top of the backported "gve: Update QPL page + registration logic". ] +Signed-off-by: Andrew Lukoshko +--- +diff --git a/drivers/net/ethernet/google/gve/gve_adminq.c b/drivers/net/ethernet/google/gve/gve_adminq.c +index 2c233009621bc..b5f105709e49b 100644 +--- a/drivers/net/ethernet/google/gve/gve_adminq.c ++++ b/drivers/net/ethernet/google/gve/gve_adminq.c +@@ -989,12 +989,10 @@ static void gve_enable_supported_features(struct gve_priv *priv, + if (dev_op_modify_ring && + (supported_features_mask & GVE_SUP_MODIFY_RING_MASK)) { + priv->modify_ring_size_enabled = true; +- +- /* max ring size for DQO QPL should not be overwritten because of device limit */ +- if (priv->queue_format != GVE_DQO_QPL_FORMAT) { +- priv->max_rx_desc_cnt = be16_to_cpu(dev_op_modify_ring->max_rx_ring_size); +- priv->max_tx_desc_cnt = be16_to_cpu(dev_op_modify_ring->max_tx_ring_size); +- } ++ priv->max_rx_desc_cnt = ++ be16_to_cpu(dev_op_modify_ring->max_rx_ring_size); ++ priv->max_tx_desc_cnt = ++ be16_to_cpu(dev_op_modify_ring->max_tx_ring_size); + if (priv->default_min_ring_size) { + /* If device hasn't provided minimums, use default minimums */ + priv->min_tx_desc_cnt = GVE_DEFAULT_MIN_TX_RING_SIZE; +-- +cgit 1.3-korg + diff --git a/kernel.spec b/kernel.spec index 942c3afe2..c54fcb71a 100644 --- a/kernel.spec +++ b/kernel.spec @@ -706,7 +706,7 @@ Name: %{package_name} License: ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-2-Clause) AND ((GPL-2.0-only WITH Linux-syscall-note) OR BSD-3-Clause) AND ((GPL-2.0-only WITH Linux-syscall-note) OR CDDL-1.0) AND ((GPL-2.0-only WITH Linux-syscall-note) OR Linux-OpenIB) AND ((GPL-2.0-only WITH Linux-syscall-note) OR MIT) AND ((GPL-2.0-or-later WITH Linux-syscall-note) OR BSD-3-Clause) AND ((GPL-2.0-or-later WITH Linux-syscall-note) OR MIT) AND 0BSD AND BSD-2-Clause AND (BSD-2-Clause OR Apache-2.0) AND BSD-3-Clause AND BSD-3-Clause-Clear AND CC0-1.0 AND GFDL-1.1-no-invariants-or-later AND GPL-1.0-or-later AND (GPL-1.0-or-later OR BSD-3-Clause) AND (GPL-1.0-or-later WITH Linux-syscall-note) AND GPL-2.0-only AND (GPL-2.0-only OR Apache-2.0) AND (GPL-2.0-only OR BSD-2-Clause) AND (GPL-2.0-only OR BSD-3-Clause) AND (GPL-2.0-only OR CDDL-1.0) AND (GPL-2.0-only OR GFDL-1.1-no-invariants-or-later) AND (GPL-2.0-only OR GFDL-1.2-no-invariants-only) AND (GPL-2.0-only WITH Linux-syscall-note) AND GPL-2.0-or-later AND (GPL-2.0-or-later OR BSD-2-Clause) AND (GPL-2.0-or-later OR BSD-3-Clause) AND (GPL-2.0-or-later OR CC-BY-4.0) AND (GPL-2.0-or-later WITH GCC-exception-2.0) AND (GPL-2.0-or-later WITH Linux-syscall-note) AND ISC AND LGPL-2.0-or-later AND (LGPL-2.0-or-later OR BSD-2-Clause) AND (LGPL-2.0-or-later WITH Linux-syscall-note) AND LGPL-2.1-only AND (LGPL-2.1-only OR BSD-2-Clause) AND (LGPL-2.1-only WITH Linux-syscall-note) AND LGPL-2.1-or-later AND (LGPL-2.1-or-later WITH Linux-syscall-note) AND (Linux-OpenIB OR GPL-2.0-only) AND (Linux-OpenIB OR GPL-2.0-only OR BSD-2-Clause) AND Linux-man-pages-copyleft AND MIT AND (MIT OR Apache-2.0) AND (MIT OR GPL-2.0-only) AND (MIT OR GPL-2.0-or-later) AND (MIT OR LGPL-2.1-only) AND (MPL-1.1 OR GPL-2.0-only) AND (X11 OR GPL-2.0-only) AND (X11 OR GPL-2.0-or-later) AND Zlib AND (copyleft-next-0.3.1 OR GPL-2.0-or-later) URL: https://www.kernel.org/ Version: %{specrpmversion} -Release: %{pkg_release} +Release: %{pkg_release}.gcp # DO NOT CHANGE THE 'ExclusiveArch' LINE TO TEMPORARILY EXCLUDE AN ARCHITECTURE BUILD. # SET %%nobuildarches (ABOVE) INSTEAD %if 0%{?fedora} @@ -1144,6 +1144,8 @@ Patch1102: 1102-rxrpc-linearize-paged-frags.patch Patch1103: 1103-net-skbuff-propagate-shared-frag-marker.patch Patch1104: 1104-ptrace-require-cap-on-mm-less-task.patch Patch1105: 1105-smb-client-reject-userspace-cifs.spnego-descriptions.patch +Patch1106: 1106-gve-Update-QPL-page-registration-logic.patch +Patch1107: 1107-gve-Enable-reading-max-ring-size-in-DQO-QPL-mode.patch # END OF PATCH DEFINITIONS @@ -2010,6 +2012,8 @@ ApplyPatch 1102-rxrpc-linearize-paged-frags.patch ApplyPatch 1103-net-skbuff-propagate-shared-frag-marker.patch ApplyPatch 1104-ptrace-require-cap-on-mm-less-task.patch ApplyPatch 1105-smb-client-reject-userspace-cifs.spnego-descriptions.patch +ApplyPatch 1106-gve-Update-QPL-page-registration-logic.patch +ApplyPatch 1107-gve-Enable-reading-max-ring-size-in-DQO-QPL-mode.patch %{log_msg "End of patch applications"} # END OF PATCH APPLICATIONS @@ -4516,6 +4520,13 @@ fi\ # # %changelog +* Thu Jun 04 2026 Andrew Lukoshko - 6.12.0-211.7.4.gcp +- Enable GCP gVNIC (gve) DQO-QPL large ring support +- gve: Update QPL page registration logic (backport of upstream + commit 07993df56091) +- gve: Enable reading max ring size from the device in DQO-QPL mode + (upstream commit a2f19184014f) + * Thu May 28 2026 Andrew Lukoshko - 6.12.0-211.7.4 - net: skbuff: propagate shared-frag marker through frag-transfer helpers (refresh to upstream v5: now also covers skb_segment() and