1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
4 */
5
6 #include <linux/eventfd.h>
7 #include <linux/device/driver.h>
8 #include <linux/file.h>
9 #include <linux/fs.h>
10 #include <linux/gunyah.h>
11 #include <linux/module.h>
12 #include <linux/printk.h>
13
14 #include <uapi/linux/gunyah.h>
15
16 struct gunyah_ioeventfd {
17 struct gunyah_vm_function_instance *f;
18 struct gunyah_vm_io_handler io_handler;
19
20 struct eventfd_ctx *ctx;
21 };
22
gunyah_write_ioeventfd(struct gunyah_vm_io_handler * io_dev,u64 addr,u32 len,u64 data)23 static int gunyah_write_ioeventfd(struct gunyah_vm_io_handler *io_dev, u64 addr,
24 u32 len, u64 data)
25 {
26 struct gunyah_ioeventfd *iofd =
27 container_of(io_dev, struct gunyah_ioeventfd, io_handler);
28
29 eventfd_signal(iofd->ctx, 1);
30 return 0;
31 }
32
33 static struct gunyah_vm_io_handler_ops io_ops = {
34 .write = gunyah_write_ioeventfd,
35 };
36
gunyah_ioeventfd_bind(struct gunyah_vm_function_instance * f)37 static long gunyah_ioeventfd_bind(struct gunyah_vm_function_instance *f)
38 {
39 const struct gunyah_fn_ioeventfd_arg *args = f->argp;
40 struct gunyah_ioeventfd *iofd;
41 struct eventfd_ctx *ctx;
42 int ret;
43
44 if (f->arg_size != sizeof(*args))
45 return -EINVAL;
46
47 /* All other flag bits are reserved for future use */
48 if (args->flags & ~GUNYAH_IOEVENTFD_FLAGS_DATAMATCH)
49 return -EINVAL;
50
51 /* must be natural-word sized, or 0 to ignore length */
52 switch (args->len) {
53 case 0:
54 case 1:
55 case 2:
56 case 4:
57 case 8:
58 break;
59 default:
60 return -EINVAL;
61 }
62
63 /* check for range overflow */
64 if (overflows_type(args->addr + args->len, u64))
65 return -EINVAL;
66
67 /* ioeventfd with no length can't be combined with DATAMATCH */
68 if (!args->len && (args->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH))
69 return -EINVAL;
70
71 ctx = eventfd_ctx_fdget(args->fd);
72 if (IS_ERR(ctx))
73 return PTR_ERR(ctx);
74
75 iofd = kzalloc(sizeof(*iofd), GFP_KERNEL);
76 if (!iofd) {
77 ret = -ENOMEM;
78 goto err_eventfd;
79 }
80
81 f->data = iofd;
82 iofd->f = f;
83
84 iofd->ctx = ctx;
85
86 if (args->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH) {
87 iofd->io_handler.datamatch = true;
88 iofd->io_handler.len = args->len;
89 iofd->io_handler.data = args->datamatch;
90 }
91 iofd->io_handler.addr = args->addr;
92 iofd->io_handler.ops = &io_ops;
93
94 ret = gunyah_vm_add_io_handler(f->ghvm, &iofd->io_handler);
95 if (ret)
96 goto err_io_dev_add;
97
98 return 0;
99
100 err_io_dev_add:
101 kfree(iofd);
102 err_eventfd:
103 eventfd_ctx_put(ctx);
104 return ret;
105 }
106
gunyah_ioevent_unbind(struct gunyah_vm_function_instance * f)107 static void gunyah_ioevent_unbind(struct gunyah_vm_function_instance *f)
108 {
109 struct gunyah_ioeventfd *iofd = f->data;
110
111 gunyah_vm_remove_io_handler(iofd->f->ghvm, &iofd->io_handler);
112 eventfd_ctx_put(iofd->ctx);
113 kfree(iofd);
114 }
115
gunyah_ioevent_compare(const struct gunyah_vm_function_instance * f,const void * arg,size_t size)116 static bool gunyah_ioevent_compare(const struct gunyah_vm_function_instance *f,
117 const void *arg, size_t size)
118 {
119 const struct gunyah_fn_ioeventfd_arg *instance = f->argp, *other = arg;
120
121 if (sizeof(*other) != size)
122 return false;
123
124 if (instance->addr != other->addr || instance->len != other->len ||
125 instance->flags != other->flags)
126 return false;
127
128 if ((instance->flags & GUNYAH_IOEVENTFD_FLAGS_DATAMATCH) &&
129 instance->datamatch != other->datamatch)
130 return false;
131
132 return true;
133 }
134
135 DECLARE_GUNYAH_VM_FUNCTION_INIT(ioeventfd, GUNYAH_FN_IOEVENTFD, 3,
136 gunyah_ioeventfd_bind, gunyah_ioevent_unbind,
137 gunyah_ioevent_compare);
138 MODULE_DESCRIPTION("Gunyah ioeventfd VM Function");
139 MODULE_LICENSE("GPL");
140