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
next prev parent reply other threads:[~2026-09-08 4:54 UTC|newest]
Thread overview: 34+ 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
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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox