[PATCH 2/4] drm/i915/dmc_wl: Add debugfs for untracked offsets
Vivekanandan, Balasubramani
balasubramani.vivekanandan at intel.com
Thu Jan 30 08:54:18 UTC 2025
On 23.01.2025 13:41, Gustavo Sousa wrote:
> Quoting Vivekanandan, Balasubramani (2025-01-23 13:11:03-03:00)
> >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 at intel.com>
> >> ---
> >> 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
>
> I think size doesn't really have to mean number of bytes, but, in this
> case, I agree, that "buffer size" could be easily thought that way.
>
> However, couldn't "buffer count" be somewhat ambiguous? One might read
> as the number of buffers and then only later after reading the code that
> it would refer to the number of entries in a single buffer.
>
> Between the two options, I think I would still go with "buffer size"...
I was thinking something like DEBUGFS_UNTRACKED_OFFSET_COUNT
Regards,
Bala
>
> >
> >> +
> >> +/*
> >> + * 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.
>
> Well, this is already supported. The use of -1 is just a convenience,
> and the user is not required to use it.
>
> The current interface doesn't provide a way of letting the user know the
> maximum value and I think it would be a bit overkill adding another
> debugfs file just for that purpose.
>
> As such, I think -1 is a useful way of letting the user use the maximum
> size, specially when she doesn't have the up-to-date documentation in
> handy to know what value that is. That's certainly better than having
> the user finding the maximum via trial and error.
>
> >
> >> + *
> >> + * 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
>
> This is very local to this function and I guess the variable name
> already convey it's meaning?
>
> >
> >> + 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.
>
> This code does get called in interrupt context. It is called by
> intel_dmc_wl_get(), which is called for most of display MMIO access
> functions, and that happens both in user and interrupt context.
>
> >
> >> +
> >> + 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.
>
> Ah, right. If overflow==false, then the length equals head, if
> overflow==true, then length equals size.
>
> Maybe the inconvenience is that we will need to calculate the length
> every time we need it. But maybe that's not too bad...
>
> Thanks! I'll remove the "len" member in v2.
>
> --
> Gustavo Sousa
>
> >
> >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
> >>
More information about the Intel-xe
mailing list