/** * 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, "\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);
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.