mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Petr Oros <poros@redhat.com>
To: Ivan Vecera <ivecera@redhat.com>, netdev@vger.kernel.org
Cc: Vadim Fedorenko <vadim.fedorenko@linux.dev>,
	Arkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>,
	Jiri Pirko <jiri@resnulli.us>, Jonathan Corbet <corbet@lwn.net>,
	Shuah Khan <skhan@linuxfoundation.org>,
	Randy Dunlap <rdunlap@infradead.org>,
	Min Li <min.li@microchip.com>,
	Tony Nguyen <anthony.l.nguyen@intel.com>,
	Przemek Kitszel <przemyslaw.kitszel@intel.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@kernel.org>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>,
	Richard Cochran <richardcochran@gmail.com>,
	linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
	intel-wired-lan@lists.osuosl.org,
	Grzegorz Nitka <grzegorz.nitka@intel.com>,
	Karol Kolacinski <karol.kolacinski@intel.com>,
	Saeed Mahameed <saeedm@nvidia.com>,
	Tariq Toukan <tariqt@nvidia.com>
Subject: Re: [PATCH net-next 06/10] dpll: add KUnit tests for core locking
Date: Tue, 10 Nov 2026 13:24:26 +0100	[thread overview]
Message-ID: <cdf28801-4eba-44c3-a7e1-29334edc491b@redhat.com> (raw)
In-Reply-To: <20261106121005.1327603-7-ivecera@redhat.com>



On 11/6/26 1:10 PM, Ivan Vecera wrote:
> Add KUnit tests using a mock driver with unrelated domains, a pin of one
> domain registered with a dpll of another and a pin of one domain under a
> MUX pin of another. They check that:
> 
> - objects with the same (module, clock_id) share a refcounted domain
> - callbacks of a device run under its domain and callbacks of a pin under
>    the domain of the pin alone, whichever driver registered them
> - a callback blocked in one domain does not stop unrelated domains
> - callbacks of one domain are serialized, also across its devices
> - a stuck callback of a foreign registration stops only the domain of the
>    pin, and a stuck foreign device or domain of the pin does not stop the
>    other side
> - a pin and its parent pin of another domain do not block each other
> - registrations can connect two domains in both directions
> - a topology change waits until running requests finish
> - a request setting several attributes of a pin is atomic against other
>    requests about the pin, also when it spans two domains
> - notifications from callbacks keep their order, also when a callback
>    fails
> 
> Requests that can block run in kthreads, so a test can report a locking
> bug instead of blocking itself. A real deadlock cannot be undone, the
> stuck request keeps its locks, so the teardown then hangs until the run
> times out. The handlers are called directly with a hand-built message,
> so they are exported for KUnit.
> 
> Only lockdep can tell which locks a callback holds. Without it the checks
> of held locks cannot fail, so the test that checks nothing else is
> skipped. The tests of what blocks what do not depend on it.
> 
> The tests do not cover what needs a netlink socket, for example the order
> of notifications against the ACK or the content of messages.
> 
> Run them with:
> 
>    tools/testing/kunit/kunit.py run --kunitconfig=drivers/dpll
> 
> Signed-off-by: Ivan Vecera <ivecera@redhat.com>
> ---
>   drivers/dpll/.kunitconfig      |    5 +
>   drivers/dpll/Kconfig           |   13 +
>   drivers/dpll/Makefile          |    1 +
>   drivers/dpll/dpll_core_kunit.c | 1471 ++++++++++++++++++++++++++++++++
>   drivers/dpll/dpll_netlink.c    |    7 +
>   5 files changed, 1497 insertions(+)
>   create mode 100644 drivers/dpll/.kunitconfig
>   create mode 100644 drivers/dpll/dpll_core_kunit.c
> 
> diff --git a/drivers/dpll/.kunitconfig b/drivers/dpll/.kunitconfig
> new file mode 100644
> index 00000000000000..017c9d5b593a4f
> --- /dev/null
> +++ b/drivers/dpll/.kunitconfig
> @@ -0,0 +1,5 @@
> +CONFIG_KUNIT=y
> +CONFIG_NET=y
> +CONFIG_DPLL_KUNIT_TEST=y
> +CONFIG_DEBUG_KERNEL=y
> +CONFIG_PROVE_LOCKING=y
> diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig
> index be98969f040ab6..12bcf706a15e14 100644
> --- a/drivers/dpll/Kconfig
> +++ b/drivers/dpll/Kconfig
> @@ -23,6 +23,19 @@ config DPLL_REFCNT_TRACKER
>   
>   	  If unsure, say N.
>   
> +config DPLL_KUNIT_TEST
> +	tristate "KUnit tests for DPLL core" if !KUNIT_ALL_TESTS
> +	depends on KUNIT && NET
> +	select DPLL
> +	default KUNIT_ALL_TESTS
> +	help
> +	  KUnit tests for locking in the DPLL core. They use a mock driver
> +	  to check that callbacks run under the locks of their domains and
> +	  that unrelated domains do not block each other. The checks of held
> +	  locks need PROVE_LOCKING.
> +
> +	  If unsure, say N.
> +
>   source "drivers/dpll/zl3073x/Kconfig"
>   
>   endmenu
> diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile
> index 9e7a3a3e592e81..75ee1608c76a93 100644
> --- a/drivers/dpll/Makefile
> +++ b/drivers/dpll/Makefile
> @@ -8,4 +8,5 @@ dpll-y                  += dpll_core.o
>   dpll-y                  += dpll_netlink.o
>   dpll-y                  += dpll_nl.o
>   
> +obj-$(CONFIG_DPLL_KUNIT_TEST)	+= dpll_core_kunit.o
>   obj-$(CONFIG_ZL3073X)	+= zl3073x/
> diff --git a/drivers/dpll/dpll_core_kunit.c b/drivers/dpll/dpll_core_kunit.c
> new file mode 100644
> index 00000000000000..a58800eda5ff51
> --- /dev/null
> +++ b/drivers/dpll/dpll_core_kunit.c
> @@ -0,0 +1,1471 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KUnit tests for locking in the DPLL core.
> + *
> + * Each test starts with two unrelated domains:
> + *
> + * Domain A: devices A0 and A1
> + *   pin P on A0 and A1, setting its state on A0 notifies pin S
> + *   pin S on A0 and A1
> + * Domain B: device B0
> + *   pin BP on B0
> + *
> + * Tests of objects connected by a pin add these domains:
> + *
> + * Domain E: device E0, pin X on E0 (the owner), pin M (MUX) on E0
> + * Domain F: device F0, pin X from E is also registered with F0
> + * Domain G: pin C registered under pin M from E
> + *
> + * Requests that can block run in kthreads, so a test can report a locking bug
> + * instead of blocking itself. A real deadlock cannot be undone, the stuck
> + * request keeps its locks, so the teardown then hangs until the run times out.
> + */
> +
> +#include <kunit/test.h>
> +#include <linux/completion.h>
> +#include <linux/debug_locks.h>
> +#include <linux/dpll.h>
> +#include <linux/kthread.h>
> +#include <linux/lockdep.h>
> +#include <linux/notifier.h>
> +#include <linux/property.h>
> +#include <linux/wait.h>
> +#include <net/genetlink.h>
> +#include <net/netlink.h>
> +
> +#include "dpll_core.h"
> +
> +MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
> +
> +#define DT_CLOCK_ID_A		0xd0110a
> +#define DT_CLOCK_ID_B		0xd0110b
> +#define DT_CLOCK_ID_E		0xd0110e
> +#define DT_CLOCK_ID_F		0xd0110f
> +#define DT_CLOCK_ID_G		0xd01107
> +
> +#define DT_PIN_IDX_P		0
> +#define DT_PIN_IDX_S		1
> +#define DT_PIN_IDX_BP		2
> +#define DT_PIN_IDX_M		3
> +#define DT_PIN_IDX_X		4
> +#define DT_PIN_IDX_C		5
> +#define DT_PIN_IDX_Y		6
> +#define DT_PIN_IDX_EXTRA	7
> +
> +#define DT_MAX_EVENTS		8
> +#define DT_TIMEOUT		msecs_to_jiffies(5000)
> +#define DT_SHORT_TIMEOUT	msecs_to_jiffies(100)
> +
> +struct dt_ctx;
> +
> +/* Counters and blocking of the callbacks of one driver instance */
> +struct dt_dom {
> +	struct dt_ctx *ctx;
> +	struct completion entered;
> +	atomic_t cur;
> +	atomic_t peak;
> +	bool block;
> +};
> +
> +struct dt_dev {
> +	struct dt_dom *dom;
> +	struct dpll_device *dpll;
> +	dpll_tracker tracker;
> +	struct dt_dev *notify;
> +	enum dpll_mode mode;
> +	bool block;
> +};
> +
> +struct dt_pin {
> +	struct dpll_pin *pin;
> +	dpll_tracker tracker;
> +};
> +
> +struct dt_reg {
> +	struct dt_dom *dom;
> +	struct dt_pin *pin;
> +	struct dt_dev *dev;
> +	struct dt_pin *parent;
> +	struct dt_pin *notify;
> +	struct dpll_domain *forbid;
> +	enum dpll_pin_state state;
> +	enum dpll_pin_direction direction;
> +	atomic_t freq_gets;
> +	u32 prio;
> +	bool block_prio_set;
> +};
> +
> +struct dt_ctx {
> +	struct kunit *test;
> +	struct dt_dom dom_a, dom_b, dom_e, dom_f, dom_g;
> +	struct dt_dev a0, a1, b0, e0, f0;
> +	struct dt_pin p, s, bp, x, y, m, c, extra;
> +	struct dt_reg reg_p_a0, reg_p_a1, reg_s_a0, reg_s_a1, reg_bp_b0;
> +	struct dt_reg reg_x_e0, reg_x_f0, reg_y_e0, reg_y_f0, reg_m_e0, reg_c_m;
> +	struct dt_reg *atomic_regs[2];
> +	struct notifier_block nb;
> +	wait_queue_head_t wq;
> +	atomic_t cur_total;
> +	atomic_t peak_total;
> +	atomic_t wrong_lock;
> +	atomic_t bad_ntf;
> +	atomic_t torn;
> +	struct task_struct *set_task;
> +	void *events[DT_MAX_EVENTS];
> +	int num_events;
> +	bool record;
> +	bool fail_after_ntf;
> +	bool debug_locks_at_start;
> +};
> +
> +struct dt_req {
> +	struct dt_ctx *ctx;
> +	int (*fn)(void *arg);
> +	void *arg;
> +	int ret;
> +	struct completion done;
> +	bool finished;
> +};
> +
> +/* A nested attribute of a pin set request */
> +struct dt_nest {
> +	int type;
> +	u32 parent_id;
> +	int attr;
> +	u32 val;
> +};
> +
> +/* Allocated with kunit_kzalloc(), a failed assertion must not free them */
> +struct dt_pin_set_args {
> +	struct dt_ctx *ctx;
> +	struct dpll_pin *pin;
> +	struct dt_nest nests[2];
> +	int num;
> +};
> +
> +struct dt_mode_set_args {
> +	struct dpll_device *dpll;
> +	enum dpll_mode mode;
> +};
> +
> +/*
> + * The lock may be held by another task, so mutex_is_locked() is not enough.
> + * Without lockdep no check of a held lock can fail.
> + */
> +static bool dt_held(struct dpll_domain *domain)
> +{
> +#ifdef CONFIG_LOCKDEP
> +	return lockdep_is_held(&domain->lock) != 0;
> +#else
> +	return true;
> +#endif
> +}
> +
> +static bool dt_surely_held(struct dpll_domain *domain)
> +{
> +#ifdef CONFIG_LOCKDEP
> +	return lockdep_is_held(&domain->lock) > 0;
> +#else
> +	return false;
> +#endif
> +}
> +
> +static void dt_peak(atomic_t *peak, int val)
> +{
> +	int old = atomic_read(peak);
> +
> +	while (val > old && !atomic_try_cmpxchg(peak, &old, val))
> +		;
> +}
> +
> +static void dt_block(struct dt_dom *dom, bool *block)
> +{
> +	complete(&dom->entered);
> +	wait_event(dom->ctx->wq, !READ_ONCE(*block));
> +}
> +
> +static void dt_enter(struct dt_dom *dom)
> +{
> +	struct dt_ctx *ctx = dom->ctx;
> +
> +	dt_peak(&ctx->peak_total, atomic_inc_return(&ctx->cur_total));
> +	dt_peak(&dom->peak, atomic_inc_return(&dom->cur));
> +
> +	if (READ_ONCE(dom->block))
> +		dt_block(dom, &dom->block);
> +}
> +
> +static void dt_leave(struct dt_dom *dom)
> +{
> +	atomic_dec(&dom->cur);
> +	atomic_dec(&dom->ctx->cur_total);
> +}
> +
> +static void dt_dev_enter(struct dt_dev *dev, const struct dpll_device *dpll)
> +{
> +	if (!dt_held(dpll->domain))
> +		atomic_inc(&dev->dom->ctx->wrong_lock);
> +	dt_enter(dev->dom);
> +	if (READ_ONCE(dev->block))
> +		dt_block(dev->dom, &dev->block);
> +}
> +
> +/* A callback of a pin runs under the domain of the pin alone */
> +static void dt_pin_enter(struct dt_reg *reg, const struct dpll_pin *pin)
> +{
> +	struct dt_ctx *ctx = reg->dom->ctx;
> +
> +	if (!dt_held(pin->domain) ||
> +	    (reg->forbid && dt_surely_held(reg->forbid)))
> +		atomic_inc(&ctx->wrong_lock);
> +	dt_enter(reg->dom);
> +}
> +
> +static int dt_mode_get(const struct dpll_device *dpll, void *priv,
> +		       enum dpll_mode *mode, struct netlink_ext_ack *extack)
> +{
> +	struct dt_dev *dev = priv;
> +
> +	dt_dev_enter(dev, dpll);
> +	*mode = dev->mode;
> +	dt_leave(dev->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_mode_set(const struct dpll_device *dpll, void *priv,
> +		       enum dpll_mode mode, struct netlink_ext_ack *extack)
> +{
> +	struct dt_dev *dev = priv;
> +
> +	dt_dev_enter(dev, dpll);
> +	dev->mode = mode;
> +	dt_leave(dev->dom);
> +
> +	if (dev->notify)
> +		__dpll_device_change_ntf(dev->notify->dpll);
> +
> +	return 0;
> +}
> +
> +static int dt_supported_modes_get(const struct dpll_device *dpll, void *priv,
> +				  unsigned long *modes,
> +				  struct netlink_ext_ack *extack)
> +{
> +	struct dt_dev *dev = priv;
> +
> +	dt_dev_enter(dev, dpll);
> +	__set_bit(DPLL_MODE_AUTOMATIC, modes);
> +	__set_bit(DPLL_MODE_MANUAL, modes);
> +	dt_leave(dev->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_lock_status_get(const struct dpll_device *dpll, void *priv,
> +			      enum dpll_lock_status *status,
> +			      enum dpll_lock_status_error *status_error,
> +			      struct netlink_ext_ack *extack)
> +{
> +	struct dt_dev *dev = priv;
> +
> +	dt_dev_enter(dev, dpll);
> +	*status = DPLL_LOCK_STATUS_LOCKED;
> +	dt_leave(dev->dom);
> +
> +	return 0;
> +}
> +
> +static const struct dpll_device_ops dt_dev_ops = {
> +	.mode_get = dt_mode_get,
> +	.mode_set = dt_mode_set,
> +	.supported_modes_get = dt_supported_modes_get,
> +	.lock_status_get = dt_lock_status_get,
> +};
> +
> +static int dt_frequency_get(const struct dpll_pin *pin, void *pin_priv,
> +			    const struct dpll_device *dpll, void *dpll_priv,
> +			    u64 *frequency, struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	atomic_inc(&reg->freq_gets);
> +	*frequency = 1;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_state_on_dpll_get(const struct dpll_pin *pin, void *pin_priv,
> +				const struct dpll_device *dpll, void *dpll_priv,
> +				enum dpll_pin_state *state,
> +				struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	*state = reg->state;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_state_on_dpll_set(const struct dpll_pin *pin, void *pin_priv,
> +				const struct dpll_device *dpll, void *dpll_priv,
> +				const enum dpll_pin_state state,
> +				struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	reg->state = state;
> +	dt_leave(reg->dom);
> +
> +	if (reg->notify)
> +		__dpll_pin_change_ntf(reg->notify->pin);
> +
> +	return reg->dom->ctx->fail_after_ntf ? -EIO : 0;
> +}
> +
> +static int dt_state_on_pin_get(const struct dpll_pin *pin, void *pin_priv,
> +			       const struct dpll_pin *parent_pin,
> +			       void *parent_pin_priv,
> +			       enum dpll_pin_state *state,
> +			       struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	*state = reg->state;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_state_on_pin_set(const struct dpll_pin *pin, void *pin_priv,
> +			       const struct dpll_pin *parent_pin,
> +			       void *parent_pin_priv,
> +			       const enum dpll_pin_state state,
> +			       struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	reg->state = state;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_prio_get(const struct dpll_pin *pin, void *pin_priv,
> +		       const struct dpll_device *dpll, void *dpll_priv,
> +		       u32 *prio, struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +	struct dt_ctx *ctx = reg->dom->ctx;
> +
> +	dt_pin_enter(reg, pin);
> +	/* Only the request doing the set may see it half done */
> +	if (READ_ONCE(ctx->set_task) && current != ctx->set_task &&
> +	    ctx->atomic_regs[0]->prio != ctx->atomic_regs[1]->prio)
> +		atomic_inc(&ctx->torn);
> +	*prio = reg->prio;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_prio_set(const struct dpll_pin *pin, void *pin_priv,
> +		       const struct dpll_device *dpll, void *dpll_priv,
> +		       const u32 prio, struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	if (READ_ONCE(reg->block_prio_set))
> +		dt_block(reg->dom, &reg->block_prio_set);
> +	reg->prio = prio;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_direction_get(const struct dpll_pin *pin, void *pin_priv,
> +			    const struct dpll_device *dpll, void *dpll_priv,
> +			    enum dpll_pin_direction *direction,
> +			    struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	*direction = reg->direction;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_direction_set(const struct dpll_pin *pin, void *pin_priv,
> +			    const struct dpll_device *dpll, void *dpll_priv,
> +			    const enum dpll_pin_direction direction,
> +			    struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	reg->direction = direction;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static int dt_ref_sync_get(const struct dpll_pin *pin, void *pin_priv,
> +			   const struct dpll_pin *ref_sync_pin,
> +			   void *ref_sync_pin_priv, enum dpll_pin_state *state,
> +			   struct netlink_ext_ack *extack)
> +{
> +	struct dt_reg *reg = pin_priv;
> +
> +	dt_pin_enter(reg, pin);
> +	*state = DPLL_PIN_STATE_DISCONNECTED;
> +	dt_leave(reg->dom);
> +
> +	return 0;
> +}
> +
> +static const struct dpll_pin_ops dt_pin_ops = {
> +	.frequency_get = dt_frequency_get,
> +	.state_on_dpll_get = dt_state_on_dpll_get,
> +	.state_on_dpll_set = dt_state_on_dpll_set,
> +	.state_on_pin_get = dt_state_on_pin_get,
> +	.state_on_pin_set = dt_state_on_pin_set,
> +	.prio_get = dt_prio_get,
> +	.prio_set = dt_prio_set,
> +	.direction_get = dt_direction_get,
> +	.direction_set = dt_direction_set,
> +	.ref_sync_get = dt_ref_sync_get,
> +};
> +
> +static const struct dpll_pin_properties dt_prop = {
> +	.type = DPLL_PIN_TYPE_EXT,
> +	.capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE |
> +			DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE |
> +			DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE,
> +};
> +
> +static const struct dpll_pin_properties dt_prop_mux = {
> +	.type = DPLL_PIN_TYPE_MUX,
> +	.capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
> +};
> +
> +/* Other dpll drivers in the system can send notifications at any time */
> +static bool dt_own_clock_id(u64 clock_id)
> +{
> +	switch (clock_id) {
> +	case DT_CLOCK_ID_A:
> +	case DT_CLOCK_ID_B:
> +	case DT_CLOCK_ID_E:
> +	case DT_CLOCK_ID_F:
> +	case DT_CLOCK_ID_G:
> +		return true;
> +	default:
> +		return false;
> +	}
> +}
> +
> +static int dt_notifier(struct notifier_block *nb, unsigned long action,
> +		       void *data)
> +{
> +	struct dt_ctx *ctx = container_of(nb, struct dt_ctx, nb);
> +	struct dpll_device_notifier_info *dev_info = data;
> +	struct dpll_pin_notifier_info *pin_info = data;
> +	struct dpll_domain *domain;
> +	u64 clock_id;
> +	void *obj;
> +
> +	switch (action) {
> +	case DPLL_DEVICE_CHANGED:
> +		clock_id = dev_info->clock_id;
> +		obj = dev_info->dpll;
> +		domain = dev_info->dpll->domain;
> +		break;
> +	case DPLL_PIN_CHANGED:
> +		clock_id = pin_info->clock_id;
> +		obj = pin_info->pin;
> +		domain = pin_info->pin->domain;
> +		break;
> +	default:
> +		return NOTIFY_DONE;
> +	}
> +
> +	if (!dt_own_clock_id(clock_id) || !ctx->record)
> +		return NOTIFY_DONE;
> +
> +	if (!dt_held(domain))
> +		atomic_inc(&ctx->bad_ntf);
> +	if (ctx->num_events < DT_MAX_EVENTS)
> +		ctx->events[ctx->num_events] = obj;
> +	ctx->num_events++;
> +
> +	return NOTIFY_OK;
> +}
> +
> +static void dt_final_check(void *data)
> +{
> +	struct dt_ctx *ctx = data;
> +
> +	KUNIT_EXPECT_EQ(ctx->test, atomic_read(&ctx->wrong_lock), 0);
> +	if (ctx->debug_locks_at_start)
> +		KUNIT_EXPECT_TRUE(ctx->test, debug_locks);
> +}
> +
> +static void dt_dev_release(void *data)
> +{
> +	struct dt_dev *dev = data;
> +
> +	dpll_device_unregister(dev->dpll, &dt_dev_ops, dev);
> +	dpll_device_put(dev->dpll, &dev->tracker);
> +}
> +
> +static void dt_dev_add(struct kunit *test, struct dt_dev *dev,
> +		       struct dt_dom *dom, u64 clock_id, u32 idx,
> +		       enum dpll_type type)
> +{
> +	int ret;
> +
> +	dev->dom = dom;
> +	dev->mode = DPLL_MODE_AUTOMATIC;
> +	dev->dpll = dpll_device_get(clock_id, idx, THIS_MODULE, &dev->tracker);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dev->dpll);
> +
> +	ret = dpll_device_register(dev->dpll, type, &dt_dev_ops, dev);
> +	if (ret)
> +		dpll_device_put(dev->dpll, &dev->tracker);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_dev_release,
> +							dev), 0);
> +}
> +
> +static void dt_pin_release(void *data)
> +{
> +	struct dt_pin *pin = data;
> +
> +	if (!IS_ERR_OR_NULL(pin->pin))
> +		dpll_pin_put(pin->pin, &pin->tracker);
> +}
> +
> +static void dt_pin_add(struct kunit *test, struct dt_pin *pin, u64 clock_id,
> +		       u32 idx, const struct dpll_pin_properties *prop)
> +{
> +	pin->pin = dpll_pin_get(clock_id, idx, THIS_MODULE, prop,
> +				&pin->tracker);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, pin->pin);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_pin_release,
> +							pin), 0);
> +}
> +
> +static void dt_reg_release(void *data)
> +{
> +	struct dt_reg *reg = data;
> +
> +	if (reg->parent)
> +		dpll_pin_on_pin_unregister(reg->parent->pin, reg->pin->pin,
> +					   &dt_pin_ops, reg);
> +	else
> +		dpll_pin_unregister(reg->dev->dpll, reg->pin->pin, &dt_pin_ops,
> +				    reg);
> +}
> +
> +static void dt_reg_add(struct kunit *test, struct dt_reg *reg,
> +		       struct dt_dom *dom, struct dt_pin *pin,
> +		       struct dt_dev *dev, struct dt_pin *parent)
> +{
> +	int ret;
> +
> +	reg->dom = dom;
> +	reg->pin = pin;
> +	reg->dev = dev;
> +	reg->parent = parent;
> +	reg->state = DPLL_PIN_STATE_SELECTABLE;
> +	reg->direction = DPLL_PIN_DIRECTION_INPUT;
> +
> +	if (parent)
> +		ret = dpll_pin_on_pin_register(parent->pin, pin->pin,
> +					       &dt_pin_ops, reg);
> +	else
> +		ret = dpll_pin_register(dev->dpll, pin->pin, &dt_pin_ops, reg);
> +	KUNIT_ASSERT_EQ(test, ret, 0);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_reg_release,
> +							reg), 0);
> +}
> +
> +static void dt_fwnode_release(void *data)
> +{
> +	fwnode_remove_software_node(data);
> +}
> +
> +/* Creates a pin with a fwnode so that it can be registered with another dpll */
> +static void dt_fwnode_pin_add(struct kunit *test, struct dt_pin *pin,
> +			      u64 clock_id, u32 idx)
> +{
> +	struct fwnode_handle *fwnode;
> +
> +	fwnode = fwnode_create_software_node(NULL, NULL);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, fwnode);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_fwnode_release,
> +							fwnode), 0);
> +	dt_pin_add(test, pin, clock_id, idx, &dt_prop);
> +	dpll_pin_fwnode_set(pin->pin, fwnode);
> +}
> +
> +/* Builds domains E and F connected by pin X */
> +static void dt_foreign_pin_add(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_dev_add(test, &ctx->e0, &ctx->dom_e, DT_CLOCK_ID_E, 0,
> +		   DPLL_TYPE_PPS);
> +	dt_dev_add(test, &ctx->f0, &ctx->dom_f, DT_CLOCK_ID_F, 0,
> +		   DPLL_TYPE_PPS);
> +	dt_fwnode_pin_add(test, &ctx->x, DT_CLOCK_ID_E, DT_PIN_IDX_X);
> +	dt_reg_add(test, &ctx->reg_x_e0, &ctx->dom_e, &ctx->x, &ctx->e0, NULL);
> +	dt_reg_add(test, &ctx->reg_x_f0, &ctx->dom_f, &ctx->x, &ctx->f0, NULL);
> +
> +	/* No callback of the pin takes the lock of the other domain */
> +	ctx->reg_x_e0.forbid = ctx->f0.dpll->domain;
> +	ctx->reg_x_f0.forbid = ctx->f0.dpll->domain;
> +}
> +
> +/*
> + * Builds domains E and G connected by pin C under pin M. The pins are added
> + * first, so that domain G is freed only after the registrations are gone.
> + */
> +static void dt_on_pin_add(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_dev_add(test, &ctx->e0, &ctx->dom_e, DT_CLOCK_ID_E, 0,
> +		   DPLL_TYPE_PPS);
> +	dt_pin_add(test, &ctx->m, DT_CLOCK_ID_E, DT_PIN_IDX_M, &dt_prop_mux);
> +	dt_pin_add(test, &ctx->c, DT_CLOCK_ID_G, DT_PIN_IDX_C, &dt_prop);
> +	dt_reg_add(test, &ctx->reg_m_e0, &ctx->dom_e, &ctx->m, &ctx->e0, NULL);
> +	dt_reg_add(test, &ctx->reg_c_m, &ctx->dom_g, &ctx->c, NULL, &ctx->m);
> +
> +	ctx->reg_c_m.forbid = ctx->e0.dpll->domain;
> +	ctx->reg_m_e0.forbid = ctx->c.pin->domain;
> +}
> +
> +static void dt_notifier_release(void *data)
> +{
> +	struct dt_ctx *ctx = data;
> +
> +	unregister_dpll_notifier(&ctx->nb);
> +}
> +
> +static void dt_dom_init(struct dt_dom *dom, struct dt_ctx *ctx)
> +{
> +	dom->ctx = ctx;
> +	init_completion(&dom->entered);
> +}
> +
> +static int dt_init(struct kunit *test)
> +{
> +	struct dt_ctx *ctx;
> +
> +	ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, ctx);
> +	test->priv = ctx;
> +
> +	ctx->test = test;
> +	dt_dom_init(&ctx->dom_a, ctx);
> +	dt_dom_init(&ctx->dom_b, ctx);
> +	dt_dom_init(&ctx->dom_e, ctx);
> +	dt_dom_init(&ctx->dom_f, ctx);
> +	dt_dom_init(&ctx->dom_g, ctx);
> +	init_waitqueue_head(&ctx->wq);
> +	ctx->debug_locks_at_start = debug_locks;
> +
> +	/* Runs last, after the deletion of all objects */
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_final_check,
> +							ctx), 0);
> +
> +	dt_dev_add(test, &ctx->a0, &ctx->dom_a, DT_CLOCK_ID_A, 0,
> +		   DPLL_TYPE_PPS);
> +	dt_dev_add(test, &ctx->a1, &ctx->dom_a, DT_CLOCK_ID_A, 1,
> +		   DPLL_TYPE_EEC);
> +	dt_dev_add(test, &ctx->b0, &ctx->dom_b, DT_CLOCK_ID_B, 0,
> +		   DPLL_TYPE_PPS);
> +	ctx->a0.notify = &ctx->a1;
> +
> +	dt_pin_add(test, &ctx->p, DT_CLOCK_ID_A, DT_PIN_IDX_P, &dt_prop);
> +	dt_reg_add(test, &ctx->reg_p_a0, &ctx->dom_a, &ctx->p, &ctx->a0, NULL);
> +	dt_reg_add(test, &ctx->reg_p_a1, &ctx->dom_a, &ctx->p, &ctx->a1, NULL);
> +	ctx->reg_p_a0.notify = &ctx->s;
> +
> +	dt_pin_add(test, &ctx->s, DT_CLOCK_ID_A, DT_PIN_IDX_S, &dt_prop);
> +	dt_reg_add(test, &ctx->reg_s_a0, &ctx->dom_a, &ctx->s, &ctx->a0, NULL);
> +	dt_reg_add(test, &ctx->reg_s_a1, &ctx->dom_a, &ctx->s, &ctx->a1, NULL);
> +
> +	dt_pin_add(test, &ctx->bp, DT_CLOCK_ID_B, DT_PIN_IDX_BP, &dt_prop);
> +	dt_reg_add(test, &ctx->reg_bp_b0, &ctx->dom_b, &ctx->bp, &ctx->b0,
> +		   NULL);
> +
> +	ctx->nb.notifier_call = dt_notifier;
> +	KUNIT_ASSERT_EQ(test, register_dpll_notifier(&ctx->nb), 0);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test,
> +							dt_notifier_release,
> +							ctx), 0);
> +
> +	atomic_set(&ctx->peak_total, 0);
> +	atomic_set(&ctx->dom_a.peak, 0);
> +	atomic_set(&ctx->dom_b.peak, 0);
> +
> +	return 0;
> +}
> +
> +static int dt_device_ntf(void *arg)
> +{
> +	struct dt_dev *dev = arg;
> +
> +	return dpll_device_change_ntf(dev->dpll);
> +}
> +
> +static int dt_pin_ntf(void *arg)
> +{
> +	struct dt_pin *pin = arg;
> +
> +	return dpll_pin_change_ntf(pin->pin);
> +}
> +
> +static int dt_extra_pin_get(void *arg)
> +{
> +	struct dt_pin *pin = arg;
> +
> +	pin->pin = dpll_pin_get(DT_CLOCK_ID_B, DT_PIN_IDX_EXTRA, THIS_MODULE,
> +				&dt_prop, &pin->tracker);
> +
> +	return PTR_ERR_OR_ZERO(pin->pin);
> +}
> +
> +static void dt_unblock(struct dt_ctx *ctx)
> +{
> +	WRITE_ONCE(ctx->dom_a.block, false);
> +	WRITE_ONCE(ctx->dom_b.block, false);
> +	WRITE_ONCE(ctx->dom_e.block, false);
> +	WRITE_ONCE(ctx->dom_f.block, false);
> +	WRITE_ONCE(ctx->dom_g.block, false);
> +	WRITE_ONCE(ctx->f0.block, false);
> +	WRITE_ONCE(ctx->reg_p_a0.block_prio_set, false);
> +	WRITE_ONCE(ctx->reg_x_e0.block_prio_set, false);
> +	WRITE_ONCE(ctx->reg_x_f0.block_prio_set, false);
> +	wake_up_all(&ctx->wq);
> +}
> +
> +static int dt_req_thread(void *data)
> +{
> +	struct dt_req *req = data;
> +
> +	req->ret = req->fn(req->arg);
> +	kthread_complete_and_exit(&req->done, 0);
> +}
> +
> +/* Runs before the objects used by the request are released */
> +static void dt_req_release(void *data)
> +{
> +	struct dt_req *req = data;
> +
> +	dt_unblock(req->ctx);
> +	if (!req->finished)
> +		wait_for_completion(&req->done);
> +}
> +
> +static struct dt_req *dt_req_start(struct kunit *test, int (*fn)(void *arg),
> +				   void *arg)
> +{
> +	struct task_struct *task;
> +	struct dt_req *req;
> +
> +	req = kunit_kzalloc(test, sizeof(*req), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, req);
> +
> +	req->ctx = test->priv;
> +	req->fn = fn;
> +	req->arg = arg;
> +	init_completion(&req->done);
> +
> +	task = kthread_run(dt_req_thread, req, "dpll-test");
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, task);
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_req_release,
> +							req), 0);
> +
> +	return req;
> +}
> +
> +/*
> + * Consumes the completion, so it must not be waited for again. Assert it before
> + * reading what the request wrote.
> + */
> +static bool dt_done(struct dt_req *req, unsigned long timeout)
> +{
> +	if (!req->finished && wait_for_completion_timeout(&req->done, timeout))
> +		req->finished = true;
> +
> +	return req->finished;
> +}
> +
> +/* Blocks a request of @dom in a callback, which holds the domain lock */
> +static struct dt_req *dt_block_in(struct kunit *test, struct dt_dom *dom,
> +				  int (*fn)(void *arg), void *arg)
> +{
> +	struct dt_req *req;
> +
> +	WRITE_ONCE(dom->block, true);
> +	req = dt_req_start(test, fn, arg);
> +	KUNIT_ASSERT_TRUE(test, wait_for_completion_timeout(&dom->entered,
> +							    DT_TIMEOUT));
> +
> +	return req;
> +}
> +
> +/* Build a set request and pass it to the handlers genetlink would call */
> +static int dt_pin_set_fn(void *arg)
> +{
> +	struct dt_pin_set_args *args = arg;
> +	struct nlattr *attrs[DPLL_A_PIN_MAX + 1] = {};
> +	struct genl_info info = {};
> +	struct nlmsghdr *nlh;
> +	struct sk_buff *skb;
> +	struct nlattr *nest;
> +	int i, ret;
> +
> +	if (args->ctx)
> +		WRITE_ONCE(args->ctx->set_task, current);
> +
> +	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> +	if (!skb)
> +		return -ENOMEM;
> +	ret = -EMSGSIZE;
> +	nlh = nlmsg_put(skb, 0, 0, NLMSG_MIN_TYPE, GENL_HDRLEN, 0);
> +	if (!nlh || nla_put_u32(skb, DPLL_A_PIN_ID, args->pin->id))
> +		goto out;
> +	for (i = 0; i < args->num; i++) {
> +		const struct dt_nest *n = &args->nests[i];
> +
> +		nest = nla_nest_start(skb, n->type);
> +		if (!nest ||
> +		    nla_put_u32(skb, DPLL_A_PIN_PARENT_ID, n->parent_id) ||
> +		    nla_put_u32(skb, n->attr, n->val))
> +			goto out;
> +		nla_nest_end(skb, nest);
> +	}
> +	nlmsg_end(skb, nlh);
> +
> +	info.genlhdr = nlmsg_data(nlh);
> +	info.attrs = attrs;
> +	attrs[DPLL_A_PIN_ID] = nla_find(genlmsg_data(info.genlhdr),
> +					genlmsg_len(info.genlhdr),
> +					DPLL_A_PIN_ID);
> +
> +	ret = dpll_pin_pre_doit(NULL, skb, &info);
> +	if (!ret) {
> +		ret = dpll_nl_pin_set_doit(skb, &info);
> +		dpll_pin_post_doit(NULL, skb, &info);
> +	}
> +out:
> +	nlmsg_free(skb);
> +
> +	return ret;
> +}
> +
> +static int dt_mode_set_fn(void *arg)
> +{
> +	struct dt_mode_set_args *args = arg;
> +	struct nlattr *attrs[DPLL_A_MAX + 1] = {};
> +	struct genl_info info = {};
> +	struct nlmsghdr *nlh;
> +	struct sk_buff *skb;
> +	int ret;
> +
> +	skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
> +	if (!skb)
> +		return -ENOMEM;
> +	ret = -EMSGSIZE;
> +	nlh = nlmsg_put(skb, 0, 0, NLMSG_MIN_TYPE, GENL_HDRLEN, 0);
> +	if (!nlh || nla_put_u32(skb, DPLL_A_ID, args->dpll->id) ||
> +	    nla_put_u32(skb, DPLL_A_MODE, args->mode))
> +		goto out;
> +	nlmsg_end(skb, nlh);
> +
> +	info.genlhdr = nlmsg_data(nlh);
> +	info.attrs = attrs;
> +	attrs[DPLL_A_ID] = nla_find(genlmsg_data(info.genlhdr),
> +				    genlmsg_len(info.genlhdr), DPLL_A_ID);
> +
> +	ret = dpll_pre_doit(NULL, skb, &info);
> +	if (!ret) {
> +		ret = dpll_nl_device_set_doit(skb, &info);
> +		dpll_post_doit(NULL, skb, &info);
> +	}
> +out:
> +	nlmsg_free(skb);
> +
> +	return ret;
> +}
> +
> +/* Runs a request that should not block in a kthread and returns its result */
> +static int dt_run(struct kunit *test, int (*fn)(void *arg), void *arg)
> +{
> +	struct dt_req *req = dt_req_start(test, fn, arg);
> +
> +	KUNIT_ASSERT_TRUE(test, dt_done(req, DT_TIMEOUT));
> +
> +	return req->ret;
> +}
> +
> +static struct dt_pin_set_args *dt_pin_set_args_alloc(struct kunit *test,
> +						     struct dpll_pin *pin,
> +						     int num)
> +{
> +	struct dt_pin_set_args *args;
> +
> +	args = kunit_kzalloc(test, sizeof(*args), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, args);
> +	args->pin = pin;
> +	args->num = num;
> +
> +	return args;
> +}
> +
> +static struct dt_pin_set_args *dt_pin_set_args_one(struct kunit *test,
> +						   struct dpll_pin *pin,
> +						   int type, u32 parent_id,
> +						   int attr, u32 val)
> +{
> +	struct dt_pin_set_args *args = dt_pin_set_args_alloc(test, pin, 1);
> +
> +	args->nests[0].type = type;
> +	args->nests[0].parent_id = parent_id;
> +	args->nests[0].attr = attr;
> +	args->nests[0].val = val;
> +
> +	return args;
> +}
> +
> +static int dt_pin_set_one(struct kunit *test, struct dpll_pin *pin, int type,
> +			  u32 parent_id, int attr, u32 val)
> +{
> +	return dt_run(test, dt_pin_set_fn,
> +		      dt_pin_set_args_one(test, pin, type, parent_id, attr,
> +					  val));
> +}
> +
> +static void dt_expect_events(struct kunit *test, void * const *expected,
> +			     int num)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int i;
> +
> +	KUNIT_ASSERT_EQ(test, ctx->num_events, num);
> +	for (i = 0; i < num; i++)
> +		KUNIT_EXPECT_PTR_EQ(test, ctx->events[i], expected[i]);
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->bad_ntf), 0);
> +}
> +
> +/* Objects with the same (module, clock_id) share a refcounted domain */
> +static void dt_test_domain_sharing(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dpll_domain *a = ctx->a0.dpll->domain;
> +	struct dpll_domain *b = ctx->b0.dpll->domain;
> +
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->a1.dpll->domain, a);
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->p.pin->domain, a);
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->bp.pin->domain, b);
> +	KUNIT_EXPECT_PTR_NE(test, a, b);
> +
> +	KUNIT_EXPECT_EQ(test, refcount_read(&a->refcount), 4);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&b->refcount), 2);
> +
> +	KUNIT_ASSERT_EQ(test, dt_extra_pin_get(&ctx->extra), 0);
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->extra.pin->domain, b);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&b->refcount), 3);
> +	dpll_pin_put(ctx->extra.pin, &ctx->extra.tracker);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&b->refcount), 2);
> +}
> +
> +/* The same clock_id used by another module means another domain */
> +static void dt_test_domain_per_module(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	static char other_module;
> +	struct dpll_device *dpll;
> +	dpll_tracker tracker;
> +
> +	/* Never registered, so the core does not dereference the module */
> +	dpll = dpll_device_get(DT_CLOCK_ID_A, 0, (struct module *)&other_module,
> +			       &tracker);
> +	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, dpll);
> +	KUNIT_EXPECT_PTR_NE(test, dpll, ctx->a0.dpll);
> +	KUNIT_EXPECT_PTR_NE(test, dpll->domain, ctx->a0.dpll->domain);
> +	dpll_device_put(dpll, &tracker);
> +}
> +
> +/* A pin on a dpll of another domain stays in its domain */
> +static void dt_test_foreign_registration_domain(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dpll_domain *e, *f;
> +
> +	dt_foreign_pin_add(test);
> +	e = ctx->e0.dpll->domain;
> +	f = ctx->f0.dpll->domain;
> +
> +	KUNIT_EXPECT_PTR_NE(test, e, f);
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->x.pin->domain, e);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&e->refcount), 2);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&f->refcount), 1);
> +}
> +
> +/* A pin under a parent pin of another domain stays in its domain */
> +static void dt_test_on_pin_registration_domain(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dpll_domain *e, *g;
> +
> +	dt_on_pin_add(test);
> +	e = ctx->e0.dpll->domain;
> +	g = ctx->c.pin->domain;
> +
> +	KUNIT_EXPECT_PTR_NE(test, e, g);
> +	KUNIT_EXPECT_PTR_EQ(test, ctx->m.pin->domain, e);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&e->refcount), 2);
> +	KUNIT_EXPECT_EQ(test, refcount_read(&g->refcount), 1);
> +}
> +
> +/* A callback blocked in one domain does not stop other domains */
> +static void dt_test_domains_run_in_parallel(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *blocked, *other;
> +
> +	blocked = dt_block_in(test, &ctx->dom_a, dt_device_ntf, &ctx->a0);
> +
> +	other = dt_req_start(test, dt_device_ntf, &ctx->b0);
> +	KUNIT_EXPECT_TRUE(test, dt_done(other, DT_TIMEOUT));
> +	KUNIT_EXPECT_FALSE(test, completion_done(&blocked->done));
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->peak_total), 2);
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +}
> +
> +/* Callbacks of one domain are serialized, across its devices too */
> +static void dt_test_domain_is_serialized(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *first, *second;
> +
> +	first = dt_block_in(test, &ctx->dom_a, dt_device_ntf, &ctx->a0);
> +
> +	second = dt_req_start(test, dt_device_ntf, &ctx->a1);
> +	KUNIT_EXPECT_FALSE(test, dt_done(second, DT_SHORT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(first, DT_TIMEOUT));
> +	KUNIT_EXPECT_TRUE(test, dt_done(second, DT_TIMEOUT));
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->dom_a.peak), 1);
> +}
> +
> +/* Callbacks of all refs of a pin run under the domain of the pin alone */
> +static void dt_test_foreign_get_locks(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	if (!IS_ENABLED(CONFIG_LOCKDEP) || !debug_locks)
> +		kunit_skip(test, "needs lockdep");
> +
> +	dt_foreign_pin_add(test);
> +
> +	KUNIT_EXPECT_EQ(test, dt_run(test, dt_pin_ntf, &ctx->x), 0);
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->wrong_lock), 0);
> +}
> +
> +/* Setters of a foreign ref run under the domain of the pin alone */
> +static void dt_test_foreign_set_locks(struct kunit *test)
> +{
> +	static const struct {
> +		int attr;
> +		u32 val;
> +	} sets[] = {
> +		{ DPLL_A_PIN_STATE, DPLL_PIN_STATE_CONNECTED },
> +		{ DPLL_A_PIN_PRIO, 7 },
> +		{ DPLL_A_PIN_DIRECTION, DPLL_PIN_DIRECTION_OUTPUT },
> +	};
> +	struct dt_ctx *ctx = test->priv;
> +	int i;
> +
> +	dt_foreign_pin_add(test);
> +
> +	for (i = 0; i < ARRAY_SIZE(sets); i++) {
> +		int ret;
> +
> +		ctx->num_events = 0;
> +		ctx->record = true;
> +		ret = dt_pin_set_one(test, ctx->x.pin, DPLL_A_PIN_PARENT_DEVICE,
> +				     ctx->f0.dpll->id, sets[i].attr,
> +				     sets[i].val);
> +		ctx->record = false;
> +
> +		KUNIT_EXPECT_EQ(test, ret, 0);
> +		dt_expect_events(test, (void *[]){ ctx->x.pin }, 1);
> +	}
> +	KUNIT_EXPECT_EQ(test, ctx->reg_x_f0.state, DPLL_PIN_STATE_CONNECTED);
> +	KUNIT_EXPECT_EQ(test, ctx->reg_x_f0.prio, 7);
> +	KUNIT_EXPECT_EQ(test, ctx->reg_x_f0.direction,
> +			DPLL_PIN_DIRECTION_OUTPUT);
> +	KUNIT_EXPECT_EQ(test, ctx->reg_x_e0.prio, 0);
> +}
> +
> +/* The callback of a foreign ref can notify objects of the domain of the pin */
> +static void dt_test_foreign_callback_notifies_pin(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int ret;
> +
> +	dt_foreign_pin_add(test);
> +	ctx->reg_x_f0.notify = &ctx->x;
> +
> +	ctx->record = true;
> +	ret = dt_pin_set_one(test, ctx->x.pin, DPLL_A_PIN_PARENT_DEVICE,
> +			     ctx->f0.dpll->id, DPLL_A_PIN_STATE,
> +			     DPLL_PIN_STATE_CONNECTED);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, 0);
> +	dt_expect_events(test, (void *[]){ ctx->x.pin, ctx->x.pin }, 2);
> +}
> +
> +/* Without an owner the pin-level attributes come from the foreign ref */
> +static void dt_test_pin_without_owner(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_dev_add(test, &ctx->f0, &ctx->dom_f, DT_CLOCK_ID_F, 0,
> +		   DPLL_TYPE_PPS);
> +	dt_fwnode_pin_add(test, &ctx->x, DT_CLOCK_ID_E, DT_PIN_IDX_X);
> +	dt_reg_add(test, &ctx->reg_x_f0, &ctx->dom_f, &ctx->x, &ctx->f0, NULL);
> +	KUNIT_ASSERT_PTR_NE(test, ctx->x.pin->domain, ctx->f0.dpll->domain);
> +	ctx->reg_x_f0.forbid = ctx->f0.dpll->domain;
> +
> +	atomic_set(&ctx->reg_x_f0.freq_gets, 0);
> +	KUNIT_EXPECT_EQ(test, dt_run(test, dt_pin_ntf, &ctx->x), 0);
> +	KUNIT_EXPECT_GT(test, atomic_read(&ctx->reg_x_f0.freq_gets), 0);
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->wrong_lock), 0);
> +}
> +
> +/* A stuck callback of a foreign ref stops only the domain of the pin */
> +static void dt_test_stuck_foreign_callback(struct kunit *test)
> +{
> +	struct dt_req *blocked, *owner, *foreign, *other;
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_foreign_pin_add(test);
> +
> +	WRITE_ONCE(ctx->reg_x_f0.block_prio_set, true);
> +	blocked = dt_req_start(test, dt_pin_set_fn,
> +			       dt_pin_set_args_one(test, ctx->x.pin,
> +						   DPLL_A_PIN_PARENT_DEVICE,
> +						   ctx->f0.dpll->id,
> +						   DPLL_A_PIN_PRIO, 7));
> +	KUNIT_ASSERT_TRUE(test, wait_for_completion_timeout(&ctx->dom_f.entered,
> +							    DT_TIMEOUT));
> +
> +	owner = dt_req_start(test, dt_device_ntf, &ctx->e0);
> +	KUNIT_EXPECT_FALSE(test, dt_done(owner, DT_SHORT_TIMEOUT));
> +	foreign = dt_req_start(test, dt_device_ntf, &ctx->f0);
> +	KUNIT_EXPECT_TRUE(test, dt_done(foreign, DT_TIMEOUT));
> +	other = dt_req_start(test, dt_device_ntf, &ctx->b0);
> +	KUNIT_EXPECT_TRUE(test, dt_done(other, DT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +	KUNIT_EXPECT_TRUE(test, dt_done(owner, DT_TIMEOUT));
> +}
> +
> +/* A stuck device callback does not stop a foreign pin registered with it */
> +static void dt_test_stuck_foreign_device(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *blocked, *pin;
> +
> +	dt_foreign_pin_add(test);
> +
> +	WRITE_ONCE(ctx->f0.block, true);
> +	blocked = dt_req_start(test, dt_device_ntf, &ctx->f0);
> +	KUNIT_ASSERT_TRUE(test, wait_for_completion_timeout(&ctx->dom_f.entered,
> +							    DT_TIMEOUT));
> +
> +	pin = dt_req_start(test, dt_pin_ntf, &ctx->x);
> +	KUNIT_EXPECT_TRUE(test, dt_done(pin, DT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +}
> +
> +/* A stuck owner stops requests for its pins, but not the foreign device */
> +static void dt_test_stuck_owner_domain(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *blocked, *foreign, *pin;
> +
> +	dt_foreign_pin_add(test);
> +
> +	blocked = dt_block_in(test, &ctx->dom_e, dt_device_ntf, &ctx->e0);
> +
> +	foreign = dt_req_start(test, dt_device_ntf, &ctx->f0);
> +	KUNIT_EXPECT_TRUE(test, dt_done(foreign, DT_TIMEOUT));
> +	pin = dt_req_start(test, dt_pin_ntf, &ctx->x);
> +	KUNIT_EXPECT_FALSE(test, dt_done(pin, DT_SHORT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +	KUNIT_EXPECT_TRUE(test, dt_done(pin, DT_TIMEOUT));
> +}
> +
> +/* A pin and its parent pin of another domain do not block each other */
> +static void dt_test_on_pin_domains_are_independent(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *blocked, *other;
> +
> +	dt_on_pin_add(test);
> +
> +	blocked = dt_block_in(test, &ctx->dom_e, dt_device_ntf, &ctx->e0);
> +	other = dt_req_start(test, dt_pin_ntf, &ctx->c);
> +	KUNIT_EXPECT_TRUE(test, dt_done(other, DT_TIMEOUT));
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +
> +	blocked = dt_block_in(test, &ctx->dom_g, dt_pin_ntf, &ctx->c);
> +	other = dt_req_start(test, dt_pin_ntf, &ctx->m);
> +	KUNIT_EXPECT_TRUE(test, dt_done(other, DT_TIMEOUT));
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +}
> +
> +/* Registrations in both directions between two domains are fine */
> +static void dt_test_registrations_both_directions(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_foreign_pin_add(test);
> +	dt_fwnode_pin_add(test, &ctx->y, DT_CLOCK_ID_F, DT_PIN_IDX_Y);
> +	dt_reg_add(test, &ctx->reg_y_f0, &ctx->dom_f, &ctx->y, &ctx->f0, NULL);
> +	dt_reg_add(test, &ctx->reg_y_e0, &ctx->dom_e, &ctx->y, &ctx->e0, NULL);
> +	ctx->reg_y_f0.forbid = ctx->e0.dpll->domain;
> +	ctx->reg_y_e0.forbid = ctx->e0.dpll->domain;
> +
> +	KUNIT_EXPECT_EQ(test, dt_run(test, dt_pin_ntf, &ctx->x), 0);
> +	KUNIT_EXPECT_EQ(test, dt_run(test, dt_pin_ntf, &ctx->y), 0);
> +}
> +
> +/* A topology change waits until running requests finish */
> +static void dt_test_topology_change_waits(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *blocked, *writer;
> +
> +	blocked = dt_block_in(test, &ctx->dom_a, dt_device_ntf, &ctx->a0);
> +
> +	/* Added before the request starts, so it runs after waiting for it */
> +	KUNIT_ASSERT_EQ(test, kunit_add_action_or_reset(test, dt_pin_release,
> +							&ctx->extra), 0);
> +	writer = dt_req_start(test, dt_extra_pin_get, &ctx->extra);
> +	KUNIT_EXPECT_FALSE(test, dt_done(writer, DT_SHORT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_EXPECT_TRUE(test, dt_done(blocked, DT_TIMEOUT));
> +	KUNIT_ASSERT_TRUE(test, dt_done(writer, DT_TIMEOUT));
> +	KUNIT_EXPECT_NOT_ERR_OR_NULL(test, ctx->extra.pin);
> +}
> +
> +/* Other requests about a pin cannot see a set of two attributes half done */
> +static void dt_check_request_atomic(struct kunit *test, struct dt_pin *pin,
> +				    struct dt_dev *dev0, struct dt_reg *reg0,
> +				    struct dt_dev *dev1, struct dt_reg *reg1)
> +{
> +	struct dt_pin_set_args *args = dt_pin_set_args_alloc(test, pin->pin, 2);
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_req *set, *other;
> +	int i;
> +
> +	args->ctx = ctx;
> +	for (i = 0; i < args->num; i++) {
> +		args->nests[i].type = DPLL_A_PIN_PARENT_DEVICE;
> +		args->nests[i].parent_id = i ? dev1->dpll->id : dev0->dpll->id;
> +		args->nests[i].attr = DPLL_A_PIN_PRIO;
> +		args->nests[i].val = 11;
> +	}
> +
> +	ctx->atomic_regs[0] = reg0;
> +	ctx->atomic_regs[1] = reg1;
> +
> +	/* The other request starts to wait before the first attribute is set */
> +	WRITE_ONCE(reg0->block_prio_set, true);
> +	set = dt_req_start(test, dt_pin_set_fn, args);
> +	KUNIT_ASSERT_TRUE(test, wait_for_completion_timeout(&reg0->dom->entered,
> +							    DT_TIMEOUT));
> +	other = dt_req_start(test, dt_pin_ntf, pin);
> +	KUNIT_EXPECT_FALSE(test, dt_done(other, DT_SHORT_TIMEOUT));
> +
> +	dt_unblock(ctx);
> +	KUNIT_ASSERT_TRUE(test, dt_done(set, DT_TIMEOUT));
> +	KUNIT_ASSERT_TRUE(test, dt_done(other, DT_TIMEOUT));
> +	KUNIT_EXPECT_EQ(test, set->ret, 0);
> +	KUNIT_EXPECT_EQ(test, reg0->prio, 11);
> +	KUNIT_EXPECT_EQ(test, reg1->prio, 11);
> +	KUNIT_EXPECT_EQ(test, atomic_read(&ctx->torn), 0);
> +}
> +
> +static void dt_test_request_is_atomic(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_check_request_atomic(test, &ctx->p, &ctx->a0, &ctx->reg_p_a0,
> +				&ctx->a1, &ctx->reg_p_a1);
> +}
> +
> +static void dt_test_spanning_request_is_atomic(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +
> +	dt_foreign_pin_add(test);
> +	dt_check_request_atomic(test, &ctx->x, &ctx->e0, &ctx->reg_x_e0,
> +				&ctx->f0, &ctx->reg_x_f0);
> +}
> +
> +/* Notifications from a callback come first, under the domain lock */
> +static void dt_test_ntf_order(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int ret;
> +
> +	ctx->record = true;
> +	ret = dt_pin_set_one(test, ctx->p.pin, DPLL_A_PIN_PARENT_DEVICE,
> +			     ctx->a0.dpll->id, DPLL_A_PIN_STATE,
> +			     DPLL_PIN_STATE_CONNECTED);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, ctx->reg_p_a0.state, DPLL_PIN_STATE_CONNECTED);
> +	dt_expect_events(test, (void *[]){ ctx->s.pin, ctx->p.pin }, 2);
> +}
> +
> +/* A notification from a callback that fails is still sent */
> +static void dt_test_ntf_on_failure(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int ret;
> +
> +	ctx->fail_after_ntf = true;
> +	ctx->record = true;
> +	ret = dt_pin_set_one(test, ctx->p.pin, DPLL_A_PIN_PARENT_DEVICE,
> +			     ctx->a0.dpll->id, DPLL_A_PIN_STATE,
> +			     DPLL_PIN_STATE_CONNECTED);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, -EIO);
> +	dt_expect_events(test, (void *[]){ ctx->s.pin }, 1);
> +}
> +
> +/* A set on a pin under a parent of another domain needs only its domain */
> +static void dt_test_set_on_pin(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int ret;
> +
> +	dt_on_pin_add(test);
> +
> +	ctx->record = true;
> +	ret = dt_pin_set_one(test, ctx->c.pin, DPLL_A_PIN_PARENT_PIN,
> +			     ctx->m.pin->id, DPLL_A_PIN_STATE,
> +			     DPLL_PIN_STATE_CONNECTED);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, ctx->reg_c_m.state, DPLL_PIN_STATE_CONNECTED);
> +	dt_expect_events(test, (void *[]){ ctx->c.pin }, 1);
> +}
> +
> +/* A device callback notifies a sibling device of its domain */
> +static void dt_test_device_set_ntf(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	struct dt_mode_set_args *args;
> +	int ret;
> +
> +	args = kunit_kzalloc(test, sizeof(*args), GFP_KERNEL);
> +	KUNIT_ASSERT_NOT_NULL(test, args);
> +	args->dpll = ctx->a0.dpll;
> +	args->mode = DPLL_MODE_MANUAL;
> +
> +	ctx->record = true;
> +	ret = dt_run(test, dt_mode_set_fn, args);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, 0);
> +	KUNIT_EXPECT_EQ(test, ctx->a0.mode, DPLL_MODE_MANUAL);
> +	dt_expect_events(test, (void *[]){ ctx->a1.dpll }, 1);
> +}
> +
> +/* A change of a reference sync pair notifies under the domain of the pin */
> +static void dt_test_ref_sync_pair(struct kunit *test)
> +{
> +	struct dt_ctx *ctx = test->priv;
> +	int ret;
> +
> +	ctx->record = true;
> +	ret = dpll_pin_ref_sync_pair_add(ctx->p.pin, ctx->s.pin);
> +	ctx->record = false;
> +
> +	KUNIT_EXPECT_EQ(test, ret, 0);
> +	dt_expect_events(test, (void *[]){ ctx->p.pin }, 1);
> +}
> +
> +static struct kunit_case dt_cases[] = {
> +	KUNIT_CASE(dt_test_domain_sharing),
> +	KUNIT_CASE(dt_test_domain_per_module),
> +	KUNIT_CASE(dt_test_foreign_registration_domain),
> +	KUNIT_CASE(dt_test_on_pin_registration_domain),
> +	KUNIT_CASE(dt_test_domains_run_in_parallel),
> +	KUNIT_CASE(dt_test_domain_is_serialized),
> +	KUNIT_CASE(dt_test_foreign_get_locks),
> +	KUNIT_CASE(dt_test_foreign_set_locks),
> +	KUNIT_CASE(dt_test_foreign_callback_notifies_pin),
> +	KUNIT_CASE(dt_test_pin_without_owner),
> +	KUNIT_CASE(dt_test_stuck_foreign_callback),
> +	KUNIT_CASE(dt_test_stuck_foreign_device),
> +	KUNIT_CASE(dt_test_stuck_owner_domain),
> +	KUNIT_CASE(dt_test_on_pin_domains_are_independent),
> +	KUNIT_CASE(dt_test_registrations_both_directions),
> +	KUNIT_CASE(dt_test_topology_change_waits),
> +	KUNIT_CASE(dt_test_request_is_atomic),
> +	KUNIT_CASE(dt_test_spanning_request_is_atomic),
> +	KUNIT_CASE(dt_test_ntf_order),
> +	KUNIT_CASE(dt_test_ntf_on_failure),
> +	KUNIT_CASE(dt_test_set_on_pin),
> +	KUNIT_CASE(dt_test_device_set_ntf),
> +	KUNIT_CASE(dt_test_ref_sync_pair),
> +	{}
> +};
> +
> +static struct kunit_suite dt_suite = {
> +	.name = "dpll_core",
> +	.init = dt_init,
> +	.test_cases = dt_cases,
> +};
> +
> +kunit_test_suite(dt_suite);
> +
> +MODULE_DESCRIPTION("KUnit tests for locking in the DPLL core");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
> index 9d8f5d1ce97248..223cd1de905c1a 100644
> --- a/drivers/dpll/dpll_netlink.c
> +++ b/drivers/dpll/dpll_netlink.c
> @@ -6,6 +6,7 @@
>    *  Copyright (c) 2023 Intel and affiliates
>    *
>    */
> +#include <kunit/visibility.h>
>   #include <linux/module.h>
>   #include <linux/kernel.h>
>   #include <linux/netdevice.h>
> @@ -1836,6 +1837,7 @@ int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info)
>   
>   	return dpll_pin_set_from_nlattr(pin, info);
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_nl_pin_set_doit);
>   
>   static struct dpll_device *
>   dpll_device_find(u64 clock_id, struct nlattr *mod_name_attr,
> @@ -2010,6 +2012,7 @@ int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info)
>   
>   	return dpll_set_from_nlattr(dpll, info);
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_nl_device_set_doit);
>   
>   int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
>   {
> @@ -2070,6 +2073,7 @@ int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   	up_read(&dpll_lock);
>   	return -ENODEV;
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_pre_doit);
>   
>   void dpll_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   		    struct genl_info *info)
> @@ -2079,6 +2083,7 @@ void dpll_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   	mutex_unlock(&dpll->domain->lock);
>   	up_read(&dpll_lock);
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_post_doit);
>   
>   int
>   dpll_lock_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
> @@ -2122,6 +2127,7 @@ int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   	up_read(&dpll_lock);
>   	return ret;
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_pin_pre_doit);
>   
>   void dpll_pin_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   			struct genl_info *info)
> @@ -2131,3 +2137,4 @@ void dpll_pin_post_doit(const struct genl_split_ops *ops, struct sk_buff *skb,
>   	mutex_unlock(&pin->domain->lock);
>   	up_read(&dpll_lock);
>   }
> +EXPORT_SYMBOL_IF_KUNIT(dpll_pin_post_doit);

