UAPI Changes: None Cross-subsystem Changes: None Core Changes: Fixed regressions introduced by commit cd82d82cbc04 ("drm/dp_mst: Add branch bandwidth validation to MST atomic check"), which would cause us to: * Calculate the available bandwidth on an MST topology incorrectly, and as a result reject most display configurations that would try to enable more then one sink on a topology * Occasionally expose MST connectors to userspace before finishing probing their PBN capabilities, resulting in us rejecting display configurations because we assumed briefly that no bandwidth was available Driver Changes: None
Unified · Split
| @@ -1935,7 +1935,7 @@ static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port, · 1934 unmodified lines | ||
| 1935 | 1935 | return parent_lct + 1; |
| 1936 | 1936 | } |
| 1937 | 1937 | |
| 1938 | −static bool drm_dp_mst_is_dp_mst_end_device(u8 pdt, bool mcs) | |
| 1938 | +static bool drm_dp_mst_is_end_device(u8 pdt, bool mcs) | |
| 1939 | 1939 | { |
| 1940 | 1940 | switch (pdt) { |
| 1941 | 1941 | case DP_PEER_DEVICE_DP_LEGACY_CONV: |
| @@ -1965,13 +1965,13 @@ drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, · 23 unmodified lines | ||
| 1965 | 1965 | |
| 1966 | 1966 | /* Teardown the old pdt, if there is one */ |
| 1967 | 1967 | if (port->pdt != DP_PEER_DEVICE_NONE) { |
| 1968 | − if (drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) { | |
| 1968 | + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { | |
| 1969 | 1969 | /* |
| 1970 | 1970 | * If the new PDT would also have an i2c bus, |
| 1971 | 1971 | * don't bother with reregistering it |
| 1972 | 1972 | */ |
| 1973 | 1973 | if (new_pdt != DP_PEER_DEVICE_NONE && |
| 1974 | − drm_dp_mst_is_dp_mst_end_device(new_pdt, new_mcs)) { | |
| 1974 | + drm_dp_mst_is_end_device(new_pdt, new_mcs)) { | |
| 1975 | 1975 | port->pdt = new_pdt; |
| 1976 | 1976 | port->mcs = new_mcs; |
| 1977 | 1977 | return 0; |
| @@ -1991,7 +1991,7 @@ drm_dp_port_set_pdt(struct drm_dp_mst_port *port, u8 new_pdt, · 13 unmodified lines | ||
| 1991 | 1991 | port->mcs = new_mcs; |
| 1992 | 1992 | |
| 1993 | 1993 | if (port->pdt != DP_PEER_DEVICE_NONE) { |
| 1994 | − if (drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) { | |
| 1994 | + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { | |
| 1995 | 1995 | /* add i2c over sideband */ |
| 1996 | 1996 | ret = drm_dp_mst_register_i2c_bus(&port->aux); |
| 1997 | 1997 | } else { |
| @@ -2172,7 +2172,7 @@ drm_dp_mst_port_add_connector(struct drm_dp_mst_branch *mstb, · 174 unmodified lines | ||
| 2172 | 2172 | } |
| 2173 | 2173 | |
| 2174 | 2174 | if (port->pdt != DP_PEER_DEVICE_NONE && |
| 2175 | − drm_dp_mst_is_dp_mst_end_device(port->pdt, port->mcs)) { | |
| 2175 | + drm_dp_mst_is_end_device(port->pdt, port->mcs)) { | |
| 2176 | 2176 | port->cached_edid = drm_get_edid(port->connector, |
| 2177 | 2177 | &port->aux.ddc); |
| 2178 | 2178 | drm_connector_set_tile_property(port->connector); |
| @@ -2302,14 +2302,18 @@ drm_dp_mst_handle_link_address_port(struct drm_dp_mst_branch *mstb, · 123 unmodified lines | ||
| 2302 | 2302 | mutex_unlock(&mgr->lock); |
| 2303 | 2303 | } |
| 2304 | 2304 | |
| 2305 | − if (old_ddps != port->ddps) { | |
| 2306 | − if (port->ddps) { | |
| 2307 | − if (!port->input) { | |
| 2308 | − drm_dp_send_enum_path_resources(mgr, mstb, | |
| 2309 | − port); | |
| 2310 | − } | |
| 2305 | + /* | |
| 2306 | + * Reprobe PBN caps on both hotplug, and when re-probing the link | |
| 2307 | + * for our parent mstb | |
| 2308 | + */ | |
| 2309 | + if (old_ddps != port->ddps || !created) { | |
| 2310 | + if (port->ddps && !port->input) { | |
| 2311 | + ret = drm_dp_send_enum_path_resources(mgr, mstb, | |
| 2312 | + port); | |
| 2313 | + if (ret == 1) | |
| 2314 | + changed = true; | |
| 2311 | 2315 | } else { |
| 2312 | − port->available_pbn = 0; | |
| 2316 | + port->full_pbn = 0; | |
| 2313 | 2317 | } |
| 2314 | 2318 | } |
| 2315 | 2319 | |
| @@ -2401,11 +2405,10 @@ drm_dp_mst_handle_conn_stat(struct drm_dp_mst_branch *mstb, · 85 unmodified lines | ||
| 2401 | 2405 | port->ddps = conn_stat->displayport_device_plug_status; |
| 2402 | 2406 | |
| 2403 | 2407 | if (old_ddps != port->ddps) { |
| 2404 | − if (port->ddps) { | |
| 2405 | − dowork = true; | |
| 2406 | − } else { | |
| 2407 | − port->available_pbn = 0; | |
| 2408 | − } | |
| 2408 | + if (port->ddps && !port->input) | |
| 2409 | + drm_dp_send_enum_path_resources(mgr, mstb, port); | |
| 2410 | + else | |
| 2411 | + port->full_pbn = 0; | |
| 2409 | 2412 | } |
| 2410 | 2413 | |
| 2411 | 2414 | new_pdt = port->input ? DP_PEER_DEVICE_NONE : conn_stat->peer_device_type; |
| @@ -2556,13 +2559,6 @@ static int drm_dp_check_and_send_link_address(struct drm_dp_mst_topology_mgr *mg · 144 unmodified lines | ||
| 2556 | 2559 | if (port->input || !port->ddps) |
| 2557 | 2560 | continue; |
| 2558 | 2561 | |
| 2559 | − if (!port->available_pbn) { | |
| 2560 | − drm_modeset_lock(&mgr->base.lock, NULL); | |
| 2561 | − drm_dp_send_enum_path_resources(mgr, mstb, port); | |
| 2562 | − drm_modeset_unlock(&mgr->base.lock); | |
| 2563 | − changed = true; | |
| 2564 | − } | |
| 2565 | − | |
| 2566 | 2562 | if (port->mstb) |
| 2567 | 2563 | mstb_child = drm_dp_mst_topology_get_mstb_validated( |
| 2568 | 2564 | mgr, port->mstb); |
| @@ -2990,6 +2986,7 @@ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, · 421 unmodified lines | ||
| 2990 | 2986 | |
| 2991 | 2987 | ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); |
| 2992 | 2988 | if (ret > 0) { |
| 2989 | + ret = 0; | |
| 2993 | 2990 | path_res = &txmsg->reply.u.path_resources; |
| 2994 | 2991 | |
| 2995 | 2992 | if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) { |
| @@ -3002,14 +2999,22 @@ drm_dp_send_enum_path_resources(struct drm_dp_mst_topology_mgr *mgr, · 6 unmodified lines | ||
| 3002 | 2999 | path_res->port_number, |
| 3003 | 3000 | path_res->full_payload_bw_number, |
| 3004 | 3001 | path_res->avail_payload_bw_number); |
| 3005 | − port->available_pbn = | |
| 3006 | − path_res->avail_payload_bw_number; | |
| 3002 | + | |
| 3003 | + /* | |
| 3004 | + * If something changed, make sure we send a | |
| 3005 | + * hotplug | |
| 3006 | + */ | |
| 3007 | + if (port->full_pbn != path_res->full_payload_bw_number || | |
| 3008 | + port->fec_capable != path_res->fec_capable) | |
| 3009 | + ret = 1; | |
| 3010 | + | |
| 3011 | + port->full_pbn = path_res->full_payload_bw_number; | |
| 3007 | 3012 | port->fec_capable = path_res->fec_capable; |
| 3008 | 3013 | } |
| 3009 | 3014 | } |
| 3010 | 3015 | |
| 3011 | 3016 | kfree(txmsg); |
| 3012 | − return 0; | |
| 3017 | + return ret; | |
| 3013 | 3018 | } |
| 3014 | 3019 | |
| 3015 | 3020 | static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm_dp_mst_branch *mstb) |
| @@ -3596,13 +3601,9 @@ drm_dp_mst_topology_mgr_invalidate_mstb(struct drm_dp_mst_branch *mstb) · 580 unmodified lines | ||
| 3596 | 3601 | /* The link address will need to be re-sent on resume */ |
| 3597 | 3602 | mstb->link_address_sent = false; |
| 3598 | 3603 | |
| 3599 | − list_for_each_entry(port, &mstb->ports, next) { | |
| 3600 | − /* The PBN for each port will also need to be re-probed */ | |
| 3601 | − port->available_pbn = 0; | |
| 3602 | − | |
| 3604 | + list_for_each_entry(port, &mstb->ports, next) | |
| 3603 | 3605 | if (port->mstb) |
| 3604 | 3606 | drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb); |
| 3605 | − } | |
| 3606 | 3607 | } |
| 3607 | 3608 | |
| 3608 | 3609 | /** |
| @@ -4829,41 +4830,102 @@ static bool drm_dp_mst_port_downstream_of_branch(struct drm_dp_mst_port *port, · 1220 unmodified lines | ||
| 4829 | 4830 | return false; |
| 4830 | 4831 | } |
| 4831 | 4832 | |
| 4832 | −static inline | |
| 4833 | −int drm_dp_mst_atomic_check_bw_limit(struct drm_dp_mst_branch *branch, | |
| 4834 | − struct drm_dp_mst_topology_state *mst_state) | |
| 4833 | +static int | |
| 4834 | +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, | |
| 4835 | + struct drm_dp_mst_topology_state *state); | |
| 4836 | + | |
| 4837 | +static int | |
| 4838 | +drm_dp_mst_atomic_check_mstb_bw_limit(struct drm_dp_mst_branch *mstb, | |
| 4839 | + struct drm_dp_mst_topology_state *state) | |
| 4835 | 4840 | { |
| 4836 | − struct drm_dp_mst_port *port; | |
| 4837 | 4841 | struct drm_dp_vcpi_allocation *vcpi; |
| 4838 | − int pbn_limit = 0, pbn_used = 0; | |
| 4842 | + struct drm_dp_mst_port *port; | |
| 4843 | + int pbn_used = 0, ret; | |
| 4844 | + bool found = false; | |
| 4839 | 4845 | |
| 4840 | − list_for_each_entry(port, &branch->ports, next) { | |
| 4841 | − if (port->mstb) | |
| 4842 | − if (drm_dp_mst_atomic_check_bw_limit(port->mstb, mst_state)) | |
| 4843 | − return -ENOSPC; | |
| 4846 | + /* Check that we have at least one port in our state that's downstream | |
| 4847 | + * of this branch, otherwise we can skip this branch | |
| 4848 | + */ | |
| 4849 | + list_for_each_entry(vcpi, &state->vcpis, next) { | |
| 4850 | + if (!vcpi->pbn || | |
| 4851 | + !drm_dp_mst_port_downstream_of_branch(vcpi->port, mstb)) | |
| 4852 | + continue; | |
| 4844 | 4853 | |
| 4845 | − if (port->available_pbn > 0) | |
| 4846 | − pbn_limit = port->available_pbn; | |
| 4854 | + found = true; | |
| 4855 | + break; | |
| 4847 | 4856 | } |
| 4848 | − DRM_DEBUG_ATOMIC("[MST BRANCH:%p] branch has %d PBN available\n", | |
| 4849 | − branch, pbn_limit); | |
| 4857 | + if (!found) | |
| 4858 | + return 0; | |
| 4850 | 4859 | |
| 4851 | − list_for_each_entry(vcpi, &mst_state->vcpis, next) { | |
| 4852 | − if (!vcpi->pbn) | |
| 4853 | − continue; | |
| 4860 | + if (mstb->port_parent) | |
| 4861 | + DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] Checking bandwidth limits on [MSTB:%p]\n", | |
| 4862 | + mstb->port_parent->parent, mstb->port_parent, | |
| 4863 | + mstb); | |
| 4864 | + else | |
| 4865 | + DRM_DEBUG_ATOMIC("[MSTB:%p] Checking bandwidth limits\n", | |
| 4866 | + mstb); | |
| 4867 | + | |
| 4868 | + list_for_each_entry(port, &mstb->ports, next) { | |
| 4869 | + ret = drm_dp_mst_atomic_check_port_bw_limit(port, state); | |
| 4870 | + if (ret < 0) | |
| 4871 | + return ret; | |
| 4854 | 4872 | |
| 4855 | − if (drm_dp_mst_port_downstream_of_branch(vcpi->port, branch)) | |
| 4856 | − pbn_used += vcpi->pbn; | |
| 4873 | + pbn_used += ret; | |
| 4857 | 4874 | } |
| 4858 | − DRM_DEBUG_ATOMIC("[MST BRANCH:%p] branch used %d PBN\n", | |
| 4859 | − branch, pbn_used); | |
| 4860 | 4875 | |
| 4861 | − if (pbn_used > pbn_limit) { | |
| 4862 | − DRM_DEBUG_ATOMIC("[MST BRANCH:%p] No available bandwidth\n", | |
| 4863 | − branch); | |
| 4876 | + return pbn_used; | |
| 4877 | +} | |
| 4878 | + | |
| 4879 | +static int | |
| 4880 | +drm_dp_mst_atomic_check_port_bw_limit(struct drm_dp_mst_port *port, | |
| 4881 | + struct drm_dp_mst_topology_state *state) | |
| 4882 | +{ | |
| 4883 | + struct drm_dp_vcpi_allocation *vcpi; | |
| 4884 | + int pbn_used = 0; | |
| 4885 | + | |
| 4886 | + if (port->pdt == DP_PEER_DEVICE_NONE) | |
| 4887 | + return 0; | |
| 4888 | + | |
| 4889 | + if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { | |
| 4890 | + bool found = false; | |
| 4891 | + | |
| 4892 | + list_for_each_entry(vcpi, &state->vcpis, next) { | |
| 4893 | + if (vcpi->port != port) | |
| 4894 | + continue; | |
| 4895 | + if (!vcpi->pbn) | |
| 4896 | + return 0; | |
| 4897 | + | |
| 4898 | + found = true; | |
| 4899 | + break; | |
| 4900 | + } | |
| 4901 | + if (!found) | |
| 4902 | + return 0; | |
| 4903 | + | |
| 4904 | + /* This should never happen, as it means we tried to | |
| 4905 | + * set a mode before querying the full_pbn | |
| 4906 | + */ | |
| 4907 | + if (WARN_ON(!port->full_pbn)) | |
| 4908 | + return -EINVAL; | |
| 4909 | + | |
| 4910 | + pbn_used = vcpi->pbn; | |
| 4911 | + } else { | |
| 4912 | + pbn_used = drm_dp_mst_atomic_check_mstb_bw_limit(port->mstb, | |
| 4913 | + state); | |
| 4914 | + if (pbn_used <= 0) | |
| 4915 | + return pbn_used; | |
| 4916 | + } | |
| 4917 | + | |
| 4918 | + if (pbn_used > port->full_pbn) { | |
| 4919 | + DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] required PBN of %d exceeds port limit of %d\n", | |
| 4920 | + port->parent, port, pbn_used, | |
| 4921 | + port->full_pbn); | |
| 4864 | 4922 | return -ENOSPC; |
| 4865 | 4923 | } |
| 4866 | − return 0; | |
| 4924 | + | |
| 4925 | + DRM_DEBUG_ATOMIC("[MSTB:%p] [MST PORT:%p] uses %d out of %d PBN\n", | |
| 4926 | + port->parent, port, pbn_used, port->full_pbn); | |
| 4927 | + | |
| 4928 | + return pbn_used; | |
| 4867 | 4929 | } |
| 4868 | 4930 | |
| 4869 | 4931 | static inline int |
| @@ -5061,9 +5123,15 @@ int drm_dp_mst_atomic_check(struct drm_atomic_state *state) · 191 unmodified lines | ||
| 5061 | 5123 | ret = drm_dp_mst_atomic_check_vcpi_alloc_limit(mgr, mst_state); |
| 5062 | 5124 | if (ret) |
| 5063 | 5125 | break; |
| 5064 | − ret = drm_dp_mst_atomic_check_bw_limit(mgr->mst_primary, mst_state); | |
| 5065 | − if (ret) | |
| 5126 | + | |
| 5127 | + mutex_lock(&mgr->lock); | |
| 5128 | + ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary, | |
| 5129 | + mst_state); | |
| 5130 | + mutex_unlock(&mgr->lock); | |
| 5131 | + if (ret < 0) | |
| 5066 | 5132 | break; |
| 5133 | + else | |
| 5134 | + ret = 0; | |
| 5067 | 5135 | } |
| 5068 | 5136 | |
| 5069 | 5137 | return ret; |
| @@ -81,7 +81,7 @@ struct drm_dp_vcpi { · 80 unmodified lines | ||
| 81 | 81 | * &drm_dp_mst_topology_mgr.base.lock. |
| 82 | 82 | * @num_sdp_stream_sinks: Number of stream sinks. Protected by |
| 83 | 83 | * &drm_dp_mst_topology_mgr.base.lock. |
| 84 | − * @available_pbn: Available bandwidth for this port. Protected by | |
| 84 | + * @full_pbn: Max possible bandwidth for this port. Protected by | |
| 85 | 85 | * &drm_dp_mst_topology_mgr.base.lock. |
| 86 | 86 | * @next: link to next port on this branch device |
| 87 | 87 | * @aux: i2c aux transport to talk to device connected to this port, protected |
| @@ -126,7 +126,7 @@ struct drm_dp_mst_port { · 38 unmodified lines | ||
| 126 | 126 | u8 dpcd_rev; |
| 127 | 127 | u8 num_sdp_streams; |
| 128 | 128 | u8 num_sdp_stream_sinks; |
| 129 | − uint16_t available_pbn; | |
| 129 | + uint16_t full_pbn; | |
| 130 | 130 | struct list_head next; |
| 131 | 131 | /** |
| 132 | 132 | * @mstb: the branch device connected to this port, if there is one. |