Intel-GFX Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Kory Maincent" <kory.maincent@bootlin.com>
Cc: dri-devel@lists.freedesktop.org, intel-gfx@lists.freedesktop.org,
	intel-xe@lists.freedesktop.org
Subject: Re: [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers
Date: Thu, 22 Oct 2026 17:02:39 +0000	[thread overview]
Message-ID: <sashiko-outbox-181840@kernel.org> (raw)
In-Reply-To: <20261022-feat_link_cap-v4-3-a4f3f6e8cad9@bootlin.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] drm: use-after-free on connector->kdev in drm_sysfs_connector_remove()
- [High] drm: kdev race in drm_sysfs_connector_add() causes -EEXIST failure
- [Medium] drm: unlocked nlanes read in dp_link_attr_visible() races with updates
- [Medium] drm: missing LTTPR validation in drm_dp_sink_sync_caps()
--

commit 9bbc7a53acf715de24f598695d290cb7880247c1
Author: Kory Maincent <kory.maincent@bootlin.com>

drm: Introduce DisplayPort link training capabilities and state helpers

This commit adds DisplayPort link training capability and state helpers to
drm_dp_helper.c and exposes source, sink, and current link parameters
through a dp_link sysfs attribute group on DisplayPort and eDP connectors.

> diff --git a/drivers/gpu/drm/display/drm_dp_helper.c b/drivers/gpu/drm/display/drm_dp_helper.c
> index 8473dbcd8808a..5e53c6a9d35fd 100644
> --- a/drivers/gpu/drm/display/drm_dp_helper.c
> +++ b/drivers/gpu/drm/display/drm_dp_helper.c

[ ... ]

> @@ -5135,6 +5136,30 @@ int drm_dp_read_dpcd_sink_rates(struct drm_dp_aux *aux,
>  }
>  EXPORT_SYMBOL_GPL(drm_dp_read_dpcd_sink_rates);
>  
> +/**
> + * drm_dp_get_max_lane_count - Get the max lane count from DPCD and LTTPR
> + *			       capabilities
> + * @dpcd: DPCD receiver capabilities
> + * @lttpr_common_caps: LTTPR common capabilities (NULL if no LTTPR supported)
> + *
> + * Returns: Number of max lane count
> + */
> +int drm_dp_get_max_lane_count(const u8 dpcd[DP_RECEIVER_CAP_SIZE],
> +			      const u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE])
> +{
> +	int nlanes, lttpr_nlanes;
> +
> +	nlanes = dpcd[DP_MAX_LANE_COUNT] & DP_MAX_LANE_COUNT_MASK;
> +	if (lttpr_common_caps) {
> +		lttpr_nlanes = drm_dp_lttpr_max_lane_count(lttpr_common_caps);
> +		if (lttpr_nlanes)
> +			nlanes = min(nlanes, lttpr_nlanes);
> +	}
> +
> +	return nlanes;
> +}
> +EXPORT_SYMBOL_GPL(drm_dp_get_max_lane_count);
> +
>  /**
>   * drm_dp_read_edp_sink_rates() - read the sink rates from the eDP DPCD
>   * @aux: DisplayPort AUX channel
> @@ -5196,3 +5221,284 @@ int drm_dp_read_dsc_dpcd(struct drm_dp_aux *aux,

[ ... ]

> +int drm_dp_sink_sync_caps(struct drm_connector *connector, struct drm_dp_aux *aux,
> +			  const struct drm_dp_tunnel *tunnel)
> +{
> +	u8 lttpr_common_caps[DP_LTTPR_COMMON_CAP_SIZE] = {0};
> +	struct drm_connector_dp_link_caps link_caps;
> +	int sink_rates[DP_MAX_SUPPORTED_RATES] = {0};
> +	u8 dsc_dpcd[DP_DSC_RECEIVER_CAP_SIZE] = {0};
> +	u8 dpcd_caps[DP_RECEIVER_CAP_SIZE] = {0};
> +	struct drm_dp_desc desc = {0};
> +	int num_sink_rates, ret;
> +	u8 edp_dpcd_rev = 0;
> +
> +	if (!connector)
> +		return -ENODEV;
> +
> +	ret = drm_dp_read_dpcd_caps(aux, dpcd_caps);
> +	if (ret < 0) {
> +		drm_dbg_kms(aux->drm_dev, "%s: error reading DPCD capabilities (%d)\n",
> +			    aux->name, ret);
> +		return ret;
> +	}
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
> +		ret = drm_dp_dpcd_read_byte(aux, DP_EDP_DPCD_REV, &edp_dpcd_rev);
> +		if (ret < 0)
> +			drm_dbg_kms(aux->drm_dev, "%s: No DPCD eDP revision found\n",
> +				    aux->name);
> +	}
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort &&
> +	    dpcd_caps[DP_DPCD_REV] >= DP_DPCD_REV_13) {
> +		ret = drm_dp_read_lttpr_common_caps(aux, dpcd_caps, lttpr_common_caps);
> +		if (ret < 0) {
> +			drm_dbg_kms(aux->drm_dev,
> +				    "%s: error reading LTTPR caps, assuming no LTTPR (%d)\n",
> +				    aux->name, ret);
> +			/* LTTPR capabilities are read block by block. Clean the
> +			 * entire capabilities if the read fails to avoid
> +			 * partial garbage
> +			 */
> +			memset(lttpr_common_caps, 0, sizeof(lttpr_common_caps));
> +		}
> +	}

