Re: [PATCH 2/4] drm/i915/dmc_wl: Add debugfs for untracked offsets

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 17.01.2025 19:06, Gustavo Sousa wrote:
> The DMC wakelock code needs to keep track of register offsets that need
> the wakelock for proper access. If one of the necessary offsets are
> missed, then the failure in asserting the wakelock is very likely to
> cause problems down the road.
> 
> A miss could happen for at least two different reasons:
> 
> - We might have forgotten to add the offset (or range) to the relevant
>   tables tracked by the driver in the first place.
> 
> - Or updates to either the DMC firmware or the display IP that require
>   new offsets to be tracked and we fail to realize that.
> 
> To help capture these cases, let's introduce a debugfs interface for the
> DMC wakelock.
> 
> In this part, we export a buffer containing offsets of registers that
> were considered not needing the wakelock by our driver. In an upcoming
> change we will also allow defining an extra set of offset ranges to be
> tracked by our driver.
> 
> Signed-off-by: Gustavo Sousa <gustavo.sousa@xxxxxxxxx>
> ---
>  drivers/gpu/drm/i915/Makefile                 |   1 +
>  .../drm/i915/display/intel_display_debugfs.c  |   2 +
>  drivers/gpu/drm/i915/display/intel_dmc_wl.c   |   5 +-
>  drivers/gpu/drm/i915/display/intel_dmc_wl.h   |   2 +
>  .../drm/i915/display/intel_dmc_wl_debugfs.c   | 251 ++++++++++++++++++
>  .../drm/i915/display/intel_dmc_wl_debugfs.h   |  29 ++
>  drivers/gpu/drm/xe/Makefile                   |   1 +
>  7 files changed, 290 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.c
>  create mode 100644 drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.h
> 
> diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
> index 3dda9f0eda82..ac1ab79de9c8 100644
> --- a/drivers/gpu/drm/i915/Makefile
> +++ b/drivers/gpu/drm/i915/Makefile
> @@ -251,6 +251,7 @@ i915-y += \
>  	display/intel_display_wa.o \
>  	display/intel_dmc.o \
>  	display/intel_dmc_wl.o \
> +	display/intel_dmc_wl_debugfs.o \
>  	display/intel_dpio_phy.o \
>  	display/intel_dpll.o \
>  	display/intel_dpll_mgr.o \
> diff --git a/drivers/gpu/drm/i915/display/intel_display_debugfs.c b/drivers/gpu/drm/i915/display/intel_display_debugfs.c
> index f1d76484025a..b032535f4830 100644
> --- a/drivers/gpu/drm/i915/display/intel_display_debugfs.c
> +++ b/drivers/gpu/drm/i915/display/intel_display_debugfs.c
> @@ -26,6 +26,7 @@
>  #include "intel_display_power_well.h"
>  #include "intel_display_types.h"
>  #include "intel_dmc.h"
> +#include "intel_dmc_wl_debugfs.h"
>  #include "intel_dp.h"
>  #include "intel_dp_link_training.h"
>  #include "intel_dp_mst.h"
> @@ -883,6 +884,7 @@ void intel_display_debugfs_register(struct drm_i915_private *i915)
>  
>  	intel_bios_debugfs_register(display);
>  	intel_cdclk_debugfs_register(display);
> +	intel_dmc_wl_debugfs_register(display);
>  	intel_dmc_debugfs_register(display);
>  	intel_dp_test_debugfs_register(display);
>  	intel_fbc_debugfs_register(display);
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl.c b/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> index 330b43a72e08..3686d4e90167 100644
> --- a/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl.c
> @@ -338,6 +338,7 @@ void intel_dmc_wl_init(struct intel_display *display)
>  	spin_lock_init(&wl->lock);
>  	refcount_set(&wl->refcount,
>  		     display->params.enable_dmc_wl == ENABLE_DMC_WL_ALWAYS_LOCKED ? 1 : 0);
> +	intel_dmc_wl_debugfs_init(display);
>  }
>  
>  /* Must only be called as part of enabling dynamic DC states. */
> @@ -444,8 +445,10 @@ void intel_dmc_wl_get(struct intel_display *display, i915_reg_t reg)
>  	spin_lock_irqsave(&wl->lock, flags);
>  
>  	if (i915_mmio_reg_valid(reg) &&
> -	    !intel_dmc_wl_check_range(display, reg, wl->dc_state))
> +	    !intel_dmc_wl_check_range(display, reg, wl->dc_state)) {
> +		intel_dmc_wl_debugfs_log_untracked(display, i915_mmio_reg_offset(reg));
>  		goto out_unlock;
> +	}
>  
>  	if (!wl->enabled) {
>  		if (!refcount_inc_not_zero(&wl->refcount))
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl.h b/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> index 5488fbdf29b8..d11b0ab50b3c 100644
> --- a/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl.h
> @@ -11,6 +11,7 @@
>  #include <linux/refcount.h>
>  
>  #include "i915_reg_defs.h"
> +#include "intel_dmc_wl_debugfs.h"
>  
>  struct intel_display;
>  
> @@ -27,6 +28,7 @@ struct intel_dmc_wl {
>  	 */
>  	u32 dc_state;
>  	struct delayed_work work;
> +	struct intel_dmc_wl_dbg dbg;
>  };
>  
>  void intel_dmc_wl_init(struct intel_display *display);
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.c b/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.c
> new file mode 100644
> index 000000000000..41e59d775fe5
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.c
> @@ -0,0 +1,251 @@
> +// SPDX-License-Identifier: MIT
> +/*
> + * Copyright (C) 2025 Intel Corporation
> + */
> +
> +#include <linux/debugfs.h>
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_managed.h>
> +#include <drm/drm_print.h>
> +
> +#include "intel_display_core.h"
> +#include "intel_dmc_wl_debugfs.h"
> +
> +#define DEBUGFS_UNTRACKED_BUFFER_SIZE_MAX 65536
This macro is not actually the size but the count of offsets stored in
the buffer. This is used as the array size for drmm_kmalloc_array call.
It makes sense to rename this macro as count

> +
> +/*
> + * DOC: DMC wakelock debugfs
> + *
> + * The DMC wakelock code needs to keep track of register offsets that need the
> + * wakelock for proper access. If one of the necessary offsets are missed, then
> + * the failure in asserting the wakelock is very likely to cause problems down
> + * the road.
> + *
> + * A miss could happen for at least two different reasons:
> + *
> + * - We might have forgotten to add the offset (or range) to the relevant
> + *   tables tracked by the driver in the first place.
> + *
> + * - Or updates to either the DMC firmware or the display IP that require new
> + *   offsets to be tracked and we fail to realize that.
> + *
> + * To help capture these cases, we provide the intel_dmc_wl/ debugfs directory,
> + * which exports a buffer of untracked register offsets.
> + *
> + * Untracked offsets
> + * -----------------
> + *
> + * This is a buffer that records every register offset that went through the
> + * DMC wakelock check and was deemed not needing the wakelock for MMIO access.
> + *
> + * To activate the logging of offsets into such a buffer, one can do::
> + *
> + *   # echo -1 > /sys/kernel/debug/dri/(...)/intel_dmc_wl/untracked_size

This knob is setting the count of offsets to be stored and not the size.
I think this should be renamed to indicate it as count.

> + *
> + * This will create a buffer with the maximum number of entries allowed
> + * (DEBUGFS_UNTRACKED_BUFFER_SIZE_MAX). A positive value can be used instead to
> + * define a different size:
> + *
> + *   # echo 1024 > /sys/kernel/debug/dri/(...)/intel_dmc_wl/untracked_size

For me passing negative number doesn't look intuitive. Thinking do we
really need the case of passing default buffer size. We can allow just 0
to disable and any positive number to enable with the buffer size set as
value passed.

> + *
> + * Every write to untracked_size will cause the buffer to be reset.
> + *
> + * It is also possible to read untracked_size in order to get the current
> + * value.
> + *
> + * After enabled, the buffer starts getting filled with offsets as MMIOs are
> + * performed by the driver.
> + *
> + * In order to view the content of the buffer, one can do::
> + *
> + *   # cat /sys/kernel/debug/dri/(...)/intel_dmc_wl/untracked
> + *   0x000c4000
> + *   0x0016fe50
> + *   0x000c7200
> + *   0x000c7204
> + *   0x00045230
> + *   0x00046440
> + *   0x00045234
> + *   0x0016fa48
> + *   0x0016fa40
> + *   0x0016fa5c
> + *   (...)
> + *
> + * The order of those offsets does not reflect the order the checks were done
> + * (some recently seen offsets are skipped to save space).
> + *
> + * Once done with it, the logging can be disabled with::
> + *
> + *   # echo 0 > /sys/kernel/debug/dri/(...)/intel_dmc_wl/untracked_size
> + */
> +
> +static int untracked_size_get(void *data, u64 *val)
> +{
> +	struct intel_display *display = data;
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dbg->lock, flags);
> +	*val = dbg->untracked.size;
> +	spin_unlock_irqrestore(&dbg->lock, flags);
> +
> +	return 0;
> +}
> +
> +static int untracked_size_set(void *data, u64 val)
> +{
> +	struct intel_display *display = data;
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +	s64 new_size;
> +	u32 *old_offsets;
> +	u32 *new_offsets;
> +	unsigned long flags;
> +
> +	new_size = (s64)val;
> +
> +	if (new_size == -1) {
> +		new_size = DEBUGFS_UNTRACKED_BUFFER_SIZE_MAX;
> +	} else if (new_size < 0) {
> +		drm_err(display->drm,
> +			"%lld is invalid for untracked_size, the only negative value allowed is -1\n",
> +			new_size);
> +		return -EINVAL;
> +	} else if (new_size > DEBUGFS_UNTRACKED_BUFFER_SIZE_MAX) {
> +		drm_err(display->drm,
> +			"%lld too big for untracked_size, maximum allowed value is %d\n",
> +			new_size,
> +			DEBUGFS_UNTRACKED_BUFFER_SIZE_MAX);
> +		return -EINVAL;
> +	}
> +
> +	if (new_size == 0) {
> +		new_offsets = NULL;
> +	} else {
> +		new_offsets = drmm_kmalloc_array(display->drm, new_size, sizeof(*new_offsets),
> +						 GFP_KERNEL);
> +
> +		if (!new_offsets)
> +			return -ENOMEM;
> +	}
> +
> +	spin_lock_irqsave(&dbg->lock, flags);
> +	old_offsets = dbg->untracked.offsets;
> +	dbg->untracked.offsets = new_offsets;
> +	dbg->untracked.size = new_size;
> +	dbg->untracked.head = 0;
> +	dbg->untracked.len = 0;
> +	dbg->untracked.overflow = false;
> +	spin_unlock_irqrestore(&dbg->lock, flags);
> +
> +	if (old_offsets)
> +		drmm_kfree(display->drm, old_offsets);
> +
> +	return 0;
> +}
> +
> +DEFINE_SIMPLE_ATTRIBUTE_SIGNED(untracked_size_fops,
> +			       untracked_size_get,
> +			       untracked_size_set,
> +			       "%lld\n");
> +
> +static int untracked_show(struct seq_file *m, void *data)
> +{
> +	struct intel_display *display = m->private;
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +	unsigned long flags;
> +	size_t remaining;
> +	size_t i;
> +
> +	spin_lock_irqsave(&dbg->lock, flags);
> +
> +	remaining = dbg->untracked.len;
> +	i = dbg->untracked.head;
> +
> +	while (remaining--) {
> +		if (i == 0)
> +			i = dbg->untracked.size;
> +
> +		seq_printf(m, "0x%08x\n", dbg->untracked.offsets[--i]);
> +	}
> +
> +	spin_unlock_irqrestore(&dbg->lock, flags);
> +
> +	return 0;
> +}
> +
> +DEFINE_SHOW_ATTRIBUTE(untracked);
> +
> +void intel_dmc_wl_debugfs_init(struct intel_display *display)
> +{
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +
> +	spin_lock_init(&dbg->lock);
> +}
> +
> +void intel_dmc_wl_debugfs_register(struct intel_display *display)
> +{
> +	struct dentry *dir;
> +
> +	if (!HAS_DMC_WAKELOCK(display))
> +		return;
> +
> +	dir = debugfs_create_dir("intel_dmc_wl", display->drm->debugfs_root);
> +	if (IS_ERR(dir))
> +		return;
> +
> +	debugfs_create_file("untracked_size", 0644, dir, display,
> +			    &untracked_size_fops);
> +	debugfs_create_file("untracked", 0644, dir, display,
> +			    &untracked_fops);
> +}
> +
> +static bool untracked_has_recent_offset(struct intel_display *display, u32 offset)
> +{
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +	int look_back = 32;
Define a macro for this magic number

> +	size_t i;
> +
> +	if (look_back > dbg->untracked.len)
> +		look_back = dbg->untracked.len;
> +
> +	i = dbg->untracked.head;
> +
> +	while (look_back--) {
> +		if (i == 0)
> +			i = dbg->untracked.size;
> +
> +		if (dbg->untracked.offsets[--i] == offset)
> +			return true;
> +	}
> +
> +	return false;
> +}
> +
> +void intel_dmc_wl_debugfs_log_untracked(struct intel_display *display, u32 offset)
> +{
> +	struct intel_dmc_wl_dbg *dbg = &display->wl.dbg;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&dbg->lock, flags);
As this code never gets called by an interrupt, we can use just the
spin_lock instead of spin_lock_irqsave. Same applies for all the places
where spin_lock/unlock_irqsave/irqrestore is used.

> +
> +	if (!dbg->untracked.size)
> +		goto out_unlock;
> +
> +	/* Save some space by not repeating recent offsets. */
> +	if (untracked_has_recent_offset(display, offset))
> +		goto out_unlock;
> +
> +	dbg->untracked.offsets[dbg->untracked.head] = offset;
> +	dbg->untracked.head = (dbg->untracked.head + 1) % dbg->untracked.size;
> +	if (dbg->untracked.len < dbg->untracked.size)
> +		dbg->untracked.len++;
> +
> +	if (dbg->untracked.len == dbg->untracked.size && !dbg->untracked.overflow) {
> +		dbg->untracked.overflow = true;
> +		drm_warn(display->drm, "Overflow detected in DMC wakelock debugfs untracked offsets\n");
> +	}
> +
> +out_unlock:
> +	spin_unlock_irqrestore(&dbg->lock, flags);
> +}
> diff --git a/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.h b/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.h
> new file mode 100644
> index 000000000000..9437c324966f
> --- /dev/null
> +++ b/drivers/gpu/drm/i915/display/intel_dmc_wl_debugfs.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: MIT */
> +/*
> + * Copyright (C) 2025 Intel Corporation
> + */
> +
> +#ifndef __INTEL_DMC_WL_DEBUGFS_H__
> +#define __INTEL_DMC_WL_DEBUGFS_H__
> +
> +#include <linux/types.h>
> +#include <linux/spinlock.h>
> +
> +struct intel_display;
> +
> +struct intel_dmc_wl_dbg {
> +	spinlock_t lock; /* protects everything below */
> +	struct {
> +		u32 *offsets;
> +		size_t head;
> +		size_t len;
> +		size_t size;
There is no need of both len and size. head will always give the count
of entries in the buffer. During overflow, we are keeping a flag to
indicate a overflow, which indicates the we also have date in the buffer
above head till the end of buffer.

Regards,
Bala

> +		bool overflow;
> +	} untracked;
> +};
> +
> +void intel_dmc_wl_debugfs_init(struct intel_display *display);
> +void intel_dmc_wl_debugfs_register(struct intel_display *display);
> +void intel_dmc_wl_debugfs_log_untracked(struct intel_display *display, u32 offset);
> +
> +#endif /* __INTEL_DMC_WL_DEBUGFS_H__ */
> diff --git a/drivers/gpu/drm/xe/Makefile b/drivers/gpu/drm/xe/Makefile
> index 81f63258a7e1..f03fbdbcb1a4 100644
> --- a/drivers/gpu/drm/xe/Makefile
> +++ b/drivers/gpu/drm/xe/Makefile
> @@ -221,6 +221,7 @@ xe-$(CONFIG_DRM_XE_DISPLAY) += \
>  	i915-display/intel_display_wa.o \
>  	i915-display/intel_dkl_phy.o \
>  	i915-display/intel_dmc.o \
> +	i915-display/intel_dmc_wl_debugfs.o \
>  	i915-display/intel_dp.o \
>  	i915-display/intel_dp_aux.o \
>  	i915-display/intel_dp_aux_backlight.o \
> -- 
> 2.48.0
> 



[Index of Archives]     [AMD Graphics]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux