From: Jakub Kicinski <kuba@kernel.org>
To: nikhil.rao@amd.com
Cc: Jakub Kicinski <kuba@kernel.org>,
netdev@vger.kernel.org, brett.creeley@amd.com,
eric.joyner@amd.com, andrew+netdev@lunn.ch, davem@davemloft.net,
edumazet@google.com, pabeni@redhat.com
Subject: Re: [PATCH net v2] pds_core: fix cmd_regs access racing BAR unmap on reset
Date: Fri, 7 Aug 2026 16:47:00 -0700 [thread overview]
Message-ID: <20260807234700.1979723-1-kuba@kernel.org> (raw)
In-Reply-To: <20260804235946.177762-1-nikhil.rao@amd.com>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
---
pds_core: fix cmd_regs access racing BAR unmap on reset
This takes devcmd_lock across the BAR unmap in pdsc_reset_prepare() and
the remap in pdsc_reset_done(), skips the unmap entirely for VFs, and adds
cmd_regs NULL checks in pdsc_core_init(), pdsc_identify() and the
pdsc_firmware_update() download loop.
Closes: https://sashiko.dev/#/patchset/20260708212222.296202-1-nikhil.rao%40amd.com?part=3
A few questions below.
> pdsc_unmap_bars() also clears info_regs, intr_status, intr_ctrl and
> db_pages.
[Severity: Low]
Is this part of the changelog accurate? pdsc_unmap_bars() clears exactly
four pointers:
pdsc->info_regs = NULL;
pdsc->cmd_regs = NULL;
pdsc->intr_status = NULL;
pdsc->intr_ctrl = NULL;
It never touches pdsc->db_pages or pdsc->phy_db_pages. Grepping the
driver, db_pages is assigned only in pdsc_map_bars():
pdsc->db_pages = bar->vaddr;
pdsc->phy_db_pages = bar->bus_addr;
and bar there is &bars[1], whose vaddr was forced to NULL by the earlier
"only map the whole bar 0" branch:
/* only map the whole bar 0 */
if (j > 0) {
bars[j].vaddr = NULL;
So db_pages is always NULL and never a live mapping. Would "db_pages is
never mapped" be the correct justification here, rather than saying the
unmap clears it?
> diff --git a/drivers/net/ethernet/amd/pds_core/core.c b/drivers/net/ethernet/amd/pds_core/core.c
> index 04ec2569c61c0..c3c27a7c63d0e 100644
> --- a/drivers/net/ethernet/amd/pds_core/core.c
> +++ b/drivers/net/ethernet/amd/pds_core/core.c
> @@ -364,6 +364,14 @@ static int pdsc_core_init(struct pdsc *pdsc)
>
> mutex_lock(&pdsc->devcmd_lock);
>
> + if (!pdsc->cmd_regs) {
> + mutex_unlock(&pdsc->devcmd_lock);
> + err = -ENXIO;
> + dev_err(pdsc->dev, "Device init command failed: %pe\n",
> + ERR_PTR(err));
> + goto err_out_uninit;
> + }
> +
[Severity: Medium]
Does this new bail-out actually avoid the unmapped-MMIO access it is meant
to avoid? The err_out_uninit path leads to interrupt-control register
writes through the same BAR0 mapping:
pdsc_core_init() -> err_out_uninit -> pdsc_core_uninit() -> pdsc_qcq_free()
-> pdsc_qcq_intr_free() -> pdsc_intr_free()
and pdsc_intr_free() does:
pds_core_intr_mask(&pdsc->intr_ctrl[index], PDS_CORE_INTR_MASK_SET);
pds_core_intr_clean(&pdsc->intr_ctrl[index]);
free_irq(intr_info->vector, intr_info->data);
pdsc_unmap_bars() clears cmd_regs and intr_ctrl in the same four
statements, so cmd_regs == NULL implies intr_ctrl == NULL. The adminq QCQ
was just allocated in this function with PDS_CORE_QCQ_F_INTR, so
intr_info->vector is non-zero and the early return in pdsc_intr_free() is
not taken. Does that turn the memcpy_toio() to a NULL cmd_regs into an
iowrite32() at &((struct pds_core_intr *)NULL)[index].mask, and also skip
the intended mask/credit-clean before free_irq() drops the handler?
There is a related asymmetry earlier in the same call sequence:
pdsc_qcq_alloc() -> pdsc_intr_alloc() dereferences pdsc->intr_ctrl in
pds_core_intr_clean_flags(&pdsc->intr_ctrl[index],
PDS_CORE_INTR_CRED_RESET_COALESCE);
with no NULL check, and pdsc_start() does the same with
pds_core_intr_mask(&pdsc->intr_ctrl[pdsc->adminqcq.intx],
PDS_CORE_INTR_MASK_CLEAR);
Is the window this check covers reachable at all? If the BARs cannot
vanish inside pdsc_core_init(), this is dead code; if they can, the
intr_ctrl users around it look equally exposed.
[Severity: Low]
This isn't a bug, but the three new checks for the same condition report it
three different ways.
Here pdsc_core_init() logs "Device init command failed: -ENXIO" on a path
that unlocks and jumps to err_out_uninit before pdsc_devcmd_locked() is
ever called, and the string is identical to the one emitted just below for
a real devcmd failure:
err = pdsc_devcmd_locked(pdsc, &cmd, &comp, pdsc->devcmd_timeout);
...
if (err) {
dev_err(pdsc->dev, "Device init command failed: %pe\n",
ERR_PTR(err));
Would something like "BARs unmapped, skipping device init" make the two
distinguishable in dmesg?
pdsc_identify() returns -ENXIO with no log line at all, and
pdsc_firmware_update() now has two checks of the same condition on the same
devlink flash_update path: the pre-existing entry check
if (!pdsc->cmd_regs)
return -ENXIO;
returns without an extack, while the new in-loop check sets "Device reset
during flash". Should userspace see the same extack regardless of which
one fires?
> diff --git a/drivers/net/ethernet/amd/pds_core/dev.c b/drivers/net/ethernet/amd/pds_core/dev.c
> index bded6b33289ce..ec96f67dc67b9 100644
> --- a/drivers/net/ethernet/amd/pds_core/dev.c
> +++ b/drivers/net/ethernet/amd/pds_core/dev.c
> @@ -294,6 +294,11 @@ static int pdsc_identify(struct pdsc *pdsc)
> */
> mutex_lock(&pdsc->devcmd_lock);
>
> + if (!pdsc->cmd_regs) {
> + mutex_unlock(&pdsc->devcmd_lock);
> + return -ENXIO;
> + }
> +
> sz = min_t(size_t, sizeof(drv), sizeof(pdsc->cmd_regs->data));
> memcpy_toio(&pdsc->cmd_regs->data, &drv, sz);
>
> diff --git a/drivers/net/ethernet/amd/pds_core/fw.c b/drivers/net/ethernet/amd/pds_core/fw.c
> index fa626719e68d1..cd7616ed9ef32 100644
> --- a/drivers/net/ethernet/amd/pds_core/fw.c
> +++ b/drivers/net/ethernet/amd/pds_core/fw.c
> @@ -134,6 +134,12 @@ int pdsc_firmware_update(struct pdsc *pdsc, const struct firmware *fw,
>
> copy_sz = min_t(unsigned int, buf_sz, fw->size - offset);
> mutex_lock(&pdsc->devcmd_lock);
> + if (!pdsc->cmd_regs) {
> + mutex_unlock(&pdsc->devcmd_lock);
> + err = -ENXIO;
> + NL_SET_ERR_MSG_MOD(extack, "Device reset during flash");
> + goto err_out;
> + }
> memcpy_toio(&pdsc->cmd_regs->data, fw->data + offset, copy_sz);
> err = pdsc_devcmd_fw_download_locked(pdsc, data_addr,
> offset, copy_sz);
> diff --git a/drivers/net/ethernet/amd/pds_core/main.c b/drivers/net/ethernet/amd/pds_core/main.c
> index 9a2c64198d03b..1b960139de4e4 100644
> --- a/drivers/net/ethernet/amd/pds_core/main.c
> +++ b/drivers/net/ethernet/amd/pds_core/main.c
> @@ -507,7 +507,11 @@ static void pdsc_reset_prepare(struct pci_dev *pdev)
> pdsc_auxbus_dev_del(pdsc, pdsc, &pdsc->padev);
> }
>
> - pdsc_unmap_bars(pdsc);
> + if (!pdev->is_virtfn) {
> + mutex_lock(&pdsc->devcmd_lock);
> + pdsc_unmap_bars(pdsc);
> + mutex_unlock(&pdsc->devcmd_lock);
> + }
[Severity: High]
This isn't a bug introduced by this patch, and the changelog already calls
the debugfs readers out of scope, but the info_regs case looks like it can
oops without any race at all, so it seems worth mentioning here.
identity_show() reads the heartbeat register with no NULL check:
seq_printf(seq, "fw_heartbeat: 0x%x\n",
ioread32(&pdsc->info_regs->fw_heartbeat));
Every other reader of that same register tests the pointer first, for
example pdsc_is_fw_running():
if (!pdsc->info_regs)
return false;
pdsc_unmap_bars() sets pdsc->info_regs = NULL, and the "identity" file
deliberately survives a reset, per pdsc_debugfs_add_ident():
/* This file will already exist in the reset flow */
dentry = debugfs_lookup("identity", pdsc->dentry);
So besides the FLR window, if pdsc_reset_done() bails out early at
pci_enable_device(), pci_request_regions() or pdsc_map_bars(), does
info_regs stay NULL permanently while the file stays registered, making
every subsequent read of /sys/kernel/debug/pds_core/<pci-name>/identity
dereference NULL plus the fw_heartbeat offset?
pdsc_remove() also calls pdsc_unmap_bars() before pdsc_debugfs_del_dev(),
which leaves the same window during unbind.
Note that the qcq intr_ctrl regset in debugfs does not have this problem:
pdsc_debugfs_del_qcq() runs from pdsc_qcq_free() via pdsc_fw_down() ->
pdsc_teardown() -> pdsc_core_uninit(), which is before the unmap.
> pci_release_regions(pdev);
[Severity: Low]
This is a pre-existing asymmetry rather than something this patch
introduces, but the new is_virtfn guard right above it makes it visible:
pci_release_regions() still runs for VFs, and the VF path never requests
regions. pdsc_init_vf() has no pci_request_regions() call, and the
counterpart in pdsc_reset_done() is PF-only:
if (!pdev->is_virtfn) {
...
err = pci_request_regions(pdsc->pdev, PDS_CORE_DRV_NAME);
For a VF this reaches pci_release_region() -> release_mem_region() on a
range that was never reserved, so __release_region() prints "Trying to free
nonexistent resource" per memory BAR on every VF FLR. Should
pci_release_regions() move inside the same !pdev->is_virtfn block?
> if (pci_is_enabled(pdev))
> pci_disable_device(pdev);
[ ... ]
--
pw-bot: cr
prev parent reply other threads:[~2026-08-07 23:49 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-04 23:59 [PATCH net v2] pds_core: fix cmd_regs access racing BAR unmap on reset Nikhil P. Rao
2026-08-07 23:47 ` Jakub Kicinski [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=20260807234700.1979723-1-kuba@kernel.org \
--to=kuba@kernel.org \
--cc=andrew+netdev@lunn.ch \
--cc=brett.creeley@amd.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=eric.joyner@amd.com \
--cc=netdev@vger.kernel.org \
--cc=nikhil.rao@amd.com \
--cc=pabeni@redhat.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.