blob: b9e2968ba9c11960670074bc05a72fcb47594b4c [file] [edit]
.. SPDX-License-Identifier: GPL-2.0
==============================
Revocable Resource Management
==============================
Overview
========
In a system with hot-pluggable devices, such as USB, resources provided by
these devices can be removed asynchronously. If a consumer holds a reference
to such a resource, the resource might be deallocated while the reference is
still held, leading to use-after-free errors upon subsequent access.
The "revocable" mechanism addresses this by establishing a weak reference to a
resource that might be freed at any time. It allows a resource consumer to
safely attempt to access the resource, guaranteeing that the access is valid
for the duration of its use, or it fails safely if the resource has already
been revoked.
The implementation is based on a provider/consumer model that uses Sleepable
RCU (SRCU) to ensure safe memory access without traditional locking.
How It Works
============
1. **Provider**: A resource provider, such as a driver for a hot-pluggable
device, allocates a ``struct revocable_provider``. This structure is
initialized with a pointer to the actual resource it manages.
2. **Consumer**: A consumer that needs to access the resource is given a
``struct revocable``, which acts as a handle containing a reference to
the provider.
3. **Accessing the Resource**: To access the resource, the consumer uses
``revocable_try_access()``. This function enters an SRCU read-side
critical section and returns a pointer to the resource. If the provider
has already revoked the resource, this function returns ``NULL``. The
consumer must check for this ``NULL`` return.
4. **Releasing the Resource**: After the consumer has finished using the
resource, it must call ``revocable_release()`` to exit the SRCU critical
section. This signals that the consumer no longer requires access. The
``REVOCABLE()`` macro is provided as a convenient and safe way to manage
the access-release cycle.
5. **Revoking the Resource**: When the provider needs to remove the resource
(e.g., the device is unplugged), it calls ``revocable_provider_free()``.
This function first sets the internal resource pointer to ``NULL``,
preventing any new consumers from accessing it. It then calls
``synchronize_srcu()``, which waits for all existing consumers currently
in the SRCU critical section to finish their work. Once all consumers
have released their access, the resource can be safely deallocated.
Revocable vs. Device-Managed (devm) Resources
=============================================
It's important to understand the distinction between a standard
device-managed (devm) resource and a resource managed by a
``revocable_provider``.
The key difference is their lifetime:
* A **devm resource** is tied to the lifetime of the device. It is
automatically freed when the device is unbound.
* A **revocable_provider** persists as long as there are active references
to it from ``revocable`` consumer handles.
This means that a ``revocable_provider`` can outlive the device that created
it. This is a deliberate design feature, allowing consumers to hold a
reference to a resource even after the underlying device has been removed,
without causing a fault. When the consumer attempts to access the resource,
it will simply be informed that the resource is no longer available.
API and Usage
=============
For Resource Providers
----------------------
``struct revocable_provider *revocable_provider_alloc(void *res);``
Allocates a provider handle for the given resource ``res``. It returns a
pointer to the ``revocable_provider`` on success, or ``NULL`` on failure.
``struct revocable_provider *devm_revocable_provider_alloc(struct device *dev, void *res);``
A device-managed version of ``revocable_provider_alloc``. It is
convenient to allocate providers via this function if the ``res`` is also
tied to the lifetime of the ``dev``. ``revocable_provider_free`` will be
called automatically when the device is unbound.
``void revocable_provider_free(struct revocable_provider *rp);``
Revokes the resource. This function marks the resource as unavailable and
waits for all current consumers to finish before the underlying memory
can be freed.
For Resource Consumers
----------------------
``struct revocable *revocable_alloc(struct revocable_provider *rp);``
Allocates a consumer handle for a given provider ``rp``.
``void revocable_free(struct revocable *rev);``
Frees a consumer handle.
``void *revocable_try_access(struct revocable *rev);``
Attempts to gain access to the resource. Returns a pointer to the
resource on success or ``NULL`` if it has been revoked.
``void revocable_release(struct revocable *rev);``
Releases access to the resource, exiting the SRCU critical section.
The ``REVOCABLE()`` Macro
=========================
The ``REVOCABLE()`` macro simplifies the access-release cycle for consumers,
ensuring that ``revocable_release()`` is always called, even in the case of
an early exit.
``REVOCABLE(rev, res)``
* ``rev``: The consumer's ``struct revocable *`` handle.
* ``res``: A pointer variable that will be assigned the resource.
The macro creates a ``for`` loop that executes exactly once. Inside the loop,
``res`` is populated with the result of ``revocable_try_access()``. The
consumer code **must** check if ``res`` is ``NULL`` before using it. The
``revocable_release()`` function is automatically called when the scope of
the loop is exited.
Example Usage
-------------
.. code-block:: c
void consumer_use_resource(struct revocable *rev)
{
struct foo_resource *res;
REVOCABLE(rev, res) {
// Always check if the resource is valid.
if (!res) {
pr_warn("Resource is not available\n");
return;
}
// At this point, 'res' is guaranteed to be valid until
// this block exits.
do_something_with(res);
}
// revocable_release() is automatically called here.
}