Commit 8d26955e authored by Paolo Abeni's avatar Paolo Abeni
Browse files

Merge branch 'dpll-zl3073x-various-fixes'

Ivan Vecera says:

====================
dpll: zl3073x: various fixes

Three fixes for the zl3073x DPLL driver.

Patch 1 exports __dpll_device_change_ntf() for use by drivers that
need to send device change notifications from within callbacks
already running under dpll_lock.

Patch 2 replaces the change_work workqueue mechanism with direct
calls to __dpll_device_change_ntf(), eliminating a race condition
where the work handler could dereference a freed dpll_dev pointer
during device teardown.

Patch 3 moves the freq_monitor flag from per-DPLL to per-device
scope to match the hardware behavior where frequency measurement
registers are shared across all DPLL channels.
====================

Link: https://patch.msgid.link/20260526074525.1451008-1-ivecera@redhat.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 1af2af70 c1224569
Loading
Loading
Loading
Loading
+11 −2
Original line number Diff line number Diff line
@@ -829,12 +829,21 @@ int dpll_device_delete_ntf(struct dpll_device *dpll)
	return dpll_device_event_send(DPLL_CMD_DEVICE_DELETE_NTF, dpll);
}

static int
__dpll_device_change_ntf(struct dpll_device *dpll)
/**
 * __dpll_device_change_ntf - notify that the dpll device has been changed
 * @dpll: registered dpll pointer
 *
 * Context: caller must hold dpll_lock. Suitable for use inside device
 *          callbacks which are already invoked under dpll_lock.
 * Return: 0 if succeeds, error code otherwise.
 */
int __dpll_device_change_ntf(struct dpll_device *dpll)
{
	lockdep_assert_held(&dpll_lock);
	dpll_device_notify(dpll, DPLL_DEVICE_CHANGED);
	return dpll_device_event_send(DPLL_CMD_DEVICE_CHANGE_NTF, dpll);
}
EXPORT_SYMBOL_GPL(__dpll_device_change_ntf);

/**
 * dpll_device_change_ntf - notify that the dpll device has been changed
+8 −11
Original line number Diff line number Diff line
@@ -762,18 +762,15 @@ zl3073x_dev_periodic_work(struct kthread_work *work)
		dev_warn(zldev->dev, "Failed to update phase offsets: %pe\n",
			 ERR_PTR(rc));

	/* Update measured input reference frequencies if any DPLL has
	 * frequency monitoring enabled.
	/* Update measured input reference frequencies if frequency
	 * monitoring is enabled.
	 */
	list_for_each_entry(zldpll, &zldev->dplls, list) {
		if (zldpll->freq_monitor) {
	if (zldev->freq_monitor) {
		rc = zl3073x_ref_freq_meas_update(zldev);
		if (rc)
			dev_warn(zldev->dev,
				 "Failed to update measured frequencies: %pe\n",
				 ERR_PTR(rc));
			break;
		}
	}

	/* Update references' fractional frequency offsets */
+3 −1
Original line number Diff line number Diff line
@@ -57,6 +57,7 @@ struct zl3073x_chip_info {
 * @work: periodic work
 * @clock_id: clock id of the device
 * @phase_avg_factor: phase offset measurement averaging factor
 * @freq_monitor: is frequency monitor enabled
 */
struct zl3073x_dev {
	struct device			*dev;
@@ -77,9 +78,10 @@ struct zl3073x_dev {
	struct kthread_worker		*kworker;
	struct kthread_delayed_work	work;

	/* Devlink parameters */
	/* Per-chip parameters */
	u64			clock_id;
	u8			phase_avg_factor;
	bool			freq_monitor;
};

extern const struct regmap_config zl3073x_regmap_config;
+23 −32
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;
@@ -1219,7 +1212,7 @@ zl3073x_dpll_freq_monitor_get(const struct dpll_device *dpll,
{
	struct zl3073x_dpll *zldpll = dpll_priv;

	if (zldpll->freq_monitor)
	if (zldpll->dev->freq_monitor)
		*state = DPLL_FEATURE_STATE_ENABLE;
	else
		*state = DPLL_FEATURE_STATE_DISABLE;
@@ -1233,9 +1226,19 @@ zl3073x_dpll_freq_monitor_set(const struct dpll_device *dpll,
			      enum dpll_feature_state state,
			      struct netlink_ext_ack *extack)
{
	struct zl3073x_dpll *zldpll = dpll_priv;
	struct zl3073x_dpll *item, *zldpll = dpll_priv;

	zldpll->dev->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);

	zldpll->freq_monitor = (state == DPLL_FEATURE_STATE_ENABLE);
	/* The frequency monitoring is common for all DPLL channels so after
	 * change we have to send a notification for other DPLL devices.
	 */
	list_for_each_entry(item, &zldpll->dev->dplls, list) {
		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 +1630,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);
}

/**
@@ -1752,7 +1755,7 @@ zl3073x_dpll_pin_measured_freq_check(struct zl3073x_dpll_pin *pin)
	u8 ref_id;
	u32 freq;

	if (!zldpll->freq_monitor)
	if (!zldpll->dev->freq_monitor)
		return false;

	ref_id = zl3073x_input_pin_ref_get(pin->id);
@@ -1785,10 +1788,8 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
	struct zl3073x_dev *zldev = zldpll->dev;
	enum dpll_lock_status lock_status;
	struct device *dev = zldev->dev;
	const struct zl3073x_chan *chan;
	struct zl3073x_dpll_pin *pin;
	int rc;
	u8 mode;

	zldpll->check_count++;

@@ -1807,15 +1808,6 @@ zl3073x_dpll_changes_check(struct zl3073x_dpll *zldpll)
		dpll_device_change_ntf(zldpll->dpll_dev);
	}

	/* Input pin monitoring does make sense only in automatic
	 * or forced reference modes.
	 */
	chan = zl3073x_chan_state_get(zldev, zldpll->id);
	mode = zl3073x_chan_mode_get(chan);
	if (mode != ZL_DPLL_MODE_REFSEL_MODE_AUTO &&
	    mode != ZL_DPLL_MODE_REFSEL_MODE_REFLOCK)
		return;

	/* Update phase offset latch registers for this DPLL if the phase
	 * offset monitor feature is enabled.
	 */
@@ -1926,7 +1918,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 −4
Original line number Diff line number Diff line
@@ -15,13 +15,11 @@
 * @id: DPLL index
 * @check_count: periodic check counter
 * @phase_monitor: is phase offset monitor enabled
 * @freq_monitor: is frequency monitor enabled
 * @ops: DPLL device operations for this instance
 * @dpll_dev: pointer to registered DPLL device
 * @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;
@@ -29,13 +27,11 @@ struct zl3073x_dpll {
	u8				id;
	u8				check_count;
	bool				phase_monitor;
	bool				freq_monitor;
	struct dpll_device_ops		ops;
	struct dpll_device		*dpll_dev;
	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);
Loading