On 8/17/26 6:47 PM, Jakub Kicinski wrote:
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
ibmveth: Enable multi-queue RX receive path
This turns on the multi-queue RX path when firmware reports
IBMVETH_ILLAN_RX_MULTI_QUEUE_SUPPORT, registering subordinate queues via
H_REG_LOGICAL_LAN_QUEUE and wiring per-queue IRQs and NAPI instances. It
also reworks open()/close() to walk every live RX queue, adds an
mq_fallback latch, and refreshes the VIO CMO entitlement at probe.
A few questions below.
Regarding this part of the changelog:
Up to IBMVETH_MAX_RX_QUEUES (16) remains available via ethtool -L.
[Severity: Low]
Is that true at this commit? ibmveth_get_channels() still does:
channels->max_rx = netdev->real_num_rx_queues;
channels->rx_count = netdev->real_num_rx_queues;
and ibmveth_set_channels() never reads channels->rx_count, so an
"ethtool -L <dev> rx N" returns success without changing anything, and 16
RX queues cannot be requested until the later get_channels/set_channels
patches land. Could this sentence move to the patch that actually wires
rx_count up?
No, not at this commit. get_channels still echoes real_num_rx_queues
and set_channels is still TX-only, so ethtool -L rx N succeeds without
changing RX.
v6 drops that sentence here. Patch 14 is the one that reads
channels->rx_count and calls resize. Patch 12 only reports the counts.
Patch 15 finishes the down-path and caps max_rx once mq_fallback
latches; it does not wire the setter.
diff --git a/drivers/net/ethernet/ibm/ibmveth.c
b/drivers/net/ethernet/ibm/ibmveth.c
index 86299c62d4ecf..36f0926711e60 100644
--- a/drivers/net/ethernet/ibm/ibmveth.c
+++ b/drivers/net/ethernet/ibm/ibmveth.c
[ ... ]
@@ -503,7 +519,7 @@ static int
ibmveth_setup_rx_interrupts(struct ibmveth_adapter *adapter)
{
struct net_device *netdev = adapter->netdev;
- int i, rc, num = adapter->num_rx_queues;
+ int i, rc, num = ibmveth_get_num_rx_queues(adapter);
for (i = 0; i < num; i++) {
if (!adapter->queue_irq[i]) {
[Severity: Low]
Now that this function can enable PHYP delivery on several queues, can the
err_disable_napi rollback leave a queue armed with no handler?
The rollback masks the already-enabled queues and calls synchronize_irq(),
but synchronize_irq() does not wait for NAPI. A poll already running for
one of those queues reaches:
if (ibmveth_poll_stopping(netdev, napi))
goto out;
rc = ibmveth_enable_irq(adapter, queue_index);
ibmveth_poll_stopping() is "!netif_running(netdev) || napi_disable_pending()",
and __dev_open() sets __LINK_STATE_START before calling ndo_open, so it is
false here and the poll re-arms PHYP. err_disable_napi then does:
for (i = 0; i < num; i++)
napi_disable(&adapter->napi[i]);
for (i = 0; i < num; i++) {
if (adapter->queue_irq[i])
free_irq(adapter->queue_irq[i], &adapter->napi[i]);
}
with no second mask pass, whereas ibmveth_cleanup_rx_interrupts()
deliberately masks again after napi_disable(). Should the rollback follow
the cleanup pattern and remask after napi_disable()?
Yes. Without a second mask after napi_disable, a poll still running
can re-arm PHYP after the first mask: poll_stopping is false during
open because __LINK_STATE_START is already set.
Already in patch 5: err_disable_napi remasks after napi_disable, same
as cleanup_rx_interrupts. This patch does not add a second rollback.
@@ -649,6 +665,24 @@ static bool ibmveth_schedule_rx_queue(struct
ibmveth_adapter *adapter,
return false;
}
+/**
+ * ibmveth_kick_rx_queue_if_pending - Schedule NAPI if PHYP posted while masked
+ * @adapter: ibmveth adapter
+ * @queue_index: RX queue index just unmasked
+ *
+ * After enable_irq() / MQ open unmask, descriptors may already be pending
+ * (buffers were posted while PHYP was masked). Use schedule_rx_queue() so
+ * PHYP is masked before NAPI runs — napi_schedule() then disable_irq() from
+ * process context can race a completing poll and leave the queue masked
+ * with NAPI idle.
+ */
+static void
+ibmveth_kick_rx_queue_if_pending(struct ibmveth_adapter *adapter,
+ int queue_index)
+{
+ if (ibmveth_rxq_pending_buffer(adapter, queue_index))
+ ibmveth_schedule_rx_queue(adapter, queue_index);
+}
[Severity: Low]
Does this commit build on its own? ibmveth_kick_rx_queue_if_pending() is a
non-inline static function with no caller anywhere in the tree at this
commit (open() uses ibmveth_restart_rx_queue() instead), so gcc/clang emit
"defined but not used", which is fatal with the default CONFIG_WERROR=y.
The first callers appear only in the later incremental resize patch. Could
the helper be introduced there instead, to keep this commit bisectable?
Yes. Unused static, fatal under CONFIG_WERROR. First-use: v6 deletes
it here. Patch 14 calls restart_rx_queue() at both sites that would
have used the helper, so there is nothing to move.
Also, the blank line before the following comment block was dropped:
+ ibmveth_schedule_rx_queue(adapter, queue_index);
+}
/* setup the initial settings for a buffer pool */
Ah, its back now.
@@ -963,11 +997,21 @@ static int ibmveth_replenish_buffer_pool(struct
ibmveth_adapter *adapter,
* because there was not a buffer in the buffer list capable of holding
* the frame.
*/
-static void ibmveth_update_rx_no_buffer(struct ibmveth_adapter *adapter)
+static void ibmveth_update_rx_no_buffer(struct ibmveth_adapter *adapter,
+ int queue_index)
{
- __be64 *p = adapter->buffer_list_addr[0] + 4096 - 8;
+ __be64 *p;
+ u64 drops;
+
+ if (queue_index < 0 ||
+ queue_index >= ibmveth_get_num_rx_queues(adapter) ||
+ !adapter->buffer_list_addr[queue_index])
+ return;
+
+ p = adapter->buffer_list_addr[queue_index] + 4096 - 8;
+ drops = be64_to_cpup(p);
- adapter->rx_no_buffer = be64_to_cpup(p);
+ adapter->rx_no_buffer = drops;
}
[Severity: Low]
The function became queue-aware, but the store target is still the single
adapter-wide field exported by ethtool -S. With several RX queues, does
rx_no_buffer end up being "whichever queue ran last", and can it decrease?
Each queue reads its own absolute PHYP page counter and overwrites the same
scalar, from per-queue replenish paths that hold different replenish_locks,
plus the lock-free loop added in ibmveth_close():
for (i = 0; i < ibmveth_get_num_rx_queues(adapter); i++)
ibmveth_update_rx_no_buffer(adapter, i);
The next patch in the series ("ibmveth: Add per-queue RX and TX statistics
collection") moves this to adapter->rx_qstats[queue_index] with a summing
helper, so only this intermediate commit reports the wrong value. Could
the per-queue slot land together with the queue_index argument?
Yes, last-writer-wins, and it can go backwards when a later queue
stores a smaller PHYP page counter.
The per-queue slot stays in patch 11 with the rest of qstats, next to
the retired-queue carry so one queue owns both. Not here: this patch
is the MQ switch.
@@ -1033,6 +1077,7 @@ static void ibmveth_replenish_task(struct ibmveth_adapter
*adapter,
dev_err_ratelimited(&adapter->netdev->dev,
"MQ buffer add H_FUNCTION (q=%d, batch=%u),
reset\n",
queue_index, fail.batch);
+ adapter->mq_fallback = true;
schedule_work(&adapter->work);
}
[Severity: High]
This isn't a bug introduced by this patch, but it adds another producer of
adapter->work while ibmveth_remove() still cancels the work before the
interface is unregistered:
cancel_work_sync(&adapter->work);
...
unregister_netdev(netdev);
free_netdev(netdev);
Can this sequence use freed memory?
CPU0 (rmmod / DLPAR remove)
ibmveth_remove()
cancel_work_sync(&adapter->work); /* returns */
CPU1 (NAPI poll)
ibmveth_replenish_task()
adapter->mq_fallback = true;
schedule_work(&adapter->work); /* re-queued */
CPU0
unregister_netdev(netdev); /* holds RTNL */
free_netdev(netdev); /* frees adapter + work_struct */
The queued ibmveth_reset() blocks in rtnl_lock() while unregister_netdev()
runs, then derives adapter via container_of() on the freed work_struct and
touches adapter->netdev. Would unregistering/quiescing first and calling
cancel_work_sync() after be safer?
Yes, UAF at this commit: cancel returns, poll can schedule_work
again, then unregister/free runs while the worker still takes RTNL
and container_of on the freed work_struct.
Unregister first, then cancel_work_sync, is the right order. Patch 11
does that, and gates the worker on NETREG_REGISTERED so a reset that
is already past cancel cannot close/open after unregister. This patch
only adds the extra schedule_work producers.
@@ -1052,6 +1097,30 @@ static void ibmveth_replenish_task(struct
ibmveth_adapter *adapter,
fail.filled, fail.lpar_rc, fail.batch);
}
+/**
+ * ibmveth_restart_rx_queue - Post buffers and ensure Q can take RX
+ * @adapter: ibmveth adapter
+ * @qindex: RX queue index
+ *
+ * SQ open leaves PHYP masked until the first poll. If schedule_prep fails,
+ * NAPI never runs and the queue stays masked (TX OK, RX/ARP dead) until
+ * reload. Replenish first so an enable_irq fallback can actually deliver.
+ * Also used after every open (SQ and MQ) and after scale-down so a
+ * queue is not left idle+masked.
+ */
+static void ibmveth_restart_rx_queue(struct ibmveth_adapter *adapter,
+ int qindex)
+{
+ int rc;
+
+ ibmveth_replenish_task(adapter, qindex);
+ if (ibmveth_schedule_rx_queue(adapter, qindex))
+ return;
+
+ rc = ibmveth_enable_irq(adapter, qindex);
+ WARN_ON(rc);
+}
[Severity: Medium]
The enable_irq() fallback here is reached exactly when
napi_schedule_prep() failed, i.e. when a poll for that queue is already
scheduled or running. In MQ mode ibmveth_setup_rx_interrupts() has already
unmasked PHYP before open() calls this helper, so a frame can arrive first:
ibmveth_interrupt(q)
ibmveth_schedule_rx_queue(q) /* masks PHYP, schedules NAPI */
ibmveth_open()
ibmveth_restart_rx_queue(adapter, q)
napi_schedule_prep() == false
ibmveth_enable_irq(adapter, q); /* re-arms PHYP mid-poll */
Since ibmveth_schedule_rx_queue() masks PHYP only inside the successful
prep branch:
if (napi_schedule_prep(napi)) {
rc = ibmveth_disable_irq(adapter, qindex);
WARN_ON(rc);
__napi_schedule(napi);
return true;
}
return false;
can the handler still quiesce the source afterwards? Each subsequent
interrupt returns IRQ_HANDLED without masking and sets NAPI_STATE_MISSED,
so napi_complete_done() keeps returning false and the queue stays unmasked
while traffic flows, giving an interrupt per frame on that queue.
Related: this WARN_ON(rc) treats a failed unmask hcall as advisory, while
ibmveth_poll() treats the same failure as a hardware-state problem:
rc = ibmveth_enable_irq(adapter, queue_index);
if (rc) {
netdev_err(...);
schedule_work(&adapter->work);
goto out;
}
Should the two paths handle the failure the same way?
Also, the comment says the helper is used "after scale-down", but no
scale-down caller exists at this commit; that path arrives with the later
resize patch.
No, later IRQs cannot quiet the source once the enable_irq fallback
has run: prep fails again, there is no mask, and you get IRQ_HANDLED
plus NAPI_STATE_MISSED until poll finishes.
That is the path we want. Mask-on-prep-fail raced poll and left RX
dead in the lab (TX still OK). enable-on-prep-fail stays. Storm
control is STOP plus poll no-rearm, not mask-on-fail.
The two enable_irq failures should not do the same thing. Poll is the
live path after complete_done; a failed unmask there is hardware-state
and schedules reset. restart_rx_queue is open/scale kickstart only;
scheduling reset from there nests close/open mid-open. v6 drops the
WARN_ON; the helper already logs the firmware rc.
The scale-down phrase is still in the kdoc. First caller is patch 14,
and it uses this same helper, so the comment is the helper's job at
the tip rather than a second function.
[ ... ]
@@ -1556,10 +1757,12 @@ static void ibmveth_free_all_queues(struct
ibmveth_adapter *adapter)
* @adapter: ibmveth adapter structure
* @mac_address: MAC address for device registration
*
- * Registers queue 0 via ibmveth_register_logical_lan(). Subordinate queue
- * registration is added when multi-queue RX is enabled.
+ * Registers queue 0 via ibmveth_register_logical_lan(), then subordinate
+ * queues 1..N when multi-queue mode is enabled.
*
- * Return: 0 on success, -ENONET if queue 0 registration fails
+ * Return: 0 on success, -ENONET if queue 0 registration fails,
+ * -EOPNOTSUPP if firmware rejects a subordinate queue (H_FUNCTION;
+ * caller latches mq_fallback), -EIO on other subordinate failures
[Severity: Low]
The kernel-doc says the caller latches mq_fallback, but the function body
does it itself and ibmveth_open() only propagates rc. Could the wording be
changed to say this function latches adapter->mq_fallback, so a future
change does not add a second latch or remove this one?
Yes. v6 says this function latches mq_fallback on -EOPNOTSUPP. open()
only propagates rc.
@@ -1592,9 +1796,67 @@ ibmveth_register_rx_queues(struct ibmveth_adapter
*adapter, u64 mac_address)
return -ENONET;
}
+ num = ibmveth_get_num_rx_queues(adapter);
+ if (num == 1 || !adapter->multi_queue) {
+ netdev_dbg(netdev,
+ "registered 1 RX queue with hypervisor (single-queue
mode)\n");
+ return 0;
+ }
+
+ netdev_dbg(netdev, "Registering %d subordinate queues (1-%d)\n",
+ num - 1, num - 1);
+
+ for (i = 1; i < num; i++) {
+ rc = ibmveth_register_single_rx_queue(adapter, i);
+ if (rc) {
+ /* Firmware MQ gone: fall back to SQ on next open. */
+ if (rc == -EOPNOTSUPP)
+ adapter->mq_fallback = true;
+ goto err_unregister;
+ }
+ }
[Severity: High]
Who performs the "next open" that consumes the latched mq_fallback?
The only automatic recovery path is the reset work:
ibmveth_reset()
rtnl_lock();
dev_close(adapter->netdev);
dev_open(adapter->netdev, NULL); /* return value discarded */
rtnl_unlock();
If firmware stops honouring H_REG_LOGICAL_LAN_QUEUE (the LPM case the code
comment describes), that dev_open() fails with -EOPNOTSUPP, mq_fallback is
set, nothing looks at the error, and no further open is attempted. Does
the interface then stay administratively up but dead until an administrator
runs ifdown/ifup, even though the driver already knows the single-queue
retry would succeed?
Would retrying single-queue inside open(), or re-arming the reset work when
dev_open() fails, match the intent stated in the changelog?
apply_mq_fallback() runs at the start of open. The next successful
ndo_open consumes the latch, from admin ifdown/ifup or from reset's
dev_open when that open succeeds.
Yes: if reset's dev_open fails with -EOPNOTSUPP, the latch stays set,
the return is discarded, and the interface can stay administratively
up but dead until ifdown/ifup. Single-queue was not applied on that
failed open.
No retry inside open(), and reset does not re-arm when that
dev_open() fails. That is the "next open drops to SQ" rule, not a
retry loop.
[ ... ]
@@ -2732,23 +3019,35 @@ static unsigned long ibmveth_get_desired_dma(struct
vio_dev *vdev)
adapter = netdev_priv(netdev);
- ret = IBMVETH_BUFF_LIST_SIZE + IBMVETH_FILT_LIST_SIZE;
+ /* One buffer list page per RX queue; filter list is shared. */
+ ret = IBMVETH_BUFF_LIST_SIZE * ibmveth_get_num_rx_queues(adapter) +
+ IBMVETH_FILT_LIST_SIZE;
ret += IOMMU_PAGE_ALIGN(netdev->mtu, tbl);
/* add size of mapped tx buffers */
ret += IOMMU_PAGE_ALIGN(IBMVETH_MAX_TX_BUF_SIZE, tbl);
[Severity: Medium]
This is a pre-existing shortfall rather than something this patch created,
but since the function is being rewritten and its result now becomes the
probe-time CMO request, should the TX term scale with the TX queue count?
ibmveth_alloc_tx_resources() maps one long-term buffer per TX queue:
for (i = 0; i < netdev->real_num_tx_queues; i++) {
if (ibmveth_allocate_tx_ltb(adapter, i))
goto err_free_ltbs;
}
with real_num_tx_queues defaulting to min(nr_cpus, 8), while the accounting
above adds exactly one IBMVETH_MAX_TX_BUF_SIZE. Can the requested
entitlement be short by up to 7 x 64KB, so that dma_map_single() in
ibmveth_allocate_tx_ltb() fails at open on an entitlement-constrained
partition? vio_cmo_set_dev_desired() returns void, so the shortfall is
silent.
Yes. One IBMVETH_MAX_TX_BUF_SIZE against up to eight mapped LTBs can
be short by 7×64KB; vio_cmo_set_dev_desired() is void, so the
shortfall is silent and dma_map_single() can fail at open.
Not in this patch. TX-term scaling is on the cover leftovers list
with the rest of the CMO refresh.
[ ... ]
@@ -2895,16 +3206,29 @@ static int ibmveth_probe(struct vio_dev *dev, const
struct vio_device_id *id)
netdev->features |= NETIF_F_FRAGLIST;
}
- /* Initialize queue count - always 1 for now */
- adapter->multi_queue = 0;
- adapter->num_rx_queues = IBMVETH_DEFAULT_RX_QUEUES;
+ if (ret == H_SUCCESS &&
+ (ret_attr & IBMVETH_ILLAN_RX_MULTI_QUEUE_SUPPORT)) {
+ adapter->multi_queue = 1;
+ ibmveth_publish_num_rx_queues(adapter,
+ min(num_online_cpus(),
+ IBMVETH_DEFAULT_QUEUES));
+ netdev_dbg(netdev, "RX multi queue mode enabled: %d queues\n",
+ ibmveth_get_num_rx_queues(adapter));
+ } else {
+ adapter->multi_queue = 0;
+ ibmveth_publish_num_rx_queues(adapter,
+ IBMVETH_DEFAULT_RX_QUEUES);
+ }
[Severity: Medium]
Activating several RX NAPI instances makes a number of adapter-wide
counters multi-writer. Can these lose increments?
ibmveth_replenish_task() bumps a shared counter outside any lock:
adapter->replenish_task_cycles++;
spin_lock_irqsave(&rxq->replenish_lock, flags);
and ibmveth_replenish_buffer_pool() updates shared counters under only the
queue-local lock:
adapter->replenish_add_buff_success += filled;
Per-queue replenish_locks give no mutual exclusion between different
queues, so plain read-modify-write on replenish_task_cycles,
replenish_add_buff_success/failure, replenish_no_mem and
hcall_stats.add_bufs_queue races. At this commit rx_invalid_buffer,
rx_large_packets and netdev->stats.rx_packets/rx_bytes are in the same
situation; the following qstats patch moves those to per-queue storage, but
the replenish counters remain shared through the end of the series. Should
they get per-queue slots as well?
Yes. Plain u64 RMW can lose increments: replenish_task_cycles is
outside any lock, and per-queue replenish_locks do not serialise
different queues.
Patch 11 moves replenish_* into the per-queue structs as plain u64,
summed on the existing adapter-wide keys. Pools are already per
queue, so that matches the lock. The eight hypercall counters are
gone (patch 7); they are not ethtool keys. No atomics.
@@ -2922,25 +3246,62 @@ static int ibmveth_probe(struct vio_dev *dev, const
struct vio_device_id *id)
for (i = 0; i < IBMVETH_NUM_BUFF_POOLS; i++) {
struct kobject *kobj = &adapter->rx_buff_pool[0][i].kobj;
- int error;
ibmveth_init_buffer_pool(&adapter->rx_buff_pool[0][i], i,
pool_count[i], pool_size[i],
pool_active[i]);
- error = kobject_init_and_add(kobj, &ktype_veth_pool,
- &dev->dev.kobj, "pool%d", i);
- if (!error)
- kobject_uevent(kobj, KOBJ_ADD);
+ rc = kobject_init_and_add(kobj, &ktype_veth_pool,
+ &dev->dev.kobj, "pool%d", i);
+ if (rc) {
+ dev_err(&dev->dev,
+ "failed to create pool%d kobject: %d\n", i, rc);
+ /* init_and_add takes a ref even on failure */
+ kobject_put(kobj);
+ ibmveth_put_pool_kobjs(adapter, pools_ready);
+ dev_set_drvdata(&dev->dev, NULL);
+ free_netdev(netdev);
+ return rc;
+ }
+
+ pools_ready++;
+ kobject_uevent(kobj, KOBJ_ADD);
}
[Severity: Low]
This is a pre-existing problem being repaired rather than a new one, but
propagating kobject_init_and_add() failure and putting already-created pool
kobjects before free_netdev() is a standalone fix for probe error handling
that predates the MQ work. Could it be split into its own patch with a
Fixes: tag so it can be backported independently of this feature?
Yes, pre-existing, and it predates MQ. The put-before-free stays in
this series so a bisect does not leak. probe_cleanup lands in patch
11. Happy to send a standalone to net the same way as the reopen
unwind, if you would rather pick it up that way.
[Severity: Medium]
This isn't a bug introduced by this patch either (ibmveth_remove() already
does kobject_put() followed by free_netdev()), but the pattern is now
replicated in four new probe error paths. Can these free memory that a
deferred kobject release still uses?
ktype_veth_pool has no release callback:
static const struct kobj_type ktype_veth_pool = {
.release = NULL,
.sysfs_ops = &veth_pool_ops,
.default_groups = veth_pool_groups,
};
and the kobjects live inside netdev_priv(). With
CONFIG_DEBUG_KOBJECT_RELEASE=y, kobject_release() in lib/kobject.c does not
clean up synchronously:
unsigned long delay = HZ + HZ * get_random_u32_below(4);
...
INIT_DELAYED_WORK(&kobj->release, kobject_delayed_cleanup);
schedule_delayed_work(&kobj->release, delay);
The delayed_work lives inside the kobject, so free_netdev() right after
kobject_put() leaves a queued work item in freed memory and
kobject_delayed_cleanup() dereferences a freed kobj 1-5 seconds later.
Would a no-op .release for ktype_veth_pool, or deferring free_netdev()
until the kobjects are really gone, avoid that?
Yes. With CONFIG_DEBUG_KOBJECT_RELEASE=y, kobject_put() then
free_netdev() can touch freed memory: the delayed_work lives inside
the kobject in netdev_priv(). This patch copies the same pattern onto
the probe error paths.
A no-op .release or deferring free_netdev() would close it. Not in
this series; it is on the cover leftovers list.
+ /*
+ * VIO CMO entitlement was set before probe (netdev NULL → default).
+ * Recompute now that num_rx_queues and pool 0 metadata are known.
+ */
+ if (firmware_has_feature(FW_FEATURE_CMO))
+ vio_cmo_set_dev_desired(dev, ibmveth_get_desired_dma(dev));
+
rc = netif_set_real_num_tx_queues(netdev, min(num_online_cpus(),
IBMVETH_DEFAULT_QUEUES));
if (rc) {
netdev_dbg(netdev, "failed to set number of tx queues rc=%d\n",
rc);
+ ibmveth_put_pool_kobjs(adapter, pools_ready);
+ dev_set_drvdata(&dev->dev, NULL);
free_netdev(netdev);
return rc;
}
[ ... ]
Cross-instance finding from sashiko-gemini
(91dce8e1ae8c5dfd96a2db94571e030c1951674452013e31a94e2df21bfc801b):
[Severity: Medium]
Missing `smp_rmb()` in `ibmveth_get_num_rx_queues` allows out-of-order reads of
uninitialized queue data.
v6 publishes with smp_store_release() and the getter uses
smp_load_acquire(), instead of smp_wmb() plus WRITE_ONCE / READ_ONCE.
That is the pair. No extra smp_rmb().
Thanks,
Mingming