blob: f4982d3545c1f673ef2132a2d04120f4338b4139 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* GCIP-integrated IIF driver sync file.
*
* Copyright (C) 2023 Google LLC
*/
#include <linux/anon_inodes.h>
#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <gcip/iif/iif-fence.h>
#include <gcip/iif/iif-sync-file.h>
static void iif_sync_file_fence_signaled(struct iif_fence *fence, struct iif_fence_poll_cb *poll_cb)
{
struct iif_sync_file *sync_file = container_of(poll_cb, struct iif_sync_file, poll_cb);
wake_up_all(&sync_file->wq);
}
static int iif_sync_file_release(struct inode *inode, struct file *file)
{
struct iif_sync_file *sync_file = file->private_data;
if (atomic_dec_and_test(&sync_file->fence->num_sync_file))
iif_fence_on_sync_file_release(sync_file->fence);
if (test_bit(IIF_SYNC_FILE_FLAGS_POLL_ENABLED, &sync_file->flags))
iif_fence_remove_poll_callback(sync_file->fence, &sync_file->poll_cb);
iif_fence_put(sync_file->fence);
kfree(sync_file);
return 0;
}
static __poll_t iif_sync_file_poll(struct file *file, poll_table *wait)
{
struct iif_sync_file *sync_file = file->private_data;
int ret;
poll_wait(file, &sync_file->wq, wait);
if (list_empty(&sync_file->poll_cb.node) &&
!test_and_set_bit(IIF_SYNC_FILE_FLAGS_POLL_ENABLED, &sync_file->flags)) {
ret = iif_fence_add_poll_callback(sync_file->fence, &sync_file->poll_cb,
iif_sync_file_fence_signaled);
/* If all signalers of the fence already signaled, just wake up all. */
if (ret < 0)
wake_up_all(&sync_file->wq);
}
return iif_fence_is_signaled(sync_file->fence) ? EPOLLIN : 0;
}
static int iif_sync_file_ioctl_get_information(struct iif_sync_file *sync_file,
struct iif_fence_get_information_ioctl __user *argp)
{
struct iif_fence *fence = sync_file->fence;
const struct iif_fence_get_information_ioctl ibuf = {
.signaler_ip = fence->signaler_ip,
.total_signalers = fence->total_signalers,
.submitted_signalers = iif_fence_submitted_signalers(fence),
.signaled_signalers = iif_fence_signaled_signalers(fence),
.outstanding_waiters = iif_fence_outstanding_waiters(fence),
};
if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
return -EFAULT;
return 0;
}
static long iif_sync_file_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct iif_sync_file *sync_file = file->private_data;
void __user *argp = (void __user *)arg;
switch (cmd) {
case IIF_FENCE_GET_INFORMATION:
return iif_sync_file_ioctl_get_information(sync_file, argp);
default:
return -ENOTTY;
}
}
static const struct file_operations iif_sync_file_fops = {
.release = iif_sync_file_release,
.poll = iif_sync_file_poll,
.unlocked_ioctl = iif_sync_file_ioctl,
};
struct iif_sync_file *iif_sync_file_create(struct iif_fence *fence)
{
struct iif_sync_file *sync_file;
int ret;
sync_file = kzalloc(sizeof(*sync_file), GFP_KERNEL);
if (!sync_file)
return ERR_PTR(-ENOMEM);
sync_file->file = anon_inode_getfile("iif_file", &iif_sync_file_fops, sync_file, 0);
if (IS_ERR(sync_file->file)) {
ret = PTR_ERR(sync_file->file);
goto err_free_sync_file;
}
sync_file->fence = iif_fence_get(fence);
atomic_inc(&fence->num_sync_file);
init_waitqueue_head(&sync_file->wq);
INIT_LIST_HEAD(&sync_file->poll_cb.node);
return sync_file;
err_free_sync_file:
kfree(sync_file);
return ERR_PTR(ret);
}
struct iif_sync_file *iif_sync_file_fdget(int fd)
{
struct file *file = fget(fd);
if (!file)
return ERR_PTR(-EBADF);
if (file->f_op != &iif_sync_file_fops) {
fput(file);
return ERR_PTR(-EINVAL);
}
return file->private_data;
}