[PATCH 5/5] drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
Chaoyi Chen
chaoyi.chen at rock-chips.com
Thu May 21 19:37:50 PDT 2026
Oh! Interesting AI. It really did uncover some issues.
On 5/21/2026 2:22 PM, sashiko-bot at kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
> - [High] Prematurely dropping the reference count of the last bridge in `cdn_bridge_add()` will cause a use-after-free.
> - [High] The `prev_port` pointer is used uninitialized in `cdn_dp_bridge_edid_read()`, leading to a kernel crash.
> - [High] `cdn_dp_connected_port()` ignores connection status for extcon configurations, breaking hotplug detection.
> - [High] Missing bounds check in `cdn_dp_parse_next_bridge_dt()` allows an out-of-bounds write to the `next_bridge_list` array.
> - [High] The `lanes` stack array is read out-of-bounds or uninitialized in `cdn_dp_connected_port()`.
> - [High] DRM bridge references are leaked on error paths during DT parsing and driver binding.
> - [High] If link training fails in `cdn_dp_switch_port()`, it returns an error but leaves the DP controller erroneously marked as active.
> - [High] `cdn_dp_switch_port()` incorrectly aborts hardware switching when the DP controller is active, causing EDID reads from the wrong port.
> - [High] Disabling an inactive bridge erroneously disables the active DP controller for another bridge.
> - [Medium] Overwriting `connector->fwnode` in `cdn_bridge_add()` causes a memory leak.
> - [Medium] Incorrect check for the active port treats valid index 0 as invalid.
> --
>
> Hi,
>
> I have reviewed the patch and found some potential issues to share.
>
> commit 082c705aa7547b521ad9df20ac76e0592b41864f
> Author: Chaoyi Chen <chaoyi.chen at rock-chips.com>
>
> drm/rockchip: cdn-dp: Add multiple bridges to support PHY port selection
>
> This commit adds support for multiple bridges to the Rockchip cdn-dp driver
> to allow switching between two USB/DP combo PHYs. It changes the driver to
> use the DRM AUX HPD bridge and adds DT parsing logic to register multiple
> bridges.
>
>> @@ -192,14 +193,27 @@ static int cdn_dp_get_sink_count(struct cdn_dp_device *dp, u8 *sink_count)
>> static struct cdn_dp_port *cdn_dp_connected_port(struct cdn_dp_device *dp)
>> {
>> struct cdn_dp_port *port;
>> - int i, lanes;
>> + int i, lanes[MAX_PHY];
>>
>> for (i = 0; i < dp->ports; i++) {
>> port = dp->port[i];
>> - lanes = cdn_dp_get_port_lanes(port);
>> - if (lanes)
>> + lanes[i] = cdn_dp_get_port_lanes(port);
>> + if (!dp->next_bridge_valid)
>> return port;
>
> [Severity: High]
> Does this unconditionally return the first port without checking if it is
> actually connected? If dp->next_bridge_valid is false (which is the case for
> extcon setups), it appears to return port on the first iteration regardless
> of lanes[i], completely breaking hotplug detection.
>
>> }
No.
>> +
>> + if (dp->next_bridge_valid) {
>> + /* If more than one port is available, pick the last active port */
>> + if (dp->active_port > 0 && lanes[dp->active_port])
>
> [Severity: Medium]
> Since dp->active_port is initialized to -1, valid port indices are 0 and 1.
> Does the check dp->active_port > 0 incorrectly evaluate to false when the
> active port is 0, skipping the active port?
>
That's right. Will fix in v2.
>> + return dp->port[dp->active_port];
>> +
>> + /* If the last active port is not available, pick an available port in order */
>> + for (i = 0; i < dp->bridge_count; i++) {
>> + if (lanes[i])
>
> [Severity: High]
> Can this read uninitialized stack memory? The lanes stack array is populated
> in a loop bounded by dp->ports, but accessed here in a loop bounded by
> dp->bridge_count.
>
> If dp->bridge_count > dp->ports, this will read uninitialized values.
> Furthermore, since lanes is sized to MAX_PHY, if dp->bridge_count > MAX_PHY,
> it will perform an out-of-bounds stack read.
>
No.
>> + return dp->port[i];
>> + }
>> + }
>> +
>> return NULL;
>> }
>
> [ ... ]
>
>> @@ -254,12 +268,45 @@ static const struct drm_edid *
>> cdn_dp_bridge_edid_read(struct drm_bridge *bridge, struct drm_connector *connector)
>> {
>> struct cdn_dp_device *dp = bridge_to_dp(bridge);
>> - const struct drm_edid *drm_edid;
>> + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
>> + struct cdn_dp_port *port = dp->port[dp_bridge->id];
>> + struct cdn_dp_port *prev_port;
>> + const struct drm_edid *drm_edid = NULL;
>> int i, ret;
>>
>> mutex_lock(&dp->lock);
>> +
>> + /* More than one port is available */
>> + if (dp->bridge_count > 1 && !port->phy_enabled) {
>> + for (i = 0; i < dp->bridge_count; i++) {
>> + /* Another port already enable */
>> + if (dp->bridge_list[i] != dp_bridge && dp->bridge_list[i]->enabled)
>> + goto get_cache;
>> + /* Find already enabled port */
>> + if (dp->port[i]->phy_enabled)
>> + prev_port = dp->port[i];
>> + }
>> +
>> + /* Switch to current port */
>> + if (prev_port) {
>
> [Severity: High]
> Can this lead to a kernel crash by dereferencing an uninitialized pointer?
> The prev_port variable is not initialized. If no ports are currently enabled
> (which is common at boot), the for loop will complete without assigning
> anything to prev_port.
>
> Then if (prev_port) evaluates garbage memory, and calling cdn_dp_switch_port
> with it will cause a kernel panic.
>
No.
>> + ret = cdn_dp_switch_port(dp, prev_port, port);
>> + if (ret)
>> + goto get_cache;
>> + }
>> + }
>
> [ ... ]
>
>> @@ -551,6 +599,54 @@ static bool cdn_dp_check_link_status(struct cdn_dp_device *dp)
>> return drm_dp_channel_eq_ok(link_status, min(port->lanes, sink_lanes));
>> }
>>
>> +static int cdn_dp_switch_port(struct cdn_dp_device *dp, struct cdn_dp_port *prev_port,
>> + struct cdn_dp_port *port)
>> +{
>> + int ret;
>> +
>> + if (dp->active)
>> + return 0;
>
> [Severity: High]
> Does this incorrectly abort hardware switching? If the DP controller is
> already active but currently on a different port, returning 0 skips the
> actual port switch. The caller cdn_dp_bridge_edid_read() will proceed with
> reading EDID from the wrong (already-active) port and caching it for the
> new port.
>
Actually, this is a redundant check. When dp->active is true,
this function is never called at all.
>> +
>> + ret = cdn_dp_disable_phy(dp, prev_port);
>> + if (ret)
>> + goto out;
>> + ret = cdn_dp_enable_phy(dp, port);
>> + if (ret)
>> + goto out;
>> +
>> + ret = cdn_dp_get_sink_capability(dp);
>> + if (ret) {
>> + cdn_dp_disable_phy(dp, port);
>> + goto out;
>> + }
>> +
>> + dp->active = true;
>> + dp->lanes = port->lanes;
>> +
>> + if (!cdn_dp_check_link_status(dp)) {
>> + dev_info(dp->dev, "Connected with sink; re-train link\n");
>> +
>> + ret = cdn_dp_train_link(dp);
>> + if (ret) {
>> + dev_err(dp->dev, "Training link failed: %d\n", ret);
>> + goto out;
>
> [Severity: High]
> If link training fails, this jumps to out and returns the error code, but
> leaves dp->active = true. Does this trap the port in a permanently broken,
> untrained state since subsequent calls to cdn_dp_switch_port() will
> immediately return 0?
>
The redundant assignment, as mentioned above, should be removed.
>> + }
>
> [ ... ]
>
>> @@ -639,9 +740,11 @@ static void cdn_dp_bridge_atomic_enable(struct drm_bridge *bridge, struct drm_at
>> static void cdn_dp_bridge_atomic_disable(struct drm_bridge *bridge, struct drm_atomic_state *state)
>> {
>> struct cdn_dp_device *dp = bridge_to_dp(bridge);
>> + struct cdn_dp_bridge *dp_bridge = bridge_to_dp_bridge(bridge);
>> int ret;
>>
>> mutex_lock(&dp->lock);
>> + dp_bridge->enabled = false;
>>
>> if (dp->active) {
>> ret = cdn_dp_disable(dp);
>
> [Severity: High]
> Does this accidentally disable the active DP controller when another inactive
> bridge is disabled? If dp->active is true, the entire DP controller is
> disabled, without checking if the bridge being disabled (dp_bridge) is
> actually the one driving the active DP port. This would break an active
> Bridge A if userspace disables an inactive Bridge B.
>
> [ ... ]
>
No.
>> @@ -967,28 +1085,16 @@ static int cdn_dp_pd_event(struct notifier_block *nb,
>> return NOTIFY_DONE;
>> }
>>
>> -static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
>> +static int cdn_bridge_add(struct device *dev,
>> + struct drm_bridge *bridge,
>> + struct drm_bridge *next_bridge,
>> + struct drm_encoder *encoder)
>> {
>> struct cdn_dp_device *dp = dev_get_drvdata(dev);
>> - struct drm_encoder *encoder;
>> + struct drm_device *drm_dev = dp->drm_dev;
>> + struct drm_bridge *last_bridge __free(drm_bridge_put) = NULL;
>
> [Severity: High]
> Will this cause a reference count underflow and a use-after-free
> vulnerability? The last_bridge pointer is populated by
> drm_bridge_chain_get_last_bridge(), which returns a raw pointer without
> incrementing the bridge's reference count. When last_bridge goes out of
> scope, the automatic drm_bridge_put() will prematurely decrement the
> refcount.
>
No.
>> struct drm_connector *connector;
>> - struct cdn_dp_port *port;
>> - struct drm_device *drm_dev = data;
>> - int ret, i;
>> -
>
> [ ... ]
>
>> + if (next_bridge) {
>> + ret = drm_bridge_attach(encoder, next_bridge, bridge,
>> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>> + if (ret)
>> + return ret;
>> +
>> + last_bridge = drm_bridge_chain_get_last_bridge(bridge->encoder);
>> + }
>> +
>> connector = drm_bridge_connector_init(drm_dev, encoder);
>> if (IS_ERR(connector)) {
>> ret = PTR_ERR(connector);
>> @@ -1030,8 +1145,99 @@ static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
>> return ret;
>> }
>>
>> + if (last_bridge)
>> + connector->fwnode = fwnode_handle_get(of_fwnode_handle(last_bridge->of_node));
>
> [Severity: Medium]
> Is this assignment leaking an fwnode reference? drm_bridge_connector_init()
> automatically sets connector->fwnode using the of_node of the last bridge
> and acquires a reference via fwnode_handle_get(). Overwriting it here without
> dropping the original reference will cause a memory leak.
>
Yes, the old fwnode should be put.
>> +
>> drm_connector_attach_encoder(connector, encoder);
>>
>> + return 0;
>> +}
>> +
>> +static int cdn_dp_parse_next_bridge_dt(struct cdn_dp_device *dp)
>> +{
>> + struct device_node *np = dp->dev->of_node;
>> + struct device_node *port __free(device_node) = of_graph_get_port_by_id(np, 1);
>> + struct drm_bridge *bridge;
>> + int count = 0;
>> + int ret = 0;
>> + int i;
>> +
>> + /* If device use extcon, do not use hpd bridge */
>> + for (i = 0; i < dp->ports; i++) {
>> + if (dp->port[i]->extcon) {
>> + dp->bridge_count = 1;
>> + return 0;
>> + }
>> + }
>> +
>> + /* One endpoint may correspond to one next bridge. */
>> + for_each_of_graph_port_endpoint(port, dp_ep) {
>> + struct device_node *next_bridge_node __free(device_node) =
>> + of_graph_get_remote_port_parent(dp_ep);
>> +
>> + bridge = of_drm_find_bridge(next_bridge_node);
>> + if (!bridge) {
>> + ret = -EPROBE_DEFER;
>> + goto out;
>
> [Severity: High]
> Does this leak the DRM bridge references acquired in earlier loop iterations?
> If of_drm_find_bridge() returns NULL on a subsequent iteration, it jumps to
> out and returns -EPROBE_DEFER without releasing the references previously
> obtained with drm_bridge_get().
>
Yes, it should be put.
>> + }
>> +
>> + dp->next_bridge_valid = true;
>> + dp->next_bridge_list[count] = drm_bridge_get(bridge);
>> + count++;
>
> [Severity: High]
> Could this overflow the next_bridge_list array? next_bridge_list is sized to
> MAX_PHY (2). If a device tree specifies more than two endpoints, count will
> exceed the array bounds and corrupt adjacent fields in the cdn_dp_device
> struct.
>
No.
>> + }
>> +
>> +out:
>> + dp->bridge_count = count ? count : 1;
>> + return ret;
>> +}
>> +
>> +static int cdn_dp_bind(struct device *dev, struct device *master, void *data)
>> +{
>
> [ ... ]
>
>> + for (i = 0; i < dp->bridge_count; i++) {
>> + encoder = &dp->bridge_list[i]->encoder.encoder;
>> + bridge = &dp->bridge_list[i]->bridge;
>> + next_bridge = dp->next_bridge_list[i];
>> + ret = cdn_bridge_add(dev, bridge, next_bridge, encoder);
>> + if (ret)
>> + return ret;
>
> [Severity: High]
> Does this leak the bridge references acquired in cdn_dp_parse_next_bridge_dt
> if cdn_bridge_add() fails? Returning directly without a cleanup path means
> drm_bridge_put() is never called for dp->next_bridge_list.
>
>> + }
>
Yes, it should be put.
--
Best,
Chaoyi
More information about the linux-phy
mailing list