DF-4748 / static_proof.log
STEP 1: sole dc_sink->priv assignment site (mst_types.c:243), never cleared:
/home/maxx/dfbsd/dfbsd/sys/dev/drm/amd/display/amdgpu_dm/amdgpu_dm_mst_types.c:243: dc_sink->priv = aconnector;
STEP 2: helpers deref stream->sink->priv with value-only guard (197/289/317), guard at 199:
197: aconnector = stream->sink->priv; 289: aconnector = stream->sink->priv; 317: aconnector = stream->sink->priv;
199: if (!aconnector || !aconnector->mst_port)
STEP 3: destroy path drops sink ref + connector ref but never clears sink->priv (mst_types.c:386-397):
12: aconnector->port = NULL;
15: dc_link_remove_remote_sink(aconnector->dc_link, aconnector->dc_sink);
16: dc_sink_release(aconnector->dc_sink);
23: drm_connector_put(connector);
STEP 4: connector destroy is a raw kfree (amdgpu_dm.c:3187):
3187: kfree(connector);
STEP 5: dc_stream_state holds its own dc_sink reference (dc_stream.c:113), so the sink outlives the connector:
113: dc_sink_release(stream->sink);
STEP 6: dc_link_remove_remote_sink (dc.c:1823) only edits remote_sinks[]; enabled streams stay live in the context:
if (!link->sink_count) {
for (i = 0; i < link->sink_count; i++) {
if (link->remote_sinks[i] == sink) {
dc_sink_release(sink);
link->remote_sinks[i] = NULL;
while (i < link->sink_count - 1) {
link->remote_sinks[i] = link->remote_sinks[i+1];
link->remote_sinks[i] = NULL;
link->sink_count--;
STEP 7: later commits reach the helpers with the old stream (dc_link.c:2621 enable / 2637 disable):
2621: allocate_mst_payload(pipe_ctx);
2637: deallocate_mst_payload(pipe_ctx);
STEP 8: destroy runs asynchronously from the topology worker, unsynchronized with dc commits (drm_dp_mst_topology.c:3116):
3116: mgr->cbs->destroy_connector(mgr, port->connector);
CHAIN COMPLETE: set-site -> free-without-clear -> ref-outlives -> later deref. QED (static).