blob: 98edc18eff4265010956609869cd95165c223a7a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* The interface for waiting on multiple inter-IP fences to complete the signaler submission.
*
* Copyright (C) 2023 Google LLC
*/
#include <linux/eventfd.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <gcip/iif/iif-fence.h>
#include <gcip/iif/iif-signaler-submission-watier.h>
static struct iif_signaler_submission_waiter *
iif_signaler_submission_waiter_alloc(unsigned int eventfd, int pending_fences)
{
struct iif_signaler_submission_waiter *waiter;
struct eventfd_ctx *ctx;
waiter = kzalloc(sizeof(*waiter), GFP_KERNEL);
if (!waiter)
return ERR_PTR(-ENOMEM);
ctx = eventfd_ctx_fdget((int)eventfd);
if (IS_ERR(ctx)) {
kfree(waiter);
return ERR_CAST(ctx);
}
waiter->ctx = ctx;
waiter->pending_fences = pending_fences;
INIT_LIST_HEAD(&waiter->cb_list);
kref_init(&waiter->kref);
spin_lock_init(&waiter->lock);
return waiter;
}
static void iif_all_signaler_submission_waiter_free(struct kref *kref)
{
struct iif_signaler_submission_waiter *waiter =
container_of(kref, struct iif_signaler_submission_waiter, kref);
eventfd_ctx_put(waiter->ctx);
kfree(waiter);
}
struct iif_signaler_submission_waiter *
iif_all_signaler_submission_waiter_get(struct iif_signaler_submission_waiter *waiter)
{
kref_get(&waiter->kref);
return waiter;
}
void iif_all_signaler_submission_waiter_put(struct iif_signaler_submission_waiter *waiter)
{
kref_put(&waiter->kref, iif_all_signaler_submission_waiter_free);
}
static void all_signaler_submitted(struct iif_fence *fence,
struct iif_fence_all_signaler_submitted_cb *fence_cb)
{
struct iif_signaler_submission_waiter_cb *cb =
container_of(fence_cb, struct iif_signaler_submission_waiter_cb, fence_cb);
struct iif_signaler_submission_waiter *waiter = cb->waiter;
unsigned long flags;
spin_lock_irqsave(&waiter->lock, flags);
/*
* The `iif_all_signaler_submission_waiter_cancel` function will delete @cb from
* @watier->cb_list, decrement the refcount of @waiter and release @cb instead.
*/
if (waiter->cancel) {
spin_unlock_irqrestore(&waiter->lock, flags);
return;
}
list_del(&cb->node);
/*
* - The callback will be executed asynchronously even while `iif_wait_signaler_submission`
* function is still registering callbacks for each fence. In this case, even though
* @waiter->cb_list is empty, we must not trigger the eventfd since not all callbacks are
* registered yet (waiter->pending_fences is non-zero).
*
* - If waiter->pending_fences is 0, it means that we have finished registering callbacks
* for all fences and the waiter should wait on @waiter->cb_list to be empty.
*
* - If there is no more fence to register the callback and all fences have finished the
* signaler submission, we can signal the eventfd.
*
* Note that this callback will be called even when @fence is going to be destroyed before
* all signalers have been submitted to clean up the callback data, @cb. We can distinguish
* the case by checking whether @fence->all_signaler_submitted_error is zero or not and we
* should not notify the eventfd if that is non-zero.
*/
if (!waiter->pending_fences && list_empty(&waiter->cb_list) &&
!fence->all_signaler_submitted_error)
eventfd_signal(waiter->ctx, 1);
spin_unlock_irqrestore(&waiter->lock, flags);
iif_all_signaler_submission_waiter_put(waiter);
kfree(cb);
}
static int iif_all_signaler_submission_waiter_wait(struct iif_signaler_submission_waiter *waiter,
struct iif_fence *fence,
int *remaining_signalers)
{
struct iif_signaler_submission_waiter_cb *cb;
int ret;
cb = kzalloc(sizeof(*cb), GFP_KERNEL);
if (!cb)
return -ENOMEM;
cb->waiter = iif_all_signaler_submission_waiter_get(waiter);
/*
* Don't call `iif_fence_get` to prevent @fence from being not released forever if the
* runtime never submits signalers somehow.
*/
cb->fence = fence;
spin_lock(&waiter->lock);
list_add_tail(&cb->node, &waiter->cb_list);
spin_unlock(&waiter->lock);
ret = iif_fence_add_all_signaler_submitted_callback(fence, &cb->fence_cb,
all_signaler_submitted);
spin_lock(&waiter->lock);
if (ret && ret != -EPERM)
goto out_list_del;
/*
* @fence has been successfully registered the callback or already finished the
* signaler submission.
*/
waiter->pending_fences--;
/* (ret = -EPERM) Already all signalers are submitted and the callback is not registered. */
if (ret) {
*remaining_signalers = 0;
ret = 0;
goto out_list_del;
}
/* (ret = 0) The callback is registered and there are remaining signalers. */
*remaining_signalers = cb->fence_cb.remaining_signalers;
spin_unlock(&waiter->lock);
return 0;
out_list_del:
list_del(&cb->node);
spin_unlock(&waiter->lock);
iif_all_signaler_submission_waiter_put(waiter);
kfree(cb);
return ret;
}
static void iif_all_signaler_submission_waiter_cancel(struct iif_signaler_submission_waiter *waiter)
{
struct iif_signaler_submission_waiter_cb *cur, *tmp;
spin_lock(&waiter->lock);
waiter->cancel = true;
spin_unlock(&waiter->lock);
/* From now on, @waiter->cb_list won't be changed. */
list_for_each_entry_safe(cur, tmp, &waiter->cb_list, node) {
iif_fence_remove_all_signaler_submitted_callback(cur->fence, &cur->fence_cb);
list_del(&cur->node);
iif_all_signaler_submission_waiter_put(waiter);
kfree(cur);
}
}
int iif_wait_signaler_submission(struct iif_fence **fences, int num_fences, unsigned int eventfd,
int *remaining_signalers)
{
struct iif_signaler_submission_waiter *waiter;
int i, ret = 0;
if (eventfd == IIF_NO_REGISTER_EVENTFD) {
for (i = 0; i < num_fences; i++)
remaining_signalers[i] = iif_fence_unsubmitted_signalers(fences[i]);
return 0;
}
waiter = iif_signaler_submission_waiter_alloc(eventfd, num_fences);
if (IS_ERR(waiter))
return PTR_ERR(waiter);
for (i = 0; i < num_fences; i++) {
ret = iif_all_signaler_submission_waiter_wait(waiter, fences[i],
&remaining_signalers[i]);
if (ret) {
iif_all_signaler_submission_waiter_cancel(waiter);
break;
}
}
iif_all_signaler_submission_waiter_put(waiter);
return ret;
}