blob: c7a16623a65282e13f5351a1f313dea5b8191d39 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2025 Google LLC
*
* Revocable resource management
*/
#include <linux/device.h>
#include <linux/kref.h>
#include <linux/revocable.h>
#include <linux/slab.h>
#include <linux/srcu.h>
/**
* DOC: Overview
*
* Some resources can be removed asynchronously, for example, resources
* provided by a hot-pluggable device like USB. When holding a reference
* to such a resource, it's possible for the resource to be removed and
* its memory freed, leading to use-after-free errors on subsequent access.
*
* Introduce the revocable to establish weak references to such resources.
* It allows a resource consumer to safely attempt to access a resource
* that might be freed at any time by the resource provider.
*
* The implementation uses a provider/consumer model built on Sleepable
* RCU (SRCU) to guarantee safe memory access:
*
* - A resource provider allocates a struct revocable_provider and
* initializes it with a pointer to the resource.
*
* - A resource consumer that wants to access the resource allocates a
* struct revocable which holds a reference to the provider.
*
* - To access the resource, the consumer uses revocable_try_access().
* This function enters an SRCU read-side critical section and returns
* the pointer to the resource. If the provider has already freed the
* resource, it returns NULL. After use, the consumer calls
* revocable_release() to exit the SRCU critical section. The
* REVOCABLE() is a convenient helper for doing that.
*
* - When the provider needs to remove the resource, it calls
* revocable_provider_free(). This function sets the internal resource
* pointer to NULL and then calls synchronize_srcu() to wait for all
* current readers to finish before the resource can be completely torn
* down.
*/
/**
* struct revocable_provider - A handle for resource provider.
* @srcu: The SRCU to protect the resource.
* @res: The pointer of resource. It can point to anything.
* @kref: The refcount for this handle.
*/
struct revocable_provider {
struct srcu_struct srcu;
void __rcu *res;
struct kref kref;
};
/**
* struct revocable - A handle for resource consumer.
* @rp: The pointer of resource provider.
* @idx: The index for the RCU critical section.
*/
struct revocable {
struct revocable_provider *rp;
int idx;
};
/**
* revocable_provider_alloc() - Allocate struct revocable_provider.
* @res: The pointer of resource.
*
* This holds an initial refcount to the struct.
*
* Return: The pointer of struct revocable_provider. NULL on errors.
*/
struct revocable_provider *revocable_provider_alloc(void *res)
{
struct revocable_provider *rp;
rp = kzalloc(sizeof(*rp), GFP_KERNEL);
if (!rp)
return NULL;
init_srcu_struct(&rp->srcu);
rcu_assign_pointer(rp->res, res);
synchronize_srcu(&rp->srcu);
kref_init(&rp->kref);
return rp;
}
EXPORT_SYMBOL_GPL(revocable_provider_alloc);
static void revocable_provider_release(struct kref *kref)
{
struct revocable_provider *rp = container_of(kref,
struct revocable_provider, kref);
cleanup_srcu_struct(&rp->srcu);
kfree(rp);
}
/**
* revocable_provider_free() - Free struct revocable_provider.
* @rp: The pointer of resource provider.
*
* This sets the resource `(struct revocable_provider *)->res` to NULL to
* indicate the resource has gone.
*
* This drops the refcount to the resource provider. If it is the final
* reference, revocable_provider_release() will be called to free the struct.
*/
void revocable_provider_free(struct revocable_provider *rp)
{
rcu_assign_pointer(rp->res, NULL);
synchronize_srcu(&rp->srcu);
kref_put(&rp->kref, revocable_provider_release);
}
EXPORT_SYMBOL_GPL(revocable_provider_free);
static void devm_revocable_provider_free(void *data)
{
struct revocable_provider *rp = data;
revocable_provider_free(rp);
}
/**
* devm_revocable_provider_alloc() - Dev-managed revocable_provider_alloc().
* @dev: The device.
* @res: The pointer of resource.
*
* 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.
*
* This holds an initial refcount to the struct.
*
* Return: The pointer of struct revocable_provider. NULL on errors.
*/
struct revocable_provider *devm_revocable_provider_alloc(struct device *dev,
void *res)
{
struct revocable_provider *rp;
rp = revocable_provider_alloc(res);
if (!rp)
return NULL;
if (devm_add_action_or_reset(dev, devm_revocable_provider_free, rp))
return NULL;
return rp;
}
EXPORT_SYMBOL_GPL(devm_revocable_provider_alloc);
/**
* revocable_alloc() - Allocate struct revocable_provider.
* @rp: The pointer of resource provider.
*
* This holds a refcount to the resource provider.
*
* Return: The pointer of struct revocable_provider. NULL on errors.
*/
struct revocable *revocable_alloc(struct revocable_provider *rp)
{
struct revocable *rev;
rev = kzalloc(sizeof(*rev), GFP_KERNEL);
if (!rev)
return NULL;
rev->rp = rp;
kref_get(&rp->kref);
return rev;
}
EXPORT_SYMBOL_GPL(revocable_alloc);
/**
* revocable_free() - Free struct revocable.
* @rev: The pointer of struct revocable.
*
* This drops a refcount to the resource provider. If it is the final
* reference, revocable_provider_release() will be called to free the struct.
*/
void revocable_free(struct revocable *rev)
{
struct revocable_provider *rp = rev->rp;
kref_put(&rp->kref, revocable_provider_release);
kfree(rev);
}
EXPORT_SYMBOL_GPL(revocable_free);
/**
* revocable_try_access() - Try to access the resource.
* @rev: The pointer of struct revocable.
*
* This tries to de-reference to the resource and enters a RCU critical
* section.
*
* Return: The pointer to the resource. NULL if the resource has gone.
*/
void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu)
{
struct revocable_provider *rp = rev->rp;
rev->idx = srcu_read_lock(&rp->srcu);
return srcu_dereference(rp->res, &rp->srcu);
}
EXPORT_SYMBOL_GPL(revocable_try_access);
/**
* revocable_release() - Stop accessing to the resource.
* @rev: The pointer of struct revocable.
*
* Call this function to indicate the resource is no longer used. It exits
* the RCU critical section.
*/
void revocable_release(struct revocable *rev) __releases(&rev->rp->srcu)
{
struct revocable_provider *rp = rev->rp;
srcu_read_unlock(&rp->srcu, rev->idx);
}
EXPORT_SYMBOL_GPL(revocable_release);