Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider:

Critical: 0 · High: 1 (1) · Medium: 1 · Low: 1

- [High] GC_REMOVING is a one-way latch that is set on service exits
  which do not necessarily remove the device.
- [Medium] The new `if (test_bit(GC_PROBE_SUCCEEDED, &gc->flags))` gate
  in `mana_gf_stats_work_handler()` (mana_en.c:4068) silently discards…
- [Low] The new service-work submission uses `queue_work(system_wq,
  &gc->serv_work)` (gdma_main.c:886).

Pre-existing issues:
- [High] `mana_gd_shutdown()` (gdma_main.c:2776) tears down the HWC —
  `mana_gd_cleanup_device()` -> `mana_hwc_destroy_channel()` ->…

--

net: mana: fix reset work race with device removal

The service work is moved into struct gdma_context and GC_IN_SERVICE now
describes whether the work may still touch gc.  Removal and the probe
unwind set a new GC_REMOVING bit to close admission and then wait for an
in-flight cycle to retire before clearing drvdata and freeing gc.  Reset
events observed during probe are latched in GC_SERVICE_DURING_PROBE
instead of being admitted immediately.

No Link: tags in the commit header.  Earlier postings referenced in the
changelog:
  https://lore.kernel.org/netdev/[email protected]
  https://lore.kernel.org/netdev/[email protected]

> diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c 
> b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> index f92b2d0bf926..28544f00e8ce 100644
> --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> @@ -678,10 +678,38 @@ static void mana_serv_rescan(struct pci_dev *pdev)
>       pci_unlock_rescan_remove();
>  }
>  
> -static void mana_serv_fpga(struct pci_dev *pdev)
> +/* Retire one service cycle: GC_IN_SERVICE is the last state published
> + * by the work, so remove() and the probe unwind may free gc once it
> + * is clear.  Must run before the exit paths take pci_lock_rescan_remove():
> + * removal can be waiting on the bit while holding that lock.
> + */
> +static void mana_service_done(struct gdma_context *gc)
> +{
> +     /* Pairs with test_bit_acquire() on the wait side. */
> +     clear_bit_unlock(GC_IN_SERVICE, &gc->flags);
> +     /* wake_up_var() requires a full barrier between the update of the
> +      * waited-on variable and the wake.
> +      */
> +     smp_mb__after_atomic();
> +     wake_up_var(&gc->flags);
> +}
> +
> +/* Retire a cycle whose exit removes the device: close admission
> + * first so no new cycle can be admitted in the retire window.
> + */
> +static void mana_service_done_removing(struct gdma_context *gc)
> +{
> +     set_bit(GC_REMOVING, &gc->flags);
> +     mana_service_done(gc);
> +}
> +
> +static void mana_serv_fpga(struct pci_dev *pdev, struct gdma_context *gc)
>  {
>       struct pci_bus *bus, *parent;
>  
> +     if (gc)
> +             mana_service_done_removing(gc);
> +
>       pci_lock_rescan_remove();
>  
>       bus = pdev->bus;
> @@ -706,9 +734,8 @@ static void mana_serv_fpga(struct pci_dev *pdev)
>       pci_unlock_rescan_remove();
>  }
>  
> -static void mana_serv_reset(struct pci_dev *pdev)
> +static void mana_serv_reset(struct pci_dev *pdev, struct gdma_context *gc)
>  {
> -     struct gdma_context *gc = pci_get_drvdata(pdev);
>       struct hw_channel_context *hwc;
>       int ret;
>  
> @@ -738,6 +765,7 @@ static void mana_serv_reset(struct pci_dev *pdev)
>       if (ret == -ETIMEDOUT || ret == -EPROTO) {
>               /* Perform PCI rescan on device if we failed on HWC */
>               dev_err(&pdev->dev, "MANA service: resume failed, 
> rescanning\n");
> +             mana_service_done_removing(gc);
>               mana_serv_rescan(pdev);
>               return;
>       }

[Severity: High]
Is GC_REMOVING guaranteed to be followed by an actual removal here?  Both
callers of mana_service_done_removing() can return with the device still
bound.

In mana_serv_fpga(), the bit is set before the bus checks:

        if (gc)
                mana_service_done_removing(gc);

        pci_lock_rescan_remove();

        bus = pdev->bus;
        if (!bus) {
                dev_err(&pdev->dev, "MANA service: no bus\n");
                goto out;
        }

        parent = bus->parent;
        if (!parent) {
                dev_err(&pdev->dev, "MANA service: no parent bus\n");
                goto out;
        }

bus->parent is NULL whenever the MANA function sits on a root bus, so
this exits through "goto out" without calling
pci_stop_and_remove_bus_device().

The failed-resume path has the same shape: mana_serv_rescan() has its own
"if (!parent)" bail-out that returns without removing the device, after
GC_REMOVING has already been latched.

I can't find any code that clears GC_REMOVING again.  Once it is set on a
live gc, mana_schedule_serv_work() refuses every subsequent request:

        if (test_bit(GC_REMOVING, &gc->flags)) {
                dev_info(gc->dev, "Device is being removed\n");
                mana_service_done(gc);
                return -EBUSY;
        }

and mana_tx_timeout() in mana_en.c also stops queueing queue-reset work
for the rest of the binding:

        if (test_bit(GC_IN_SERVICE, &gc->flags) ||
            test_bit(GC_REMOVING, &gc->flags))
                return;

Does this leave a still-bound device permanently without HWC reset
service and without TX queue reset?  Would it be better to set
GC_REMOVING only after the point where removal is known to happen, or to
clear it on the bail-out paths?

> @@ -796,48 +841,49 @@ static void mana_recovery_delayed_func(struct 
> work_struct *w)

[ ... ]

>  int mana_schedule_serv_work(struct gdma_context *gc, enum gdma_eqe_type type)
>  {
> -     struct mana_serv_work *mns_wk;
> -
>       if (test_and_set_bit(GC_IN_SERVICE, &gc->flags)) {
>               dev_info(gc->dev, "Already in service\n");
>               return -EBUSY;
>       }
>  
> +     /* Pairs with set_bit(GC_REMOVING) + smp_mb__after_atomic() in
> +      * mana_gd_remove(): the test_and_set_bit() above is fully ordered,
> +      * so either this read sees GC_REMOVING and the cycle aborts, or
> +      * remove() observes GC_IN_SERVICE and waits for the cycle to retire
> +      * before it frees gc.
> +      */
> +     if (test_bit(GC_REMOVING, &gc->flags)) {
> +             dev_info(gc->dev, "Device is being removed\n");
> +             mana_service_done(gc);
> +             return -EBUSY;
> +     }
> +
>       if (!try_module_get(THIS_MODULE)) {
>               dev_info(gc->dev, "Module is unloading\n");
> -             clear_bit(GC_IN_SERVICE, &gc->flags);
> +             mana_service_done(gc);
>               return -ENODEV;
>       }
>  
> -     mns_wk = kzalloc(sizeof(*mns_wk), GFP_ATOMIC);
> -     if (!mns_wk) {
> -             module_put(THIS_MODULE);
> -             clear_bit(GC_IN_SERVICE, &gc->flags);
> -             return -ENOMEM;
> -     }
> -
>       dev_info(gc->dev, "Start MANA service type:%d\n", type);
> -     mns_wk->pdev = to_pci_dev(gc->dev);
> -     mns_wk->type = type;
> -     pci_dev_get(mns_wk->pdev);
> -     INIT_WORK(&mns_wk->serv_work, mana_serv_func);
> -     schedule_work(&mns_wk->serv_work);
> +
> +     gc->serv_type = type;
> +     pci_dev_get(to_pci_dev(gc->dev));
> +     queue_work(system_wq, &gc->serv_work);
>       return 0;
>  }

[Severity: Low]
This isn't a bug, but was system_wq intended here rather than
system_percpu_wq?  In this tree the declaration is:

include/linux/workqueue.h:
        extern struct workqueue_struct *system_wq; /* use system_percpu_wq, 
this will be removed */

and system_wq is allocated with __WQ_DEPRECATED, so __queue_work() emits:

kernel/workqueue.c:__queue_work() {
        if (unlikely(wq->flags & __WQ_DEPRECATED))
                pr_warn_once("workqueue: work func %ps enqueued on deprecated 
workqueue. "
                        "Use system_{percpu|dfl}_wq instead.\n",
                        work->func);
}

The code being replaced used schedule_work(), which queues on
system_percpu_wq.  The stated reason for staying off gc->service_wq is
satisfied equally by schedule_work(&gc->serv_work).

[ ... ]

> @@ -2624,6 +2700,20 @@ static void mana_gd_remove(struct pci_dev *pdev)
>  {
>       struct gdma_context *gc = pci_get_drvdata(pdev);
>  
> +     /* Close admission and retire an in-flight cycle before any
> +      * teardown: the service work is the only user of gc that
> +      * remove() does not otherwise synchronise with.  The service
> +      * exits retire before taking the PCI rescan/remove lock, so
> +      * this wait cannot deadlock against them.
> +      */
> +     set_bit(GC_REMOVING, &gc->flags);
> +     /* Pairs with the ordered test_and_set_bit(GC_IN_SERVICE) in
> +      * mana_schedule_serv_work().
> +      */
> +     smp_mb__after_atomic();
> +     wait_var_event(&gc->flags,
> +                    !test_bit_acquire(GC_IN_SERVICE, &gc->flags));
> +
>       pci_disable_sriov(pdev);
>  
>       mana_rdma_remove(&gc->mana_ib);

[Severity: High]
This isn't a bug introduced by this patch, but should mana_gd_shutdown()
get the same admission-close and drain that is added here?  It tears the
HWC down with no synchronisation against an in-flight service cycle:

static void mana_gd_shutdown(struct pci_dev *pdev)
{
        struct gdma_context *gc = pci_get_drvdata(pdev);

        dev_info(&pdev->dev, "Shutdown was called\n");

        mana_rdma_remove(&gc->mana_ib);
        mana_remove(&gc->mana, true);

        mana_gd_cleanup_device(pdev);

        pci_disable_device(pdev);
}

Meanwhile mana_serv_reset() caches the HWC pointer and then sleeps for
MANA_SERVICE_PERIOD seconds across suspend/resume:

        hwc = gc->hwc.driver_data;
        ...
        hwc->hwc_timeout = 0;
        ...
        mana_gd_suspend(pdev, PMSG_SUSPEND);

        msleep(MANA_SERVICE_PERIOD * 1000);

        ret = mana_gd_resume(pdev);

mana_gd_cleanup_device() -> mana_hwc_destroy_channel() does kfree(hwc),
and both paths call it.  device_shutdown() takes only
device_lock(&pdev->dev), which the service work does not hold, so on
reboot or kexec during a reset cycle can this end up as a use-after-free
of hwc and a second kfree()/destroy_workqueue() on the same objects?

> diff --git a/drivers/net/ethernet/microsoft/mana/mana_en.c 
> b/drivers/net/ethernet/microsoft/mana/mana_en.c
> index 591fb4191d90..c297de8bb762 100644
> --- a/drivers/net/ethernet/microsoft/mana/mana_en.c
> +++ b/drivers/net/ethernet/microsoft/mana/mana_en.c

[ ... ]

> @@ -4061,9 +4061,12 @@ static void mana_gf_stats_work_handler(struct 
> work_struct *work)
>               dev_warn(gc->dev,
>                        "Gf stats wk handler: gf stats query timed out.\n");
>               /* As HWC timed out, indicating a faulty HW state and needs a
> -              * reset.
> +              * reset.  Never admit service work before the probe has
> +              * completed: a probe that is failing unwinds netdevs and the
> +              * HWC channel itself and cannot drain a cycle.
>                */
> -             mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
> +             if (test_bit(GC_PROBE_SUCCEEDED, &gc->flags))
> +                     mana_schedule_serv_work(gc, GDMA_EQE_HWC_RESET_REQUEST);
>               return;
>       }
>       schedule_delayed_work(&ac->gf_stats_work, MANA_GF_STATS_PERIOD);

[Severity: Medium]
What happens to the reset request when this gate is false but the probe
then succeeds?

mana_probe() arms this work before returning to mana_gd_probe():

        schedule_delayed_work(&ac->gf_stats_work, MANA_GF_STATS_PERIOD);

with MANA_GF_STATS_PERIOD being 2 * HZ, while mana_gd_probe() still has
mana_rdma_probe() to run before it publishes the flag:

        set_bit(GC_PROBE_SUCCEEDED, &gc->flags);
        if (test_and_set_bit(GC_SERVICE_DURING_PROBE, &gc->flags)) {

So a mana_query_gf_stats() timeout in that window takes the new branch,
drops the request, and the bare "return;" also skips the
schedule_delayed_work() re-arm below it.

The EQE handler in gdma_main.c latches and re-checks instead:

        if (!test_bit(GC_PROBE_SUCCEEDED, &gc->flags)) {
                if (!test_and_set_bit(GC_SERVICE_DURING_PROBE, &gc->flags))
                        dev_info(gc->dev,
                                 "Service is to be processed in probe\n");
                else if (test_bit(GC_PROBE_SUCCEEDED, &gc->flags))
                        mana_schedule_serv_work(gc, type);
                break;
        }

Here there is neither a latch nor a re-check, and the plain test_bit()
also races the set_bit() at the probe boundary.  If the probe goes on to
succeed, the device is left with ac->hwc_timeout_occurred set, the stats
poll never re-armed, and no reset cycle requested, where the previous
code called mana_schedule_serv_work() unconditionally.  Should this call
site latch into GC_SERVICE_DURING_PROBE (or re-check after the boundary)
so the request is not lost?

-- 
Sashiko AI review · 
https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260909042529.652301-1-fanwu01%40zju.edu.cn

Reply via email to