Skip to content

Commit

Permalink
drm/dp_mst: Kill the second sideband tx slot, save the world
Browse files Browse the repository at this point in the history
While we support using both tx slots for sideband transmissions, it
appears that DisplayPort devices in the field didn't end up doing a very
good job of supporting it. From section 5.2.1 of the DP 2.0
specification:

  There are MST Sink/Branch devices in the field that do not handle
  interleaved message transactions.

  To facilitate message transaction handling by downstream devices, an
  MST Source device shall generate message transactions in an atomic
  manner (i.e., the MST Source device shall not concurrently interleave
  multiple message transactions). Therefore, an MST Source device shall
  clear the Message_Sequence_No value in the Sideband_MSG_Header to 0.

This might come as a bit of a surprise since the vast majority of hubs
will support using both tx slots even if they don't support interleaved
message transactions, and we've also been using both tx slots since MST
was introduced into the kernel.

However, there is one device we've had trouble getting working
consistently with MST for so long that we actually assumed it was just
broken: the infamous Dell P2415Qb. Previously this monitor would appear
to work sometimes, but in most situations would end up timing out
LINK_ADDRESS messages almost at random until you power cycled the whole
display. After reading section 5.2.1 in the DP 2.0 spec, some closer
investigation into this infamous display revealed it was only ever
timing out on sideband messages in the second TX slot.

Sure enough, avoiding the second TX slot has suddenly made this monitor
function perfectly for the first time in five years. And since they
explicitly mention this in the specification, I doubt this is the only
monitor out there with this issue. This might even explain explain the
seemingly harmless garbage sideband responses we would occasionally see
with MST hubs!

So - rewrite our sideband TX handlers to only support one TX slot. In
order to simplify our sideband handling now that we don't support
transmitting to multiple MSTBs at once, we also move all state tracking
for down replies from mstbs to the topology manager.

Signed-off-by: Lyude Paul <lyude@redhat.com>
Fixes: ad7f8a1 ("drm/helper: add Displayport multi-stream helper (v0.6)")
Cc: Sean Paul <sean@poorly.run>
Cc: "Lin, Wayne" <Wayne.Lin@amd.com>
Cc: <stable@vger.kernel.org> # v3.17+
Reviewed-by: Sean Paul <sean@poorly.run>
Link: https://patchwork.freedesktop.org/patch/msgid/20200424181308.770749-1-lyude@redhat.com
  • Loading branch information
