drm/dp_mst: Enable registration of AUX devices for MST ports

All available downstream ports - physical and logical - are exposed for
each MST device. They are listed in /dev/, following the same naming
scheme as SST devices by appending an incremental ID.

Although all downstream ports are exposed, only some will work as
expected. Consider the following topology:

               +---------+
               |  ASIC   |
               +---------+
              Conn-0|
                    |
               +----v----+
          +----| MST HUB |----+
          |    +---------+    |
          |                   |
          |Port-1       Port-2|
    +-----v-----+       +-----v-----+
    |  MST      |       |  SST      |
    |  Display  |       |  Display  |
    +-----------+       +-----------+
          |Port-1
          x

 MST Path  | MST Device
 ----------+----------------------------------
 sst:0     | MST Hub
 mst:0-1   | MST Display
 mst:0-1-1 | MST Display's disconnected DP out
 mst:0-1-8 | MST Display's internal sink
 mst:0-2   | SST Display

On certain MST displays, the upstream physical port will ACK DPCD reads.
However, reads on the local logical port to the internal sink will
*NAK*. i.e. reading mst:0-1 ACKs, but mst:0-1-8 NAKs.

There may also be duplicates. Some displays will return the same GUID
when reading DPCD from both mst:0-1 and mst:0-1-8.

There are some device-dependent behavior as well. The MST hub used
during testing will actually *ACK* read requests on a disconnected
physical port, whereas the MST displays will NAK.

In light of these discrepancies, it's simpler to expose all downstream
ports - both physical and logical - and let the user decide what to use.

v3 changes:
* Change WARN_ON_ONCE -> DRM_ERROR on dpcd read errors
* Docstring and cosmetic fixes

v2 changes:

Moved remote aux device (un)registration to new mst connector late
register and early unregister helpers. Drivers should call these from
their own mst connector function hooks.

This is to solve an issue during driver unload, where mst connector
devices are unregistered before the remote aux devices are. In a setup
where aux devices are created as children of connector devices, the aux
device would be removed too early, and uncleanly. Doing so in
early_unregister solves this issue, as that is called before connector
unregistration.

Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Leo Li <sunpeng.li@amd.com>
Reviewed-by: Lyude Paul <lyude@redhat.com>
Signed-off-by: Harry Wentland <harry.wentland@amd.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190723232808.28128-3-sunpeng.li@amd.com
This commit is contained in:
Ville Syrjälä 2019-07-23 19:28:01 -04:00 committed by Harry Wentland
parent 3935ec4a11
commit 562836a269
4 changed files with 160 additions and 12 deletions

View File

@ -37,6 +37,7 @@
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_dp_helper.h> #include <drm/drm_dp_helper.h>
#include <drm/drm_dp_mst_helper.h>
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include "drm_crtc_helper_internal.h" #include "drm_crtc_helper_internal.h"
@ -162,7 +163,12 @@ static ssize_t auxdev_read_iter(struct kiocb *iocb, struct iov_iter *to)
break; break;
} }
res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo); if (aux_dev->aux->is_remote)
res = drm_dp_mst_dpcd_read(aux_dev->aux, pos, buf,
todo);
else
res = drm_dp_dpcd_read(aux_dev->aux, pos, buf, todo);
if (res <= 0) if (res <= 0)
break; break;
@ -209,7 +215,12 @@ static ssize_t auxdev_write_iter(struct kiocb *iocb, struct iov_iter *from)
break; break;
} }
res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo); if (aux_dev->aux->is_remote)
res = drm_dp_mst_dpcd_write(aux_dev->aux, pos, buf,
todo);
else
res = drm_dp_dpcd_write(aux_dev->aux, pos, buf, todo);
if (res <= 0) if (res <= 0)
break; break;

View File

