Re: [PATCH net] net: liquidio: lock upstream bridge for function reset

Simon Horman <[email protected]>
Newsgroups org.kernel.vger.netdev,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/[email protected]?part=1
---
--- Patch [1]: [PATCH net] net: liquidio: lock upstream bridge for function reset ---
commit 2860fd8a604e5a859143abde28004c138ba9f2fa
Author: Runyu Xiao <[email protected]>

net: liquidio: lock upstream bridge for function reset

This adds a pci_cfg_access_lock() on the upstream bridge around the
__pci_reset_function_locked() call in octeon_pci_flr(), and moves
pci_save_state()/pci_restore_state() inside the locked region so both
locks are held across the reset and the state restore.

> diff --git a/drivers/net/ethernet/cavium/liquidio/lio_main.c b/drivers/net/ethernet/cavium/liquidio/lio_main.c
> index e303956b4bf1..9aab310ba229 100644
> --- a/drivers/net/ethernet/cavium/liquidio/lio_main.c
> +++ b/drivers/net/ethernet/cavium/liquidio/lio_main.c
> @@ -914,12 +914,15 @@ static bool fw_type_is_auto(void)
>   */
>  static void octeon_pci_flr(struct octeon_device *oct)
>  {

[Severity: Low]
[Finding: gpt-5-6-sol-2-3]
[Sources: gpt-5-6-sol, opus-5]
This isn't a bug introduced by this patch, but the kernel-doc directly
above octeon_pci_flr() still says "PCI FLR for each Octeon device" while
the body calls the generic helper, which can pick any of the methods in
dev->reset_methods:

drivers/pci/pci.c:__pci_reset_function_locked() {
	for (i = 0; i < PCI_NUM_RESET_METHODS; i++) {
		m = dev->reset_methods[i];
		...
		rc = method->reset_fn(dev, PCI_RESET_DO_RESET);

and one of those methods ends in a secondary bus reset:

drivers/pci/pci.c:pci_reset_bus_function() {
	rc = pci_dev_reset_slot_function(dev, probe);
	if (rc != -ENOTTY)
		goto done;

	rc = pci_parent_bus_reset(dev, probe);

Since the changelog's premise is exactly that the bus-reset method may be
selected, would it make sense to update the doc string in the same patch?

> +	struct pci_dev *bridge = pci_upstream_bridge(oct->pci_dev);
>  	int rc;
>  
> -	pci_save_state(oct->pci_dev);
> -
> +	if (bridge)
> +		pci_cfg_access_lock(bridge);
>  	pci_cfg_access_lock(oct->pci_dev);

[Severity: High]
[Finding: gpt-5-6-sol-5-8]
[Sources: gpt-5-6-sol, opus-5]
Can this acquisition order deadlock against the PCI core?

octeon_pci_flr() is only reached from octeon_destroy_resources():

	if (atomic_read(oct->adapter_fw_state) == FW_IS_PRELOADED)
		octeon_pci_flr(oct);

which runs from liquidio_remove() and from the liquidio_remove(pdev)
calls on the probe-failure paths in liquidio_probe(). Both of those run
with device_lock(&pdev->dev) already held by the driver core, which is
why __pci_reset_function_locked() can be used at all:

drivers/pci/pci.c:__pci_reset_function_locked() {
	might_sleep();
	device_lock_assert(&dev->dev);

So after this change the driver holds device_lock(endpoint) and then takes
the bridge config-access lock. The core takes those two objects in the
opposite order:

drivers/pci/pci.c:pci_reset_function() {
	bridge = pci_upstream_bridge(dev);
	if (bridge)
		pci_dev_lock(bridge);

	pci_dev_lock(dev);

drivers/pci/pci.c:pci_dev_lock() {
	/* block PM suspend, driver probe, etc. */
	device_lock(&dev->dev);
	pci_cfg_access_lock(dev);

__pci_bus_lock() does the same bridge-first walk before locking each
child.

That gives an AB-BA pattern:

CPU0 (liquidio remove or probe failure)
  device_lock(endpoint)             [held by driver core]
  octeon_pci_flr()
    pci_cfg_access_lock(bridge)     -> blocks in pci_wait_cfg()

CPU1 (echo 1 > /sys/bus/pci/devices/.../reset, or AER/DPC recovery via
      pci_bus_error_reset() -> pci_bus_reset() -> pci_bus_lock())
  pci_dev_lock(bridge)              [sets bridge->block_cfg_access]
  pci_dev_lock(endpoint)
    device_lock(endpoint)           -> blocks, held by CPU0

Both waits look uninterruptible and untimed:

drivers/pci/access.c:pci_cfg_access_lock() {
	might_sleep();

	raw_spin_lock_irq(&pci_lock);
	if (dev->block_cfg_access)
		pci_wait_cfg(dev);

Since block_cfg_access is not lockdep-tracked, would this show up only as
a hung task, with the bridge's config-access flag left blocked?

Before this change the driver took only the endpoint's own config-access
lock, which the core also acquires after device_lock(endpoint), so there
was no inversion. Would deferring the bridge lock to a path that does not
already hold the endpoint device lock, or using the core's
pci_reset_function() style bridge-first sequence, avoid the cycle?

>  
> +	pci_save_state(oct->pci_dev);
> +
>  	/* Quiesce the device completely */
>  	pci_write_config_word(oct->pci_dev, PCI_COMMAND,
>  			      PCI_COMMAND_INTX_DISABLE);
> @@ -930,9 +933,10 @@ static void octeon_pci_flr(struct octeon_device *oct)
>  		dev_err(&oct->pci_dev->dev, "Error %d resetting PCI function %d\n",
>  			rc, oct->pf_num);
>  
> -	pci_cfg_access_unlock(oct->pci_dev);
> -
>  	pci_restore_state(oct->pci_dev);
> +	pci_cfg_access_unlock(oct->pci_dev);
> +	if (bridge)
> +		pci_cfg_access_unlock(bridge);
>  }

[ ... ]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.