Netdev List
 help / color / mirror / Atom feed
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

      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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox