|
DMA fences are synchronisation primitives that will be needed by all
Rust GPU drivers.
The dma_fence framework sets a number of rules, notably:
- fences must only be signaled once
- all fences must be signaled at some point
- fence error codes must only be set before signaling
- every pointer to a fence must be backed by a reference
All those rules are being addressed by these abstractions.
To cleanly decouple fence issuers and consumers, two types are provided:
- DriverFence: the only fence type that can be signaled and that
carries driver-specific data.
- Fence: the fence type to be shared with other drivers and / or
userspace. The only type callbacks can be registered on.
Cannot be signaled.
Hereby, a Fence lives in the same chunk of memory as a DriverFence. Both
share the refcount of the underlying C dma_fence. Since this
implementation does not provide a custom dma_fence_backend_ops.release()
function, the memory is freed by the dma_fence backend once the refcount
drops to 0.
To create a DriverFence, the user must first allocate a
DriverFenceAllocation, so that the creation of the DriverFence later on
can always succeed. Otherwise, deadlocks could occur if fences need to
be created in a GPU job submission path.
Synchronization is ensured by the dma_fence backend.
All DriverFence's created through this abstraction must be signaled by
the creator with an error code. In case a DriverFence drops without
being signaled beforehand, it is signaled with -ECANCELLED as its
error and a warning is printed. This allows the Rust abstraction to very
cleanly decouple fence issuer and consumer by relying on the decoupling
mechanisms in the C backend, which ensures through RCU and the
'signaled' fence-flag that dma_fence_backend_ops functions cannot
access the potentially unloaded driver code anymore.
Signalling fences on drop thus grants many advantages. Not signaling
fences on drop would risk deadlock and does not grant real advantages:
By definition only the drivers can ensure that a fence always represents
the hardware's state correctly.
This implementation models a DmaFenceContext object on which fences are
to be created, thereby ensuring correct sequence numbering according to
the timeline.
dma_fence supports a variety of callbacks. The mandatory callbacks
(get_timeline_name() and get_driver_name()) are implemented in this
patch. For convenience, they store those name parameters in the fence
context, saving the driver from implementing these two callbacks.
Support for other callbacks (like for hardware signaling) is prepared
for through the fact that both DriverFence and Fence live in the same
allocation, allowing for usage of container_of from the callback to
access the driver-specific data.
It is expected that other callbacks, added in the future, also mostly
operate on the generic data in the FenceContext. To make this safe, the
implementation ensures through a lifetime that a DriverFence cannot
outlive its FenceContext.
Synchronization for dma_fence_ops callbacks is ensured by only running the
Rust deconstructor delayed with call_rcu(), which prevents UAF-bugs
should a DriverFence drop while a Fence callback is currently operating
on the associated driver data. Since they can also operate on the
FenceContext's data, its drop implementation also performs the necessary
delay with rcu_barrier().
An additional issue discovered during the review process of this code is
that there is (currently) no mechanism in Rust to prevent someone from
circumventing the DriverFence's FenceContext-reference's lifetime by
"forgetting" the fence, e.g. with core::mem::forget(). This would enable
UAF bugs on the FenceContext. Throw a panic if this happens and document
a path towards a more robust solution.
Add abstractions for dma_fence in Rust.
Signed-off-by: Philipp Stanner <phasta@kernel.org>
Tested-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Link: https://patch.msgid.link/20260905085343.1827305-3-phasta@kernel.org
[ In dma_fence_callback(), split combined unsafe block into separate
blocks for container_of!() and pointer dereference. - Danilo ]
Signed-off-by: Danilo Krummrich <dakr@kernel.org>
|