/** * DOC: dp mst helper * * These functions contain parts of the DisplayPort 1.2a MultiStream Transport * protocol. The helpers contain a topology manager and bandwidth manager. * The helpers encapsulate the sending and received of sideband msgs.
*/ struct drm_dp_pending_up_req { struct drm_dp_sideband_msg_hdr hdr; struct drm_dp_sideband_msg_req_body msg; struct list_head next;
};
/* Decode a sideband request we've encoded, mainly used for debugging */ int
drm_dp_decode_sideband_req(conststruct drm_dp_sideband_msg_tx *raw, struct drm_dp_sideband_msg_req_body *req)
{ const u8 *buf = raw->msg; int i, idx = 0;
req->req_type = buf[idx++] & 0x7f; switch (req->req_type) { case DP_ENUM_PATH_RESOURCES: case DP_POWER_DOWN_PHY: case DP_POWER_UP_PHY:
req->u.port_num.port_number = (buf[idx] >> 4) & 0xf; break; case DP_ALLOCATE_PAYLOAD:
{ struct drm_dp_allocate_payload *a =
&req->u.allocate_payload;
ret = drm_dp_decode_sideband_req(txmsg, &req); if (ret) {
drm_printf(p, "<failed to decode sideband req: %d>\n", ret); return;
}
drm_dp_dump_sideband_msg_req_body(&req, 1, p);
switch (req.req_type) { case DP_REMOTE_DPCD_WRITE:
kfree(req.u.dpcd_write.bytes); break; case DP_REMOTE_I2C_READ: for (i = 0; i < req.u.i2c_read.num_transactions; i++)
kfree(req.u.i2c_read.transactions[i].bytes); break; case DP_REMOTE_I2C_WRITE:
kfree(req.u.i2c_write.bytes); break;
}
}
staticint drm_dp_sideband_msg_set_header(struct drm_dp_sideband_msg_rx *msg, struct drm_dp_sideband_msg_hdr *hdr,
u8 hdrlen)
{ /* * ignore out-of-order messages or messages that are part of a * failed transaction
*/ if (!hdr->somt && !msg->have_somt) returnfalse;
/* get length contained in this portion */
msg->curchunk_idx = 0;
msg->curchunk_len = hdr->msg_len;
msg->curchunk_hdrlen = hdrlen;
/* we have already gotten an somt - don't bother parsing */ if (hdr->somt && msg->have_somt) returnfalse;
if (hdr->somt) {
memcpy(&msg->initial_hdr, hdr, sizeof(struct drm_dp_sideband_msg_hdr));
msg->have_somt = true;
} if (hdr->eomt)
msg->have_eomt = true;
returntrue;
}
/* this adds a chunk of msg to the builder to get the final msg */ staticbool drm_dp_sideband_append_payload(struct drm_dp_sideband_msg_rx *msg,
u8 *replybuf, u8 replybuflen)
{
u8 crc4;
/* * NOTE: It's my impression from reading the spec that the below parsing * is correct. However I noticed while testing with an HDCP 1.4 display * through an HDCP 2.2 hub that only bit 3 was set. In that case, I * would expect both bits to be set. So keep the parsing following the * spec, but beware reality might not match the spec (at least for some * configurations).
*/
reply->hdcp_1x_device_present = raw->msg[2] & BIT(4);
reply->hdcp_2x_device_present = raw->msg[2] & BIT(3);
/* * All updates to txmsg->state are protected by mgr->qlock, and the two * cases we check here are terminal states. For those the barriers * provided by the wake_up/wait_event pair are enough.
*/
state = READ_ONCE(txmsg->state); return (state == DRM_DP_SIDEBAND_TX_RX ||
state == DRM_DP_SIDEBAND_TX_TIMEOUT);
}
for (;;) { /* * If the driver provides a way for this, change to * poll-waiting for the MST reply interrupt if we didn't receive * it for 50 msec. This would cater for cases where the HPD * pulse signal got lost somewhere, even though the sink raised * the corresponding MST interrupt correctly. One example is the * Club 3D CAC-1557 TypeC -> DP adapter which for some reason * filters out short pulses with a duration less than ~540 usec. * * The poll period is 50 msec to avoid missing an interrupt * after the sink has cleared it (after a 110msec timeout * since it raised the interrupt).
*/
ret = wait_event_timeout(mgr->tx_waitq,
check_txmsg_state(mgr, txmsg),
mgr->cbs->poll_hpd_irq ?
msecs_to_jiffies(50) :
wait_timeout);
if (ret || !mgr->cbs->poll_hpd_irq ||
time_after(jiffies, wait_expires)) break;
mgr->cbs->poll_hpd_irq(mgr);
}
mutex_lock(&mgr->qlock); if (ret > 0) { if (txmsg->state == DRM_DP_SIDEBAND_TX_TIMEOUT) {
ret = -EIO; goto out;
}
} else {
drm_dbg_kms(mgr->dev, "timedout msg send %p %d %d\n",
txmsg, txmsg->state, txmsg->seqno);
/* dump some state */
ret = -EIO;
/* remove from q */ if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED ||
txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND ||
txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
list_del(&txmsg->next);
}
out: if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) { struct drm_printer p = drm_dbg_printer(mgr->dev, DRM_UT_DP,
DBG_PREFIX);
if (mstb->port_parent)
drm_dp_mst_put_port_malloc(mstb->port_parent);
kfree(mstb);
}
/** * DOC: Branch device and port refcounting * * Topology refcount overview * ~~~~~~~~~~~~~~~~~~~~~~~~~~ * * The refcounting schemes for &struct drm_dp_mst_branch and &struct * drm_dp_mst_port are somewhat unusual. Both ports and branch devices have * two different kinds of refcounts: topology refcounts, and malloc refcounts. * * Topology refcounts are not exposed to drivers, and are handled internally * by the DP MST helpers. The helpers use them in order to prevent the * in-memory topology state from being changed in the middle of critical * operations like changing the internal state of payload allocations. This * means each branch and port will be considered to be connected to the rest * of the topology until its topology refcount reaches zero. Additionally, * for ports this means that their associated &struct drm_connector will stay * registered with userspace until the port's refcount reaches 0. * * Malloc refcount overview * ~~~~~~~~~~~~~~~~~~~~~~~~ * * Malloc references are used to keep a &struct drm_dp_mst_port or &struct * drm_dp_mst_branch allocated even after all of its topology references have * been dropped, so that the driver or MST helpers can safely access each * branch's last known state before it was disconnected from the topology. * When the malloc refcount of a port or branch reaches 0, the memory * allocation containing the &struct drm_dp_mst_branch or &struct * drm_dp_mst_port respectively will be freed. * * For &struct drm_dp_mst_branch, malloc refcounts are not currently exposed * to drivers. As of writing this documentation, there are no drivers that * have a usecase for accessing &struct drm_dp_mst_branch outside of the MST * helpers. Exposing this API to drivers in a race-free manner would take more * tweaking of the refcounting scheme, however patches are welcome provided * there is a legitimate driver usecase for this. * * Refcount relationships in a topology * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * Let's take a look at why the relationship between topology and malloc * refcounts is designed the way it is. * * .. kernel-figure:: dp-mst/topology-figure-1.dot * * An example of topology and malloc refs in a DP MST topology with two * active payloads. Topology refcount increments are indicated by solid * lines, and malloc refcount increments are indicated by dashed lines. * Each starts from the branch which incremented the refcount, and ends at * the branch to which the refcount belongs to, i.e. the arrow points the * same way as the C pointers used to reference a structure. * * As you can see in the above figure, every branch increments the topology * refcount of its children, and increments the malloc refcount of its * parent. Additionally, every payload increments the malloc refcount of its * assigned port by 1. * * So, what would happen if MSTB #3 from the above figure was unplugged from * the system, but the driver hadn't yet removed payload #2 from port #3? The * topology would start to look like the figure below. * * .. kernel-figure:: dp-mst/topology-figure-2.dot * * Ports and branch devices which have been released from memory are * colored grey, and references which have been removed are colored red. * * Whenever a port or branch device's topology refcount reaches zero, it will * decrement the topology refcounts of all its children, the malloc refcount * of its parent, and finally its own malloc refcount. For MSTB #4 and port * #4, this means they both have been disconnected from the topology and freed * from memory. But, because payload #2 is still holding a reference to port * #3, port #3 is removed from the topology but its &struct drm_dp_mst_port * is still accessible from memory. This also means port #3 has not yet * decremented the malloc refcount of MSTB #3, so its &struct * drm_dp_mst_branch will also stay allocated in memory until port #3's * malloc refcount reaches 0. * * This relationship is necessary because in order to release payload #2, we * need to be able to figure out the last relative of port #3 that's still * connected to the topology. In this case, we would travel up the topology as * shown below. * * .. kernel-figure:: dp-mst/topology-figure-3.dot * * And finally, remove payload #2 by communicating with port #2 through * sideband transactions.
*/
/** * drm_dp_mst_get_mstb_malloc() - Increment the malloc refcount of a branch * device * @mstb: The &struct drm_dp_mst_branch to increment the malloc refcount of * * Increments &drm_dp_mst_branch.malloc_kref. When * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb * will be released and @mstb may no longer be used. * * See also: drm_dp_mst_put_mstb_malloc()
*/ staticvoid
drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb)
{
kref_get(&mstb->malloc_kref);
drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref));
}
/** * drm_dp_mst_put_mstb_malloc() - Decrement the malloc refcount of a branch * device * @mstb: The &struct drm_dp_mst_branch to decrement the malloc refcount of * * Decrements &drm_dp_mst_branch.malloc_kref. When * &drm_dp_mst_branch.malloc_kref reaches 0, the memory allocation for @mstb * will be released and @mstb may no longer be used. * * See also: drm_dp_mst_get_mstb_malloc()
*/ staticvoid
drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb)
{
drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1);
kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device);
}
/** * drm_dp_mst_get_port_malloc() - Increment the malloc refcount of an MST port * @port: The &struct drm_dp_mst_port to increment the malloc refcount of * * Increments &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref * reaches 0, the memory allocation for @port will be released and @port may * no longer be used. * * Because @port could potentially be freed at any time by the DP MST helpers * if &drm_dp_mst_port.malloc_kref reaches 0, including during a call to this * function, drivers that which to make use of &struct drm_dp_mst_port should * ensure that they grab at least one main malloc reference to their MST ports * in &drm_dp_mst_topology_cbs.add_connector. This callback is called before * there is any chance for &drm_dp_mst_port.malloc_kref to reach 0. * * See also: drm_dp_mst_put_port_malloc()
*/ void
drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port)
{
kref_get(&port->malloc_kref);
drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref));
}
EXPORT_SYMBOL(drm_dp_mst_get_port_malloc);
/** * drm_dp_mst_put_port_malloc() - Decrement the malloc refcount of an MST port * @port: The &struct drm_dp_mst_port to decrement the malloc refcount of * * Decrements &drm_dp_mst_port.malloc_kref. When &drm_dp_mst_port.malloc_kref * reaches 0, the memory allocation for @port will be released and @port may * no longer be used. * * See also: drm_dp_mst_get_port_malloc()
*/ void
drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port)
{
drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->malloc_kref) - 1);
kref_put(&port->malloc_kref, drm_dp_free_mst_port);
}
EXPORT_SYMBOL(drm_dp_mst_put_port_malloc);
n = stack_trace_save(stack_entries, ARRAY_SIZE(stack_entries), 1);
backtrace = stack_depot_save(stack_entries, n, GFP_KERNEL); if (!backtrace) return;
/* Try to find an existing entry for this backtrace */ for (i = 0; i < history->len; i++) { if (history->entries[i].backtrace == backtrace) {
entry = &history->entries[i]; break;
}
}
/* Otherwise add one */ if (!entry) { struct drm_dp_mst_topology_ref_entry *new; int new_len = history->len + 1;
new = krealloc(history->entries, sizeof(*new) * new_len,
GFP_KERNEL); if (!new) return;
/* First, sort the list so that it goes from oldest to newest * reference entry
*/
sort(history->entries, history->len, sizeof(*history->entries),
topology_ref_history_cmp, NULL);
/* * This can get called under mgr->mutex, so we need to perform the * actual destruction of the mstb in another worker
*/
mutex_lock(&mgr->delayed_destroy_lock);
list_add(&mstb->destroy_next, &mgr->destroy_branch_device_list);
mutex_unlock(&mgr->delayed_destroy_lock);
queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work);
}
/** * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a * branch device unless it's zero * @mstb: &struct drm_dp_mst_branch to increment the topology refcount of * * Attempts to grab a topology reference to @mstb, if it hasn't yet been * removed from the topology (e.g. &drm_dp_mst_branch.topology_kref has * reached 0). Holding a topology reference implies that a malloc reference * will be held to @mstb as long as the user holds the topology reference. * * Care should be taken to ensure that the user has at least one malloc * reference to @mstb. If you already have a topology reference to @mstb, you * should use drm_dp_mst_topology_get_mstb() instead. * * See also: * drm_dp_mst_topology_get_mstb() * drm_dp_mst_topology_put_mstb() * * Returns: * * 1: A topology reference was grabbed successfully * * 0: @port is no longer in the topology, no reference was grabbed
*/ staticint __must_check
drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb)
{ int ret;
topology_ref_history_lock(mstb->mgr);
ret = kref_get_unless_zero(&mstb->topology_kref); if (ret) {
drm_dbg(mstb->mgr->dev, "mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref));
save_mstb_topology_ref(mstb, DRM_DP_MST_TOPOLOGY_REF_GET);
}
topology_ref_history_unlock(mstb->mgr);
return ret;
}
/** * drm_dp_mst_topology_get_mstb() - Increment the topology refcount of a * branch device * @mstb: The &struct drm_dp_mst_branch to increment the topology refcount of * * Increments &drm_dp_mst_branch.topology_refcount without checking whether or * not it's already reached 0. This is only valid to use in scenarios where * you are already guaranteed to have at least one active topology reference * to @mstb. Otherwise, drm_dp_mst_topology_try_get_mstb() must be used. * * See also: * drm_dp_mst_topology_try_get_mstb() * drm_dp_mst_topology_put_mstb()
*/ staticvoid drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb)
{
topology_ref_history_lock(mstb->mgr);
/** * drm_dp_mst_topology_put_mstb() - release a topology reference to a branch * device * @mstb: The &struct drm_dp_mst_branch to release the topology reference from * * Releases a topology reference from @mstb by decrementing * &drm_dp_mst_branch.topology_kref. * * See also: * drm_dp_mst_topology_try_get_mstb() * drm_dp_mst_topology_get_mstb()
*/ staticvoid
drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb)
{
topology_ref_history_lock(mstb->mgr);
/* There's nothing that needs locking to destroy an input port yet */ if (port->input) {
drm_dp_mst_put_port_malloc(port); return;
}
drm_edid_free(port->cached_edid);
/* * we can't destroy the connector here, as we might be holding the * mode_config.mutex from an EDID retrieval
*/
mutex_lock(&mgr->delayed_destroy_lock);
list_add(&port->next, &mgr->destroy_port_list);
mutex_unlock(&mgr->delayed_destroy_lock);
queue_work(mgr->delayed_destroy_wq, &mgr->delayed_destroy_work);
}
/** * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a * port unless it's zero * @port: &struct drm_dp_mst_port to increment the topology refcount of * * Attempts to grab a topology reference to @port, if it hasn't yet been * removed from the topology (e.g. &drm_dp_mst_port.topology_kref has reached * 0). Holding a topology reference implies that a malloc reference will be * held to @port as long as the user holds the topology reference. * * Care should be taken to ensure that the user has at least one malloc * reference to @port. If you already have a topology reference to @port, you * should use drm_dp_mst_topology_get_port() instead. * * See also: * drm_dp_mst_topology_get_port() * drm_dp_mst_topology_put_port() * * Returns: * * 1: A topology reference was grabbed successfully * * 0: @port is no longer in the topology, no reference was grabbed
*/ staticint __must_check
drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port)
{ int ret;
topology_ref_history_lock(port->mgr);
ret = kref_get_unless_zero(&port->topology_kref); if (ret) {
drm_dbg(port->mgr->dev, "port %p (%d)\n", port, kref_read(&port->topology_kref));
save_port_topology_ref(port, DRM_DP_MST_TOPOLOGY_REF_GET);
}
/** * drm_dp_mst_topology_get_port() - Increment the topology refcount of a port * @port: The &struct drm_dp_mst_port to increment the topology refcount of * * Increments &drm_dp_mst_port.topology_refcount without checking whether or * not it's already reached 0. This is only valid to use in scenarios where * you are already guaranteed to have at least one active topology reference * to @port. Otherwise, drm_dp_mst_topology_try_get_port() must be used. * * See also: * drm_dp_mst_topology_try_get_port() * drm_dp_mst_topology_put_port()
*/ staticvoid drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port)
{
topology_ref_history_lock(port->mgr);
/** * drm_dp_mst_topology_put_port() - release a topology reference to a port * @port: The &struct drm_dp_mst_port to release the topology reference from * * Releases a topology reference from @port by decrementing * &drm_dp_mst_port.topology_kref. * * See also: * drm_dp_mst_topology_try_get_port() * drm_dp_mst_topology_get_port()
*/ staticvoid drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port)
{
topology_ref_history_lock(port->mgr);
list_for_each_entry(port, &mstb->ports, next) { if (port->port_num == port_num) {
ret = drm_dp_mst_topology_try_get_port(port); return ret ? port : NULL;
}
}
return NULL;
}
/* * calculate a new RAD for this MST branch device * if parent has an LCT of 2 then it has 1 nibble of RAD, * if parent has an LCT of 3 then it has 2 nibbles of RAD,
*/ static u8 drm_dp_calculate_rad(struct drm_dp_mst_port *port,
u8 *rad)
{ int parent_lct = port->parent->lct; int shift = 4; int idx = (parent_lct - 1) / 2;
staticbool drm_dp_mst_is_end_device(u8 pdt, bool mcs)
{ switch (pdt) { case DP_PEER_DEVICE_DP_LEGACY_CONV: case DP_PEER_DEVICE_SST_SINK: returntrue; case DP_PEER_DEVICE_MST_BRANCHING: /* For sst branch device */ if (!mcs) returntrue;
if (port->pdt == new_pdt && port->mcs == new_mcs) return 0;
/* Teardown the old pdt, if there is one */ if (port->pdt != DP_PEER_DEVICE_NONE) { if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) { /* * If the new PDT would also have an i2c bus, * don't bother with reregistering it
*/ if (new_pdt != DP_PEER_DEVICE_NONE &&
drm_dp_mst_is_end_device(new_pdt, new_mcs)) {
port->pdt = new_pdt;
port->mcs = new_mcs; return 0;
}
/* * Make sure this port's memory allocation stays * around until its child MSTB releases it
*/
drm_dp_mst_get_port_malloc(port);
mutex_unlock(&mgr->lock);
/* And make sure we send a link address for this */
ret = 1;
}
}
/** * drm_dp_mst_dpcd_read() - read a series of bytes from the DPCD via sideband * @aux: Fake sideband AUX CH * @offset: address of the (first) register to read * @buffer: buffer to store the register values * @size: number of bytes in @buffer * * Performs the same functionality for remote devices via * sideband messaging as drm_dp_dpcd_read() does for local * devices via actual AUX CH. * * Return: Number of bytes read, or negative error code on failure.
*/
ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux, unsignedint offset, void *buffer, size_t size)
{ struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
aux);
/** * drm_dp_mst_dpcd_write() - write a series of bytes to the DPCD via sideband * @aux: Fake sideband AUX CH * @offset: address of the (first) register to write * @buffer: buffer containing the values to write * @size: number of bytes in @buffer * * Performs the same functionality for remote devices via * sideband messaging as drm_dp_dpcd_write() does for local * devices via actual AUX CH. * * Return: number of bytes written on success, negative error code on failure.
*/
ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux, unsignedint offset, void *buffer, size_t size)
{ struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
aux);
/** * drm_dp_mst_connector_late_register() - Late MST connector registration * @connector: The MST connector * @port: The MST port for this connector * * Helper to register the remote aux device for this MST port. Drivers should * call this from their mst connector's late_register hook to enable MST aux * devices. * * Return: 0 on success, negative error code on failure.
*/ int drm_dp_mst_connector_late_register(struct drm_connector *connector, struct drm_dp_mst_port *port)
{
drm_dbg_kms(port->mgr->dev, "registering %s remote bus for %s\n",
port->aux.name, connector->kdev->kobj.name);
/** * drm_dp_mst_connector_early_unregister() - Early MST connector unregistration * @connector: The MST connector * @port: The MST port for this connector * * Helper to unregister the remote aux device for this MST port, registered by * drm_dp_mst_connector_late_register(). Drivers should call this from their mst * connector's early_unregister hook.
*/ void drm_dp_mst_connector_early_unregister(struct drm_connector *connector, struct drm_dp_mst_port *port)
{
drm_dbg_kms(port->mgr->dev, "unregistering %s remote bus for %s\n",
port->aux.name, connector->kdev->kobj.name);
drm_dp_aux_unregister_devnode(&port->aux);
}
EXPORT_SYMBOL(drm_dp_mst_connector_early_unregister);
error:
drm_err(mgr->dev, "Failed to create connector for port %p: %d\n", port, ret);
}
/* * Drop a topology reference, and unlink the port from the in-memory topology * layout
*/ staticvoid
drm_dp_mst_topology_unlink_port(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
{
mutex_lock(&mgr->lock);
port->parent->num_ports--;
list_del(&port->next);
mutex_unlock(&mgr->lock);
drm_dp_mst_topology_put_port(port);
}
/* initialize the MST downstream port's AUX crc work queue */
port->aux.drm_dev = dev;
drm_dp_remote_aux_init(&port->aux);
/* * Make sure the memory allocation for our parent branch stays * around until our own memory allocation is released
*/
drm_dp_mst_get_mstb_malloc(mstb);
port = drm_dp_get_port(mstb, port_msg->port_number); if (!port) {
port = drm_dp_mst_add_port(dev, mgr, mstb,
port_msg->port_number); if (!port) return -ENOMEM;
created = true;
changed = true;
} elseif (!port->input && port_msg->input_port && port->connector) { /* Since port->connector can't be changed here, we create a * new port if input_port changes from 0 to 1
*/
drm_dp_mst_topology_unlink_port(mgr, port);
drm_dp_mst_topology_put_port(port);
port = drm_dp_mst_add_port(dev, mgr, mstb,
port_msg->port_number); if (!port) return -ENOMEM;
changed = true;
created = true;
} elseif (port->input && !port_msg->input_port) {
changed = true;
} elseif (port->connector) { /* We're updating a port that's exposed to userspace, so do it * under lock
*/
drm_modeset_lock(&mgr->base.lock, NULL);
/* manage mstb port lists with mgr lock - take a reference
for this list */ if (created) {
mutex_lock(&mgr->lock);
drm_dp_mst_topology_get_port(port);
list_add(&port->next, &mstb->ports);
mstb->num_ports++;
mutex_unlock(&mgr->lock);
}
/* * Reprobe PBN caps on both hotplug, and when re-probing the link * for our parent mstb
*/ if (port->ddps && !port->input) {
ret = drm_dp_send_enum_path_resources(mgr, mstb,
port); if (ret == 1)
changed = true;
} else {
port->full_pbn = 0;
}
ret = drm_dp_port_set_pdt(port, new_pdt, new_mcs); if (ret == 1) {
send_link_addr = true;
} elseif (ret < 0) {
drm_err(dev, "Failed to change PDT on port %p: %d\n", port, ret); goto fail;
}
/* * If this port wasn't just created, then we're reprobing because * we're coming out of suspend. In this case, always resend the link * address if there's an MSTB on this port
*/ if (!created && port->pdt == DP_PEER_DEVICE_MST_BRANCHING &&
port->mcs)
send_link_addr = true;
if (port->connector)
drm_modeset_unlock(&mgr->base.lock); elseif (!port->input)
drm_dp_mst_port_add_connector(mstb, port);
if (send_link_addr && port->mstb) {
ret = drm_dp_send_link_address(mgr, port->mstb); if (ret == 1) /* MSTB below us changed */
changed = true; elseif (ret < 0) goto fail_put;
}
/* put reference to this port */
drm_dp_mst_topology_put_port(port); return changed;
port = drm_dp_get_port(mstb, conn_stat->port_number); if (!port) return 0;
if (port->connector) { if (!port->input && conn_stat->input_port) { /* * We can't remove a connector from an already exposed * port, so just throw the port out and make sure we * reprobe the link address of it's parent MSTB
*/
drm_dp_mst_topology_unlink_port(mgr, port);
mstb->link_address_sent = false;
dowork = true; goto out;
}
/* Locking is only needed if the port's exposed to userspace */
drm_modeset_lock(&mgr->base.lock, NULL);
} elseif (port->input && !conn_stat->input_port) {
create_connector = true; /* Reprobe link address so we get num_sdp_streams */
mstb->link_address_sent = false;
dowork = true;
}
staticstruct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_topology_mgr *mgr,
u8 lct, u8 *rad)
{ struct drm_dp_mst_branch *mstb; struct drm_dp_mst_port *port; int i, ret; /* find the port by iterating down */
mutex_lock(&mgr->lock);
mstb = mgr->mst_primary;
if (!mstb) goto out;
for (i = 1; i < lct; i++) { int port_num = drm_dp_mst_get_ufp_num_at_lct_from_rad(i + 1, rad);
list_for_each_entry(port, &mstb->ports, next) { if (port->port_num == port_num) {
mstb = port->mstb; if (!mstb) {
drm_err(mgr->dev, "failed to lookup MSTB with lct %d, rad %02x\n",
lct, rad[0]); goto out;
}
break;
}
}
}
ret = drm_dp_mst_topology_try_get_mstb(mstb); if (!ret)
mstb = NULL;
out:
mutex_unlock(&mgr->lock); return mstb;
}
mstb = mgr->mst_primary; if (mstb) {
ret = drm_dp_mst_topology_try_get_mstb(mstb); if (!ret)
mstb = NULL;
}
mutex_unlock(&mgr->lock); if (!mstb) {
mutex_unlock(&mgr->probe_lock); return;
}
/* * Certain branch devices seem to incorrectly report an available_pbn * of 0 on downstream sinks, even after clearing the * DP_PAYLOAD_ALLOCATE_* registers in * drm_dp_mst_topology_mgr_set_mst(). Namely, the CableMatters USB-C * 2x DP hub. Sending a CLEAR_PAYLOAD_ID_TABLE message seems to make * things work again.
*/ if (clear_payload_id_table) {
drm_dbg_kms(dev, "Clearing payload ID table\n");
drm_dp_send_clear_payload_id_table(mgr, mstb);
}
ret = drm_dp_check_and_send_link_address(mgr, mstb);
drm_dp_mst_topology_put_mstb(mstb);
mutex_unlock(&mgr->probe_lock); if (ret > 0)
drm_kms_helper_hotplug_event(dev);
}
staticint drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, bool up, u8 *msg, int len)
{ int ret; int regbase = up ? DP_SIDEBAND_MSG_UP_REP_BASE : DP_SIDEBAND_MSG_DOWN_REQ_BASE; int tosend, total, offset; int retries = 0;
retry:
total = len;
offset = 0; do {
tosend = min3(mgr->max_dpcd_transaction_bytes, 16, total);
return 0;
} /* * process a single block of the next message in the sideband queue
*/ staticint process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_sideband_msg_tx *txmsg, bool up)
{
u8 chunk[48]; struct drm_dp_sideband_msg_hdr hdr; int len, space, idx, tosend; int ret;
if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT) return 0;
ret = drm_dp_send_sideband_msg(mgr, up, chunk, idx); if (ret) { if (drm_debug_enabled(DRM_UT_DP)) { struct drm_printer p = drm_dbg_printer(mgr->dev,
DRM_UT_DP,
DBG_PREFIX);
/* FIXME: Actually do some real error handling here */
ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); if (ret < 0) {
drm_err(mgr->dev, "Sending link address failed with %d\n", ret); goto out;
} if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) {
drm_err(mgr->dev, "link address NAK received\n");
ret = -EIO; goto out;
}
for (i = 0; i < reply->nports; i++) {
port_mask |= BIT(reply->ports[i].port_number);
ret = drm_dp_mst_handle_link_address_port(mstb, mgr->dev,
&reply->ports[i]); if (ret == 1)
changed = true; elseif (ret < 0) goto out;
}
/* Prune any ports that are currently a part of mstb in our in-memory * topology, but were not seen in this link address. Usually this * means that they were removed while the topology was out of sync, * e.g. during suspend/resume
*/
mutex_lock(&mgr->lock);
list_for_each_entry_safe(port, tmp, &mstb->ports, next) { if (port_mask & BIT(port->port_num)) continue;
drm_dbg_kms(mgr->dev, "port %d was not in link address, removing\n",
port->port_num);
list_del(&port->next);
drm_dp_mst_topology_put_port(port);
changed = true;
}
mutex_unlock(&mgr->lock);
out: if (ret < 0)
mstb->link_address_sent = false;
kfree(txmsg); return ret < 0 ? ret : changed;
}
/* * If something changed, make sure we send a * hotplug
*/ if (port->full_pbn != path_res->full_payload_bw_number ||
port->fec_capable != path_res->fec_capable)
ret = 1;
/* * Searches upwards in the topology starting from mstb to try to find the * closest available parent of mstb that's still connected to the rest of the * topology. This can be used in order to perform operations like releasing * payloads, where the branch device which owned the payload may no longer be * around and thus would require that the payload on the last living relative * be freed instead.
*/ staticstruct drm_dp_mst_branch *
drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_branch *mstb, int *port_num)
{ struct drm_dp_mst_branch *rmstb = NULL; struct drm_dp_mst_port *found_port;
mutex_lock(&mgr->lock); if (!mgr->mst_primary) goto out;
do {
found_port = drm_dp_get_last_connected_port_to_mstb(mstb); if (!found_port) break;
if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) {
rmstb = found_port->parent;
*port_num = found_port->port_num;
} else { /* Search again, starting from this parent */
mstb = found_port->parent;
}
} while (!rmstb);
out:
mutex_unlock(&mgr->lock); return rmstb;
}
staticint drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, int id, int pbn)
{ struct drm_dp_sideband_msg_tx *txmsg; struct drm_dp_mst_branch *mstb; int ret, port_num;
u8 sinks[DRM_DP_MAX_SDP_STREAMS]; int i;
/* * FIXME: there is a small chance that between getting the last * connected mstb and sending the payload message, the last connected * mstb could also be removed from the topology. In the future, this * needs to be fixed by restarting the * drm_dp_get_last_connected_port_and_mstb() search in the event of a * timeout if the topology is still connected to the system.
*/
ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); if (ret > 0) { if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
ret = -EINVAL; else
ret = 0;
}
kfree(txmsg);
fail_put:
drm_dp_mst_topology_put_mstb(mstb); return ret;
}
int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, bool power_up)
{ struct drm_dp_sideband_msg_tx *txmsg; int ret;
port = drm_dp_mst_topology_get_port_validated(mgr, port); if (!port) return -EINVAL;
ret = drm_dp_mst_wait_tx_reply(port->parent, txmsg); if (ret > 0) { if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
ret = -EINVAL; else
ret = 0;
}
kfree(txmsg);
drm_dp_mst_topology_put_port(port);
txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); if (!txmsg) return -ENOMEM;
port = drm_dp_mst_topology_get_port_validated(mgr, port); if (!port) {
ret = -EINVAL; goto out_get_port;
}
get_random_bytes(nonce, sizeof(nonce));
drm_modeset_lock(&mgr->base.lock, NULL);
state = to_drm_dp_mst_topology_state(mgr->base.state);
payload = drm_atomic_get_mst_payload_state(state, port);
/* * "Source device targets the QUERY_STREAM_ENCRYPTION_STATUS message * transaction at the MST Branch device directly connected to the * Source"
*/
txmsg->dst = mgr->mst_primary;
/* it's okay for these to fail */ if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_REMOTE) {
drm_dp_payload_send_msg(mgr, payload->port, payload->vcpi, 0);
payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_DFP;
}
if (payload->payload_allocation_status == DRM_DP_MST_PAYLOAD_ALLOCATION_DFP)
drm_dp_dpcd_write_payload(mgr->aux, payload->vcpi, payload->vc_start_slot, 0);
}
/** * drm_dp_add_payload_part1() - Execute payload update part 1 * @mgr: Manager to use. * @mst_state: The MST atomic state * @payload: The payload to write * * Determines the starting time slot for the given payload, and programs the VCPI for this payload * into the DPCD of DPRX. After calling this, the driver should generate ACT and payload packets. * * Returns: 0 on success, error code on failure.
*/ int drm_dp_add_payload_part1(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_atomic_payload *payload)
{ struct drm_dp_mst_port *port; int ret;
/* Update mst mgr info */ if (mgr->payload_count == 0)
mgr->next_start_slot = mst_state->start_slot;
/* Allocate payload to immediate downstream facing port */
port = drm_dp_mst_topology_get_port_validated(mgr, payload->port); if (!port) {
drm_dbg_kms(mgr->dev, "VCPI %d for port %p not in topology, not creating a payload to remote\n",
payload->vcpi, payload->port); return -EIO;
}
ret = drm_dp_create_payload_at_dfp(mgr, payload); if (ret < 0) {
drm_dbg_kms(mgr->dev, "Failed to create MST payload for port %p: %d\n",
payload->port, ret); goto put_port;
}
/** * drm_dp_remove_payload_part1() - Remove an MST payload along the virtual channel * @mgr: Manager to use. * @mst_state: The MST atomic state * @payload: The payload to remove * * Removes a payload along the virtual channel if it was successfully allocated. * After calling this, the driver should set HW to generate ACT and then switch to new * payload allocation state.
*/ void drm_dp_remove_payload_part1(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_atomic_payload *payload)
{ /* Remove remote payload allocation */ bool send_remove = false;
if (send_remove)
drm_dp_destroy_payload_at_remote_and_dfp(mgr, mst_state, payload); else
drm_dbg_kms(mgr->dev, "Payload for VCPI %d not in topology, not sending remove\n",
payload->vcpi);
/** * drm_dp_remove_payload_part2() - Remove an MST payload locally * @mgr: Manager to use. * @mst_state: The MST atomic state * @old_payload: The payload with its old state * @new_payload: The payload with its latest state * * Updates the starting time slots of all other payloads which would have been shifted towards * the start of the payload ID table as a result of removing a payload. Driver should call this * function whenever it removes a payload in its HW. It's independent to the result of payload * allocation/deallocation at branch devices along the virtual channel.
*/ void drm_dp_remove_payload_part2(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, conststruct drm_dp_mst_atomic_payload *old_payload, struct drm_dp_mst_atomic_payload *new_payload)
{ struct drm_dp_mst_atomic_payload *pos;
if (new_payload->delete)
drm_dp_mst_put_port_malloc(new_payload->port);
new_payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_NONE;
}
EXPORT_SYMBOL(drm_dp_remove_payload_part2); /** * drm_dp_add_payload_part2() - Execute payload update part 2 * @mgr: Manager to use. * @payload: The payload to update * * If @payload was successfully assigned a starting time slot by drm_dp_add_payload_part1(), this * function will send the sideband messages to finish allocating this payload. * * Returns: 0 on success, negative error code on failure.
*/ int drm_dp_add_payload_part2(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_atomic_payload *payload)
{ int ret = 0;
/* Skip failed payloads */ if (payload->payload_allocation_status != DRM_DP_MST_PAYLOAD_ALLOCATION_DFP) {
drm_dbg_kms(mgr->dev, "Part 1 of payload creation for %s failed, skipping part 2\n",
payload->port->connector->name); return -EIO;
}
/* Allocate payload to remote end */
ret = drm_dp_create_payload_to_remote(mgr, payload); if (ret < 0)
drm_err(mgr->dev, "Step 2 of creating MST payload for %p failed: %d\n",
payload->port, ret); else
payload->payload_allocation_status = DRM_DP_MST_PAYLOAD_ALLOCATION_REMOTE;
mutex_lock(&mgr->qlock); /* construct a chunk from the first msg in the tx_msg queue */
process_single_tx_qlock(mgr, txmsg, true);
mutex_unlock(&mgr->qlock);
kfree(txmsg); return 0;
}
/** * drm_dp_get_vc_payload_bw - get the VC payload BW for an MTP link * @link_rate: link rate in 10kbits/s units * @link_lane_count: lane count * * Calculate the total bandwidth of a MultiStream Transport link. The returned * value is in units of PBNs/(timeslots/1 MTP). This value can be used to * convert the number of PBNs required for a given stream to the number of * timeslots this stream requires in each MTP. * * Returns the BW / timeslot value in 20.12 fixed point format.
*/
fixed20_12 drm_dp_get_vc_payload_bw(int link_rate, int link_lane_count)
{ int ch_coding_efficiency =
drm_dp_bw_channel_coding_efficiency(drm_dp_is_uhbr_rate(link_rate));
fixed20_12 ret;
/** * drm_dp_read_mst_cap() - Read the sink's MST mode capability * @aux: The DP AUX channel to use * @dpcd: A cached copy of the DPCD capabilities for this sink * * Returns: enum drm_dp_mst_mode to indicate MST mode capability
*/ enum drm_dp_mst_mode drm_dp_read_mst_cap(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{
u8 mstm_cap;
if (dpcd[DP_DPCD_REV] < DP_DPCD_REV_12) return DRM_DP_SST;
if (drm_dp_dpcd_read_byte(aux, DP_MSTM_CAP, &mstm_cap) < 0) return DRM_DP_SST;
if (mstm_cap & DP_MST_CAP) return DRM_DP_MST;
if (mstm_cap & DP_SINGLE_STREAM_SIDEBAND_MSG) return DRM_DP_SST_SIDEBAND_MSG;
/** * drm_dp_mst_topology_mgr_set_mst() - Set the MST state for a topology manager * @mgr: manager to set state for * @mst_state: true to enable MST on this connector - false to disable. * * This is called by the driver when it detects an MST capable device plugged * into a DP MST capable port, or when a DP MST capable device is unplugged.
*/ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool mst_state)
{ int ret = 0; struct drm_dp_mst_branch *mstb = NULL;
mutex_lock(&mgr->lock); if (mst_state == mgr->mst_state) goto out_unlock;
mgr->mst_state = mst_state; /* set the device into MST mode */ if (mst_state) {
WARN_ON(mgr->mst_primary);
/* get dpcd info */
ret = drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd); if (ret < 0) {
drm_dbg_kms(mgr->dev, "%s: failed to read DPCD, ret %d\n",
mgr->aux->name, ret); goto out_unlock;
}
/* add initial branch device at LCT 1 */
mstb = drm_dp_add_mst_branch_device(1, NULL); if (mstb == NULL) {
ret = -ENOMEM; goto out_unlock;
}
mstb->mgr = mgr;
/* give this the main reference */
mgr->mst_primary = mstb;
drm_dp_mst_topology_get_mstb(mgr->mst_primary);
ret = drm_dp_dpcd_write_byte(mgr->aux, DP_MSTM_CTRL,
DP_MST_EN |
DP_UP_REQ_EN |
DP_UPSTREAM_IS_SRC); if (ret < 0) goto out_unlock;
ret = 0;
} else { /* disable MST on the device */
mstb = mgr->mst_primary;
mgr->mst_primary = NULL; /* this can fail if the device is gone */
drm_dp_dpcd_write_byte(mgr->aux, DP_MSTM_CTRL, 0);
ret = 0;
mgr->payload_id_table_cleared = false;
mgr->reset_rx_state = true;
}
out_unlock:
mutex_unlock(&mgr->lock); if (mstb)
drm_dp_mst_topology_put_mstb(mstb); return ret;
/* The link address will need to be re-sent on resume */
mstb->link_address_sent = false;
list_for_each_entry(port, &mstb->ports, next) if (port->mstb)
drm_dp_mst_topology_mgr_invalidate_mstb(port->mstb);
}
/** * drm_dp_mst_topology_queue_probe - Queue a topology probe * @mgr: manager to probe * * Queue a work to probe the MST topology. Driver's should call this only to * sync the topology's HW->SW state after the MST link's parameters have * changed in a way the state could've become out-of-sync. This is the case * for instance when the link rate between the source and first downstream * branch device has switched between UHBR and non-UHBR rates. Except of those * cases - for instance when a sink gets plugged/unplugged to a port - the SW * state will get updated automatically via MST UP message notifications.
*/ void drm_dp_mst_topology_queue_probe(struct drm_dp_mst_topology_mgr *mgr)
{
mutex_lock(&mgr->lock);
if (drm_WARN_ON(mgr->dev, !mgr->mst_state || !mgr->mst_primary)) goto out_unlock;
/** * drm_dp_mst_topology_mgr_suspend() - suspend the MST manager * @mgr: manager to suspend * * This function tells the MST device that we can't handle UP messages * anymore. This should stop it from sending any since we are suspended.
*/ void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr)
{
mutex_lock(&mgr->lock);
drm_dp_dpcd_write_byte(mgr->aux, DP_MSTM_CTRL,
DP_MST_EN | DP_UPSTREAM_IS_SRC);
mutex_unlock(&mgr->lock);
flush_work(&mgr->up_req_work);
flush_work(&mgr->work);
flush_work(&mgr->delayed_destroy_work);
mutex_lock(&mgr->lock); if (mgr->mst_state && mgr->mst_primary)
drm_dp_mst_topology_mgr_invalidate_mstb(mgr->mst_primary);
mutex_unlock(&mgr->lock);
}
EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend);
/** * drm_dp_mst_topology_mgr_resume() - resume the MST manager * @mgr: manager to resume * @sync: whether or not to perform topology reprobing synchronously * * This will fetch DPCD and see if the device is still there, * if it is, it will rewrite the MSTM control bits, and return. * * If the device fails this returns -1, and the driver should do * a full MST reprobe, in case we were undocked. * * During system resume (where it is assumed that the driver will be calling * drm_atomic_helper_resume()) this function should be called beforehand with * @sync set to true. In contexts like runtime resume where the driver is not * expected to be calling drm_atomic_helper_resume(), this function should be * called with @sync set to false in order to avoid deadlocking. * * Returns: -1 if the MST topology was removed while we were suspended, 0 * otherwise.
*/ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr, bool sync)
{
u8 buf[UUID_SIZE];
guid_t guid; int ret;
mutex_lock(&mgr->lock); if (!mgr->mst_primary) goto out_fail;
if (drm_dp_read_dpcd_caps(mgr->aux, mgr->dpcd) < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); goto out_fail;
}
ret = drm_dp_dpcd_write_byte(mgr->aux, DP_MSTM_CTRL,
DP_MST_EN |
DP_UP_REQ_EN |
DP_UPSTREAM_IS_SRC); if (ret < 0) {
drm_dbg_kms(mgr->dev, "mst write failed - undocked during suspend?\n"); goto out_fail;
}
/* Some hubs forget their guids after they resume */
ret = drm_dp_dpcd_read_data(mgr->aux, DP_GUID, buf, sizeof(buf)); if (ret < 0) {
drm_dbg_kms(mgr->dev, "dpcd read failed - undocked during suspend?\n"); goto out_fail;
}
import_guid(&guid, buf);
ret = drm_dp_check_mstb_guid(mgr->mst_primary, &guid); if (ret) {
drm_dbg_kms(mgr->dev, "check mstb failed - undocked during suspend?\n"); goto out_fail;
}
/* * For the final step of resuming the topology, we need to bring the * state of our in-memory topology back into sync with reality. So, * restart the probing process as if we're probing a new hub
*/
drm_dp_mst_queue_probe_work(mgr);
mutex_unlock(&mgr->lock);
if (sync) {
drm_dbg_kms(mgr->dev, "Waiting for link probe work to finish re-syncing topology...\n");
flush_work(&mgr->work);
}
if (!up) { /* Caller is responsible for giving back this reference */
*mstb = drm_dp_get_mst_branch_device(mgr, hdr.lct, hdr.rad); if (!*mstb) {
drm_dbg_kms(mgr->dev, "Got MST reply from unknown device %d\n", hdr.lct); returnfalse;
}
}
if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) {
drm_dbg_kms(mgr->dev, "sideband msg set header failed %d\n", replyblock[0]); returnfalse;
}
/** * drm_dp_mst_hpd_irq_handle_event() - MST hotplug IRQ handle MST event * @mgr: manager to notify irq for. * @esi: 4 bytes from SINK_COUNT_ESI * @ack: 4 bytes used to ack events starting from SINK_COUNT_ESI * @handled: whether the hpd interrupt was consumed or not * * This should be called from the driver when it detects a HPD IRQ, * along with the value of the DEVICE_SERVICE_IRQ_VECTOR_ESI0. The * topology manager will process the sideband messages received * as indicated in the DEVICE_SERVICE_IRQ_VECTOR_ESI0 and set the * corresponding flags that Driver has to ack the DP receiver later. * * Note that driver shall also call * drm_dp_mst_hpd_irq_send_new_request() if the 'handled' is set * after calling this function, to try to kick off a new request in * the queue if the previous message transaction is completed. * * See also: * drm_dp_mst_hpd_irq_send_new_request()
*/ int drm_dp_mst_hpd_irq_handle_event(struct drm_dp_mst_topology_mgr *mgr, const u8 *esi,
u8 *ack, bool *handled)
{ int ret = 0; int sc;
*handled = false;
sc = DP_GET_SINK_COUNT(esi[0]);
/** * drm_dp_mst_hpd_irq_send_new_request() - MST hotplug IRQ kick off new request * @mgr: manager to notify irq for. * * This should be called from the driver when mst irq event is handled * and acked. Note that new down request should only be sent when * previous message transaction is completed. Source is not supposed to generate * interleaved message transactions.
*/ void drm_dp_mst_hpd_irq_send_new_request(struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_dp_sideband_msg_tx *txmsg; bool kick = true;
mutex_lock(&mgr->qlock);
txmsg = list_first_entry_or_null(&mgr->tx_msg_downq, struct drm_dp_sideband_msg_tx, next); /* If last transaction is not completed yet*/ if (!txmsg ||
txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND ||
txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
kick = false;
mutex_unlock(&mgr->qlock);
if (kick)
drm_dp_mst_kick_tx(mgr);
}
EXPORT_SYMBOL(drm_dp_mst_hpd_irq_send_new_request); /** * drm_dp_mst_detect_port() - get connection status for an MST port * @connector: DRM connector for this port * @ctx: The acquisition context to use for grabbing locks * @mgr: manager for this port * @port: pointer to a port * * This returns the current connection state for a port.
*/ int
drm_dp_mst_detect_port(struct drm_connector *connector, struct drm_modeset_acquire_ctx *ctx, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
{ int ret;
/* we need to search for the port in the mgr in case it's gone */
port = drm_dp_mst_topology_get_port_validated(mgr, port); if (!port) return connector_status_disconnected;
ret = drm_modeset_lock(&mgr->base.lock, ctx); if (ret) goto out;
ret = connector_status_disconnected;
if (!port->ddps) goto out;
switch (port->pdt) { case DP_PEER_DEVICE_NONE: break; case DP_PEER_DEVICE_MST_BRANCHING: if (!port->mcs)
ret = connector_status_connected; break;
case DP_PEER_DEVICE_SST_SINK:
ret = connector_status_connected; /* for logical ports - cache the EDID */ if (drm_dp_mst_port_is_logical(port) && !port->cached_edid)
port->cached_edid = drm_edid_read_ddc(connector, &port->aux.ddc); break; case DP_PEER_DEVICE_DP_LEGACY_CONV: if (port->ldps)
ret = connector_status_connected; break;
}
out:
drm_dp_mst_topology_put_port(port); return ret;
}
EXPORT_SYMBOL(drm_dp_mst_detect_port);
/** * drm_dp_mst_edid_read() - get EDID for an MST port * @connector: toplevel connector to get EDID for * @mgr: manager for this port * @port: unverified pointer to a port. * * This returns an EDID for the port connected to a connector, * It validates the pointer still exists so the caller doesn't require a * reference.
*/ conststruct drm_edid *drm_dp_mst_edid_read(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
{ conststruct drm_edid *drm_edid;
/* we need to search for the port in the mgr in case it's gone */
port = drm_dp_mst_topology_get_port_validated(mgr, port); if (!port) return NULL;
if (port->cached_edid)
drm_edid = drm_edid_dup(port->cached_edid); else
drm_edid = drm_edid_read_ddc(connector, &port->aux.ddc);
/** * drm_dp_mst_get_edid() - get EDID for an MST port * @connector: toplevel connector to get EDID for * @mgr: manager for this port * @port: unverified pointer to a port. * * This function is deprecated; please use drm_dp_mst_edid_read() instead. * * This returns an EDID for the port connected to a connector, * It validates the pointer still exists so the caller doesn't require a * reference.
*/ struct edid *drm_dp_mst_get_edid(struct drm_connector *connector, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
{ conststruct drm_edid *drm_edid; struct edid *edid;
/** * drm_dp_atomic_find_time_slots() - Find and add time slots to the state * @state: global atomic state * @mgr: MST topology manager for the port * @port: port to find time slots for * @pbn: bandwidth required for the mode in PBN * * Allocates time slots to @port, replacing any previous time slot allocations it may * have had. Any atomic drivers which support MST must call this function in * their &drm_encoder_helper_funcs.atomic_check() callback unconditionally to * change the current time slot allocation for the new state, and ensure the MST * atomic state is added whenever the state of payloads in the topology changes. * * Allocations set by this function are not checked against the bandwidth * restraints of @mgr until the driver calls drm_dp_mst_atomic_check(). * * Additionally, it is OK to call this function multiple times on the same * @port as needed. It is not OK however, to call this function and * drm_dp_atomic_release_time_slots() in the same atomic check phase. * * See also: * drm_dp_atomic_release_time_slots() * drm_dp_mst_atomic_check() * * Returns: * Total slots in the atomic state assigned for this port, or a negative error * code if the port no longer exists
*/ int drm_dp_atomic_find_time_slots(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, int pbn)
{ struct drm_dp_mst_topology_state *topology_state; struct drm_dp_mst_atomic_payload *payload = NULL; struct drm_connector_state *conn_state; int prev_slots = 0, prev_bw = 0, req_slots;
topology_state = drm_atomic_get_mst_topology_state(state, mgr); if (IS_ERR(topology_state)) return PTR_ERR(topology_state);
/* Find the current allocation for this port, if any */
payload = drm_atomic_get_mst_payload_state(topology_state, port); if (payload) {
prev_slots = payload->time_slots;
prev_bw = payload->pbn;
/* * This should never happen, unless the driver tries * releasing and allocating the same timeslot allocation, * which is an error
*/ if (drm_WARN_ON(mgr->dev, payload->delete)) {
drm_err(mgr->dev, "cannot allocate and release time slots on [MST PORT:%p] in the same state\n",
port); return -EINVAL;
}
}
/* Add the new allocation to the state, note the VCPI isn't assigned until the end */ if (!payload) {
payload = kzalloc(sizeof(*payload), GFP_KERNEL); if (!payload) return -ENOMEM;
/** * drm_dp_atomic_release_time_slots() - Release allocated time slots * @state: global atomic state * @mgr: MST topology manager for the port * @port: The port to release the time slots from * * Releases any time slots that have been allocated to a port in the atomic * state. Any atomic drivers which support MST must call this function * unconditionally in their &drm_connector_helper_funcs.atomic_check() callback. * This helper will check whether time slots would be released by the new state and * respond accordingly, along with ensuring the MST state is always added to the * atomic state whenever a new state would modify the state of payloads on the * topology. * * It is OK to call this even if @port has been removed from the system. * Additionally, it is OK to call this function multiple times on the same * @port as needed. It is not OK however, to call this function and * drm_dp_atomic_find_time_slots() on the same @port in a single atomic check * phase. * * See also: * drm_dp_atomic_find_time_slots() * drm_dp_mst_atomic_check() * * Returns: * 0 on success, negative error code otherwise
*/ int drm_dp_atomic_release_time_slots(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port)
{ struct drm_dp_mst_topology_state *topology_state; struct drm_dp_mst_atomic_payload *payload; struct drm_connector_state *old_conn_state, *new_conn_state; bool update_payload = true;
old_conn_state = drm_atomic_get_old_connector_state(state, port->connector); if (!old_conn_state->crtc) return 0;
/* If the CRTC isn't disabled by this state, don't release it's payload */
new_conn_state = drm_atomic_get_new_connector_state(state, port->connector); if (new_conn_state->crtc) { struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
/* No modeset means no payload changes, so it's safe to not pull in the MST state */ if (!crtc_state || !drm_atomic_crtc_needs_modeset(crtc_state)) return 0;
if (!crtc_state->mode_changed && !crtc_state->connectors_changed)
update_payload = false;
}
topology_state = drm_atomic_get_mst_topology_state(state, mgr); if (IS_ERR(topology_state)) return PTR_ERR(topology_state);
topology_state->pending_crtc_mask |= drm_crtc_mask(old_conn_state->crtc); if (!update_payload) return 0;
payload = drm_atomic_get_mst_payload_state(topology_state, port); if (WARN_ON(!payload)) {
drm_err(mgr->dev, "No payload for [MST PORT:%p] found in mst state %p\n",
port, &topology_state->base); return -EINVAL;
}
/** * drm_dp_mst_atomic_setup_commit() - setup_commit hook for MST helpers * @state: global atomic state * * This function saves all of the &drm_crtc_commit structs in an atomic state that touch any CRTCs * currently assigned to an MST topology. Drivers must call this hook from their * &drm_mode_config_helper_funcs.atomic_commit_setup hook. * * Returns: * 0 if all CRTC commits were retrieved successfully, negative error code otherwise
*/ int drm_dp_mst_atomic_setup_commit(struct drm_atomic_state *state)
{ struct drm_dp_mst_topology_mgr *mgr; struct drm_dp_mst_topology_state *mst_state; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state; int i, j, commit_idx, num_commit_deps;
for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) { if (!mst_state->pending_crtc_mask) continue;
/** * drm_dp_mst_atomic_wait_for_dependencies() - Wait for all pending commits on MST topologies, * prepare new MST state for commit * @state: global atomic state * * Goes through any MST topologies in this atomic state, and waits for any pending commits which * touched CRTCs that were/are on an MST topology to be programmed to hardware and flipped to before * returning. This is to prevent multiple non-blocking commits affecting an MST topology from racing * with eachother by forcing them to be executed sequentially in situations where the only resources * the modeset objects in these commits share are an MST topology. * * This function also prepares the new MST state for commit by performing some state preparation * which can't be done until this point, such as reading back the final VC start slots (which are * determined at commit-time) from the previous state. * * All MST drivers must call this function after calling drm_atomic_helper_wait_for_dependencies(), * or whatever their equivalent of that is.
*/ void drm_dp_mst_atomic_wait_for_dependencies(struct drm_atomic_state *state)
{ struct drm_dp_mst_topology_state *old_mst_state, *new_mst_state; struct drm_dp_mst_topology_mgr *mgr; struct drm_dp_mst_atomic_payload *old_payload, *new_payload; int i, j, ret;
for_each_oldnew_mst_mgr_in_state(state, mgr, old_mst_state, new_mst_state, i) { for (j = 0; j < old_mst_state->num_commit_deps; j++) {
ret = drm_crtc_commit_wait(old_mst_state->commit_deps[j]); if (ret < 0)
drm_err(state->dev, "Failed to wait for %s: %d\n",
old_mst_state->commit_deps[j]->crtc->name, ret);
}
/* Now that previous state is committed, it's safe to copy over the start slot * and allocation status assignments
*/
list_for_each_entry(old_payload, &old_mst_state->payloads, next) { if (old_payload->delete) continue;
/** * drm_dp_mst_root_conn_atomic_check() - Serialize CRTC commits on MST-capable connectors operating * in SST mode * @new_conn_state: The new connector state of the &drm_connector * @mgr: The MST topology manager for the &drm_connector * * Since MST uses fake &drm_encoder structs, the generic atomic modesetting code isn't able to * serialize non-blocking commits happening on the real DP connector of an MST topology switching * into/away from MST mode - as the CRTC on the real DP connector and the CRTCs on the connector's * MST topology will never share the same &drm_encoder. * * This function takes care of this serialization issue, by checking a root MST connector's atomic * state to determine if it is about to have a modeset - and then pulling in the MST topology state * if so, along with adding any relevant CRTCs to &drm_dp_mst_topology_state.pending_crtc_mask. * * Drivers implementing MST must call this function from the * &drm_connector_helper_funcs.atomic_check hook of any physical DP &drm_connector capable of * driving MST sinks. * * Returns: * 0 on success, negative error code otherwise
*/ int drm_dp_mst_root_conn_atomic_check(struct drm_connector_state *new_conn_state, struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_atomic_state *state = new_conn_state->state; struct drm_connector_state *old_conn_state =
drm_atomic_get_old_connector_state(state, new_conn_state->connector); struct drm_crtc_state *crtc_state; struct drm_dp_mst_topology_state *mst_state = NULL;
if (new_conn_state->crtc) {
crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); if (crtc_state && drm_atomic_crtc_needs_modeset(crtc_state)) {
mst_state = drm_atomic_get_mst_topology_state(state, mgr); if (IS_ERR(mst_state)) return PTR_ERR(mst_state);
/** * drm_dp_mst_update_slots() - updates the slot info depending on the DP ecoding format * @mst_state: mst_state to update * @link_encoding_cap: the ecoding format on the link
*/ void drm_dp_mst_update_slots(struct drm_dp_mst_topology_state *mst_state, uint8_t link_encoding_cap)
{ if (link_encoding_cap == DP_CAP_ANSI_128B132B) {
mst_state->total_avail_slots = 64;
mst_state->start_slot = 0;
} else {
mst_state->total_avail_slots = 63;
mst_state->start_slot = 1;
}
DRM_DEBUG_KMS("%s encoding format on mst_state 0x%p\n",
(link_encoding_cap == DP_CAP_ANSI_128B132B) ? "128b/132b":"8b/10b",
mst_state);
}
EXPORT_SYMBOL(drm_dp_mst_update_slots);
/** * drm_dp_check_act_status() - Polls for ACT handled status. * @mgr: manager to use * * Tries waiting for the MST hub to finish updating it's payload table by * polling for the ACT handled bit for up to 3 seconds (yes-some hubs really * take that long). * * Returns: * 0 if the ACT was handled in time, negative error code on failure.
*/ int drm_dp_check_act_status(struct drm_dp_mst_topology_mgr *mgr)
{ /* * There doesn't seem to be any recommended retry count or timeout in * the MST specification. Since some hubs have been observed to take * over 1 second to update their payload allocations under certain * conditions, we use a rather large timeout value of 3 seconds.
*/ return drm_dp_dpcd_poll_act_handled(mgr->aux, 3000);
}
EXPORT_SYMBOL(drm_dp_check_act_status);
/** * drm_dp_calc_pbn_mode() - Calculate the PBN for a mode. * @clock: dot clock * @bpp: bpp as .4 binary fixed point * * This uses the formula in the spec to calculate the PBN value for a mode.
*/ int drm_dp_calc_pbn_mode(int clock, int bpp)
{ /* * The unit of 54/64Mbytes/sec is an arbitrary unit chosen based on * common multiplier to render an integer PBN for all link rate/lane * counts combinations * calculate * peak_kbps = clock * bpp / 16 * peak_kbps *= SSC overhead / 1000000 * peak_kbps /= 8 convert to Kbytes * peak_kBps *= (64/54) / 1000 convert to PBN
*/ /* * TODO: Use the actual link and mode parameters to calculate * the overhead. For now it's assumed that these are * 4 link lanes, 4096 hactive pixels, which don't add any * significant data padding overhead and that there is no DSC * or FEC overhead.
*/ int overhead = drm_dp_bw_overhead(4, 4096, 0, bpp,
DRM_DP_BW_OVERHEAD_MST |
DRM_DP_BW_OVERHEAD_SSC_REF_CLK);
/* we want to kick the TX after we've ack the up/down IRQs. */ staticvoid drm_dp_mst_kick_tx(struct drm_dp_mst_topology_mgr *mgr)
{
queue_work(system_long_wq, &mgr->tx_work);
}
/* * Helper function for parsing DP device types into convenient strings * for use with dp_mst_topology
*/ staticconstchar *pdt_to_string(u8 pdt)
{ switch (pdt) { case DP_PEER_DEVICE_NONE: return"NONE"; case DP_PEER_DEVICE_SOURCE_OR_SST: return"SOURCE OR SST"; case DP_PEER_DEVICE_MST_BRANCHING: return"MST BRANCHING"; case DP_PEER_DEVICE_SST_SINK: return"SST SINK"; case DP_PEER_DEVICE_DP_LEGACY_CONV: return"DP LEGACY CONV"; default: return"ERR";
}
}
staticvoid drm_dp_mst_dump_mstb(struct seq_file *m, struct drm_dp_mst_branch *mstb)
{ struct drm_dp_mst_port *port; int tabs = mstb->lct; char prefix[10]; int i;
for (i = 0; i < tabs; i++)
prefix[i] = '\t';
prefix[i] = '\0';
staticbool dump_dp_payload_table(struct drm_dp_mst_topology_mgr *mgr, char *buf)
{ int i;
for (i = 0; i < DP_PAYLOAD_TABLE_SIZE; i += 16) { if (drm_dp_dpcd_read_data(mgr->aux,
DP_PAYLOAD_TABLE_UPDATE_STATUS + i,
&buf[i], 16) < 0) returnfalse;
} returntrue;
}
/** * drm_dp_mst_dump_topology(): dump topology to seq file. * @m: seq_file to dump output to * @mgr: manager to dump current topology for. * * helper to dump MST topology to a seq file for debugfs.
*/ void drm_dp_mst_dump_topology(struct seq_file *m, struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_dp_mst_topology_state *state; struct drm_dp_mst_atomic_payload *payload; int i, ret;
/* * Not a regular list traverse as we have to drop the destroy * connector lock before destroying the mstb/port, to avoid AB->BA * ordering between this lock and the config mutex.
*/ do {
go_again = false;
list_for_each_entry_safe(pos, tmp, &mst_state->payloads, next) { /* We only keep references to ports with active payloads */ if (!pos->delete)
drm_dp_mst_put_port_malloc(pos->port);
kfree(pos);
}
for (i = 0; i < mst_state->num_commit_deps; i++)
drm_crtc_commit_put(mst_state->commit_deps[i]);
/** * drm_dp_mst_port_downstream_of_parent - check if a port is downstream of a parent port * @mgr: MST topology manager * @port: the port being looked up * @parent: the parent port * * The function returns %true if @port is downstream of @parent. If @parent is * %NULL - denoting the root port - the function returns %true if @port is in * @mgr's topology.
*/ bool
drm_dp_mst_port_downstream_of_parent(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_port *port, struct drm_dp_mst_port *parent)
{ bool ret;
mutex_lock(&mgr->lock);
ret = drm_dp_mst_port_downstream_of_parent_locked(mgr, port, parent);
mutex_unlock(&mgr->lock);
/* Check that we have at least one port in our state that's downstream * of this branch, otherwise we can skip this branch
*/
list_for_each_entry(payload, &state->payloads, next) { if (!payload->pbn ||
!drm_dp_mst_port_downstream_of_branch(payload->port, mstb)) continue;
if (drm_dp_mst_is_end_device(port->pdt, port->mcs)) {
payload = drm_atomic_get_mst_payload_state(state, port); if (!payload) return 0;
/* * This could happen if the sink deasserted its HPD line, but * the branch device still reports it as attached (PDT != NONE).
*/ if (!port->full_pbn) {
drm_dbg_atomic(port->mgr->dev, "[MSTB:%p] [MST PORT:%p] no BW available for the port\n",
port->parent, port);
*failing_port = port; return -EINVAL;
}
list_for_each_entry(payload, &mst_state->payloads, next) { /* Releasing payloads is always OK-even if the port is gone */ if (payload->delete) {
drm_dbg_atomic(mgr->dev, "[MST PORT:%p] releases all time slots\n",
payload->port); continue;
}
drm_dbg_atomic(mgr->dev, "[MST PORT:%p] requires %d time slots\n",
payload->port, payload->time_slots);
avail_slots -= payload->time_slots; if (avail_slots < 0) {
drm_dbg_atomic(mgr->dev, "[MST PORT:%p] not enough time slots in mst state %p (avail=%d)\n",
payload->port, mst_state, avail_slots + payload->time_slots); return -ENOSPC;
}
if (++payload_count > mgr->max_payloads) {
drm_dbg_atomic(mgr->dev, "[MST MGR:%p] state %p has too many payloads (max=%d)\n",
mgr, mst_state, mgr->max_payloads); return -EINVAL;
}
if (!payload_count)
mst_state->pbn_div.full = dfixed_const(0);
drm_dbg_atomic(mgr->dev, "[MST MGR:%p] mst state %p TU pbn_div=%d avail=%d used=%d\n",
mgr, mst_state, dfixed_trunc(mst_state->pbn_div), avail_slots,
mst_state->total_avail_slots - avail_slots);
return 0;
}
/** * drm_dp_mst_add_affected_dsc_crtcs * @state: Pointer to the new struct drm_dp_mst_topology_state * @mgr: MST topology manager * * Whenever there is a change in mst topology * DSC configuration would have to be recalculated * therefore we need to trigger modeset on all affected * CRTCs in that topology * * See also: * drm_dp_mst_atomic_enable_dsc()
*/ int drm_dp_mst_add_affected_dsc_crtcs(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_dp_mst_topology_state *mst_state; struct drm_dp_mst_atomic_payload *pos; struct drm_connector *connector; struct drm_connector_state *conn_state; struct drm_crtc *crtc; struct drm_crtc_state *crtc_state;
/** * drm_dp_mst_atomic_enable_dsc - Set DSC Enable Flag to On/Off * @state: Pointer to the new drm_atomic_state * @port: Pointer to the affected MST Port * @pbn: Newly recalculated bw required for link with DSC enabled * @enable: Boolean flag to enable or disable DSC on the port * * This function enables DSC on the given Port * by recalculating its vcpi from pbn provided * and sets dsc_enable flag to keep track of which * ports have DSC enabled *
*/ int drm_dp_mst_atomic_enable_dsc(struct drm_atomic_state *state, struct drm_dp_mst_port *port, int pbn, bool enable)
{ struct drm_dp_mst_topology_state *mst_state; struct drm_dp_mst_atomic_payload *payload; int time_slots = 0;
mst_state = drm_atomic_get_mst_topology_state(state, port->mgr); if (IS_ERR(mst_state)) return PTR_ERR(mst_state);
payload = drm_atomic_get_mst_payload_state(mst_state, port); if (!payload) {
drm_dbg_atomic(state->dev, "[MST PORT:%p] Couldn't find payload in mst state %p\n",
port, mst_state); return -EINVAL;
}
if (payload->dsc_enabled == enable) {
drm_dbg_atomic(state->dev, "[MST PORT:%p] DSC flag is already set to %d, returning %d time slots\n",
port, enable, payload->time_slots);
time_slots = payload->time_slots;
}
if (enable) {
time_slots = drm_dp_atomic_find_time_slots(state, port->mgr, port, pbn);
drm_dbg_atomic(state->dev, "[MST PORT:%p] Enabling DSC flag, reallocating %d time slots on the port\n",
port, time_slots); if (time_slots < 0) return -EINVAL;
}
/** * drm_dp_mst_atomic_check_mgr - Check the atomic state of an MST topology manager * @state: The global atomic state * @mgr: Manager to check * @mst_state: The MST atomic state for @mgr * @failing_port: Returns the port with a BW limitation * * Checks the given MST manager's topology state for an atomic update to ensure * that it's valid. This includes checking whether there's enough bandwidth to * support the new timeslot allocations in the atomic update. * * Any atomic drivers supporting DP MST must make sure to call this or * the drm_dp_mst_atomic_check() function after checking the rest of their state * in their &drm_mode_config_funcs.atomic_check() callback. * * See also: * drm_dp_mst_atomic_check() * drm_dp_atomic_find_time_slots() * drm_dp_atomic_release_time_slots() * * Returns: * - 0 if the new state is valid * - %-ENOSPC, if the new state is invalid, because of BW limitation * @failing_port is set to: * * - The non-root port where a BW limit check failed * with all the ports downstream of @failing_port passing * the BW limit check. * The returned port pointer is valid until at least * one payload downstream of it exists. * - %NULL if the BW limit check failed at the root port * with all the ports downstream of the root port passing * the BW limit check. * * - %-EINVAL, if the new state is invalid, because the root port has * too many payloads.
*/ int drm_dp_mst_atomic_check_mgr(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_port **failing_port)
{ int ret;
*failing_port = NULL;
if (!mgr->mst_state) return 0;
mutex_lock(&mgr->lock);
ret = drm_dp_mst_atomic_check_mstb_bw_limit(mgr->mst_primary,
mst_state,
failing_port);
mutex_unlock(&mgr->lock);
/** * drm_dp_mst_atomic_check - Check that the new state of an MST topology in an * atomic update is valid * @state: Pointer to the new &struct drm_dp_mst_topology_state * * Checks the given topology state for an atomic update to ensure that it's * valid, calling drm_dp_mst_atomic_check_mgr() for all MST manager in the * atomic state. This includes checking whether there's enough bandwidth to * support the new timeslot allocations in the atomic update. * * Any atomic drivers supporting DP MST must make sure to call this after * checking the rest of their state in their * &drm_mode_config_funcs.atomic_check() callback. * * See also: * drm_dp_mst_atomic_check_mgr() * drm_dp_atomic_find_time_slots() * drm_dp_atomic_release_time_slots() * * Returns: * 0 if the new state is valid, negative error code otherwise.
*/ int drm_dp_mst_atomic_check(struct drm_atomic_state *state)
{ struct drm_dp_mst_topology_mgr *mgr; struct drm_dp_mst_topology_state *mst_state; int i, ret = 0;
/** * drm_atomic_get_mst_topology_state: get MST topology state * @state: global atomic state * @mgr: MST topology manager, also the private object in this case * * This function wraps drm_atomic_get_priv_obj_state() passing in the MST atomic * state vtable so that the private object state returned is that of a MST * topology object. * * RETURNS: * The MST topology state or error pointer.
*/ struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr)
{ return to_dp_mst_topology_state(drm_atomic_get_private_obj_state(state, &mgr->base));
}
EXPORT_SYMBOL(drm_atomic_get_mst_topology_state);
/** * drm_atomic_get_old_mst_topology_state: get old MST topology state in atomic state, if any * @state: global atomic state * @mgr: MST topology manager, also the private object in this case * * This function wraps drm_atomic_get_old_private_obj_state() passing in the MST atomic * state vtable so that the private object state returned is that of a MST * topology object. * * Returns: * The old MST topology state, or NULL if there's no topology state for this MST mgr * in the global atomic state
*/ struct drm_dp_mst_topology_state *
drm_atomic_get_old_mst_topology_state(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_private_state *old_priv_state =
drm_atomic_get_old_private_obj_state(state, &mgr->base);
/** * drm_atomic_get_new_mst_topology_state: get new MST topology state in atomic state, if any * @state: global atomic state * @mgr: MST topology manager, also the private object in this case * * This function wraps drm_atomic_get_new_private_obj_state() passing in the MST atomic * state vtable so that the private object state returned is that of a MST * topology object. * * Returns: * The new MST topology state, or NULL if there's no topology state for this MST mgr * in the global atomic state
*/ struct drm_dp_mst_topology_state *
drm_atomic_get_new_mst_topology_state(struct drm_atomic_state *state, struct drm_dp_mst_topology_mgr *mgr)
{ struct drm_private_state *new_priv_state =
drm_atomic_get_new_private_obj_state(state, &mgr->base);
/** * drm_dp_mst_topology_mgr_init - initialise a topology manager * @mgr: manager struct to initialise * @dev: device providing this structure - for i2c addition. * @aux: DP helper aux channel to talk to this device * @max_dpcd_transaction_bytes: hw specific DPCD transaction limit * @max_payloads: maximum number of payloads this GPU can source * @conn_base_id: the connector object ID the MST device is connected to. * * Return 0 for success, or negative error code on failure
*/ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr, struct drm_device *dev, struct drm_dp_aux *aux, int max_dpcd_transaction_bytes, int max_payloads, int conn_base_id)
{ struct drm_dp_mst_topology_state *mst_state;
/* * delayed_destroy_work will be queued on a dedicated WQ, so that any * requeuing will be also flushed when deiniting the topology manager.
*/
mgr->delayed_destroy_wq = alloc_ordered_workqueue("drm_dp_mst_wq", 0); if (mgr->delayed_destroy_wq == NULL) return -ENOMEM;
/** * drm_dp_mst_register_i2c_bus() - register an I2C adapter for I2C-over-AUX * @port: The port to add the I2C bus on * * Returns 0 on success or a negative error code on failure.
*/ staticint drm_dp_mst_register_i2c_bus(struct drm_dp_mst_port *port)
{ struct drm_dp_aux *aux = &port->aux; struct device *parent_dev = port->mgr->dev->dev;
aux->ddc.owner = THIS_MODULE; /* FIXME: set the kdev of the port's connector as parent */
aux->ddc.dev.parent = parent_dev;
aux->ddc.dev.of_node = parent_dev->of_node;
/** * drm_dp_mst_unregister_i2c_bus() - unregister an I2C-over-AUX adapter * @port: The port to remove the I2C bus from
*/ staticvoid drm_dp_mst_unregister_i2c_bus(struct drm_dp_mst_port *port)
{
i2c_del_adapter(&port->aux.ddc);
}
/** * drm_dp_mst_is_virtual_dpcd() - Is the given port a virtual DP Peer Device * @port: The port to check * * A single physical MST hub object can be represented in the topology * by multiple branches, with virtual ports between those branches. * * As of DP1.4, An MST hub with internal (virtual) ports must expose * certain DPCD registers over those ports. See sections 2.6.1.1.1 * and 2.6.1.1.2 of Display Port specification v1.4 for details. * * May acquire mgr->lock * * Returns: * true if the port is a virtual DP peer device, false otherwise
*/ staticbool drm_dp_mst_is_virtual_dpcd(struct drm_dp_mst_port *port)
{ struct drm_dp_mst_port *downstream_port;
if (!port || port->dpcd_rev < DP_DPCD_REV_14) returnfalse;
/** * drm_dp_mst_aux_for_parent() - Get the AUX device for an MST port's parent * @port: MST port whose parent's AUX device is returned * * Return the AUX device for @port's parent or NULL if port's parent is the * root port.
*/ struct drm_dp_aux *drm_dp_mst_aux_for_parent(struct drm_dp_mst_port *port)
{ if (!port->parent || !port->parent->port_parent) return NULL;
/** * drm_dp_mst_dsc_aux_for_port() - Find the correct aux for DSC * @port: The port to check. A leaf of the MST tree with an attached display. * * Depending on the situation, DSC may be enabled via the endpoint aux, * the immediately upstream aux, or the connector's physical aux. * * This is both the correct aux to read DSC_CAPABILITY and the * correct aux to write DSC_ENABLED. * * This operation can be expensive (up to four aux reads), so * the caller should cache the return. * * Returns: * NULL if DSC cannot be enabled on this port, otherwise the aux device
*/ struct drm_dp_aux *drm_dp_mst_dsc_aux_for_port(struct drm_dp_mst_port *port)
{ struct drm_dp_mst_port *immediate_upstream_port; struct drm_dp_aux *immediate_upstream_aux; struct drm_dp_mst_port *fec_port; struct drm_dp_desc desc = {};
u8 upstream_dsc;
u8 endpoint_fec;
u8 endpoint_dsc;
if (!port) return NULL;
if (port->parent->port_parent)
immediate_upstream_port = port->parent->port_parent; else
immediate_upstream_port = NULL;
fec_port = immediate_upstream_port; while (fec_port) { /* * Each physical link (i.e. not a virtual port) between the * output and the primary device must support FEC
*/ if (!drm_dp_mst_is_virtual_dpcd(fec_port) &&
!fec_port->fec_capable) return NULL;
fec_port = fec_port->parent->port_parent;
}
/* DP-to-DP peer device */ if (drm_dp_mst_is_virtual_dpcd(immediate_upstream_port)) { if (drm_dp_dpcd_read_data(&port->aux,
DP_DSC_SUPPORT, &endpoint_dsc, 1) < 0) return NULL; if (drm_dp_dpcd_read_data(&port->aux,
DP_FEC_CAPABILITY, &endpoint_fec, 1) < 0) return NULL; if (drm_dp_dpcd_read_data(&immediate_upstream_port->aux,
DP_DSC_SUPPORT, &upstream_dsc, 1) < 0) return NULL;
/* Virtual DPCD decompression with DP-to-HDMI or Virtual DP Sink */ if (drm_dp_mst_is_virtual_dpcd(port)) return &port->aux;
/* * Synaptics quirk * Applies to ports for which: * - Physical aux has Synaptics OUI * - DPv1.4 or higher * - Port is on primary branch device * - Not a VGA adapter (DP_DWN_STRM_PORT_TYPE_ANALOG)
*/ if (immediate_upstream_port)
immediate_upstream_aux = &immediate_upstream_port->aux; else
immediate_upstream_aux = port->mgr->aux;
if (drm_dp_read_desc(immediate_upstream_aux, &desc, true)) return NULL;
if (drm_dp_has_quirk(&desc, DP_DPCD_QUIRK_DSC_WITHOUT_VIRTUAL_DPCD)) {
u8 dpcd_ext[DP_RECEIVER_CAP_SIZE];
if (drm_dp_dpcd_read_data(immediate_upstream_aux,
DP_DSC_SUPPORT, &upstream_dsc, 1) < 0) return NULL;
if (!(upstream_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED)) return NULL;
if (drm_dp_read_dpcd_caps(immediate_upstream_aux, dpcd_ext) < 0) return NULL;
/* * The check below verifies if the MST sink * connected to the GPU is capable of DSC - * therefore the endpoint needs to be * both DSC and FEC capable.
*/ if (drm_dp_dpcd_read_data(&port->aux,
DP_DSC_SUPPORT, &endpoint_dsc, 1) < 0) return NULL; if (drm_dp_dpcd_read_data(&port->aux,
DP_FEC_CAPABILITY, &endpoint_fec, 1) < 0) return NULL; if ((endpoint_dsc & DP_DSC_DECOMPRESSION_IS_SUPPORTED) &&
(endpoint_fec & DP_FEC_CAPABLE)) return &port->aux;
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.111Bemerkung:
(Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können 2026-04-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.