All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Mario Limonciello" <mario.limonciello@amd.com>
Cc: intel-xe@lists.freedesktop.org, intel-gfx@lists.freedesktop.org,
	dri-devel@lists.freedesktop.org
Subject: Re: [PATCH v8 04/14] drm: add connector backlight (LUMINANCE) infrastructure
Date: Tue, 08 Sep 2026 04:54:38 +0000	[thread overview]
Message-ID: <20260908045439.3C27E1F00A3A@smtp.kernel.org> (raw)
In-Reply-To: <20260908044035.62093-5-mario.limonciello@amd.com>

Thank you for your contribution! Sashiko AI review found 13 potential issue(s) to consider:
- [Critical] Concurrent backlight unlinking and connector teardown causes a Use-After-Free due to missing references and early return bypassing `cancel_work_sync`.
- [High] Turning off the backlight asynchronously during pipeline disable breaks hardware shutdown sequencing, risking screen corruption.
- [High] Backlight hardware state is mutated asynchronously before atomic commit validation, failing to roll back on commit failure.
- [High] Lockless, in-place modification of `connector->state->luminance` from asynchronous notifier context causes Write-After-Free.
- [High] Lockless read of `crtc->state` in `drm_atomic_helper_connector_apply_luminance` causes a Use-After-Free.
- [High] Lockless read of `connector->state` of unrelated connectors in `drm_atomic_crtc_set_backlight` causes a Use-After-Free.
- [Medium] Concurrency between `drm_backlight_inhibit_legacy_all` and `drm_backlight_link` leads to double-counted or overwritten `luminance_clients`.
- [Medium] Triggering a `WARN_ON` for routine hardware communication failures allows trivial kernel log spam and potential system panics.
- [Medium] Legacy DPMS changes incorrectly dim all connectors on shared CRTCs, and conflict with the atomic commit tail logic.
- [Medium] Backlight is enabled on disconnected/un-driven panels during DPMS ON.
- [Low] The fallback brightness assignment in `drm_backlight_bd_set_luminance` is mathematically guaranteed to fail due to a hardcoded `U16_MAX`.
- [Low] Property bounds are not updated when dynamically linking a backend with a different brightness range.
- [Low] Broken teardown symmetry leaves the backlight sysfs symlink unremoved, resulting in dead code.
--

commit 4014f9d380d34194182229a03855db38075d5806
Author: Mario Limonciello <mario.limonciello@amd.com>
Subject: drm: add connector backlight (LUMINANCE) infrastructure

This commit introduces a per-connector LUMINANCE range property to control
backlight brightness through the DRM atomic modeset path. It embeds a
backlight state in each connector and synchronizes brightness changes
asynchronously via a workqueue.

> diff --git a/drivers/gpu/drm/drm_atomic_helper.c b/drivers/gpu/drm/drm_atomic_helper.c
> index 285aac3554dfd..110cd81e5d6db 100644
> --- a/drivers/gpu/drm/drm_atomic_helper.c
> +++ b/drivers/gpu/drm/drm_atomic_helper.c
[ ... ]
> @@ -1230,6 +1231,10 @@ drm_atomic_helper_commit_encoder_bridge_disable(struct drm_device *dev,
>  		 * it away), so we won't call disable hooks twice.
>  		 */
>  		bridge = drm_bridge_chain_get_first_bridge(encoder);
> +
> +		/* Turn the backlight off before disabling the pipeline. */
> +		drm_backlight_set_luminance(connector, 0);
> +
>  		drm_atomic_bridge_chain_disable(bridge, state);
>  		drm_bridge_put(bridge);

[Severity: High]
Does this sequence break the display hardware shutdown requirements?

By queuing the backlight off operation asynchronously via
drm_backlight_set_luminance() and immediately disabling the bridge chain,
the pipeline might be shut down before the backlight actually turns off.
Could this lead to screen corruption or panel damage?

