All of lore.kernel.org
 help / color / mirror / Atom feed
From: Moshe Shemesh <moshe@nvidia.com>
To: <dcostantino@meta.com>
Cc: <saeedm@nvidia.com>, <leon@kernel.org>, <tariqt@nvidia.com>,
	<mbloch@nvidia.com>, <andrew+netdev@lunn.ch>,
	<davem@davemloft.net>, <edumazet@google.com>, <kuba@kernel.org>,
	<pabeni@redhat.com>, <eranbe@nvidia.com>,
	<netdev@vger.kernel.org>, <linux-rdma@vger.kernel.org>,
	<linux-kernel@vger.kernel.org>, <stable@vger.kernel.org>
Subject: Re: [PATCH net v2] net/mlx5: Don't return firmware-owned command mailboxes to the DMA pool
Date: Mon, 28 Sep 2026 16:05:23 +0300	[thread overview]
Message-ID: <c92a3b53-afb7-477b-8ff8-e3f119f96155@nvidia.com> (raw)
In-Reply-To: <179053692935.3145.9499750768128839597@kernel.org>



On 9/27/2026 10:22 PM, netdev-bot+sashiko@kernel.org wrote:
> 
> Thank you for your contribution! Sashiko AI review found 5 potential
> issue(s) to consider.
> 
> Critical: 0 · High: 1 (2) · Medium: 3 · Low: 1
> 
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
> 
> - [High] Moving the mailbox free into cmd_ent_put() makes it run on
>    whichever context drops the last reference, with nothing synchronising…
> - [Medium] The commit message says an entry that firmware never completes
>    "keeps its mailboxes for the lifetime of the device" and calls the…
> - [Medium] Mailbox retention depends on which reference happens to be
>    dropped last, not on whether firmware still owns the descriptor.
> - [Medium] Retained mailboxes can outlive the dma_pool they came from,
>    but are freed through whatever dev->cmd.pool points to at put time.
> - [Low] The kernel-doc above mlx5_cmd_invoke() (cmd.c ~1252-1266) says
>    that for !callback, 'ret < 0 : Command execution couldn't be submitted…
> 
> Pre-existing issues:
> - [High] This problem wasn't introduced by this patch, but a command that
>    times out while the device is up holds its command slot, and the slot…
> - [High] This problem wasn't introduced by this patch, but on the
>    synchronous path a sync timeout can race with a real completion and…
> 
> --
> 
> net/mlx5: Don't return firmware-owned command mailboxes to the DMA pool
> 
> This patch uses a new own_msgs flag to put off freeing a timed-out
> command's mailboxes until the final cmd_ent_put() of the command entry.
> It does this on both the synchronous and the callback paths. The aim is to
> stop late firmware writes from landing in blocks already returned to the
> mlx5_cmd dma_pool.
> 
>> An entry firmware never completes keeps its mailboxes for the lifetime of
>> the device. dma_pool_destroy() then reports the pool busy and declines to
>> free its pages, which is the memory safe outcome: the pages stay mapped,
>> so a late write lands there rather than in memory handed back to the
>> allocator.
> 
> [Severity: Medium]
> Is "for the lifetime of the device" accurate here? The mlx5_cmd dma_pool
> is created in mlx5_cmd_enable() and destroyed in mlx5_cmd_disable(). Those
> run from mlx5_function_enable()/mlx5_function_disable() on every
> load/unload cycle, not only at probe/remove:
> 
> mlx5_cmd_enable()
>      cmd->pool = dma_pool_create("mlx5_cmd", mlx5_core_dma_dev(dev), size, align, 0);
>      ...
>      create_msg_cache(dev);
> 
> mlx5_cmd_disable()
>      destroy_msg_cache(dev);
>      free_cmd_page(dev, cmd);
>      dma_pool_destroy(cmd->pool);
> 
> If a retained entry still holds blocks, dma_pool_destroy() skips
> dma_free_coherent() for every page in the pool. That includes the pages
> behind the message cache that destroy_msg_cache() has just returned:
> 512x1 + 32x2 + 16x16 + 8x256 + 2x512 = 3904 blocks of 1024 bytes.
> 
> So each command interface incarnation that ends with an unrecovered
> timed-out mailbox appears to leak about 4 MB of DMA-coherent memory. The
> entry and its msg structs are never freed either. Before this patch the
> pool was torn down cleanly, because cmd_exec() had already freed the
> mailboxes.
> 
> Two ways this can be reached:
> 
> - A MANAGE_PAGES timeout in
>    mlx5_function_disable()->mlx5_reclaim_startup_pages(), followed by
>    mlx5_core_disable_hca() and mlx5_cmd_disable().
> - A timeout during mlx5_function_enable()/mlx5_function_open(), followed
>    by the error unwind.
> 
> Later pool incarnations start fresh, so this happens once per
> unrecovered timeout followed by a teardown, not on every later disable.
> 
> Given the Cc: stable tag, could the commit message describe this cost
> more precisely?
> 
>> diff --git a/drivers/net/ethernet/mellanox/mlx5/core/cmd.c b/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
>> index 84583dc5eb1c0..4051f97b2ae12 100644
>> --- a/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
>> +++ b/drivers/net/ethernet/mellanox/mlx5/core/cmd.c
> 
> [ ... ]
> 
>> @@ -207,6 +214,11 @@ static void cmd_ent_put(struct mlx5_cmd_work_ent *ent)
>>        }
>>        spin_unlock_irqrestore(&cmd->alloc_lock, flags);
>>
>> +     if (ent->own_msgs) {
>> +             mlx5_free_cmd_msg(dev, ent->out);
>> +             free_msg(dev, ent->in);
>> +     }
>> +
> 
> [Severity: Medium]
> Can a retained mailbox end up freed into a different dma_pool than the
> one it was allocated from? These frees always go through whatever
> dev->cmd.pool and dev->cmd.cache[] are when the last put happens.
> 
> One possible sequence:
> 
> 1. A timed-out entry keeps its FW reference, with own_msgs set.
> 
> 2. mlx5_cmd_disable() runs destroy_msg_cache() and dma_pool_destroy().
>     The pool is busy, so its pages are kept, but the struct dma_pool is
>     still kfree()d.
> 
> 3. mlx5_cmd_enable() does memset(&cmd->vars, 0, ...), sets
>     cmd->vars.bitmask = MLX5_CMD_MASK, and creates a new pool and cache.
>     cmd->ent_arr[] is outside vars and is not cleared, so ent_arr[i]
>     still points at the stale entry.
> 
> 4. A non-forced completion for bit i arrives before slot i is reused.
>     mlx5_cmd_comp_handler() finds PENDING_COMP clear, logs "Command
>     completion arrived after timeout" and calls cmd_ent_put().
> 
> 5. That is the final reference. mlx5_free_cmd_msg()->free_cmd_box() then
>     calls dma_pool_free(dev->cmd.pool, ...) on the new pool with a block
>     from the old one. Without DMAPOOL_DEBUG, pool_block_err() does not
>     catch this. The block goes onto the new pool's free list, and the new
>     pool's nr_active is decremented.
> 
> 6. free_msg(dev, ent->in) also adds an old-pool cached msg to the rebuilt
>     cache list.
> 
> After that, the new pool's nr_active can read 0 while another timed-out
> entry still owns a real block. A later dma_pool_destroy() would then
> dma_free_coherent() pages that firmware may still write to.
> 
> Before this patch, a stale final put only did cmd_free_index(), up() and
> kfree(ent). It never touched the pool or the cache.
> 
>>        cmd_free_ent(ent);
>>   }
>>
>> @@ -958,10 +970,6 @@ static void cb_timeout_handler(struct work_struct *work)
>>        cmd_ent_put(ent); /* for the cmd_ent_get() took on schedule delayed work */
>>   }
> 
> [Severity: High]
> Now that cmd_ent_put() can free mailboxes, can this put race with
> command interface teardown?

That's a valid concern, in cmd_ent_put() mailbox should be freed before 
up() not after.
Also I would drop the last paragraph of the commit message, as we don't 
handle here "An entry firmware never completes".
Thanks, Moshe.

> 
> cmd_work_handler() takes a reference for the timeout work:
> 
>      if (ent->callback && schedule_delayed_work(&ent->cb_timeout_work, timeout))
>          cmd_ent_get(ent);
> 
> Take the "Async, recovered after timeout" case.
> mlx5_cmd_eq_recover() recovers the EQE, and mlx5_cmd_comp_handler()
> cannot cancel the running work, because cancel_delayed_work() returns
> false. The handler drops the FW and consumer references, sets own_msgs
> and calls the callback.
> 
> That leaves the put above in cb_timeout_handler(), running on
> system_wq, as the final one. The same happens when a real completion
> races with the timeout work.
> 
> In cmd_ent_put() the slot and semaphore are released under alloc_lock,
> and the mailboxes are only freed after the lock is dropped:
> 
>      cmd_free_index(cmd, ent->idx);
>      up(ent->page_queue ? &cmd->vars.pages_sem : &cmd->vars.sem);
>      ...
>      spin_unlock_irqrestore(&cmd->alloc_lock, flags);
> 
>      if (ent->own_msgs) {
>          mlx5_free_cmd_msg(dev, ent->out);
>          free_msg(dev, ent->in);
>      }
> 
> The teardown paths don't wait for that free:
> 
> - mlx5_cmd_allowed_opcode() and mlx5_cmd_flush() only wait for the up().
> - mlx5_cmd_cleanup_async_ctx() only waits for the callback.
> - mlx5_cmd_disable() only flushes cmd->wq, not system_wq.
> 
> Suppose this worker is delayed just after the up(). Could
> mlx5_cmd_disable() get through destroy_msg_cache() and dma_pool_destroy()
> first? The pool is busy, so its struct is kfree()d. The worker would then
> call dma_pool_free() on the freed pool, or on a newly created one, and
> list_add_tail() onto a destroyed or re-initialised cache list.
> 
> [ ... ]
> 
>> @@ -1313,7 +1321,12 @@ static int mlx5_cmd_invoke(struct mlx5_core_dev *dev, struct mlx5_cmd_msg *in,
>>                return 0; /* mlx5_cmd_comp_handler() will put(ent) */
>>
>>        err = wait_func(dev, ent);
>> -     if (err == -ETIMEDOUT || err == -ECANCELED || err == -EBUSY)
>> +     if (err == -ETIMEDOUT) {
>> +             /* firmware may still DMA into the mailboxes; keep them */
>> +             ent->own_msgs = true;
>> +             goto out_free;
>> +     }
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but can a sync timeout race
> with a real completion and free ent and its messages while the IRQ side
> is still using them?
> 
> CPU A: wait_func_handle_exec_timeout()
>      ent->ret = -ETIMEDOUT;
>      mlx5_cmd_comp_handler(dev, 1ULL << ent->idx, true);
>          loses test_and_clear_bit(MLX5_CMD_ENT_STATE_PENDING_COMP)
>          continue;
> 
> CPU B: EQ IRQ, mlx5_cmd_comp_handler(dev, vec, false)
>      wins test_and_clear_bit(MLX5_CMD_ENT_STATE_PENDING_COMP)
>      cmd_ent_put(ent);        /* refcount 2 -> 1 */
>      ent->ts2 = ktime_get_ns();
>      memcpy(ent->out->first.data, ent->lay->out, sizeof(ent->lay->out));
>      dump_command(dev, ent, 0);
>      ...
>      complete(&ent->done);
> 
> On CPU A, wait_func() returns -ETIMEDOUT without waiting on ent->done
> again. mlx5_cmd_invoke() sets own_msgs and makes the final
> cmd_ent_put() (1 -> 0), which frees ent->out, ent->in and ent. CPU B may
> still be writing ent->out and calling complete(&ent->done) at that point.
> 
> The same window existed before, with cmd_exec() freeing the messages
> right after the put. This change moves the frees into that final put but
> doesn't close the window.
> 
> [Severity: Low]
> This isn't a bug, but the kernel-doc above mlx5_cmd_invoke() still says:
> 
>   * return value in case (!callback):
>   *      ret < 0 : Command execution couldn't be submitted by driver
> 
> With this change, -ETIMEDOUT means the command was submitted and in/out
> now belong to the entry, so the caller must not free them. Only
> cmd_exec() knows this, through the new goto out_up. Could the comment
> document the ownership transfer, so a future caller that frees on a
> negative return doesn't double-free?
> 
>> +     if (err == -ECANCELED || err == -EBUSY)
>>                goto out_free;
>>
>>        ds = ent->ts2 - ent->ts1;
>> @@ -1816,8 +1829,10 @@ static void mlx5_cmd_comp_handler(struct mlx5_core_dev *dev, u64 vec, bool force
>>                                                                 ent->out,
>>                                                                 ent->uout_size);
>>
>> -                             mlx5_free_cmd_msg(dev, ent->out);
>> -                             free_msg(dev, ent->in);
>> +                             /* firmware may still DMA into the mailboxes;
>> +                              * keep them
>> +                              */
>> +                             ent->own_msgs = true;
>>
>>                                /* final consumer is done, release ent */
>>                                cmd_ent_put(ent);
> 
> [Severity: High]
> This is a pre-existing issue, not one introduced by this patch. It is
> raised here because the patch handles exactly these retained entries.
> Can a command that times out while the device is up hold its slot
> semaphore forever, and leave mlx5_cmd_flush() spinning?
> 
> After the timeout, the forced mlx5_cmd_comp_handler() clears
> PENDING_COMP but keeps the FW reference, because mlx5_cmd_is_down() is
> false and the opcode is allowed. The final cmd_ent_put(), which does
> up(&cmd->vars.sem), therefore only runs on a real completion.
> 
> enter_error_state() then sets MLX5_DEVICE_STATE_INTERNAL_ERROR and calls
> mlx5_cmd_flush():
> 
>          for (i = 0; i < cmd->vars.max_reg_cmds; i++) {
>                  while (down_trylock(&cmd->vars.sem)) {
>                          mlx5_cmd_trigger_completions(dev);
>                          cond_resched();
>                  }
>          }
> 
> For the timed-out entry, mlx5_cmd_trigger_completions() takes and drops
> a temporary reference. The forced handler finds PENDING_COMP already
> clear and hits continue without a put. cmd_comp_notifier() ignores real
> EQEs once dev->state == MLX5_DEVICE_STATE_INTERNAL_ERROR, so the permit
> never comes back.
> 
> This loop is reached from the fatal health reporter, PCI err_detected,
> sync FW reset, and shutdown through
> mlx5_try_fast_unload()->mlx5_enter_error_state(). Those callers hold
> intf_state_mutex while it runs. On unload, the same held permit also
> blocks the down() loops in mlx5_cmd_allowed_opcode() and
> mlx5_cmd_change_mod().
> 
> Wouldn't recovery, unload and reboot all hang here?
> 
> [ ... ]
> 
>> @@ -2012,6 +2027,9 @@ static int cmd_exec(struct mlx5_core_dev *dev, void *in, int in_size, void *out,
>>        if (callback && !err)
>>                return 0;
>>
>> +     if (err == -ETIMEDOUT) /* the command entry owns the mailboxes now */
>> +             goto out_up;
>> +
> 
> [Severity: Medium]
> Does this cover every case where firmware still owns the mailboxes?
> Whether they are kept seems to depend on which reference is dropped
> last, not on whether the descriptor is still owned by hardware.
> 
> First, a forced completion in mlx5_cmd_comp_handler() still drops the
> FW reference when the device looks down:
> 
>                          if (!forced || /* Real FW completion */
>                               mlx5_cmd_is_down(dev) || /* No real FW completion is expected */
>                               !opcode_allowed(cmd, ent->op))
>                                  cmd_ent_put(ent);
> 
> mlx5_cmd_is_down() becomes true as soon as poll_health() sets
> MLX5_DEVICE_STATE_INTERNAL_ERROR on a fatal sensor (for example
> MLX5_SENSOR_FW_SYND_RFR). That happens before any reset or DMA
> quiescence.
> 
> If wait_func_handle_exec_timeout() fires in that state, the put in
> mlx5_cmd_invoke() is final. cmd_ent_put() then frees ent->in and ent->out
> straight away, even though own_msgs was just set. On the async path the
> put in cb_timeout_handler() is the final one.
> 
> Second, mlx5_cmd_flush()->mlx5_cmd_trigger_completions() force-completes
> every allocated slot with MLX5_TRIGGERED_CMD_COMP, so ent->ret becomes
> -ENXIO.
> 
> For sync commands, only -ETIMEDOUT is special-cased above. -ENXIO falls
> through to out_out, and mlx5_free_cmd_msg()/free_msg() release blocks
> for commands that have already been doorbelled. For callback commands,
> own_msgs is set, but the is_down check drops the FW reference and the
> trigger loop's cmd_ent_put() frees them.
> 
> If firmware completes one of these commands before the reset, couldn't
> the late write land in a freed block, or in pool pages freed by a later
> clean dma_pool_destroy()? That is the same corruption the commit message
> describes.
> 
> The new "firmware may still DMA into the mailboxes; keep them" comments
> promise a retention that these paths don't provide.
> 
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260923190542.848049-1-dcostantino%40meta.com


      reply	other threads:[~2026-09-28 13:06 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-23 19:05 [PATCH net v2] net/mlx5: Don't return firmware-owned command mailboxes to the DMA pool Danielle Costantino
2026-09-23 19:12 ` sashiko-bot
2026-09-27 19:22 ` netdev-bot+sashiko
2026-09-28 13:05   ` Moshe Shemesh [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=c92a3b53-afb7-477b-8ff8-e3f119f96155@nvidia.com \
    --to=moshe@nvidia.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=dcostantino@meta.com \
    --cc=edumazet@google.com \
    --cc=eranbe@nvidia.com \
    --cc=kuba@kernel.org \
    --cc=leon@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-rdma@vger.kernel.org \
    --cc=mbloch@nvidia.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=saeedm@nvidia.com \
    --cc=stable@vger.kernel.org \
    --cc=tariqt@nvidia.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.