Lyude Paul committed Apr 27, 2020
1 parent 611e22b commit d308a88
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 128 deletions.
149 changes: 42 additions & 107 deletions drivers/gpu/drm/drm_dp_mst_topology.c
Original file line number Diff line number Diff line change
Expand Up @@ -1197,16 +1197,8 @@ static int drm_dp_mst_wait_tx_reply(struct drm_dp_mst_branch *mstb,

/* 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_START_SEND)
list_del(&txmsg->next);
}

if (txmsg->state == DRM_DP_SIDEBAND_TX_START_SEND ||
txmsg->state == DRM_DP_SIDEBAND_TX_SENT) {
mstb->tx_slots[txmsg->seqno] = NULL;
}
mgr->is_waiting_for_dwn_reply = false;

}
out:
if (unlikely(ret == -EIO) && drm_debug_enabled(DRM_UT_DP)) {
Expand Down Expand Up @@ -2685,22 +2677,6 @@ static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr,
struct drm_dp_mst_branch *mstb = txmsg->dst;
u8 req_type;

/* both msg slots are full */
if (txmsg->seqno == -1) {
if (mstb->tx_slots[0] && mstb->tx_slots[1]) {
DRM_DEBUG_KMS("%s: failed to find slot\n", __func__);
return -EAGAIN;
}
if (mstb->tx_slots[0] == NULL && mstb->tx_slots[1] == NULL) {
txmsg->seqno = mstb->last_seqno;
mstb->last_seqno ^= 1;
} else if (mstb->tx_slots[0] == NULL)
txmsg->seqno = 0;
else
txmsg->seqno = 1;
mstb->tx_slots[txmsg->seqno] = txmsg;
}

req_type = txmsg->msg[0] & 0x7f;
if (req_type == DP_CONNECTION_STATUS_NOTIFY ||
req_type == DP_RESOURCE_STATUS_NOTIFY)
Expand All @@ -2712,7 +2688,7 @@ static int set_hdr_from_dst_qlock(struct drm_dp_sideband_msg_hdr *hdr,
hdr->lcr = mstb->lct - 1;
if (mstb->lct > 1)
memcpy(hdr->rad, mstb->rad, mstb->lct / 2);
hdr->seqno = txmsg->seqno;

return 0;
}
/*
Expand All @@ -2727,15 +2703,15 @@ static int process_single_tx_qlock(struct drm_dp_mst_topology_mgr *mgr,
int len, space, idx, tosend;
int ret;

if (txmsg->state == DRM_DP_SIDEBAND_TX_SENT)
return 0;

memset(&hdr, 0, sizeof(struct drm_dp_sideband_msg_hdr));

if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED) {
txmsg->seqno = -1;
if (txmsg->state == DRM_DP_SIDEBAND_TX_QUEUED)
txmsg->state = DRM_DP_SIDEBAND_TX_START_SEND;
}

/* make hdr from dst mst - for replies use seqno
otherwise assign one */
/* make hdr from dst mst */
ret = set_hdr_from_dst_qlock(&hdr, txmsg);
if (ret < 0)
return ret;
Expand Down Expand Up @@ -2788,42 +2764,17 @@ static void process_single_down_tx_qlock(struct drm_dp_mst_topology_mgr *mgr)
if (list_empty(&mgr->tx_msg_downq))
return;

txmsg = list_first_entry(&mgr->tx_msg_downq, struct drm_dp_sideband_msg_tx, next);
txmsg = list_first_entry(&mgr->tx_msg_downq,
struct drm_dp_sideband_msg_tx, next);
ret = process_single_tx_qlock(mgr, txmsg, false);
if (ret == 1) {
/* txmsg is sent it should be in the slots now */
mgr->is_waiting_for_dwn_reply = true;
list_del(&txmsg->next);
} else if (ret) {
if (ret < 0) {
DRM_DEBUG_KMS("failed to send msg in q %d\n", ret);
mgr->is_waiting_for_dwn_reply = false;
list_del(&txmsg->next);
if (txmsg->seqno != -1)
txmsg->dst->tx_slots[txmsg->seqno] = NULL;
txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
wake_up_all(&mgr->tx_waitq);
}
}

/* called holding qlock */
static void process_single_up_tx_qlock(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_sideband_msg_tx *txmsg)
{
int ret;

/* construct a chunk from the first msg in the tx_msg queue */
ret = process_single_tx_qlock(mgr, txmsg, true);

if (ret != 1)
DRM_DEBUG_KMS("failed to send msg in q %d\n", ret);

if (txmsg->seqno != -1) {
WARN_ON((unsigned int)txmsg->seqno >
ARRAY_SIZE(txmsg->dst->tx_slots));
txmsg->dst->tx_slots[txmsg->seqno] = NULL;
}
}

static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_sideband_msg_tx *txmsg)
{
Expand All @@ -2836,8 +2787,7 @@ static void drm_dp_queue_down_tx(struct drm_dp_mst_topology_mgr *mgr,
drm_dp_mst_dump_sideband_msg_tx(&p, txmsg);
}

if (list_is_singular(&mgr->tx_msg_downq) &&
!mgr->is_waiting_for_dwn_reply)
if (list_is_singular(&mgr->tx_msg_downq))
process_single_down_tx_qlock(mgr);
mutex_unlock(&mgr->qlock);
}
Expand Down Expand Up @@ -3457,7 +3407,7 @@ static int drm_dp_encode_up_ack_reply(struct drm_dp_sideband_msg_tx *msg, u8 req

static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_branch *mstb,
int req_type, int seqno, bool broadcast)
int req_type, bool broadcast)
{
struct drm_dp_sideband_msg_tx *txmsg;

Expand All @@ -3466,13 +3416,11 @@ static int drm_dp_send_up_ack_reply(struct drm_dp_mst_topology_mgr *mgr,
return -ENOMEM;

txmsg->dst = mstb;
txmsg->seqno = seqno;
drm_dp_encode_up_ack_reply(txmsg, req_type);

mutex_lock(&mgr->qlock);

process_single_up_tx_qlock(mgr, txmsg);

/* 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);
Expand Down Expand Up @@ -3697,22 +3645,23 @@ int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr,
}
EXPORT_SYMBOL(drm_dp_mst_topology_mgr_resume);

static bool drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up,
struct drm_dp_mst_branch **mstb, int *seqno)
static bool
drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up,
struct drm_dp_mst_branch **mstb)
{
int len;
u8 replyblock[32];
int replylen, curreply;
int ret;
u8 hdrlen;
struct drm_dp_sideband_msg_hdr hdr;
struct drm_dp_sideband_msg_rx *msg;
struct drm_dp_sideband_msg_rx *msg =
up ? &mgr->up_req_recv : &mgr->down_rep_recv;
int basereg = up ? DP_SIDEBAND_MSG_UP_REQ_BASE :
DP_SIDEBAND_MSG_DOWN_REP_BASE;

if (!up)
*mstb = NULL;
*seqno = -1;

len = min(mgr->max_dpcd_transaction_bytes, 16);
ret = drm_dp_dpcd_read(mgr->aux, basereg, replyblock, len);
Expand All @@ -3729,19 +3678,14 @@ static bool drm_dp_get_one_sb_msg(struct drm_dp_mst_topology_mgr *mgr, bool up,
return false;
}

*seqno = hdr.seqno;

if (up) {
msg = &mgr->up_req_recv;
} else {
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_DEBUG_KMS("Got MST reply from unknown device %d\n",
hdr.lct);
return false;
}
msg = &(*mstb)->down_rep_recv[hdr.seqno];
}

if (!drm_dp_sideband_msg_set_header(msg, &hdr, hdrlen)) {
Expand Down Expand Up @@ -3785,25 +3729,23 @@ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_dp_sideband_msg_tx *txmsg;
struct drm_dp_mst_branch *mstb = NULL;
struct drm_dp_sideband_msg_rx *msg = NULL;
int seqno = -1;

if (!drm_dp_get_one_sb_msg(mgr, false, &mstb, &seqno))
goto out_clear_reply;
struct drm_dp_sideband_msg_rx *msg = &mgr->down_rep_recv;

msg = &mstb->down_rep_recv[seqno];
if (!drm_dp_get_one_sb_msg(mgr, false, &mstb))
goto out;

/* Multi-packet message transmission, don't clear the reply */
if (!msg->have_eomt)
goto out;

/* find the message */
mutex_lock(&mgr->qlock);
txmsg = mstb->tx_slots[seqno];
/* remove from slots */
txmsg = list_first_entry_or_null(&mgr->tx_msg_downq,
struct drm_dp_sideband_msg_tx, next);
mutex_unlock(&mgr->qlock);

if (!txmsg) {
/* Were we actually expecting a response, and from this mstb? */
if (!txmsg || txmsg->dst != mstb) {
struct drm_dp_sideband_msg_hdr *hdr;
hdr = &msg->initial_hdr;
DRM_DEBUG_KMS("Got MST reply with no msg %p %d %d %02x %02x\n",
Expand All @@ -3828,20 +3770,15 @@ static int drm_dp_mst_handle_down_rep(struct drm_dp_mst_topology_mgr *mgr)

mutex_lock(&mgr->qlock);
txmsg->state = DRM_DP_SIDEBAND_TX_RX;
mstb->tx_slots[seqno] = NULL;
mgr->is_waiting_for_dwn_reply = false;
list_del(&txmsg->next);
mutex_unlock(&mgr->qlock);

wake_up_all(&mgr->tx_waitq);

return 0;

out_clear_reply:
mutex_lock(&mgr->qlock);
mgr->is_waiting_for_dwn_reply = false;
mutex_unlock(&mgr->qlock);
if (msg)
memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx));
memset(msg, 0, sizeof(struct drm_dp_sideband_msg_rx));
out:
if (mstb)
drm_dp_mst_topology_put_mstb(mstb);
Expand Down Expand Up @@ -3921,9 +3858,8 @@ static void drm_dp_mst_up_req_work(struct work_struct *work)
static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
{
struct drm_dp_pending_up_req *up_req;
int seqno;

if (!drm_dp_get_one_sb_msg(mgr, true, NULL, &seqno))
if (!drm_dp_get_one_sb_msg(mgr, true, NULL))
goto out;

if (!mgr->up_req_recv.have_eomt)
Expand All @@ -3947,7 +3883,7 @@ static int drm_dp_mst_handle_up_req(struct drm_dp_mst_topology_mgr *mgr)
}

drm_dp_send_up_ack_reply(mgr, mgr->mst_primary, up_req->msg.req_type,
seqno, false);
false);

if (up_req->msg.req_type == DP_CONNECTION_STATUS_NOTIFY) {
const struct drm_dp_connection_status_notify *conn_stat =
Expand Down Expand Up @@ -4692,7 +4628,7 @@ static void drm_dp_tx_work(struct work_struct *work)
struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, tx_work);

mutex_lock(&mgr->qlock);
if (!list_empty(&mgr->tx_msg_downq) && !mgr->is_waiting_for_dwn_reply)
if (!list_empty(&mgr->tx_msg_downq))
process_single_down_tx_qlock(mgr);
mutex_unlock(&mgr->qlock);
}
Expand All @@ -4713,26 +4649,25 @@ static inline void
drm_dp_delayed_destroy_mstb(struct drm_dp_mst_branch *mstb)
{
struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
struct drm_dp_mst_port *port, *tmp;
struct drm_dp_mst_port *port, *port_tmp;
struct drm_dp_sideband_msg_tx *txmsg, *txmsg_tmp;
bool wake_tx = false;

mutex_lock(&mgr->lock);
list_for_each_entry_safe(port, tmp, &mstb->ports, next) {
list_for_each_entry_safe(port, port_tmp, &mstb->ports, next) {
list_del(&port->next);
drm_dp_mst_topology_put_port(port);
}
mutex_unlock(&mgr->lock);

/* drop any tx slots msg */
/* drop any tx slot msg */
mutex_lock(&mstb->mgr->qlock);
if (mstb->tx_slots[0]) {
mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
mstb->tx_slots[0] = NULL;
wake_tx = true;
}
if (mstb->tx_slots[1]) {
mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
mstb->tx_slots[1] = NULL;
list_for_each_entry_safe(txmsg, txmsg_tmp, &mgr->tx_msg_downq, next) {
if (txmsg->dst != mstb)
continue;

txmsg->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
list_del(&txmsg->next);
wake_tx = true;
}
mutex_unlock(&mstb->mgr->qlock);
Expand Down
Loading

0 comments on commit d308a88

Please sign in to comment.