[ ... ]
> @@ -1775,6 +1780,24 @@ void drm_atomic_helper_commit_modeset_enables(struct drm_device *dev,
>  }
>  EXPORT_SYMBOL(drm_atomic_helper_commit_modeset_enables);
>  
> +/**
> + * drm_atomic_helper_connector_apply_luminance - apply connector luminance from atomic state
> + * @conn_state: atomic connector state to apply luminance for
> + *
> + * Updates the backlight luminance from the atomic connector state. If the
> + * connector has a linked backlight and is associated with an active CRTC,
> + * push the luminance value to hardware.
> + */
> +void drm_atomic_helper_connector_apply_luminance(const struct drm_connector_state *conn_state)
> +{
> +	struct drm_connector *connector = conn_state->connector;
> +
> +	if (conn_state->crtc && conn_state->crtc->state &&
> +	    conn_state->crtc->state->active)

[Severity: High]
Can a concurrent CRTC modeset free the CRTC state while it is being accessed
here?

Since the CRTC lock might not be held during pure connector property
commits, reading conn_state->crtc->state->active could result in a
use-after-free if the CRTC state is swapped and freed simultaneously.

> +		drm_backlight_set_luminance(connector, conn_state->luminance);
> +}
> +EXPORT_SYMBOL(drm_atomic_helper_connector_apply_luminance);
> +
[ ... ]
> diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c
> index 5ea593b3a98ec..07d4bcb16cd23 100644
> --- a/drivers/gpu/drm/drm_atomic_uapi.c
> +++ b/drivers/gpu/drm/drm_atomic_uapi.c
[ ... ]
> @@ -1133,6 +1144,22 @@ static struct drm_pending_vblank_event *create_vblank_event(
>  	return e;
>  }
>  
> +static void drm_atomic_crtc_set_backlight(struct drm_crtc *crtc, bool active)
> +{
> +	struct drm_connector_list_iter conn_iter;
> +	struct drm_connector *connector;
> +
> +	drm_connector_list_iter_begin(crtc->dev, &conn_iter);
> +	drm_for_each_connector_iter(connector, &conn_iter) {
> +		if (!connector->state || connector->state->crtc != crtc)
> +			continue;

[Severity: High]
Could this lockless read of connector->state trigger a use-after-free?

This function iterates all connectors and reads their states while holding
only the connection_mutex. If another connector receives a concurrent
property update, its state could be swapped and freed while being read here.

> +
> +		drm_backlight_set_luminance(connector,
> +					    active ? connector->state->luminance : 0);
> +	}
> +	drm_connector_list_iter_end(&conn_iter);
> +}
> +
>  int drm_atomic_connector_commit_dpms(struct drm_atomic_commit *state,
>  				     struct drm_connector *connector,
>  				     int mode)
> @@ -1155,9 +1182,29 @@ int drm_atomic_connector_commit_dpms(struct drm_atomic_commit *state,
>  	if (connector->dpms == mode)
>  		goto out;
>  
> +	crtc = connector->state ? connector->state->crtc : NULL;
> +
> +	/* Handle backlight brightness coordination with DPMS state changes */
> +	if (old_mode != DRM_MODE_DPMS_OFF && mode == DRM_MODE_DPMS_OFF) {
> +		/* DPMS ON -> OFF: dim all connectors driven by this CRTC. */
> +		if (crtc)
> +			drm_atomic_crtc_set_backlight(crtc, false);

[Severity: Medium]
Will this cause unintended dimming or flickering on cloned displays?

When one connector on a shared CRTC transitions to DPMS OFF, calling
drm_atomic_crtc_set_backlight() queues an OFF request for all connectors
driven by that CRTC, even if the other connectors are still supposed to
remain active.

> +		else
> +			drm_backlight_set_luminance(connector, 0);
> +	}
> +
>  	connector->dpms = mode;
>  
> -	crtc = connector->state->crtc;
> +	/* DPMS OFF -> ON: restore brightness to property value */
> +	if (old_mode == DRM_MODE_DPMS_OFF && mode == DRM_MODE_DPMS_ON &&
> +	    connector->state) {
> +		if (crtc)
> +			drm_atomic_crtc_set_backlight(crtc, true);
> +		else
> +			drm_backlight_set_luminance(connector,
> +						    connector->state->luminance);

[Severity: Medium]
Is it safe to turn on the backlight for a disconnected panel?

If a connector is turned on (DPMS ON) but has no active CRTC (crtc is NULL),
this branch restores the committed luminance. Illuminating a panel that isn't
receiving a video signal could violate hardware sequencing constraints and
stress the panel.

[Severity: High]
Does this rollback path properly revert the physical backlight changes?

The physical backlight state is mutated asynchronously before calling
drm_atomic_commit(state). If the commit fails (for example, due to 
validation failure), the software DPMS state is rolled back at the out
label:

out:
	if (ret != 0)
		connector->dpms = old_mode;

However, the physical backlight might remain turned off since the
asynchronous work was already dispatched and cannot be rolled back here.

> +	}
> +
>  	if (!crtc)
>  		goto out;
>  	ret = drm_atomic_add_affected_connectors(state, crtc);
[ ... ]
> diff --git a/drivers/gpu/drm/drm_backlight.c b/drivers/gpu/drm/drm_backlight.c
> new file mode 100644
> index 0000000000000..dd02d1878727e
> --- /dev/null
> +++ b/drivers/gpu/drm/drm_backlight.c
[ ... ]
> +static void __drm_backlight_worker(struct work_struct *w)
> +{
> +	struct drm_backlight *b = container_of(w, struct drm_backlight, work);
[ ... ]
> +	/*
> +	 * The backend stays valid here: drm_backlight_do_unlink() clears
> +	 * @backend and drops its reference only after cancel_work_sync(), so an
> +	 * in-flight worker always sees a live backend.
> +	 */
> +	if (funcs && funcs->set_luminance)
> +		WARN_ON(funcs->set_luminance(b, v));

[Severity: Medium]
Could this WARN_ON() be triggered by routine hardware communication failures?

If the backend communicates over I2C or similar buses, transient timeouts 
or disconnections might routinely return error codes. Using WARN_ON() could 
allow local userspace to spam the kernel log or cause a system panic if 
panic_on_warn is enabled.

[ ... ]
> +/* caller must hold @drm_backlight_lock */
> +static void __drm_backlight_readback(struct drm_backlight *b, unsigned int v)
> +{
[ ... ]
> +	/*
> +	 * Reflect a hardware-side brightness change (firmware hotkeys, or a
> +	 * legacy sysfs write while not inhibited) back into the connector's
> +	 * committed luminance so a read-back returns the real value.
> +	 */
> +	if (connector->state)
> +		connector->state->luminance = min(v, max);

[Severity: High]
Can this in-place modification of connector->state cause a write-after-free?

Since this function is called asynchronously from a sysfs handler holding 
only drm_backlight_lock, a concurrent atomic commit could duplicate, commit, 
and free the connector->state object, causing this write to corrupt memory.

> +}
[ ... ]
> +static void drm_backlight_do_unlink(struct drm_backlight *b)
> +{
> +	struct backlight_device *bd;
> +	unsigned int clients;
> +
> +	/*
> +	 * Stop new work first, but leave @backend in place so an in-flight
> +	 * worker keeps a valid backend to operate on. Capture the linked
> +	 * backlight_device (if any) while @funcs is still set.
> +	 */
> +	scoped_guard(spinlock, &drm_backlight_lock) {
> +		if (!b->funcs)
> +			return;

[Severity: Critical]
Does this early return leak a pending or running worker?

If b->funcs is NULL, the function returns immediately without calling
cancel_work_sync(&b->work). This could leave the worker operating on
memory that might be subsequently freed.

> +		bd = drm_backlight_bd(b);
> +		clients = b->luminance_clients;
> +		b->funcs = NULL;
> +		list_del_init(&b->list);
> +	}
[ ... ]
> +int drm_backlight_link(struct drm_connector *connector,
> +		       struct backlight_device *bd)
> +{
[ ... ]
> +	max = bd->props.max_brightness;
> +	if (max && !connector->luminance_property) {
> +		ret = drm_backlight_create_property(connector, max, false);

[Severity: Low]
Will the LUMINANCE property bounds remain stale if a new backend is linked?

If a backend is unlinked and a new one is linked with a different 
max_brightness range, the property is not recreated because 
connector->luminance_property already exists. This might artificially 
clamp the new backend to the old backend's maximum brightness.

> +		if (ret)
> +			return ret;
> +	}
> +
> +	scoped_guard(spinlock, &drm_backlight_lock) {
> +		b->funcs = &drm_backlight_bd_funcs;
> +		b->backend = bd;
> +		backlight_device_ref(bd);
> +		list_add(&b->list, &drm_backlight_list);
> +		/*
> +		 * Inherit the device-wide inhibit level so a connector linked
> +		 * (or hotplugged) while luminance-aware clients are active also
> +		 * has its legacy sysfs control inhibited.
> +		 */
> +		b->luminance_clients = connector->dev->mode_config.luminance_clients;

[Severity: Medium]
Is there a race condition here that could double-count luminance_clients?

If a new connector is linked concurrently with drm_backlight_inhibit_legacy_all(),
it might inherit the already-incremented global dev->mode_config.luminance_clients
count here.

> +		if (b->luminance_clients)
> +			atomic_add(b->luminance_clients, &bd->drm_takeover);
[ ... ]
> +void drm_backlight_inhibit_legacy_all(struct drm_device *dev)
> +{
> +	struct drm_connector_list_iter iter;
> +	struct drm_connector *connector;
> +
> +	scoped_guard(spinlock, &drm_backlight_lock)
> +		dev->mode_config.luminance_clients++;
> +
> +	drm_connector_list_iter_begin(dev, &iter);
> +	drm_for_each_connector_iter(connector, &iter)
> +		drm_backlight_inhibit_legacy(connector);

[Severity: Medium]
Since the lock is dropped between incrementing the global luminance_clients
count and iterating the connectors, the iterator could hit the newly linked
connector and increment its count a second time. This double-count would
prevent the legacy sysfs interface from ever being uninhibited.

> +	drm_connector_list_iter_end(&iter);
> +}
[ ... ]
> +static int drm_backlight_bd_set_luminance(struct drm_backlight *b,
> +					  unsigned int value)
> +{
> +	struct backlight_device *bd = b->backend;
> +	int rc;
> +
> +	rc = backlight_set_brightness(bd, value, BACKLIGHT_UPDATE_DRM);
> +	if (rc)
> +		backlight_set_brightness(bd, U16_MAX, BACKLIGHT_UPDATE_DRM);

[Severity: Low]
Is this fallback brightness assignment guaranteed to fail?

Because backlight_set_brightness() strictly bounds-checks the requested
value against bd->props.max_brightness (which is typically around 255),
passing U16_MAX (65535) will mathematically guarantee an -EINVAL failure,
making this error recovery path ineffective.

> +
> +	return rc;
> +}
[ ... ]
> +static int drm_backlight_notify(struct notifier_block *self,
> +				unsigned long event, void *data)
> +{
[ ... ]
> +		for (;;) {
> +			struct drm_backlight *found = NULL;
> +
> +			scoped_guard(spinlock, &drm_backlight_lock) {
> +				list_for_each_entry(b, &drm_backlight_list, list) {
> +					if (drm_backlight_bd(b) == bd) {
> +						found = b;
> +						break;
> +					}
> +				}
> +			}
> +			if (!found)
> +				break;
> +			drm_backlight_do_unlink(found);

[Severity: Critical]
Is there a risk of the connector being freed concurrently here?

The notifier iterates the list under the lock, finds a match, drops the 
lock, and then calls drm_backlight_do_unlink(found). Without holding a 
reference to the connector, a concurrent teardown (like a hardware hotplug)
could free the connector memory, resulting in a use-after-free when 
drm_backlight_do_unlink accesses found->funcs.

> +		}
> +		break;
[ ... ]
> diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
> index 8b4baed060f3a..92ed0c1879d53 100644
> --- a/drivers/gpu/drm/drm_connector.c
> +++ b/drivers/gpu/drm/drm_connector.c
[ ... ]
> @@ -944,6 +948,8 @@ EXPORT_SYMBOL(drm_connector_dynamic_register);
>  void drm_connector_unregister(struct drm_connector *connector)
>  {
>  	mutex_lock(&connector->mutex);
> +	drm_backlight_unregister(connector);
> +
>  	if (connector->registration_state != DRM_CONNECTOR_REGISTERED) {

[Severity: Low]
Does unregistering the backlight here break the sysfs teardown symmetry?

Because this clears the backend link before drm_sysfs_connector_remove_early()
is called, the latter will fail to fetch the backlight device and miss
calling sysfs_remove_link(&connector->kdev->kobj, "backlight").

[ ... ]
> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
> index ef4e923a87284..2f4ce7d040f9d 100644
> --- a/drivers/gpu/drm/drm_sysfs.c
> +++ b/drivers/gpu/drm/drm_sysfs.c
[ ... ]
>  void drm_sysfs_connector_remove_early(struct drm_connector *connector)
>  {
> +	struct backlight_device *bd = drm_backlight_get_device(connector);
> +
> +	if (bd) {
> +		sysfs_remove_link(&connector->kdev->kobj, "backlight");

[Severity: Low]
Because drm_backlight_unregister() has already been called in 
drm_connector_unregister(), drm_backlight_get_device() will return NULL, 
leaving this cleanup path unreachable.

> +		backlight_device_unref(bd);
> +	}
> +
>  	if (connector->ddc)
>  		sysfs_remove_link(&connector->kdev->kobj, "ddc");
>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260908044035.62093-1-mario.limonciello@amd.com?part=4

  reply	other threads:[~2026-09-08  4:54 UTC|newest]

Thread overview: 36+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-08  4:40 [PATCH v8 00/14] Add support for a DRM backlight capability Mario Limonciello
2026-09-08  4:40 ` [PATCH v8 01/14] Revert "backlight: Remove notifier" Mario Limonciello
2026-09-08  4:51   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 02/14] backlight: add kernel-internal backlight API Mario Limonciello
2026-09-08  4:52   ` sashiko-bot
2026-09-08 15:45   ` Jani Nikula
2026-09-08 16:00     ` Mario Limonciello
2026-09-08 16:33       ` Jani Nikula
2026-09-08  4:40 ` [PATCH v8 03/14] drm/property: add a per-connector luminance flag Mario Limonciello
2026-09-08  4:54   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 04/14] drm: add connector backlight (LUMINANCE) infrastructure Mario Limonciello
2026-09-08  4:54   ` sashiko-bot [this message]
2026-09-08 15:48   ` Jani Nikula
2026-09-08  4:40 ` [PATCH v8 05/14] drm: add DRM_CLIENT_CAP_LUMINANCE Mario Limonciello
2026-09-08  4:55   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 06/14] drm/amd/display: Pass up errors reading actual brightness Mario Limonciello
2026-09-08  4:40 ` [PATCH v8 07/14] drm/amd: Indicate driver supports luminance Mario Limonciello
2026-09-08  4:40 ` [PATCH v8 08/14] drm/amd/display: use drm backlight Mario Limonciello
2026-09-08  4:57   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 09/14] drm/amdgpu: Check bios_scratch_reg_offset in backlight level helper Mario Limonciello
2026-09-08  4:51   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 10/14] drm/amd/display: Update KUnit backlight tests for luminance property and fixtures Mario Limonciello
2026-09-08  4:54   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 11/14] drm/bridge: auto-link panel backlight in bridge connector Mario Limonciello
2026-09-08  4:40 ` [PATCH v8 12/14] drm/xe: Indicate support for luminance on the connector Mario Limonciello
2026-09-08  4:57   ` sashiko-bot
2026-09-08  4:40 ` [PATCH v8 13/14] drm/i915: " Mario Limonciello
2026-09-08  4:40 ` [PATCH v8 14/14] drm/i915/display: use drm backlight Mario Limonciello
2026-09-08  5:03   ` sashiko-bot
2026-09-08  4:51 ` ✗ CI.checkpatch: warning for Add support for a DRM backlight capability (rev3) Patchwork
2026-09-08  4:53 ` ✓ CI.KUnit: success " Patchwork
2026-09-08  5:09 ` ✗ CI.checksparse: warning " Patchwork
2026-09-08  5:51 ` ✓ Xe.CI.BAT: success " Patchwork
2026-09-08  6:31 ` ✗ Xe.CI.FULL: failure " Patchwork
2026-09-08  9:32 ` ✓ i915.CI.BAT: success " Patchwork
2026-09-08 18:45 ` ✗ i915.CI.Full: failure " Patchwork

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=20260908045439.3C27E1F00A3A@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=intel-gfx@lists.freedesktop.org \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=mario.limonciello@amd.com \
    --cc=sashiko-reviews@lists.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.