[ ... ]

> +	link_caps.nlanes = drm_dp_get_max_lane_count(dpcd_caps, lttpr_common_caps);
> +	link_caps.nlink_rates = num_sink_rates;
> +	link_caps.link_rates = sink_rates;
> +	link_caps.dsc = drm_dp_sink_supports_dsc(dsc_dpcd);
> +
> +	return drm_dp_sink_set_caps(connector, &link_caps);
> +}
> +EXPORT_SYMBOL_GPL(drm_dp_sink_sync_caps);

[Severity: Medium]
Can drm_dp_sink_sync_caps() and drm_dp_get_max_lane_count() clamp
sink_link_caps.nlanes and sink_link_caps.link_rates using unvalidated
repeater bytes when a sink does not have repeaters present?

In drivers/gpu/drm/display/drm_dp_helper.c, drm_dp_sink_sync_caps() calls
drm_dp_read_lttpr_common_caps(aux, dpcd_caps, lttpr_common_caps) for any
DisplayPort 1.3 or newer sink and only zeroes lttpr_common_caps if the read
returns a negative error code.

If a sink without repeaters succeeds on that read and returns non-zero
bytes where lttpr_common_caps[0] < 0x14 or
drm_dp_lttpr_count(lttpr_common_caps) <= 0, neither
drm_dp_sink_sync_caps(), drm_dp_get_max_lane_count(), nor the 8b/10b path
in drm_dp_read_dpcd_sink_rates() validates the repeater revision or count
the way drm_dp_get_128b132b_link_rates() does:

