mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-11-30 06:36:44 +07:00
133890103b
Change the eventfd interface to de-couple the eventfd memory context, from the file pointer instance. Without such change, there is no clean way to racely free handle the POLLHUP event sent when the last instance of the file* goes away. Also, now the internal eventfd APIs are using the eventfd context instead of the file*. This patch is required by KVM's IRQfd code, which is still under development. Signed-off-by: Davide Libenzi <davidel@xmailserver.org> Cc: Gregory Haskins <ghaskins@novell.com> Cc: Rusty Russell <rusty@rustcorp.com.au> Cc: Benjamin LaHaise <bcrl@kvack.org> Cc: Avi Kivity <avi@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
338 lines
8.1 KiB
C
338 lines
8.1 KiB
C
/*
|
|
* fs/eventfd.c
|
|
*
|
|
* Copyright (C) 2007 Davide Libenzi <davidel@xmailserver.org>
|
|
*
|
|
*/
|
|
|
|
#include <linux/file.h>
|
|
#include <linux/poll.h>
|
|
#include <linux/init.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/list.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/anon_inodes.h>
|
|
#include <linux/syscalls.h>
|
|
#include <linux/module.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/eventfd.h>
|
|
|
|
struct eventfd_ctx {
|
|
struct kref kref;
|
|
wait_queue_head_t wqh;
|
|
/*
|
|
* Every time that a write(2) is performed on an eventfd, the
|
|
* value of the __u64 being written is added to "count" and a
|
|
* wakeup is performed on "wqh". A read(2) will return the "count"
|
|
* value to userspace, and will reset "count" to zero. The kernel
|
|
* side eventfd_signal() also, adds to the "count" counter and
|
|
* issue a wakeup.
|
|
*/
|
|
__u64 count;
|
|
unsigned int flags;
|
|
};
|
|
|
|
/**
|
|
* eventfd_signal - Adds @n to the eventfd counter.
|
|
* @ctx: [in] Pointer to the eventfd context.
|
|
* @n: [in] Value of the counter to be added to the eventfd internal counter.
|
|
* The value cannot be negative.
|
|
*
|
|
* This function is supposed to be called by the kernel in paths that do not
|
|
* allow sleeping. In this function we allow the counter to reach the ULLONG_MAX
|
|
* value, and we signal this as overflow condition by returining a POLLERR
|
|
* to poll(2).
|
|
*
|
|
* Returns @n in case of success, a non-negative number lower than @n in case
|
|
* of overflow, or the following error codes:
|
|
*
|
|
* -EINVAL : The value of @n is negative.
|
|
*/
|
|
int eventfd_signal(struct eventfd_ctx *ctx, int n)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (n < 0)
|
|
return -EINVAL;
|
|
spin_lock_irqsave(&ctx->wqh.lock, flags);
|
|
if (ULLONG_MAX - ctx->count < n)
|
|
n = (int) (ULLONG_MAX - ctx->count);
|
|
ctx->count += n;
|
|
if (waitqueue_active(&ctx->wqh))
|
|
wake_up_locked_poll(&ctx->wqh, POLLIN);
|
|
spin_unlock_irqrestore(&ctx->wqh.lock, flags);
|
|
|
|
return n;
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_signal);
|
|
|
|
static void eventfd_free(struct kref *kref)
|
|
{
|
|
struct eventfd_ctx *ctx = container_of(kref, struct eventfd_ctx, kref);
|
|
|
|
kfree(ctx);
|
|
}
|
|
|
|
/**
|
|
* eventfd_ctx_get - Acquires a reference to the internal eventfd context.
|
|
* @ctx: [in] Pointer to the eventfd context.
|
|
*
|
|
* Returns: In case of success, returns a pointer to the eventfd context.
|
|
*/
|
|
struct eventfd_ctx *eventfd_ctx_get(struct eventfd_ctx *ctx)
|
|
{
|
|
kref_get(&ctx->kref);
|
|
return ctx;
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_ctx_get);
|
|
|
|
/**
|
|
* eventfd_ctx_put - Releases a reference to the internal eventfd context.
|
|
* @ctx: [in] Pointer to eventfd context.
|
|
*
|
|
* The eventfd context reference must have been previously acquired either
|
|
* with eventfd_ctx_get() or eventfd_ctx_fdget()).
|
|
*/
|
|
void eventfd_ctx_put(struct eventfd_ctx *ctx)
|
|
{
|
|
kref_put(&ctx->kref, eventfd_free);
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_ctx_put);
|
|
|
|
static int eventfd_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct eventfd_ctx *ctx = file->private_data;
|
|
|
|
wake_up_poll(&ctx->wqh, POLLHUP);
|
|
eventfd_ctx_put(ctx);
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int eventfd_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct eventfd_ctx *ctx = file->private_data;
|
|
unsigned int events = 0;
|
|
unsigned long flags;
|
|
|
|
poll_wait(file, &ctx->wqh, wait);
|
|
|
|
spin_lock_irqsave(&ctx->wqh.lock, flags);
|
|
if (ctx->count > 0)
|
|
events |= POLLIN;
|
|
if (ctx->count == ULLONG_MAX)
|
|
events |= POLLERR;
|
|
if (ULLONG_MAX - 1 > ctx->count)
|
|
events |= POLLOUT;
|
|
spin_unlock_irqrestore(&ctx->wqh.lock, flags);
|
|
|
|
return events;
|
|
}
|
|
|
|
static ssize_t eventfd_read(struct file *file, char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
struct eventfd_ctx *ctx = file->private_data;
|
|
ssize_t res;
|
|
__u64 ucnt = 0;
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
if (count < sizeof(ucnt))
|
|
return -EINVAL;
|
|
spin_lock_irq(&ctx->wqh.lock);
|
|
res = -EAGAIN;
|
|
if (ctx->count > 0)
|
|
res = sizeof(ucnt);
|
|
else if (!(file->f_flags & O_NONBLOCK)) {
|
|
__add_wait_queue(&ctx->wqh, &wait);
|
|
for (res = 0;;) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (ctx->count > 0) {
|
|
res = sizeof(ucnt);
|
|
break;
|
|
}
|
|
if (signal_pending(current)) {
|
|
res = -ERESTARTSYS;
|
|
break;
|
|
}
|
|
spin_unlock_irq(&ctx->wqh.lock);
|
|
schedule();
|
|
spin_lock_irq(&ctx->wqh.lock);
|
|
}
|
|
__remove_wait_queue(&ctx->wqh, &wait);
|
|
__set_current_state(TASK_RUNNING);
|
|
}
|
|
if (likely(res > 0)) {
|
|
ucnt = (ctx->flags & EFD_SEMAPHORE) ? 1 : ctx->count;
|
|
ctx->count -= ucnt;
|
|
if (waitqueue_active(&ctx->wqh))
|
|
wake_up_locked_poll(&ctx->wqh, POLLOUT);
|
|
}
|
|
spin_unlock_irq(&ctx->wqh.lock);
|
|
if (res > 0 && put_user(ucnt, (__u64 __user *) buf))
|
|
return -EFAULT;
|
|
|
|
return res;
|
|
}
|
|
|
|
static ssize_t eventfd_write(struct file *file, const char __user *buf, size_t count,
|
|
loff_t *ppos)
|
|
{
|
|
struct eventfd_ctx *ctx = file->private_data;
|
|
ssize_t res;
|
|
__u64 ucnt;
|
|
DECLARE_WAITQUEUE(wait, current);
|
|
|
|
if (count < sizeof(ucnt))
|
|
return -EINVAL;
|
|
if (copy_from_user(&ucnt, buf, sizeof(ucnt)))
|
|
return -EFAULT;
|
|
if (ucnt == ULLONG_MAX)
|
|
return -EINVAL;
|
|
spin_lock_irq(&ctx->wqh.lock);
|
|
res = -EAGAIN;
|
|
if (ULLONG_MAX - ctx->count > ucnt)
|
|
res = sizeof(ucnt);
|
|
else if (!(file->f_flags & O_NONBLOCK)) {
|
|
__add_wait_queue(&ctx->wqh, &wait);
|
|
for (res = 0;;) {
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
if (ULLONG_MAX - ctx->count > ucnt) {
|
|
res = sizeof(ucnt);
|
|
break;
|
|
}
|
|
if (signal_pending(current)) {
|
|
res = -ERESTARTSYS;
|
|
break;
|
|
}
|
|
spin_unlock_irq(&ctx->wqh.lock);
|
|
schedule();
|
|
spin_lock_irq(&ctx->wqh.lock);
|
|
}
|
|
__remove_wait_queue(&ctx->wqh, &wait);
|
|
__set_current_state(TASK_RUNNING);
|
|
}
|
|
if (likely(res > 0)) {
|
|
ctx->count += ucnt;
|
|
if (waitqueue_active(&ctx->wqh))
|
|
wake_up_locked_poll(&ctx->wqh, POLLIN);
|
|
}
|
|
spin_unlock_irq(&ctx->wqh.lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static const struct file_operations eventfd_fops = {
|
|
.release = eventfd_release,
|
|
.poll = eventfd_poll,
|
|
.read = eventfd_read,
|
|
.write = eventfd_write,
|
|
};
|
|
|
|
/**
|
|
* eventfd_fget - Acquire a reference of an eventfd file descriptor.
|
|
* @fd: [in] Eventfd file descriptor.
|
|
*
|
|
* Returns a pointer to the eventfd file structure in case of success, or the
|
|
* following error pointer:
|
|
*
|
|
* -EBADF : Invalid @fd file descriptor.
|
|
* -EINVAL : The @fd file descriptor is not an eventfd file.
|
|
*/
|
|
struct file *eventfd_fget(int fd)
|
|
{
|
|
struct file *file;
|
|
|
|
file = fget(fd);
|
|
if (!file)
|
|
return ERR_PTR(-EBADF);
|
|
if (file->f_op != &eventfd_fops) {
|
|
fput(file);
|
|
return ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
return file;
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_fget);
|
|
|
|
/**
|
|
* eventfd_ctx_fdget - Acquires a reference to the internal eventfd context.
|
|
* @fd: [in] Eventfd file descriptor.
|
|
*
|
|
* Returns a pointer to the internal eventfd context, otherwise the error
|
|
* pointers returned by the following functions:
|
|
*
|
|
* eventfd_fget
|
|
*/
|
|
struct eventfd_ctx *eventfd_ctx_fdget(int fd)
|
|
{
|
|
struct file *file;
|
|
struct eventfd_ctx *ctx;
|
|
|
|
file = eventfd_fget(fd);
|
|
if (IS_ERR(file))
|
|
return (struct eventfd_ctx *) file;
|
|
ctx = eventfd_ctx_get(file->private_data);
|
|
fput(file);
|
|
|
|
return ctx;
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_ctx_fdget);
|
|
|
|
/**
|
|
* eventfd_ctx_fileget - Acquires a reference to the internal eventfd context.
|
|
* @file: [in] Eventfd file pointer.
|
|
*
|
|
* Returns a pointer to the internal eventfd context, otherwise the error
|
|
* pointer:
|
|
*
|
|
* -EINVAL : The @fd file descriptor is not an eventfd file.
|
|
*/
|
|
struct eventfd_ctx *eventfd_ctx_fileget(struct file *file)
|
|
{
|
|
if (file->f_op != &eventfd_fops)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
return eventfd_ctx_get(file->private_data);
|
|
}
|
|
EXPORT_SYMBOL_GPL(eventfd_ctx_fileget);
|
|
|
|
SYSCALL_DEFINE2(eventfd2, unsigned int, count, int, flags)
|
|
{
|
|
int fd;
|
|
struct eventfd_ctx *ctx;
|
|
|
|
/* Check the EFD_* constants for consistency. */
|
|
BUILD_BUG_ON(EFD_CLOEXEC != O_CLOEXEC);
|
|
BUILD_BUG_ON(EFD_NONBLOCK != O_NONBLOCK);
|
|
|
|
if (flags & ~EFD_FLAGS_SET)
|
|
return -EINVAL;
|
|
|
|
ctx = kmalloc(sizeof(*ctx), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
kref_init(&ctx->kref);
|
|
init_waitqueue_head(&ctx->wqh);
|
|
ctx->count = count;
|
|
ctx->flags = flags;
|
|
|
|
/*
|
|
* When we call this, the initialization must be complete, since
|
|
* anon_inode_getfd() will install the fd.
|
|
*/
|
|
fd = anon_inode_getfd("[eventfd]", &eventfd_fops, ctx,
|
|
flags & EFD_SHARED_FCNTL_FLAGS);
|
|
if (fd < 0)
|
|
kfree(ctx);
|
|
return fd;
|
|
}
|
|
|
|
SYSCALL_DEFINE1(eventfd, unsigned int, count)
|
|
{
|
|
return sys_eventfd2(count, 0);
|
|
}
|
|
|