@ -36,6 +36,8 @@
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include "drm_crtc_helper_internal.h"
/** /**
* DOC: dp mst helper * DOC: dp mst helper
* *
@ -53,6 +55,9 @@ static int drm_dp_dpcd_write_payload(struct drm_dp_mst_topology_mgr *mgr,
int id, int id,
struct drm_dp_payload *payload); struct drm_dp_payload *payload);
static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port,
int offset, int size, u8 *bytes);
static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, struct drm_dp_mst_port *port,
int offset, int size, u8 *bytes); int offset, int size, u8 *bytes);
@ -1483,6 +1488,52 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port)
return send_link; return send_link;
} }
/**
* 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,
unsigned int offset, void *buffer, size_t size)
{
struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
aux);
return drm_dp_send_dpcd_read(port->mgr, port,
offset, size, buffer);
}
/**
* 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: 0 on success, negative error code on failure.
*/
ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux,
unsigned int offset, void *buffer, size_t size)
{
struct drm_dp_mst_port *port = container_of(aux, struct drm_dp_mst_port,
aux);
return drm_dp_send_dpcd_write(port->mgr, port,
offset, size, buffer);
}
static void drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid) static void drm_dp_check_mstb_guid(struct drm_dp_mst_branch *mstb, u8 *guid)
{ {
int ret; int ret;
@ -1526,6 +1577,46 @@ static void build_mst_prop_path(const struct drm_dp_mst_branch *mstb,
strlcat(proppath, temp, proppath_size); strlcat(proppath, temp, proppath_size);
} }
/**
* drm_dp_mst_connector_late_register() - Late MST connector registration
* @drm_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_DEBUG_KMS("registering %s remote bus for %s\n",
port->aux.name, connector->kdev->kobj.name);
port->aux.dev = connector->kdev;
return drm_dp_aux_register_devnode(&port->aux);
}
EXPORT_SYMBOL(drm_dp_mst_connector_late_register);
/**
* drm_dp_mst_connector_early_unregister() - Early MST connector unregistration
* @drm_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_DEBUG_KMS("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);
static void drm_dp_add_port(struct drm_dp_mst_branch *mstb, static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
struct drm_device *dev, struct drm_device *dev,
struct drm_dp_link_addr_reply_port *port_msg) struct drm_dp_link_addr_reply_port *port_msg)
@ -1548,6 +1639,7 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
port->mgr = mstb->mgr; port->mgr = mstb->mgr;
port->aux.name = "DPMST"; port->aux.name = "DPMST";
port->aux.dev = dev->dev; port->aux.dev = dev->dev;
port->aux.is_remote = true;
/* /*
* Make sure the memory allocation for our parent branch stays * Make sure the memory allocation for our parent branch stays
@ -1816,7 +1908,6 @@ static bool drm_dp_validate_guid(struct drm_dp_mst_topology_mgr *mgr,
return false; return false;
} }
#if 0
static int build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, u8 port_num, u32 offset, u8 num_bytes) static int build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, u8 port_num, u32 offset, u8 num_bytes)
{ {
struct drm_dp_sideband_msg_req_body req; struct drm_dp_sideband_msg_req_body req;
@ -1829,7 +1920,6 @@ static int build_dpcd_read(struct drm_dp_sideband_msg_tx *msg, u8 port_num, u32
return 0; return 0;
} }
#endif
static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr, static int drm_dp_send_sideband_msg(struct drm_dp_mst_topology_mgr *mgr,
bool up, u8 *msg, int len) bool up, u8 *msg, int len)
@ -2441,26 +2531,58 @@ int drm_dp_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr)
} }
EXPORT_SYMBOL(drm_dp_update_payload_part2); EXPORT_SYMBOL(drm_dp_update_payload_part2);
#if 0 /* unused as of yet */
static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr, static int drm_dp_send_dpcd_read(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, struct drm_dp_mst_port *port,
int offset, int size) int offset, int size, u8 *bytes)
{ {
int len; int len;
int ret = 0;
struct drm_dp_sideband_msg_tx *txmsg; struct drm_dp_sideband_msg_tx *txmsg;
struct drm_dp_mst_branch *mstb;
mstb = drm_dp_mst_topology_get_mstb_validated(mgr, port->parent);
if (!mstb)
return -EINVAL;
txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL); txmsg = kzalloc(sizeof(*txmsg), GFP_KERNEL);
if (!txmsg) if (!txmsg) {
return -ENOMEM; ret = -ENOMEM;
goto fail_put;
}
len = build_dpcd_read(txmsg, port->port_num, 0, 8); len = build_dpcd_read(txmsg, port->port_num, offset, size);
txmsg->dst = port->parent; txmsg->dst = port->parent;
drm_dp_queue_down_tx(mgr, txmsg); drm_dp_queue_down_tx(mgr, txmsg);
return 0; ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
if (ret < 0)
goto fail_free;
/* DPCD read should never be NACKed */
if (txmsg->reply.reply_type == 1) {
DRM_ERROR("mstb %p port %d: DPCD read on addr 0x%x for %d bytes NAKed\n",
mstb, port->port_num, offset, size);
ret = -EIO;
goto fail_free;
}
if (txmsg->reply.u.remote_dpcd_read_ack.num_bytes != size) {
ret = -EPROTO;
goto fail_free;
}
ret = min_t(size_t, txmsg->reply.u.remote_dpcd_read_ack.num_bytes,
size);
memcpy(bytes, txmsg->reply.u.remote_dpcd_read_ack.bytes, ret);
fail_free:
kfree(txmsg);
fail_put:
drm_dp_mst_topology_put_mstb(mstb);
return ret;
} }
#endif
static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr, static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, struct drm_dp_mst_port *port,
@ -2489,7 +2611,7 @@ static int drm_dp_send_dpcd_write(struct drm_dp_mst_topology_mgr *mgr,
ret = drm_dp_mst_wait_tx_reply(mstb, txmsg); ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
if (ret > 0) { if (ret > 0) {
if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK) if (txmsg->reply.reply_type == DP_SIDEBAND_REPLY_NAK)
ret = -EINVAL; ret = -EIO;
else else
ret = 0; ret = 0;
} }

View File

@ -1309,6 +1309,10 @@ struct drm_dp_aux {
* @cec: struct containing fields used for CEC-Tunneling-over-AUX. * @cec: struct containing fields used for CEC-Tunneling-over-AUX.
*/ */
struct drm_dp_aux_cec cec; struct drm_dp_aux_cec cec;
/**
* @is_remote: Is this AUX CH actually using sideband messaging.
*/
bool is_remote;
}; };
ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset, ssize_t drm_dp_dpcd_read(struct drm_dp_aux *aux, unsigned int offset,

View File

@ -643,6 +643,17 @@ void drm_dp_mst_dump_topology(struct seq_file *m,
void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr); void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr);
int __must_check int __must_check
drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr); drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr);
ssize_t drm_dp_mst_dpcd_read(struct drm_dp_aux *aux,
unsigned int offset, void *buffer, size_t size);
ssize_t drm_dp_mst_dpcd_write(struct drm_dp_aux *aux,
unsigned int offset, void *buffer, size_t size);
int drm_dp_mst_connector_late_register(struct drm_connector *connector,
struct drm_dp_mst_port *port);
void drm_dp_mst_connector_early_unregister(struct drm_connector *connector,
struct drm_dp_mst_port *port);
struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr); struct drm_dp_mst_topology_mgr *mgr);
int __must_check int __must_check