Reviewed-by: Petr Oros <poros@redhat.com>


  reply	other threads:[~2026-11-10 12:24 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06 12:09 [PATCH net-next 00/10] dpll: do not serialize driver callbacks under global dpll_lock Ivan Vecera
2026-11-06 12:09 ` [PATCH net-next 01/10] ice: dpll: notify TSPLL lock status from the periodic work only Ivan Vecera
2026-11-10 12:20   ` Petr Oros
2026-11-06 12:09 ` [PATCH net-next 02/10] ice: dpll: serialize changes of the SyncE CGU registers Ivan Vecera
2026-11-06 12:09 ` [PATCH net-next 03/10] dpll: add domain object Ivan Vecera
2026-11-10 12:23   ` Petr Oros
2026-11-06 12:09 ` [PATCH net-next 04/10] dpll: take domain locks around driver callbacks Ivan Vecera
2026-11-10 12:23   ` Petr Oros
2026-11-06 12:10 ` [PATCH net-next 05/10] dpll: convert dpll_lock to rwsem Ivan Vecera
2026-11-10 12:24   ` Petr Oros
2026-11-06 12:10 ` [PATCH net-next 06/10] dpll: add KUnit tests for core locking Ivan Vecera
2026-11-10 12:24   ` Petr Oros [this message]
2026-11-06 12:10 ` [PATCH net-next 07/10] dpll: reject measured_freq_get for foreign and on-pin registrations Ivan Vecera
2026-11-10 12:24   ` Petr Oros
2026-11-06 12:10 ` [PATCH net-next 08/10] dpll: zl3073x: update locking comments Ivan Vecera
2026-11-10 12:24   ` Petr Oros
2026-11-06 12:10 ` [PATCH net-next 09/10] ice: dpll: " Ivan Vecera
2026-11-06 12:10 ` [PATCH net-next 10/10] dpll: document locking rules Ivan Vecera
2026-11-10 12:25   ` Petr Oros

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=cdf28801-4eba-44c3-a7e1-29334edc491b@redhat.com \
    --to=poros@redhat.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=anthony.l.nguyen@intel.com \
    --cc=arkadiusz.kubalewski@intel.com \
    --cc=corbet@lwn.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=grzegorz.nitka@intel.com \
    --cc=intel-wired-lan@lists.osuosl.org \
    --cc=ivecera@redhat.com \
    --cc=jiri@resnulli.us \
    --cc=karol.kolacinski@intel.com \
    --cc=kuba@kernel.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=min.li@microchip.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=przemyslaw.kitszel@intel.com \
    --cc=rdunlap@infradead.org \
    --cc=richardcochran@gmail.com \
    --cc=saeedm@nvidia.com \
    --cc=skhan@linuxfoundation.org \
    --cc=tariqt@nvidia.com \
    --cc=vadim.fedorenko@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®