140 lines
3.2 KiB
C
Executable File
140 lines
3.2 KiB
C
Executable File
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/eventfd.h>
|
|
#include <linux/device/driver.h>
|
|
#include <linux/file.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/gunyah.h>
|
|
#include <linux/module.h>
|
|
#include <linux/printk.h>
|
|
|
|
#include <uapi/linux/gunyah.h>
|
|
|
|
struct gunyah_ioeventfd {
|
|
struct gunyah_vm_function_instance *f;
|
|
struct gunyah_vm_io_handler io_handler;
|
|
|
|
struct eventfd_ctx *ctx;
|
|
};
|
|
|
|
static int gunyah_write_ioeventfd(struct gunyah_vm_io_handler *io_dev, u64 addr,
|
|
u32 len, u64 data)
|
|
{
|
|
struct gunyah_ioeventfd *iofd =
|
|
container_of(io_dev, struct gunyah_ioeventfd, io_handler);
|
|
|
|
eventfd_signal(iofd->ctx, 1);
|
|
return 0;
|
|
}
|
|
|
|
static struct gunyah_vm_io_handler_ops io_ops = {
|
|
.write = gunyah_write_ioeventfd,
|
|
};
|
|
|
|
static long gunyah_ioeventfd_bind(struct gunyah_vm_function_instance *f)
|
|
{
|
|
const struct gunyah_fn_ioeventfd_arg *args = f->argp;
|
|
struct gunyah_ioeventfd *iofd;
|
|
struct eventfd_ctx *ctx;
|
|
int ret;
|
|
|
|
if (f->arg_size != sizeof(*args))
|
|
return -EINVAL;
|
|
|
|
/* All other flag bits are reserved for future use */
|
|
if (args->flags & ~GUNYAH_IOEVENTFD_FLAGS_DATAMATCH)
|
|
return -EINVAL;
|
|
|
|
/* must be natural-word sized, or 0 to ignore length */
|
|
switch (args->len) {
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 4:
|
|
case 8:
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* check for range overflow */
|
|
if (overflows_type(args->addr + args->len, u64))
|
|
return -EINVAL;
|
|
|
|
/* ioeventfd with no length can't be combined with DATAMATCH */
|
|
if (!args->len && (args->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH))
|
|
return -EINVAL;
|
|
|
|
ctx = eventfd_ctx_fdget(args->fd);
|
|
if (IS_ERR(ctx))
|
|
return PTR_ERR(ctx);
|
|
|
|
iofd = kzalloc(sizeof(*iofd), GFP_KERNEL);
|
|
if (!iofd) {
|
|
ret = -ENOMEM;
|
|
goto err_eventfd;
|
|
}
|
|
|
|
f->data = iofd;
|
|
iofd->f = f;
|
|
|
|
iofd->ctx = ctx;
|
|
|
|
if (args->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH) {
|
|
iofd->io_handler.datamatch = true;
|
|
iofd->io_handler.len = args->len;
|
|
iofd->io_handler.data = args->datamatch;
|
|
}
|
|
iofd->io_handler.addr = args->addr;
|
|
iofd->io_handler.ops = &io_ops;
|
|
|
|
ret = gunyah_vm_add_io_handler(f->ghvm, &iofd->io_handler);
|
|
if (ret)
|
|
goto err_io_dev_add;
|
|
|
|
return 0;
|
|
|
|
err_io_dev_add:
|
|
kfree(iofd);
|
|
err_eventfd:
|
|
eventfd_ctx_put(ctx);
|
|
return ret;
|
|
}
|
|
|
|
static void gunyah_ioevent_unbind(struct gunyah_vm_function_instance *f)
|
|
{
|
|
struct gunyah_ioeventfd *iofd = f->data;
|
|
|
|
gunyah_vm_remove_io_handler(iofd->f->ghvm, &iofd->io_handler);
|
|
eventfd_ctx_put(iofd->ctx);
|
|
kfree(iofd);
|
|
}
|
|
|
|
static bool gunyah_ioevent_compare(const struct gunyah_vm_function_instance *f,
|
|
const void *arg, size_t size)
|
|
{
|
|
const struct gunyah_fn_ioeventfd_arg *instance = f->argp, *other = arg;
|
|
|
|
if (sizeof(*other) != size)
|
|
return false;
|
|
|
|
if (instance->addr != other->addr || instance->len != other->len ||
|
|
instance->flags != other->flags)
|
|
return false;
|
|
|
|
if ((instance->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH) &&
|
|
instance->datamatch != other->datamatch)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
DECLARE_GUNYAH_VM_FUNCTION_INIT(ioeventfd, GUNYAH_FN_IOEVENTFD, 3,
|
|
gunyah_ioeventfd_bind, gunyah_ioevent_unbind,
|
|
gunyah_ioevent_compare);
|
|
MODULE_DESCRIPTION("Gunyah ioeventfd VM Function");
|
|
MODULE_LICENSE("GPL");
|