[Nouveau] [PATCH v5 06/20] drm/dp_mst: Introduce new refcounting scheme for mstbs and ports

Daniel Vetter daniel at ffwll.ch
Thu Jan 10 09:12:12 UTC 2019


On Tue, Jan 08, 2019 at 07:35:03PM -0500, Lyude Paul wrote:
> The current way of handling refcounting in the DP MST helpers is really
> confusing and probably just plain wrong because it's been hacked up many
> times over the years without anyone actually going over the code and
> seeing if things could be simplified.
> 
> To the best of my understanding, the current scheme works like this:
> drm_dp_mst_port and drm_dp_mst_branch both have a single refcount. When
> this refcount hits 0 for either of the two, they're removed from the
> topology state, but not immediately freed. Both ports and branch devices
> will reinitialize their kref once it's hit 0 before actually destroying
> themselves. The intended purpose behind this is so that we can avoid
> problems like not being able to free a remote payload that might still
> be active, due to us having removed all of the port/branch device
> structures in memory, as per:
> 
> commit 91a25e463130 ("drm/dp/mst: deallocate payload on port destruction")
> 
> Which may have worked, but then it caused use-after-free errors. Being
> new to MST at the time, I tried fixing it;
> 
> commit 263efde31f97 ("drm/dp/mst: Get validated port ref in drm_dp_update_payload_part1()")
> 
> But, that was broken: both drm_dp_mst_port and drm_dp_mst_branch structs
> are validated in almost every DP MST helper function. Simply put, this
> means we go through the topology and try to see if the given
> drm_dp_mst_branch or drm_dp_mst_port is still attached to something
> before trying to use it in order to avoid dereferencing freed memory
> (something that has happened a LOT in the past with this library).
> Because of this it doesn't actually matter whether or not we keep keep
> the ports and branches around in memory as that's not enough, because
> any function that validates the branches and ports passed to it will
> still reject them anyway since they're no longer in the topology
> structure. So, use-after-free errors were fixed but payload deallocation
> was completely broken.
> 
> Two years later, AMD informed me about this issue and I attempted to
> come up with a temporary fix, pending a long-overdue cleanup of this
> library:
> 
> commit c54c7374ff44 ("drm/dp_mst: Skip validating ports during destruction, just ref")
> 
> But then that introduced use-after-free errors, so I quickly reverted
> it:
> 
> commit 9765635b3075 ("Revert "drm/dp_mst: Skip validating ports during destruction, just ref"")
> 
> And in the process, learned that there is just no simple fix for this:
> the design is just broken. Unfortunately, the usage of these helpers are
> quite broken as well. Some drivers like i915 have been smart enough to
> avoid accessing any kind of information from MST port structures, but
> others like nouveau have assumed, understandably so, that
> drm_dp_mst_port structures are normal and can just be accessed at any
> time without worrying about use-after-free errors.
> 
> After a lot of discussion, me and Daniel Vetter came up with a better
> idea to replace all of this.
> 
> To summarize, since this is documented far more indepth in the
> documentation this patch introduces, we make it so that drm_dp_mst_port
> and drm_dp_mst_branch structures have two different classes of
> refcounts: topology_kref, and malloc_kref. topology_kref corresponds to
> the lifetime of the given drm_dp_mst_port or drm_dp_mst_branch in it's
> given topology. Once it hits zero, any associated connectors are removed
> and the branch or port can no longer be validated. malloc_kref
> corresponds to the lifetime of the memory allocation for the actual
> structure, and will always be non-zero so long as the topology_kref is
> non-zero. This gives us a way to allow callers to hold onto port and
> branch device structures past their topology lifetime, and dramatically
> simplifies the lifetimes of both structures. This also finally fixes the
> port deallocation problem, properly.
> 
> Additionally: since this now means that we can keep ports and branch
> devices allocated in memory for however long we need, we no longer need
> a significant amount of the port validation that we currently do.
> 
> Additionally, there is one last scenario that this fixes, which couldn't
> have been fixed properly beforehand:
> 
> - CPU1 unrefs port from topology (refcount 1->0)
> - CPU2 refs port in topology(refcount 0->1)
> 
> Since we now can guarantee memory safety for ports and branches
> as-needed, we also can make our main reference counting functions fix
> this problem by using kref_get_unless_zero() internally so that topology
> refcounts can only ever reach 0 once.
> 
> Changes since v3:
> * Remove rebase detritus - danvet
> * Split out purely style changes into separate patches - hwentlan
> 
> Changes since v2:
> * Fix commit message - checkpatch
> * s/)-1/) - 1/g - checkpatch
> 
> Changes since v1:
> * Remove forward declarations - danvet
> * Move "Branch device and port refcounting" section from documentation
>   into kernel-doc comments - danvet
> * Export internal topology lifetime functions into their own section in
>   the kernel-docs - danvet
> * s/@/&/g for struct references in kernel-docs - danvet
> * Drop the "when they are no longer being used" bits from the kernel
>   docs - danvet
> * Modify diagrams to show how the DRM driver interacts with the topology
>   and payloads - danvet
> * Make suggested documentation changes for
>   drm_dp_mst_topology_get_mstb() and drm_dp_mst_topology_get_port() -
>   danvet
> * Better explain the relationship between malloc refs and topology krefs
>   in the documentation for drm_dp_mst_topology_get_port() and
>   drm_dp_mst_topology_get_mstb() - danvet
> * Fix "See also" in drm_dp_mst_topology_get_mstb() - danvet
> * Rename drm_dp_mst_topology_get_(port|mstb)() ->
>   drm_dp_mst_topology_try_get_(port|mstb)() and
>   drm_dp_mst_topology_ref_(port|mstb)() ->
>   drm_dp_mst_topology_get_(port|mstb)() - danvet
> * s/should/must in docs - danvet
> * WARN_ON(refcount == 0) in topology_get_(mstb|port) - danvet
> * Move kdocs for mstb/port structs inline - danvet
> * Split drm_dp_get_last_connected_port_and_mstb() changes into their own
>   commit - danvet
> 
> Signed-off-by: Lyude Paul <lyude at redhat.com>
> Reviewed-by: Harry Wentland <harry.wentland at amd.com>
> Cc: Daniel Vetter <daniel at ffwll.ch>
> Cc: David Airlie <airlied at redhat.com>
> Cc: Jerry Zuo <Jerry.Zuo at amd.com>
> Cc: Juston Li <juston.li at intel.com>
> ---
>  .../gpu/dp-mst/topology-figure-1.dot          |  52 ++
>  .../gpu/dp-mst/topology-figure-2.dot          |  56 +++
>  .../gpu/dp-mst/topology-figure-3.dot          |  59 +++
>  Documentation/gpu/drm-kms-helpers.rst         |  26 +-
>  drivers/gpu/drm/drm_dp_mst_topology.c         | 457 +++++++++++++++---
>  include/drm/drm_dp_mst_helper.h               |  32 +-
>  6 files changed, 613 insertions(+), 69 deletions(-)
>  create mode 100644 Documentation/gpu/dp-mst/topology-figure-1.dot
>  create mode 100644 Documentation/gpu/dp-mst/topology-figure-2.dot
>  create mode 100644 Documentation/gpu/dp-mst/topology-figure-3.dot
> 
> diff --git a/Documentation/gpu/dp-mst/topology-figure-1.dot b/Documentation/gpu/dp-mst/topology-figure-1.dot
> new file mode 100644
> index 000000000000..157e17c7e0b0
> --- /dev/null
> +++ b/Documentation/gpu/dp-mst/topology-figure-1.dot
> @@ -0,0 +1,52 @@
> +digraph T {
> +    /* Make sure our payloads are always drawn below the driver node */
> +    subgraph cluster_driver {
> +        fillcolor = grey;
> +        style = filled;
> +        driver -> {payload1, payload2} [dir=none];
> +    }
> +
> +    /* Driver malloc references */
> +    edge [style=dashed];
> +    driver -> port1;
> +    driver -> port2;
> +    driver -> port3:e;
> +    driver -> port4;
> +
> +    payload1:s -> port1:e;
> +    payload2:s -> port3:e;
> +    edge [style=""];
> +
> +    subgraph cluster_topology {
> +        label="Topology Manager";
> +        labelloc=bottom;
> +
> +        /* Topology references */
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        port2 -> mstb3 -> {port3, port4};
> +        port3 -> mstb4;
> +
> +        /* Malloc references */
> +        edge [style=dashed;dir=back];
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        port2 -> mstb3 -> {port3, port4};
> +        port3 -> mstb4;
> +    }
> +
> +    driver [label="DRM driver";style=filled;shape=box;fillcolor=lightblue];
> +
> +    payload1 [label="Payload #1";style=filled;shape=box;fillcolor=lightblue];
> +    payload2 [label="Payload #2";style=filled;shape=box;fillcolor=lightblue];
> +
> +    mstb1 [label="MSTB #1";style=filled;fillcolor=palegreen;shape=oval];
> +    mstb2 [label="MSTB #2";style=filled;fillcolor=palegreen;shape=oval];
> +    mstb3 [label="MSTB #3";style=filled;fillcolor=palegreen;shape=oval];
> +    mstb4 [label="MSTB #4";style=filled;fillcolor=palegreen;shape=oval];
> +
> +    port1 [label="Port #1";shape=oval];
> +    port2 [label="Port #2";shape=oval];
> +    port3 [label="Port #3";shape=oval];
> +    port4 [label="Port #4";shape=oval];
> +}
> diff --git a/Documentation/gpu/dp-mst/topology-figure-2.dot b/Documentation/gpu/dp-mst/topology-figure-2.dot
> new file mode 100644
> index 000000000000..4243dd1737cb
> --- /dev/null
> +++ b/Documentation/gpu/dp-mst/topology-figure-2.dot
> @@ -0,0 +1,56 @@
> +digraph T {
> +    /* Make sure our payloads are always drawn below the driver node */
> +    subgraph cluster_driver {
> +        fillcolor = grey;
> +        style = filled;
> +        driver -> {payload1, payload2} [dir=none];
> +    }
> +
> +    /* Driver malloc references */
> +    edge [style=dashed];
> +    driver -> port1;
> +    driver -> port2;
> +    driver -> port3:e;
> +    driver -> port4 [color=red];
> +
> +    payload1:s -> port1:e;
> +    payload2:s -> port3:e;
> +    edge [style=""];
> +
> +    subgraph cluster_topology {
> +        label="Topology Manager";
> +        labelloc=bottom;
> +
> +        /* Topology references */
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        edge [color=red];
> +        port2 -> mstb3 -> {port3, port4};
> +        port3 -> mstb4;
> +        edge [color=""];
> +
> +        /* Malloc references */
> +        edge [style=dashed;dir=back];
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        port2 -> mstb3 -> port3;
> +        edge [color=red];
> +        mstb3 -> port4;
> +        port3 -> mstb4;
> +    }
> +
> +    mstb1 [label="MSTB #1";style=filled;fillcolor=palegreen];
> +    mstb2 [label="MSTB #2";style=filled;fillcolor=palegreen];
> +    mstb3 [label="MSTB #3";style=filled;fillcolor=palegreen];
> +    mstb4 [label="MSTB #4";style=filled;fillcolor=grey];
> +
> +    port1 [label="Port #1"];
> +    port2 [label="Port #2"];
> +    port3 [label="Port #3"];
> +    port4 [label="Port #4";style=filled;fillcolor=grey];
> +
> +    driver [label="DRM driver";style=filled;shape=box;fillcolor=lightblue];
> +
> +    payload1 [label="Payload #1";style=filled;shape=box;fillcolor=lightblue];
> +    payload2 [label="Payload #2";style=filled;shape=box;fillcolor=lightblue];
> +}
> diff --git a/Documentation/gpu/dp-mst/topology-figure-3.dot b/Documentation/gpu/dp-mst/topology-figure-3.dot
> new file mode 100644
> index 000000000000..6cd78d06778b
> --- /dev/null
> +++ b/Documentation/gpu/dp-mst/topology-figure-3.dot
> @@ -0,0 +1,59 @@
> +digraph T {
> +    /* Make sure our payloads are always drawn below the driver node */
> +    subgraph cluster_driver {
> +        fillcolor = grey;
> +        style = filled;
> +        edge [dir=none];
> +        driver -> payload1;
> +        driver -> payload2 [penwidth=3];
> +        edge [dir=""];
> +    }
> +
> +    /* Driver malloc references */
> +    edge [style=dashed];
> +    driver -> port1;
> +    driver -> port2;
> +    driver -> port3:e;
> +    driver -> port4 [color=grey];
> +    payload1:s -> port1:e;
> +    payload2:s -> port3:e [penwidth=3];
> +    edge [style=""];
> +
> +    subgraph cluster_topology {
> +        label="Topology Manager";
> +        labelloc=bottom;
> +
> +        /* Topology references */
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        edge [color=grey];
> +        port2 -> mstb3 -> {port3, port4};
> +        port3 -> mstb4;
> +        edge [color=""];
> +
> +        /* Malloc references */
> +        edge [style=dashed;dir=back];
> +        mstb1 -> {port1, port2};
> +        port1 -> mstb2;
> +        port2 -> mstb3 [penwidth=3];
> +        mstb3 -> port3 [penwidth=3];
> +        edge [color=grey];
> +        mstb3 -> port4;
> +        port3 -> mstb4;
> +    }
> +
> +    mstb1 [label="MSTB #1";style=filled;fillcolor=palegreen];
> +    mstb2 [label="MSTB #2";style=filled;fillcolor=palegreen];
> +    mstb3 [label="MSTB #3";style=filled;fillcolor=palegreen;penwidth=3];
> +    mstb4 [label="MSTB #4";style=filled;fillcolor=grey];
> +
> +    port1 [label="Port #1"];
> +    port2 [label="Port #2";penwidth=5];
> +    port3 [label="Port #3";penwidth=3];
> +    port4 [label="Port #4";style=filled;fillcolor=grey];
> +
> +    driver [label="DRM driver";style=filled;shape=box;fillcolor=lightblue];
> +
> +    payload1 [label="Payload #1";style=filled;shape=box;fillcolor=lightblue];
> +    payload2 [label="Payload #2";style=filled;shape=box;fillcolor=lightblue;penwidth=3];
> +}
> diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst
> index b422eb8edf16..7a3fc569bc68 100644
> --- a/Documentation/gpu/drm-kms-helpers.rst
> +++ b/Documentation/gpu/drm-kms-helpers.rst
> @@ -208,18 +208,40 @@ Display Port Dual Mode Adaptor Helper Functions Reference
>  .. kernel-doc:: drivers/gpu/drm/drm_dp_dual_mode_helper.c
>     :export:
>  
> -Display Port MST Helper Functions Reference
> -===========================================
> +Display Port MST Helpers
> +========================
> +
> +Overview
> +--------
>  
>  .. kernel-doc:: drivers/gpu/drm/drm_dp_mst_topology.c
>     :doc: dp mst helper
>  
> +.. kernel-doc:: drivers/gpu/drm/drm_dp_mst_topology.c
> +   :doc: Branch device and port refcounting
> +
> +Functions Reference
> +-------------------
> +
>  .. kernel-doc:: include/drm/drm_dp_mst_helper.h
>     :internal:
>  
>  .. kernel-doc:: drivers/gpu/drm/drm_dp_mst_topology.c
>     :export:
>  
> +Topology Lifetime Internals
> +---------------------------
> +
> +These functions aren't exported to drivers, but are documented here to help make
> +the MST topology helpers easier to understand
> +
> +.. kernel-doc:: drivers/gpu/drm/drm_dp_mst_topology.c
> +   :functions: drm_dp_mst_topology_try_get_mstb drm_dp_mst_topology_get_mstb
> +               drm_dp_mst_topology_put_mstb
> +               drm_dp_mst_topology_try_get_port drm_dp_mst_topology_get_port
> +               drm_dp_mst_topology_put_port
> +               drm_dp_mst_get_mstb_malloc drm_dp_mst_put_mstb_malloc
> +
>  MIPI DSI Helper Functions Reference
>  ===================================
>  
> diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c
> index 074e985093ca..c53cf7eb1dbc 100644
> --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> @@ -850,46 +850,211 @@ static struct drm_dp_mst_branch *drm_dp_add_mst_branch_device(u8 lct, u8 *rad)
>  	if (lct > 1)
>  		memcpy(mstb->rad, rad, lct / 2);
>  	INIT_LIST_HEAD(&mstb->ports);
> -	kref_init(&mstb->kref);
> +	kref_init(&mstb->topology_kref);
> +	kref_init(&mstb->malloc_kref);
>  	return mstb;
>  }
>  
> -static void drm_dp_free_mst_port(struct kref *kref);
> -
>  static void drm_dp_free_mst_branch_device(struct kref *kref)
>  {
> -	struct drm_dp_mst_branch *mstb = container_of(kref, struct drm_dp_mst_branch, kref);
> -	if (mstb->port_parent) {
> -		if (list_empty(&mstb->port_parent->next))
> -			kref_put(&mstb->port_parent->kref, drm_dp_free_mst_port);
> -	}
> +	struct drm_dp_mst_branch *mstb =
> +		container_of(kref, struct drm_dp_mst_branch, malloc_kref);
> +
> +	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 it's 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 had to take a double take on this one here.  Maybe add ", i.e. the
arrows point the same way as the C pointers used to reference a
structure."

> + *
> + * As you can see in figure 1, every branch increments the topology refcount

Maybe s/figure 1/above figure/, the output doesn't have numbered figures
(and if it did, we'd need an rst reference).

> + * of it's children, and increments the malloc refcount of it's parent.
> + * Additionally, every payload increments the malloc refcount of it's 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 figure 2.
> + *
> + * .. 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 it's &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 it's &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 in figure 3.
> + *
> + * .. 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()
> + */
> +static void
> +drm_dp_mst_get_mstb_malloc(struct drm_dp_mst_branch *mstb)
> +{
> +	kref_get(&mstb->malloc_kref);
> +	DRM_DEBUG("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()
> + */
> +static void
> +drm_dp_mst_put_mstb_malloc(struct drm_dp_mst_branch *mstb)
> +{
> +	DRM_DEBUG("mstb %p (%d)\n", mstb, kref_read(&mstb->malloc_kref) - 1);
> +	kref_put(&mstb->malloc_kref, drm_dp_free_mst_branch_device);
> +}
> +
> +static void drm_dp_free_mst_port(struct kref *kref)
> +{
> +	struct drm_dp_mst_port *port =
> +		container_of(kref, struct drm_dp_mst_port, malloc_kref);
> +
> +	drm_dp_mst_put_mstb_malloc(port->parent);
> +	kfree(port);
> +}
> +
> +/**
> + * 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_DEBUG("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_DEBUG("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);
> +
>  static void drm_dp_destroy_mst_branch_device(struct kref *kref)
>  {
> -	struct drm_dp_mst_branch *mstb = container_of(kref, struct drm_dp_mst_branch, kref);
> +	struct drm_dp_mst_branch *mstb =
> +		container_of(kref, struct drm_dp_mst_branch, topology_kref);
> +	struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
>  	struct drm_dp_mst_port *port, *tmp;
>  	bool wake_tx = false;
>  
> -	/*
> -	 * init kref again to be used by ports to remove mst branch when it is
> -	 * not needed anymore
> -	 */
> -	kref_init(kref);
> -
> -	if (mstb->port_parent && list_empty(&mstb->port_parent->next))
> -		kref_get(&mstb->port_parent->kref);
> -
> -	/*
> -	 * destroy all ports - don't need lock
> -	 * as there are no more references to the mst branch
> -	 * device at this point.
> -	 */
> +	mutex_lock(&mgr->lock);
>  	list_for_each_entry_safe(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 */
>  	mutex_lock(&mstb->mgr->qlock);
> @@ -908,14 +1073,83 @@ static void drm_dp_destroy_mst_branch_device(struct kref *kref)
>  	if (wake_tx)
>  		wake_up_all(&mstb->mgr->tx_waitq);
>  
> -	kref_put(kref, drm_dp_free_mst_branch_device);
> +	drm_dp_mst_put_mstb_malloc(mstb);
>  }
>  
> -static void drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb)
> +/**
> + * drm_dp_mst_topology_try_get_mstb() - Increment the topology refcount of a
> + * branch device unless its 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
> + */
> +static int __must_check
> +drm_dp_mst_topology_try_get_mstb(struct drm_dp_mst_branch *mstb)
>  {
> -	kref_put(&mstb->kref, drm_dp_destroy_mst_branch_device);
> +	int ret = kref_get_unless_zero(&mstb->topology_kref);
> +
> +	if (ret)
> +		DRM_DEBUG("mstb %p (%d)\n", mstb,
> +			  kref_read(&mstb->topology_kref));
> +
> +	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()
> + */
> +static void drm_dp_mst_topology_get_mstb(struct drm_dp_mst_branch *mstb)
> +{
> +	WARN_ON(kref_read(&mstb->topology_kref) == 0);
> +	kref_get(&mstb->topology_kref);
> +	DRM_DEBUG("mstb %p (%d)\n", mstb, kref_read(&mstb->topology_kref));
> +}
> +
> +/**
> + * 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()
> + */
> +static void
> +drm_dp_mst_topology_put_mstb(struct drm_dp_mst_branch *mstb)
> +{
> +	DRM_DEBUG("mstb %p (%d)\n",
> +		  mstb, kref_read(&mstb->topology_kref) - 1);
> +	kref_put(&mstb->topology_kref, drm_dp_destroy_mst_branch_device);
> +}
>  
>  static void drm_dp_port_teardown_pdt(struct drm_dp_mst_port *port, int old_pdt)
>  {
> @@ -937,7 +1171,8 @@ static void drm_dp_port_teardown_pdt(struct drm_dp_mst_port *port, int old_pdt)
>  
>  static void drm_dp_destroy_port(struct kref *kref)
>  {
> -	struct drm_dp_mst_port *port = container_of(kref, struct drm_dp_mst_port, kref);
> +	struct drm_dp_mst_port *port =
> +		container_of(kref, struct drm_dp_mst_port, topology_kref);
>  	struct drm_dp_mst_topology_mgr *mgr = port->mgr;
>  
>  	if (!port->input) {
> @@ -956,7 +1191,6 @@ static void drm_dp_destroy_port(struct kref *kref)
>  			 * from an EDID retrieval */
>  
>  			mutex_lock(&mgr->destroy_connector_lock);
> -			kref_get(&port->parent->kref);
>  			list_add(&port->next, &mgr->destroy_connector_list);
>  			mutex_unlock(&mgr->destroy_connector_lock);
>  			schedule_work(&mgr->destroy_connector_work);
> @@ -967,12 +1201,79 @@ static void drm_dp_destroy_port(struct kref *kref)
>  		drm_dp_port_teardown_pdt(port, port->pdt);
>  		port->pdt = DP_PEER_DEVICE_NONE;
>  	}
> -	kfree(port);
> +	drm_dp_mst_put_port_malloc(port);
> +}
> +
> +/**
> + * drm_dp_mst_topology_try_get_port() - Increment the topology refcount of a
> + * port unless its 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
> + */
> +static int __must_check
> +drm_dp_mst_topology_try_get_port(struct drm_dp_mst_port *port)
> +{
> +	int ret = kref_get_unless_zero(&port->topology_kref);
> +
> +	if (ret)
> +		DRM_DEBUG("port %p (%d)\n", port,
> +			  kref_read(&port->topology_kref));
> +
> +	return ret;
>  }
>  
> +/**
> + * 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()
> + */
> +static void drm_dp_mst_topology_get_port(struct drm_dp_mst_port *port)
> +{
> +	WARN_ON(kref_read(&port->topology_kref) == 0);
> +	kref_get(&port->topology_kref);
> +	DRM_DEBUG("port %p (%d)\n", port, kref_read(&port->topology_kref));
> +}
> +
> +/**
> + * 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()
> + */
>  static void drm_dp_mst_topology_put_port(struct drm_dp_mst_port *port)
>  {
> -	kref_put(&port->kref, drm_dp_destroy_port);
> +	DRM_DEBUG("port %p (%d)\n",
> +		  port, kref_read(&port->topology_kref) - 1);
> +	kref_put(&port->topology_kref, drm_dp_destroy_port);
>  }
>  
>  static struct drm_dp_mst_branch *
> @@ -981,10 +1282,10 @@ drm_dp_mst_topology_get_mstb_validated_locked(struct drm_dp_mst_branch *mstb,
>  {
>  	struct drm_dp_mst_port *port;
>  	struct drm_dp_mst_branch *rmstb;
> -	if (to_find == mstb) {
> -		kref_get(&mstb->kref);
> +
> +	if (to_find == mstb)
>  		return mstb;
> -	}
> +
>  	list_for_each_entry(port, &mstb->ports, next) {
>  		if (port->mstb) {
>  			rmstb = drm_dp_mst_topology_get_mstb_validated_locked(
> @@ -1001,25 +1302,32 @@ drm_dp_mst_topology_get_mstb_validated(struct drm_dp_mst_topology_mgr *mgr,
>  				       struct drm_dp_mst_branch *mstb)
>  {
>  	struct drm_dp_mst_branch *rmstb = NULL;
> +
>  	mutex_lock(&mgr->lock);
> -	if (mgr->mst_primary)
> +	if (mgr->mst_primary) {
>  		rmstb = drm_dp_mst_topology_get_mstb_validated_locked(
>  		    mgr->mst_primary, mstb);
> +
> +		if (rmstb && !drm_dp_mst_topology_try_get_mstb(rmstb))
> +			rmstb = NULL;
> +	}
>  	mutex_unlock(&mgr->lock);
>  	return rmstb;
>  }
>  
> -static struct drm_dp_mst_port *drm_dp_mst_get_port_ref_locked(struct drm_dp_mst_branch *mstb, struct drm_dp_mst_port *to_find)
> +static struct drm_dp_mst_port *
> +drm_dp_mst_topology_get_port_validated_locked(struct drm_dp_mst_branch *mstb,
> +					      struct drm_dp_mst_port *to_find)
>  {
>  	struct drm_dp_mst_port *port, *mport;
>  
>  	list_for_each_entry(port, &mstb->ports, next) {
> -		if (port == to_find) {
> -			kref_get(&port->kref);
> +		if (port == to_find)
>  			return port;
> -		}
> +
>  		if (port->mstb) {
> -			mport = drm_dp_mst_get_port_ref_locked(port->mstb, to_find);
> +			mport = drm_dp_mst_topology_get_port_validated_locked(
> +			    port->mstb, to_find);
>  			if (mport)
>  				return mport;
>  		}
> @@ -1032,9 +1340,15 @@ drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr,
>  				       struct drm_dp_mst_port *port)
>  {
>  	struct drm_dp_mst_port *rport = NULL;
> +
>  	mutex_lock(&mgr->lock);
> -	if (mgr->mst_primary)
> -		rport = drm_dp_mst_get_port_ref_locked(mgr->mst_primary, port);
> +	if (mgr->mst_primary) {
> +		rport = drm_dp_mst_topology_get_port_validated_locked(
> +		    mgr->mst_primary, port);
> +
> +		if (rport && !drm_dp_mst_topology_try_get_port(rport))
> +			rport = NULL;
> +	}
>  	mutex_unlock(&mgr->lock);
>  	return rport;
>  }
> @@ -1042,11 +1356,12 @@ drm_dp_mst_topology_get_port_validated(struct drm_dp_mst_topology_mgr *mgr,
>  static struct drm_dp_mst_port *drm_dp_get_port(struct drm_dp_mst_branch *mstb, u8 port_num)
>  {
>  	struct drm_dp_mst_port *port;
> +	int ret;
>  
>  	list_for_each_entry(port, &mstb->ports, next) {
>  		if (port->port_num == port_num) {
> -			kref_get(&port->kref);
> -			return port;
> +			ret = drm_dp_mst_topology_try_get_port(port);
> +			return ret ? port : NULL;
>  		}
>  	}
>  
> @@ -1095,6 +1410,11 @@ static bool drm_dp_port_setup_pdt(struct drm_dp_mst_port *port)
>  		if (port->mstb) {
>  			port->mstb->mgr = port->mgr;
>  			port->mstb->port_parent = port;
> +			/*
> +			 * Make sure this port's memory allocation stays
> +			 * around until it's child MSTB releases it
> +			 */
> +			drm_dp_mst_get_port_malloc(port);
>  
>  			send_link = true;
>  		}
> @@ -1155,17 +1475,26 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
>  	bool created = false;
>  	int old_pdt = 0;
>  	int old_ddps = 0;
> +
>  	port = drm_dp_get_port(mstb, port_msg->port_number);
>  	if (!port) {
>  		port = kzalloc(sizeof(*port), GFP_KERNEL);
>  		if (!port)
>  			return;
> -		kref_init(&port->kref);
> +		kref_init(&port->topology_kref);
> +		kref_init(&port->malloc_kref);
>  		port->parent = mstb;
>  		port->port_num = port_msg->port_number;
>  		port->mgr = mstb->mgr;
>  		port->aux.name = "DPMST";
>  		port->aux.dev = dev->dev;
> +
> +		/*
> +		 * 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);
> +
>  		created = true;
>  	} else {
>  		old_pdt = port->pdt;
> @@ -1185,7 +1514,7 @@ static void drm_dp_add_port(struct drm_dp_mst_branch *mstb,
>  	   for this list */
>  	if (created) {
>  		mutex_lock(&mstb->mgr->lock);
> -		kref_get(&port->kref);
> +		drm_dp_mst_topology_get_port(port);
>  		list_add(&port->next, &mstb->ports);
>  		mutex_unlock(&mstb->mgr->lock);
>  	}
> @@ -1284,7 +1613,7 @@ static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_
>  {
>  	struct drm_dp_mst_branch *mstb;
>  	struct drm_dp_mst_port *port;
> -	int i;
> +	int i, ret;
>  	/* find the port by iterating down */
>  
>  	mutex_lock(&mgr->lock);
> @@ -1309,7 +1638,9 @@ static struct drm_dp_mst_branch *drm_dp_get_mst_branch_device(struct drm_dp_mst_
>  			}
>  		}
>  	}
> -	kref_get(&mstb->kref);
> +	ret = drm_dp_mst_topology_try_get_mstb(mstb);
> +	if (!ret)
> +		mstb = NULL;
>  out:
>  	mutex_unlock(&mgr->lock);
>  	return mstb;
> @@ -1344,14 +1675,17 @@ drm_dp_get_mst_branch_device_by_guid(struct drm_dp_mst_topology_mgr *mgr,
>  				     uint8_t *guid)
>  {
>  	struct drm_dp_mst_branch *mstb;
> +	int ret;
>  
>  	/* find the port by iterating down */
>  	mutex_lock(&mgr->lock);
>  
>  	mstb = get_mst_branch_device_by_guid_helper(mgr->mst_primary, guid);
> -
> -	if (mstb)
> -		kref_get(&mstb->kref);
> +	if (mstb) {
> +		ret = drm_dp_mst_topology_try_get_mstb(mstb);
> +		if (!ret)
> +			mstb = NULL;
> +	}
>  
>  	mutex_unlock(&mgr->lock);
>  	return mstb;
> @@ -1390,11 +1724,14 @@ static void drm_dp_mst_link_probe_work(struct work_struct *work)
>  {
>  	struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, work);
>  	struct drm_dp_mst_branch *mstb;
> +	int ret;
>  
>  	mutex_lock(&mgr->lock);
>  	mstb = mgr->mst_primary;
>  	if (mstb) {
> -		kref_get(&mstb->kref);
> +		ret = drm_dp_mst_topology_try_get_mstb(mstb);
> +		if (!ret)
> +			mstb = NULL;
>  	}
>  	mutex_unlock(&mgr->lock);
>  	if (mstb) {
> @@ -1722,8 +2059,10 @@ static struct drm_dp_mst_branch *drm_dp_get_last_connected_port_and_mstb(struct
>  
>  		if (found_port) {
>  			rmstb = found_port->parent;
> -			kref_get(&rmstb->kref);
> -			*port_num = found_port->port_num;
> +			if (drm_dp_mst_topology_try_get_mstb(rmstb))
> +				*port_num = found_port->port_num;
> +			else
> +				rmstb = NULL;
>  		}
>  	}
>  	mutex_unlock(&mgr->lock);
> @@ -2176,7 +2515,7 @@ int drm_dp_mst_topology_mgr_set_mst(struct drm_dp_mst_topology_mgr *mgr, bool ms
>  
>  		/* give this the main reference */
>  		mgr->mst_primary = mstb;
> -		kref_get(&mgr->mst_primary->kref);
> +		drm_dp_mst_topology_get_mstb(mgr->mst_primary);
>  
>  		ret = drm_dp_dpcd_writeb(mgr->aux, DP_MSTM_CTRL,
>  							 DP_MST_EN | DP_UP_REQ_EN | DP_UPSTREAM_IS_SRC);
> @@ -3096,13 +3435,6 @@ static void drm_dp_tx_work(struct work_struct *work)
>  	mutex_unlock(&mgr->qlock);
>  }
>  
> -static void drm_dp_free_mst_port(struct kref *kref)
> -{
> -	struct drm_dp_mst_port *port = container_of(kref, struct drm_dp_mst_port, kref);
> -	kref_put(&port->parent->kref, drm_dp_free_mst_branch_device);
> -	kfree(port);
> -}
> -
>  static void drm_dp_destroy_connector_work(struct work_struct *work)
>  {
>  	struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct drm_dp_mst_topology_mgr, destroy_connector_work);
> @@ -3123,7 +3455,6 @@ static void drm_dp_destroy_connector_work(struct work_struct *work)
>  		list_del(&port->next);
>  		mutex_unlock(&mgr->destroy_connector_lock);
>  
> -		kref_init(&port->kref);
>  		INIT_LIST_HEAD(&port->next);
>  
>  		mgr->cbs->destroy_connector(mgr, port->connector);
> @@ -3137,7 +3468,7 @@ static void drm_dp_destroy_connector_work(struct work_struct *work)
>  			drm_dp_mst_put_payload_id(mgr, port->vcpi.vcpi);
>  		}
>  
> -		kref_put(&port->kref, drm_dp_free_mst_port);
> +		drm_dp_mst_put_port_malloc(port);
>  		send_hotplug = true;
>  	}
>  	if (send_hotplug)
> diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h
> index 371cc2816477..8eca5f29242c 100644
> --- a/include/drm/drm_dp_mst_helper.h
> +++ b/include/drm/drm_dp_mst_helper.h
> @@ -44,7 +44,6 @@ struct drm_dp_vcpi {
>  
>  /**
>   * struct drm_dp_mst_port - MST port
> - * @kref: reference count for this port.
>   * @port_num: port number
>   * @input: if this port is an input port.
>   * @mcs: message capability status - DP 1.2 spec.
> @@ -67,7 +66,18 @@ struct drm_dp_vcpi {
>   * in the MST topology.
>   */
>  struct drm_dp_mst_port {
> -	struct kref kref;
> +	/**
> +	 * @topology_kref: refcount for this port's lifetime in the topology,
> +	 * only the DP MST helpers should need to touch this
> +	 */
> +	struct kref topology_kref;
> +
> +	/**
> +	 * @malloc_kref: refcount for the memory allocation containing this
> +	 * structure. See drm_dp_mst_get_port_malloc() and
> +	 * drm_dp_mst_put_port_malloc().
> +	 */
> +	struct kref malloc_kref;
>  
>  	u8 port_num;
>  	bool input;
> @@ -102,7 +112,6 @@ struct drm_dp_mst_port {
>  
>  /**
>   * struct drm_dp_mst_branch - MST branch device.
> - * @kref: reference count for this port.
>   * @rad: Relative Address to talk to this branch device.
>   * @lct: Link count total to talk to this branch device.
>   * @num_ports: number of ports on the branch.
> @@ -121,7 +130,19 @@ struct drm_dp_mst_port {
>   * to downstream port of parent branches.
>   */
>  struct drm_dp_mst_branch {
> -	struct kref kref;
> +	/**
> +	 * @topology_kref: refcount for this branch device's lifetime in the
> +	 * topology, only the DP MST helpers should need to touch this
> +	 */
> +	struct kref topology_kref;
> +
> +	/**
> +	 * @malloc_kref: refcount for the memory allocation containing this
> +	 * structure. See drm_dp_mst_get_mstb_malloc() and
> +	 * drm_dp_mst_put_mstb_malloc().
> +	 */
> +	struct kref malloc_kref;
> +
>  	u8 rad[8];
>  	u8 lct;
>  	int num_ports;
> @@ -626,4 +647,7 @@ int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
>  int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr,
>  				 struct drm_dp_mst_port *port, bool power_up);
>  
> +void drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port);
> +void drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port);
> +


_Really_ nice documentation, and the graphs look pretty!

Reviewed-by: Daniel Vetter <daniel.vetter at ffwll.ch>

Aside: We're lacking kerneldoc for drm_dp_mst_topology_cbs. A follow-up to
document a bit what drivers are expected to do in each callback would be
neat.
-Daniel


>  #endif
> -- 
> 2.20.1
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch


More information about the Nouveau mailing list