For Resource Consumers
----------------------
-.. kernel-doc:: drivers/base/revocable.c
+.. kernel-doc:: include/linux/revocable.h
:identifiers: revocable
.. kernel-doc:: drivers/base/revocable.c
- :identifiers: revocable_alloc
+ :identifiers: revocable_init
.. kernel-doc:: drivers/base/revocable.c
- :identifiers: revocable_free
+ :identifiers: revocable_deinit
.. kernel-doc:: drivers/base/revocable.c
:identifiers: revocable_try_access
.. code-block:: c
- void consumer_use_resource(struct revocable *rev)
+ void consumer_use_resource(struct revocable_provider *rp)
{
struct foo_resource *res;
- REVOCABLE_TRY_ACCESS_WITH(rev, res);
+ REVOCABLE_TRY_ACCESS_WITH(rp, res);
// Always check if the resource is valid.
if (!res) {
pr_warn("Resource is not available\n");
.. code-block:: c
- void consumer_use_resource(struct revocable *rev)
+ void consumer_use_resource(struct revocable_provider *rp)
{
struct foo_resource *res;
- REVOCABLE_TRY_ACCESS_SCOPED(rev, res) {
+ REVOCABLE_TRY_ACCESS_SCOPED(rp, res) {
// Always check if the resource is valid.
if (!res) {
pr_warn("Resource is not available\n");
struct rcu_head rcu;
};
-/**
- * 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.
EXPORT_SYMBOL_GPL(revocable_provider_revoke);
/**
- * revocable_alloc() - Allocate struct revocable.
+ * revocable_init() - Initialize struct revocable.
* @_rp: The pointer of resource provider.
+ * @rev: The pointer of resource consumer.
*
* This holds a refcount to the resource provider.
*
- * Return: The pointer of struct revocable. NULL on errors.
+ * Return: 0 on success, -errno otherwise.
*/
-struct revocable *revocable_alloc(struct revocable_provider __rcu *_rp)
+int revocable_init(struct revocable_provider __rcu *_rp, struct revocable *rev)
{
struct revocable_provider *rp;
- struct revocable *rev;
if (!_rp)
- return NULL;
+ return -ENODEV;
/*
* Enter a read-side critical section.
rp = rcu_dereference(_rp);
if (!rp)
/* The revocable provider has been revoked. */
- return NULL;
+ return -ENODEV;
if (!kref_get_unless_zero(&rp->kref))
/*
* The revocable provider is releasing (i.e.,
* revocable_provider_release() has been called).
*/
- return NULL;
+ return -ENODEV;
}
/* At this point, `rp` is safe to access as holding a kref of it */
- rev = kzalloc(sizeof(*rev), GFP_KERNEL);
- if (!rev) {
- kref_put(&rp->kref, revocable_provider_release);
- return NULL;
- }
-
rev->rp = rp;
- return rev;
+ return 0;
}
-EXPORT_SYMBOL_GPL(revocable_alloc);
+EXPORT_SYMBOL_GPL(revocable_init);
/**
- * revocable_free() - Free struct revocable.
+ * revocable_deinit() - Deinitialize 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)
+void revocable_deinit(struct revocable *rev)
{
struct revocable_provider *rp = rev->rp;
kref_put(&rp->kref, revocable_provider_release);
- kfree(rev);
}
-EXPORT_SYMBOL_GPL(revocable_free);
+EXPORT_SYMBOL_GPL(revocable_deinit);
/**
* revocable_try_access() - Try to access the resource.
* - Try Access Macro: Same as "Revocation" but uses the
* REVOCABLE_TRY_ACCESS_WITH() and REVOCABLE_TRY_ACCESS_SCOPED().
*
- * - Provider Use-after-free: Verifies revocable_alloc() correctly handles
+ * - Provider Use-after-free: Verifies revocable_init() correctly handles
* race conditions where the provider is being released.
*/
static void revocable_test_basic(struct kunit *test)
{
struct revocable_provider __rcu *rp;
- struct revocable *rev;
+ struct revocable rev;
void *real_res = (void *)0x12345678, *res;
+ int ret;
rp = revocable_provider_alloc(real_res);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp);
- rev = revocable_alloc(rp);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev);
+ ret = revocable_init(rp, &rev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
- res = revocable_try_access(rev);
+ res = revocable_try_access(&rev);
KUNIT_EXPECT_PTR_EQ(test, res, real_res);
- revocable_withdraw_access(rev);
+ revocable_withdraw_access(&rev);
- revocable_free(rev);
+ revocable_deinit(&rev);
revocable_provider_revoke(&rp);
KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL);
}
static void revocable_test_revocation(struct kunit *test)
{
struct revocable_provider __rcu *rp;
- struct revocable *rev;
+ struct revocable rev;
void *real_res = (void *)0x12345678, *res;
+ int ret;
rp = revocable_provider_alloc(real_res);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp);
- rev = revocable_alloc(rp);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev);
+ ret = revocable_init(rp, &rev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
- res = revocable_try_access(rev);
+ res = revocable_try_access(&rev);
KUNIT_EXPECT_PTR_EQ(test, res, real_res);
- revocable_withdraw_access(rev);
+ revocable_withdraw_access(&rev);
revocable_provider_revoke(&rp);
KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL);
- res = revocable_try_access(rev);
+ res = revocable_try_access(&rev);
KUNIT_EXPECT_PTR_EQ(test, res, NULL);
- revocable_withdraw_access(rev);
+ revocable_withdraw_access(&rev);
- revocable_free(rev);
+ revocable_deinit(&rev);
}
static void revocable_test_try_access_macro(struct kunit *test)
{
struct revocable_provider __rcu *rp;
- struct revocable *rev;
void *real_res = (void *)0x12345678, *res;
rp = revocable_provider_alloc(real_res);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp);
- rev = revocable_alloc(rp);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev);
-
{
- REVOCABLE_TRY_ACCESS_WITH(rev, res);
+ REVOCABLE_TRY_ACCESS_WITH(rp, res);
KUNIT_EXPECT_PTR_EQ(test, res, real_res);
}
KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL);
{
- REVOCABLE_TRY_ACCESS_WITH(rev, res);
+ REVOCABLE_TRY_ACCESS_WITH(rp, res);
KUNIT_EXPECT_PTR_EQ(test, res, NULL);
}
-
- revocable_free(rev);
}
static void revocable_test_try_access_macro2(struct kunit *test)
{
struct revocable_provider __rcu *rp;
- struct revocable *rev;
void *real_res = (void *)0x12345678, *res;
bool accessed;
rp = revocable_provider_alloc(real_res);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp);
- rev = revocable_alloc(rp);
- KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rev);
-
accessed = false;
- REVOCABLE_TRY_ACCESS_SCOPED(rev, res) {
+ REVOCABLE_TRY_ACCESS_SCOPED(rp, res) {
KUNIT_EXPECT_PTR_EQ(test, res, real_res);
accessed = true;
}
KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL);
accessed = false;
- REVOCABLE_TRY_ACCESS_SCOPED(rev, res) {
+ REVOCABLE_TRY_ACCESS_SCOPED(rp, res) {
KUNIT_EXPECT_PTR_EQ(test, res, NULL);
accessed = true;
}
KUNIT_EXPECT_TRUE(test, accessed);
-
- revocable_free(rev);
}
static void revocable_test_provider_use_after_free(struct kunit *test)
{
struct revocable_provider __rcu *rp;
struct revocable_provider *old_rp;
+ struct revocable rev;
void *real_res = (void *)0x12345678;
- struct revocable *rev;
+ int ret;
rp = revocable_provider_alloc(real_res);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, rp);
- rev = revocable_alloc(NULL);
- KUNIT_EXPECT_PTR_EQ(test, rev, NULL);
+ ret = revocable_init(NULL, &rev);
+ KUNIT_EXPECT_NE(test, ret, 0);
/* Simulate the provider has been freed. */
old_rp = rcu_replace_pointer(rp, NULL, 1);
- rev = revocable_alloc(rp);
- KUNIT_EXPECT_PTR_EQ(test, rev, NULL);
+ ret = revocable_init(rp, &rev);
+ KUNIT_EXPECT_NE(test, ret, 0);
rcu_replace_pointer(rp, old_rp, 1);
struct {
/* Simulate the provider is releasing. */
refcount_set(&rp_internal->kref.refcount, 0);
- rev = revocable_alloc(rp);
- KUNIT_EXPECT_PTR_EQ(test, rev, NULL);
+ ret = revocable_init(rp, &rev);
+ KUNIT_EXPECT_NE(test, ret, 0);
refcount_set(&rp_internal->kref.refcount, 1);
revocable_provider_revoke(&rp);
KUNIT_EXPECT_PTR_EQ(test, unrcu_pointer(rp), NULL);
+ ret = revocable_init(rp, &rev);
+ KUNIT_EXPECT_NE(test, ret, 0);
}
static struct kunit_case revocable_test_cases[] = {
#include <linux/cleanup.h>
struct device;
-struct revocable;
struct revocable_provider;
+/**
+ * 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;
+};
+
struct revocable_provider __rcu *revocable_provider_alloc(void *res);
void revocable_provider_revoke(struct revocable_provider __rcu **rp);
-struct revocable *revocable_alloc(struct revocable_provider __rcu *rp);
-void revocable_free(struct revocable *rev);
+int revocable_init(struct revocable_provider __rcu *rp, struct revocable *rev);
+void revocable_deinit(struct revocable *rev);
void *revocable_try_access(struct revocable *rev) __acquires(&rev->rp->srcu);
void revocable_withdraw_access(struct revocable *rev) __releases(&rev->rp->srcu);
-DEFINE_FREE(access_rev, struct revocable *, if (_T) revocable_withdraw_access(_T))
+DEFINE_FREE(access_rev, struct revocable *, {
+ if ((_T)->idx != -1)
+ revocable_withdraw_access(_T);
+ if ((_T)->rp)
+ revocable_deinit(_T);
+})
+
+#define _REVOCABLE_TRY_ACCESS_WITH(_rp, _rev, _res) \
+ struct revocable _rev = {.rp = NULL, .idx = -1}; \
+ struct revocable *__UNIQUE_ID(name) __free(access_rev) = &_rev; \
+ _res = revocable_init(_rp, &_rev) ? NULL : revocable_try_access(&_rev)
/**
* REVOCABLE_TRY_ACCESS_WITH() - A helper for accessing revocable resource
- * @_rev: The consumer's ``struct revocable *`` handle.
+ * @_rp: The provider's ``struct revocable_provider *`` handle.
* @_res: A pointer variable that will be assigned the resource.
*
* The macro simplifies the access-release cycle for consumers, ensuring that
- * revocable_withdraw_access() is always called, even in the case of an early
- * exit.
+ * corresponding revocable_withdraw_access() and revocable_deinit() are called,
+ * even in the case of an early exit.
*
* It creates a local variable in the current scope. @_res is populated with
* the result of revocable_try_access(). The consumer code **must** check if
* are allowed before the helper. Otherwise, the compiler fails with
* "jump bypasses initialization of variable with __attribute__((cleanup))".
*/
-#define REVOCABLE_TRY_ACCESS_WITH(_rev, _res) \
- struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \
- _res = revocable_try_access(_rev)
+#define REVOCABLE_TRY_ACCESS_WITH(_rp, _res) \
+ _REVOCABLE_TRY_ACCESS_WITH(_rp, __UNIQUE_ID(name), _res)
-#define _REVOCABLE_TRY_ACCESS_SCOPED(_rev, _label, _res) \
- for (struct revocable *__UNIQUE_ID(name) __free(access_rev) = _rev; \
- (_res = revocable_try_access(_rev)) || true; ({ goto _label; })) \
+#define _REVOCABLE_TRY_ACCESS_SCOPED(_rp, _rev, _label, _res) \
+ for (struct revocable _rev = {.rp = NULL, .idx = -1}, \
+ *__UNIQUE_ID(name) __free(access_rev) = &_rev; \
+ (_res = revocable_init(_rp, &_rev) ? NULL : \
+ revocable_try_access(&_rev)) || true; \
+ ({ goto _label; })) \
if (0) { \
_label: \
break; \
/**
* REVOCABLE_TRY_ACCESS_SCOPED() - A helper for accessing revocable resource
- * @_rev: The consumer's ``struct revocable *`` handle.
+ * @_rp: The provider's ``struct revocable_provider *`` handle.
* @_res: A pointer variable that will be assigned the resource.
*
* Similar to REVOCABLE_TRY_ACCESS_WITH() but with an explicit scope from a
* temporary ``for`` loop.
*/
-#define REVOCABLE_TRY_ACCESS_SCOPED(_rev, _res) \
- _REVOCABLE_TRY_ACCESS_SCOPED(_rev, __UNIQUE_ID(label), _res)
+#define REVOCABLE_TRY_ACCESS_SCOPED(_rp, _res) \
+ _REVOCABLE_TRY_ACCESS_SCOPED(_rp, __UNIQUE_ID(name), \
+ __UNIQUE_ID(label), _res)
#endif /* __LINUX_REVOCABLE_H */
static int revocable_test_consumer_open(struct inode *inode, struct file *filp)
{
- struct revocable *rev;
- struct revocable_provider __rcu *rp = inode->i_private;
-
- rev = revocable_alloc(rp);
- if (!rev)
- return -ENOMEM;
- filp->private_data = rev;
-
- return 0;
-}
-
-static int revocable_test_consumer_release(struct inode *inode,
- struct file *filp)
-{
- struct revocable *rev = filp->private_data;
-
- revocable_free(rev);
+ filp->private_data = inode->i_private;
return 0;
}
char __user *buf,
size_t count, loff_t *offset)
{
+ int ret;
char *res;
char data[16];
size_t len;
- struct revocable *rev = filp->private_data;
+ struct revocable rev;
+ struct revocable_provider __rcu *rp = filp->private_data;
switch (*offset) {
case 0:
- res = revocable_try_access(rev);
+ ret = revocable_init(rp, &rev);
+ if (ret) {
+ snprintf(data, sizeof(data), "%s", "(null)");
+ break;
+ }
+ res = revocable_try_access(&rev);
snprintf(data, sizeof(data), "%s", res ?: "(null)");
- revocable_withdraw_access(rev);
+ revocable_withdraw_access(&rev);
+ revocable_deinit(&rev);
break;
case TEST_MAGIC_OFFSET:
{
- REVOCABLE_TRY_ACCESS_WITH(rev, res);
+ REVOCABLE_TRY_ACCESS_WITH(rp, res);
snprintf(data, sizeof(data), "%s", res ?: "(null)");
}
break;
case TEST_MAGIC_OFFSET2:
- REVOCABLE_TRY_ACCESS_SCOPED(rev, res)
+ REVOCABLE_TRY_ACCESS_SCOPED(rp, res)
snprintf(data, sizeof(data), "%s", res ?: "(null)");
break;
default:
static const struct file_operations revocable_test_consumer_fops = {
.open = revocable_test_consumer_open,
- .release = revocable_test_consumer_release,
.read = revocable_test_consumer_read,
.llseek = default_llseek,
};