]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
dpll: zl3073x: use __dpll_device_change_ntf() and remove change_work
authorIvan Vecera <ivecera@redhat.com>
Tue, 26 May 2026 07:45:24 +0000 (09:45 +0200)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 28 May 2026 12:05:29 +0000 (14:05 +0200)
The change_work was introduced to send device change notifications
from DPLL device callbacks without deadlocking on dpll_lock, since
the callbacks are already invoked under that lock. Now that
__dpll_device_change_ntf() is exported for callers that already
hold dpll_lock, use it directly and remove the change_work
infrastructure entirely.

This eliminates a race condition where change_work could be
re-scheduled after cancel_work_sync() during device teardown,
potentially causing the handler to dereference a freed or NULL
dpll_dev pointer.

Fixes: 9363b4837659 ("dpll: zl3073x: Allow to configure phase offset averaging factor")
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260526074525.1451008-3-ivecera@redhat.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
drivers/dpll/zl3073x/dpll.c
drivers/dpll/zl3073x/dpll.h

index 64b4e9e3e8fe1468042ca2e13d85f245c0000427..0770bd895de9071d6e98a7e3dcc76c0950c24201 100644 (file)
@@ -1079,15 +1079,6 @@ zl3073x_dpll_phase_offset_avg_factor_get(const struct dpll_device *dpll,
        return 0;
 }
 
-static void
-zl3073x_dpll_change_work(struct work_struct *work)
-{
-       struct zl3073x_dpll *zldpll;
-
-       zldpll = container_of(work, struct zl3073x_dpll, change_work);
-       dpll_device_change_ntf(zldpll->dpll_dev);
-}
-
 static int
 zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
                                         void *dpll_priv, u32 factor,
@@ -1113,8 +1104,10 @@ zl3073x_dpll_phase_offset_avg_factor_set(const struct dpll_device *dpll,
         * we have to send a notification for other DPLL devices.
         */
        list_for_each_entry(item, &zldpll->dev->dplls, list) {
-               if (item != zldpll)
-                       schedule_work(&item->change_work);
+               struct dpll_device *dpll_dev = READ_ONCE(item->dpll_dev);
+
+               if (item != zldpll && dpll_dev)
+                       __dpll_device_change_ntf(dpll_dev);
        }
 
        return 0;
@@ -1627,13 +1620,13 @@ zl3073x_dpll_device_register(struct zl3073x_dpll *zldpll)
 static void
 zl3073x_dpll_device_unregister(struct zl3073x_dpll *zldpll)
 {
-       WARN(!zldpll->dpll_dev, "DPLL device is not registered\n");
+       struct dpll_device *dpll_dev = READ_ONCE(zldpll->dpll_dev);
 
-       cancel_work_sync(&zldpll->change_work);
+       WARN(!dpll_dev, "DPLL device is not registered\n");
 
-       dpll_device_unregister(zldpll->dpll_dev, &zldpll->ops, zldpll);
-       dpll_device_put(zldpll->dpll_dev, &zldpll->tracker);
-       zldpll->dpll_dev = NULL;
+       WRITE_ONCE(zldpll->dpll_dev, NULL);
+       dpll_device_unregister(dpll_dev, &zldpll->ops, zldpll);
+       dpll_device_put(dpll_dev, &zldpll->tracker);
 }
 
 /**
@@ -1926,7 +1919,6 @@ zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch)
        zldpll->dev = zldev;
        zldpll->id = ch;
        INIT_LIST_HEAD(&zldpll->pins);
-       INIT_WORK(&zldpll->change_work, zl3073x_dpll_change_work);
 
        return zldpll;
 }
index 434c32a7db12329638240bf26a06758f3e2a8d5d..c8bc8437a709938dc1efdcf431e8cceef3b0b7a1 100644 (file)
@@ -21,7 +21,6 @@
  * @tracker: tracking object for the acquired reference
  * @lock_status: last saved DPLL lock status
  * @pins: list of pins
- * @change_work: device change notification work
  */
 struct zl3073x_dpll {
        struct list_head                list;
@@ -35,7 +34,6 @@ struct zl3073x_dpll {
        dpll_tracker                    tracker;
        enum dpll_lock_status           lock_status;
        struct list_head                pins;
-       struct work_struct              change_work;
 };
 
 struct zl3073x_dpll *zl3073x_dpll_alloc(struct zl3073x_dev *zldev, u8 ch);