Commit d733f519 authored by Ivan Vecera's avatar Ivan Vecera Committed by Paolo Abeni
Browse files

dpll: zl3073x: use __dpll_device_change_ntf() and remove change_work



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: 9363b483 ("dpll: zl3073x: Allow to configure phase offset averaging factor")
Signed-off-by: default avatarIvan Vecera <ivecera@redhat.com>
Link: https://patch.msgid.link/20260526074525.1451008-3-ivecera@redhat.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent 20040b2a
Loading
Loading
Loading
Loading
+9 −17
Original line number Diff line number Diff line
@@ -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;
}
+0 −2
Original line number Diff line number Diff line
@@ -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);