drivers/gpu/drm/display/drm_dp_helper.c:drm_dp_get_128b132b_link_rates() {
	...
	if (lttpr_common_caps && drm_dp_lttpr_count(lttpr_common_caps) > 0) {
		/* We have a repeater */
		if (lttpr_common_caps[0] >= 0x20 &&
	...
}

In addition, drm_dp_sink_sync_caps() passes tunnel to
drm_dp_read_dpcd_sink_rates(), but drm_dp_get_max_lane_count() does not
account for drm_dp_tunnel_max_dprx_lane_count(tunnel).

Should lttpr_common_caps be validated (checking lttpr_common_caps[0] >=
0x14 and drm_dp_lttpr_count(lttpr_common_caps) > 0) before clamping
nlanes and 8b/10b link rates, and should tunnel lane count limits also be
applied?

[ ... ]

> diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
> index 7b16d32b6ff65..ac9762a066db4 100644
> --- a/drivers/gpu/drm/drm_sysfs.c
> +++ b/drivers/gpu/drm/drm_sysfs.c
> @@ -340,6 +340,151 @@ static const struct attribute_group *connector_dev_groups[] = {

[ ... ]

> +static umode_t dp_link_attr_visible(struct kobject *kobj,
> +				    struct attribute *a, int n)
> +{
> +	struct device *dev = kobj_to_dev(kobj);
> +	struct drm_connector *connector = to_drm_connector(dev);
> +	struct drm_connector_dp_link_caps *caps;
> +
> +	if (a == &dev_attr_source_link_caps_nlanes.attr ||
> +	    a == &dev_attr_source_link_caps_link_rates.attr ||
> +	    a == &dev_attr_source_link_caps_dsc.attr)
> +		caps = &connector->dp.source_link_caps;
> +	else if (a == &dev_attr_sink_link_caps_nlanes.attr ||
> +		 a == &dev_attr_sink_link_caps_link_rates.attr ||
> +		 a == &dev_attr_sink_link_caps_dsc.attr)
> +		caps = &connector->dp.sink_link_caps;
> +	else
> +		caps = &connector->dp.cur_link_info;
> +
> +	/* Checking nlanes is enough to decide whether the attributes should
> +	 * be exposed: without lanes there is no working DisplayPort link
> +	 */
> +	return caps->nlanes ? a->mode : 0;
> +}

[Severity: Medium]
What prevents a data race on caps->nlanes in dp_link_attr_visible() when
sink capabilities or active link parameters are updated concurrently?

In drivers/gpu/drm/display/drm_dp_helper.c, drm_dp_sink_set_caps(),
drm_dp_set_cur_link_params(), and drm_dp_sink_reset_caps() modify
sink_link_caps.nlanes and cur_link_info.nlanes under connector->dp.mutex
and release that lock before drm_sysfs_connector_dp_link_update() acquires
connector->dp.sysfs_mutex:

drivers/gpu/drm/display/drm_dp_helper.c:drm_dp_sink_set_caps() {
	...
	scoped_guard(mutex, &connector->dp.mutex) {
		/* Only refresh the group when the visibility changes. */
		update = !connector->dp.sink_link_caps.nlanes != !link_caps->nlanes;

		ret = drm_dp_copy_caps(&connector->dp.sink_link_caps, link_caps);
		if (ret)
			return ret;
	}

	if (update)
		drm_sysfs_connector_dp_link_update(connector);
	...
}

Meanwhile, dp_link_attr_visible() in drivers/gpu/drm/drm_sysfs.c reads
caps->nlanes without holding connector->dp.mutex during
sysfs_create_group(), sysfs_update_group(), or sysfs_group_change_owner().

If a writer modifies caps->nlanes on another processor while
create_files() in fs/sysfs/group.c iterates over connector_dp_link_attrs,
could dp_link_attr_visible() observe different values of caps->nlanes
across the three attributes in the same group, or cause a concurrent
sysfs_group_change_owner() call to see non-zero visibility before the
kernfs nodes are created?

> +
> +static const struct attribute_group connector_dp_link_group = {
> +	.name = "dp_link",
> +	.attrs = connector_dp_link_attrs,
> +	.is_visible = dp_link_attr_visible,
> +};
> +
> +/**
> + * drm_sysfs_connector_dp_link_update - Update the visibility of the DP link
> + *                                      attributes
> + * @connector: DisplayPort connector
> + */
> +void drm_sysfs_connector_dp_link_update(struct drm_connector *connector)
> +{
> +	int ret;
> +
> +	if (connector->connector_type != DRM_MODE_CONNECTOR_DisplayPort &&
> +	    connector->connector_type != DRM_MODE_CONNECTOR_eDP)
> +		return;
> +
> +	scoped_guard(mutex, &connector->dp.sysfs_mutex) {
> +		if (!connector->kdev)
> +			return;
> +
> +		ret = sysfs_update_group(&connector->kdev->kobj,
> +					 &connector_dp_link_group);
> +	}
> +	if (ret)
> +		drm_err(connector->dev,
> +			"failed to update DP link sysfs attributes: %d\n", ret);
> +}
> +EXPORT_SYMBOL_GPL(drm_sysfs_connector_dp_link_update);

[Severity: High]
Could drm_sysfs_connector_dp_link_update() race with
drm_sysfs_connector_remove() and trigger a use-after-free on
connector->kdev?

In drivers/gpu/drm/drm_sysfs.c, drm_sysfs_connector_dp_link_update()
checks connector->kdev and passes &connector->kdev->kobj to
sysfs_update_group() under connector->dp.sysfs_mutex, without holding
connector->mutex.

However, when drm_connector_unregister() calls
drm_sysfs_connector_remove(), connector->kdev is unregistered and freed
before connector->kdev is cleared and without taking
connector->dp.sysfs_mutex:

drivers/gpu/drm/drm_sysfs.c:drm_sysfs_connector_remove() {
	if (!connector->kdev)
		return;
	...
	device_unregister(connector->kdev);
	connector->kdev = NULL;
}

Since device_unregister(connector->kdev) calls device_del() and
put_device() (freeing connector->kdev via drm_sysfs_release()) before
clearing connector->kdev, wouldn't a concurrent call to
drm_sysfs_connector_dp_link_update() from drm_dp_sink_set_caps(),
drm_dp_sink_sync_caps(), drm_dp_set_cur_link_params(), or
drm_dp_sink_reset_caps() observe a non-null connector->kdev and
dereference &connector->kdev->kobj while or after it is freed?

> +
>  int drm_sysfs_connector_add(struct drm_connector *connector)
>  {
>  	struct drm_device *dev = connector->dev;
> @@ -376,6 +521,18 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
>  
>  	connector->kdev = kdev;
>  
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_DisplayPort ||
> +	    connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
> +		scoped_guard(mutex, &connector->dp.sysfs_mutex)
> +			r = sysfs_create_group(&connector->kdev->kobj,
> +					       &connector_dp_link_group);
> +
> +		if (r) {
> +			drm_err(dev, "failed to create DP connector sysfs: %d\n", r);
> +			goto err_dp_sysfs;
> +		}
> +	}
> +
>  	if (dev_fwnode(kdev)) {
>  		r = component_add(kdev, &typec_connector_ops);
>  		if (r)
> @@ -384,6 +541,9 @@ int drm_sysfs_connector_add(struct drm_connector *connector)
>  
>  	return 0;
>  
> +err_dp_sysfs:
> +	connector->kdev = NULL;
> +	device_del(kdev);
>  err_free:
>  	put_device(kdev);
>  	return r;

[Severity: High]
What happens if drm_sysfs_connector_dp_link_update() runs between
assigning connector->kdev = kdev and acquiring connector->dp.sysfs_mutex
in drm_sysfs_connector_add()?

In drivers/gpu/drm/drm_sysfs.c, drm_sysfs_connector_add() publishes
connector->kdev = kdev outside connector->dp.sysfs_mutex before calling
sysfs_create_group(&connector->kdev->kobj, &connector_dp_link_group).

If drm_sysfs_connector_dp_link_update() runs in that window (for example,
from a concurrent hotplug or detect worker), it acquires
connector->dp.sysfs_mutex first, sees a non-null connector->kdev, and
calls sysfs_update_group(). Because the dp_link directory does not exist
yet, internal_create_group() in fs/sysfs/group.c creates the dp_link
directory when source_link_caps.nlanes is non-zero.

When drm_sysfs_connector_add() then acquires connector->dp.sysfs_mutex
and calls sysfs_create_group(), kernfs_create_dir_ns() fails because the
directory already exists and triggers sysfs_warn_dup(). That jumps to
err_dp_sysfs, where connector->kdev is cleared and device_del(kdev) and
put_device(kdev) run outside connector->dp.sysfs_mutex, aborting
connector registration and leaving kdev teardown unsynchronized with
drm_sysfs_connector_dp_link_update().

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261022-feat_link_cap-v4-0-a4f3f6e8cad9@bootlin.com?part=3

  reply	other threads:[~2026-10-22 17:02 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-22 16:37 [PATCH v4 0/8] Add support for DisplayPort link training information report Kory Maincent
2026-10-22 16:37 ` [PATCH v4 1/8] drm: Move sink link rates reading helpers out of i915 Kory Maincent
2026-10-22 16:56   ` sashiko-bot
2026-10-22 16:37 ` [PATCH v4 2/8] drm: Move DSC capability reading helper " Kory Maincent
2026-10-22 16:37 ` [PATCH v4 3/8] drm: Introduce DisplayPort link training capabilities and state helpers Kory Maincent
2026-10-22 17:02   ` sashiko-bot [this message]
2026-10-22 16:38 ` [PATCH v4 4/8] drm/i915/display/dp: Read DPCD DSC capabilities in any case Kory Maincent
2026-10-22 16:38 ` [PATCH v4 5/8] drm/i915/display/dp: Use dp_connector helpers to expose link training state Kory Maincent
2026-10-22 17:03   ` sashiko-bot
2026-10-22 16:38 ` [PATCH v4 6/8] drm/bridge: Expose link training state via new DRM_BRIDGE_OP_DP flag Kory Maincent
2026-10-22 16:38 ` [PATCH v4 7/8] drm/analogix/anx7625: Use dp_connector helpers to expose link training state Kory Maincent
2026-10-22 17:03   ` sashiko-bot
2026-10-22 16:38 ` [PATCH v4 8/8] drm/mediatek: " Kory Maincent
2026-10-22 17:01   ` sashiko-bot
2026-10-22 16:48 ` ✗ Fi.CI.BUILD: failure for Add support for DisplayPort link training information report 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=sashiko-outbox-181840@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=kory.maincent@bootlin.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