From: mingming cao <mmc@linux.ibm.com>
To: Jakub Kicinski <kuba@kernel.org>
Cc: netdev@vger.kernel.org, davem@davemloft.net, edumazet@google.com,
pabeni@redhat.com, andrew+netdev@lunn.ch, nnac123@linux.ibm.com,
maddy@linux.ibm.com, mpe@ellerman.id.au,
linuxppc-dev@lists.ozlabs.org, haren@linux.ibm.com,
ricklind@linux.ibm.com, davemarq@linux.ibm.com,
bjking1@linux.ibm.com, shaik.abdulla1@ibm.com
Subject: Re: [PATCH net-next v5 15/15] ibmveth: Wire ethtool set_channels to MQ RX queue resize
Date: Mon, 31 Aug 2026 12:24:50 -0700 [thread overview]
Message-ID: <9576ceac-04a7-4b43-99af-c2b390a3e02b@linux.ibm.com> (raw)
In-Reply-To: <20260818014739.3854502-1-kuba@kernel.org>
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: Wire ethtool set_channels to MQ RX queue resize
>
> This completes the RX side of ibmveth_set_channels(): while the device is
> down the desired RX count is stashed, and while it is up RX is resized
> first and the TX LTB stop/alloc/set_real_num_tx/free/wake path runs
> afterwards. It also adds up-front TX range validation and an RX rollback
> when TX cannot reach the requested count.
>
> A few questions on the new paths below.
>
>> diff --git a/drivers/net/ethernet/ibm/ibmveth.c b/drivers/net/ethernet/ibm/ibmveth.c
>> index 84f4a0deb0c55..35cbd2553e867 100644
>> --- a/drivers/net/ethernet/ibm/ibmveth.c
>> +++ b/drivers/net/ethernet/ibm/ibmveth.c
> [ ... ]
>
>> @@ -3175,28 +3176,75 @@ static int ibmveth_set_channels(struct net_device *netdev,
>> struct ethtool_channels *channels)
>> {
>> struct ibmveth_adapter *adapter = netdev_priv(netdev);
>> - unsigned int old = netdev->real_num_tx_queues,
>> - goal = channels->tx_count;
>> + unsigned int old_rx = ibmveth_get_num_rx_queues(adapter);
>> + unsigned int goal_rx = channels->rx_count;
>> + unsigned int old_tx = netdev->real_num_tx_queues;
>> + unsigned int goal_tx = channels->tx_count;
>> + unsigned int want_tx = goal_tx;
>> + int rxq_entries = adapter->rx_queue[0].num_slots;
>> + bool rx_changed = false;
>> int rc, i;
>>
>> - /* Validate RX (and resize when opened) before the down-path early
>> - * return so MQ/range errors are not deferred to the wiring patch.
>> - * RX stash + CMO while down still lands with that patch.
>> - */
>> - rc = ibmveth_resize_rx_channels(adapter, channels->rx_count);
>> + if (goal_tx < 1 || goal_tx > ibmveth_real_max_tx_queues()) {
>> + netdev_err(netdev,
>> + "Invalid TX queue count %u (must be 1-%u)\n",
>> + goal_tx, ibmveth_real_max_tx_queues());
>> + return -EINVAL;
>> + }
> [Severity: Medium]
> This isn't a bug introduced by this patch, but the bound used here is
> CPU-count dependent while the reported current value is not, so the two can
> disagree.
>
> ibmveth_get_channels() fills the reply from two different sources:
>
> channels->max_tx = ibmveth_real_max_tx_queues();
> channels->tx_count = netdev->real_num_tx_queues;
>
> and ibmveth_real_max_tx_queues() is min(num_online_cpus(),
> IBMVETH_MAX_QUEUES). After configuring N TX queues and then offlining CPUs
> so that num_online_cpus() < N, the reply advertises tx_count > max_tx.
>
> Both ethtool entry points reject a request whose tx_count exceeds the
> driver-advertised max_tx:
>
> net/ethtool/ioctl.c:ethtool_set_channels() {
> ...
> if (channels.rx_count > curr.max_rx ||
> channels.tx_count > curr.max_tx || ...)
> return -EINVAL;
> ...
> }
>
> so even an RX-only change (which resubmits the current tx_count) fails with
> -EINVAL until the CPUs come back online. Should max_tx be latched from the
> configured count instead, or should the new range check be against
> IBMVETH_MAX_QUEUES?
Yes, the pair can disagree after CPU hotplug. Pre-existing TX; left
alone here. On the cover leftovers list.
>
>> +
>> + /* RX range / MQ checks live in ibmveth_resize_rx_channels(). */
>> + rc = ibmveth_resize_rx_channels(adapter, goal_rx);
>> if (rc)
>> return rc;
>>
>> - if (!adapter->opened)
>> - return netif_set_real_num_tx_queues(netdev, goal);
>> + /* If RX resources are not live (never opened, or close+open failed
>> + * while IFF_UP stayed set), only stash desired queue counts.
>> + */
>> + if (!adapter->opened) {
>> + /* Apply TX first so a failure leaves RX stash unchanged. */
>> + rc = netif_set_real_num_tx_queues(netdev, goal_tx);
>> + if (rc)
>> + return rc;
>> +
>> + /* Stash desired RX count; open() publishes it via
>> + * netif_set_real_num_rx_queues() after queue registration.
>> + * Refresh CMO now so open() can map the larger footprint;
>> + * open itself does not call vio_cmo_set_dev_desired.
>> + */
> [Severity: Low]
> Is this comment describing what the code does? The block right below
> publishes immediately rather than deferring to open():
> ibmveth_publish_num_rx_queues() updates adapter->num_rx_queues and
> netif_set_real_num_rx_queues() updates netdev->real_num_rx_queues, which
> creates or removes the /sys/class/net/*/queues/rx-N entries while the
> interface is down. ibmveth_open() only re-applies the same value later:
>
> rc = netif_set_real_num_rx_queues(netdev,
> ibmveth_get_num_rx_queues(adapter));
>
> The commit message says the same thing:
>
> - When down: set TX queues first, then stash the desired RX count
> in adapter->num_rx_queues for the next open() (open publishes via
> netif_set_real_num_rx_queues).
>
> Could the comment and the changelog mention that real_num_rx_queues, the
> rx-N sysfs entries and (below) the CMO entitlement all change immediately
> on a down interface?
Yes. The comment said stash; the code publishes immediately. The commit
message now says that: real_num_rx_queues, rx-N sysfs and CMO desired
all change while down.
>
>> + if (goal_rx != ibmveth_get_num_rx_queues(adapter)) {
>> + ibmveth_publish_num_rx_queues(adapter, goal_rx);
> [Severity: Medium]
> Can publishing a count with no allocated queues behind it let a lockless
> reader take an uninitialized spinlock?
>
> adapter->num_rx_queues is the only validity bound the RX readers use, and
> rx_queue[i].replenish_lock is only spin_lock_init()'ed for queues that are
> actually allocated (ibmveth_alloc_rx_queues() and
> ibmveth_alloc_single_rx_queue()).
>
> ibmveth_replenish_task() checks nothing else before locking:
>
> if (queue_index >= ibmveth_get_num_rx_queues(adapter)) {
> ...
> return;
> }
> ...
> spin_lock_irqsave(&rxq->replenish_lock, flags);
>
> and ibmveth_poll_controller() has no adapter->opened check:
>
> unsigned int num = ibmveth_get_num_rx_queues(adapter);
>
> for (i = 0; i < num; i++)
> ibmveth_replenish_task(adapter, i);
>
> That is reachable in the state this branch targets: pool_store and
> change_mtu call ibmveth_close()+ibmveth_open() directly, so IFF_UP can stay
> set (netif_running() true, netpoll still attached) with adapter->opened
> false. An "ethtool -L ethX rx N" with N above any previously allocated
> count then publishes N here, and the window stays open for as long as the
> device is down.
>
> ibmveth_publish_num_rx_queues() itself states the invariant:
>
> /* Per-queue state must be visible before readers observe num. */
> smp_wmb();
> WRITE_ONCE(adapter->num_rx_queues, num);
>
> Does this call site break it, given the per-queue state does not exist yet?
The locks are initialised in probe for every slot (patch 8). The hole
was walking rings that alloc_rx_queues() has not created. v6 returns
from poll_controller() unless adapter->opened.
>> + rc = netif_set_real_num_rx_queues(netdev, goal_rx);
>> + if (rc) {
>> + ibmveth_publish_num_rx_queues(adapter, old_rx);
>> + return rc;
>> + }
> [Severity: Medium]
> On this failure path, netdev->real_num_tx_queues was already moved to
> goal_tx by the netif_set_real_num_tx_queues() call above, and only the RX
> publish is undone.
>
> netif_set_real_num_rx_queues() has a real failure path:
>
> net/core/dev.c:netif_set_real_num_rx_queues() {
> ...
> rc = net_rx_queue_update_kobjects(dev, dev->real_num_rx_queues, rxq);
> if (rc)
> return rc;
> ...
> }
>
> so ethtool can report failure while the TX count has permanently changed.
> Should netif_set_real_num_tx_queues(netdev, old_tx) run here before
> returning rc, and does the current code match the commit message claim
> "Make TX/RX updates fail atomically"?
It did leave TX moved. v6 restores the old TX real_num if down-path RX
set_real fails. The commit message no longer says fail atomically.
>> + if (firmware_has_feature(FW_FEATURE_CMO)) {
>> + unsigned long dma;
>> +
>> + dma = ibmveth_get_desired_dma(adapter->vdev);
>> + vio_cmo_set_dev_desired(adapter->vdev, dma);
>> + }
> [Severity: Medium]
> Is the entitlement raised here ever lowered again if the stashed count is
> never realized? The desired value is sized for goal_rx RX queues, but no
> buffer lists, queue DMA or pools are mapped while down.
>
> If the next open() fails for the larger count, or if it applies the MQ
> fallback and drops the count back to 1, neither ibmveth_open(),
> ibmveth_close() nor ibmveth_apply_mq_fallback() recomputes desired DMA, so
> the partition keeps CMO entitlement reserved for RX resources that do not
> exist until a later successful set_channels or change_mtu.
No. Desired is a ceiling, not consumed pages, but it is still wrong.
Same CMO leftover as TX still sizing one LTB. Cover leftovers.
>> + }
>> + return 0;
>> + }
> [Severity: Medium]
> Can a request that returns 0 from this branch be silently discarded on the
> next open()?
>
> If firmware rejected MQ during a previous ibmveth_register_rx_queues(),
> adapter->mq_fallback is latched while adapter->multi_queue is still
> non-zero, so "ethtool -L ethX rx 8" passes the MQ check in
> ibmveth_resize_rx_channels(), takes this !opened branch, publishes 8, sets
> real_num_rx_queues = 8, raises CMO desired and returns success.
>
> ibmveth_apply_mq_fallback() then runs first in ibmveth_open():
>
> adapter->multi_queue = 0;
> ibmveth_publish_num_rx_queues(adapter, 1);
>
> with no error back to the user and no entitlement recompute, and
> ibmveth_get_channels() afterwards reports:
>
> channels->rx_count = ibmveth_get_num_rx_queues(adapter);
>
> which is 1, contradicting the request that returned 0. Should the down
> path reject (or at least warn about) goal_rx > 1 when mq_fallback is
> latched?
Yes. Patch 14 rejects rx > 1 with -EOPNOTSUPP once mq_fallback is set.
This patch keeps reporting the live rx_count and caps max_rx at that
count, so a TX-only -L is not a silent shrink and the core blocks
growth.
>> +
>> + if (goal_rx != old_rx)
>> + rx_changed = true;
>>
>> /* We have IBMVETH_MAX_QUEUES netdev_queue's allocated
>> * but we may need to alloc/free the ltb's.
>> */
>> + if (goal_tx == old_tx)
>> + return 0;
>> +
>> netif_tx_stop_all_queues(netdev);
>>
>> - /* Allocate any queue that we need */
>> - for (i = old; i < goal; i++) {
>> + /* Allocate any new TX LTBs. i starts at old_tx for the free walk
>> + * below when this loop body never runs (goal_tx == old_tx already
>> + * returned; goal_tx < old_tx is scale-down).
>> + */
>> + i = old_tx;
>> + for (; i < goal_tx; i++) {
> [Severity: Low]
> The commit message says:
>
> Initialize the TX alloc loop bound so a scale-down path that never
> allocates still has defined cleanup if set_real_num_tx_queues() fails.
>
> Was there anything undefined before? The removed line is:
>
> for (i = old; i < goal; i++) {
>
> whose initializer runs even when the body does not (old >= goal), so i was
> already defined for the later "for (i = old; i > goal; i--)" free walk.
> Could this be described as a readability change rather than a fix?
Yes. i was already defined. The commit message now calls it
readability.
>> if (adapter->tx_ltb_ptr[i])
>> continue;
>>
>> @@ -3205,28 +3253,43 @@ static int ibmveth_set_channels(struct net_device *netdev,
> [ ... ]
>
>> netif_tx_wake_all_queues(netdev);
>>
>> - return rc;
>> + if (netdev->real_num_tx_queues != want_tx) {
>> + if (rx_changed) {
>> + int rb;
>> +
>> + rb = ibmveth_resize_rx_queues_incremental(adapter,
>> + old_rx,
>> + rxq_entries);
>> + if (rb)
>> + netdev_err(netdev,
>> + "Failed to roll back RX queues to %u after TX failure: %d\n",
>> + old_rx, rb);
>> + }
>> + return rc ? rc : -ENOMEM;
>> + }
> [Severity: Medium]
> This rollback is best effort only, so the same "fail atomically" question
> applies to the up path. For "ethtool -L ethX rx <smaller> tx <larger>",
> the RX scale-down has already destroyed queues; if
> ibmveth_allocate_tx_ltb() then fails, the rollback here is a scale-up whose
> own steps can fail too:
>
> rc = ibmveth_alloc_single_rx_queue(adapter, i, rxq_entries);
> if (rc) { ... goto cleanup_new_queues; }
>
> and the same for ibmveth_register_single_rx_queue(),
> ibmveth_setup_single_rx_interrupt(), ibmveth_enable_irq() and
> netif_set_real_num_rx_queues(). Its cleanup path leaves RX at the reduced
> count, and here that is only logged before returning an error.
>
> Is there a way to order this so the destructive RX change happens only
> after the TX LTB allocations have succeeded, so no partial state can be
> left behind when the call reports failure?
Not without holding both sets. Live path stays teardown-first with
best-effort rollback. The commit message no longer says fail
atomically.
Regards,
Mingming
prev parent reply other threads:[~2026-08-31 19:25 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-14 7:36 [PATCH net-next v5 00/15] ibmveth: Add multi-queue RX support Mingming Cao
2026-08-14 7:36 ` [PATCH net-next v5 01/15] ibmveth: Add MQ RX hypercall wrappers and call definitions Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 17:58 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 02/15] ibmveth: Prepare MQ RX adapter data structures Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:03 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 03/15] ibmveth: Refactor RX resource allocation for MQ RX bring-up Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:12 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 04/15] ibmveth: Refactor buffer pool management for per-queue MQ RX Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:20 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 05/15] ibmveth: Refactor RX interrupt control for MQ RX queues Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:24 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 06/15] ibmveth: Refactor TX resource allocation in open/close paths Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:46 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 07/15] ibmveth: Add RX queue register helpers for MQ Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 18:54 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 08/15] ibmveth: Add queue-aware RX buffer submit helper " Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:00 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 09/15] ibmveth: Harden RX poll path with helpers Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:06 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 10/15] ibmveth: Enable multi-queue RX receive path Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:10 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 11/15] ibmveth: Add per-queue RX and TX statistics collection Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:12 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 12/15] ibmveth: Report MQ-aware RX counts in ethtool get_channels Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:19 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 13/15] ibmveth: Expose per-queue buffer pool details via debugfs Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:20 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 14/15] ibmveth: Implement incremental MQ RX queue resize Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:22 ` mingming cao
2026-08-14 7:36 ` [PATCH net-next v5 15/15] ibmveth: Wire ethtool set_channels to " Mingming Cao
2026-08-18 1:47 ` Jakub Kicinski
2026-08-31 19:24 ` mingming cao [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=9576ceac-04a7-4b43-99af-c2b390a3e02b@linux.ibm.com \
--to=mmc@linux.ibm.com \
--cc=andrew+netdev@lunn.ch \
--cc=bjking1@linux.ibm.com \
--cc=davem@davemloft.net \
--cc=davemarq@linux.ibm.com \
--cc=edumazet@google.com \
--cc=haren@linux.ibm.com \
--cc=kuba@kernel.org \
--cc=linuxppc-dev@lists.ozlabs.org \
--cc=maddy@linux.ibm.com \
--cc=mpe@ellerman.id.au \
--cc=netdev@vger.kernel.org \
--cc=nnac123@linux.ibm.com \
--cc=pabeni@redhat.com \
--cc=ricklind@linux.ibm.com \
--cc=shaik.abdulla1@ibm.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.