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/poll.h>
13 #include <linux/printk.h>
14
15 #include <uapi/linux/gunyah.h>
16
17 struct gunyah_irqfd {
18 struct gunyah_resource *ghrsc;
19 struct gunyah_vm_resource_ticket ticket;
20 struct gunyah_vm_function_instance *f;
21
22 bool level;
23
24 struct eventfd_ctx *ctx;
25 wait_queue_entry_t wait;
26 poll_table pt;
27 };
28
irqfd_wakeup(wait_queue_entry_t * wait,unsigned int mode,int sync,void * key)29 static int irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode, int sync,
30 void *key)
31 {
32 struct gunyah_irqfd *irqfd =
33 container_of(wait, struct gunyah_irqfd, wait);
34 __poll_t flags = key_to_poll(key);
35 int ret = 0;
36
37 if (flags & EPOLLIN) {
38 if (irqfd->ghrsc) {
39 ret = gunyah_hypercall_bell_send(irqfd->ghrsc->capid, 1,
40 NULL);
41 if (ret)
42 pr_err_ratelimited(
43 "Failed to inject interrupt %d: %d\n",
44 irqfd->ticket.label, ret);
45 } else
46 pr_err_ratelimited(
47 "Premature injection of interrupt\n");
48 }
49
50 return 0;
51 }
52
irqfd_ptable_queue_proc(struct file * file,wait_queue_head_t * wqh,poll_table * pt)53 static void irqfd_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh,
54 poll_table *pt)
55 {
56 struct gunyah_irqfd *irq_ctx =
57 container_of(pt, struct gunyah_irqfd, pt);
58
59 add_wait_queue(wqh, &irq_ctx->wait);
60 }
61
gunyah_irqfd_populate(struct gunyah_vm_resource_ticket * ticket,struct gunyah_resource * ghrsc)62 static bool gunyah_irqfd_populate(struct gunyah_vm_resource_ticket *ticket,
63 struct gunyah_resource *ghrsc)
64 {
65 struct gunyah_irqfd *irqfd =
66 container_of(ticket, struct gunyah_irqfd, ticket);
67 int ret;
68
69 if (irqfd->ghrsc) {
70 pr_warn("irqfd%d already got a Gunyah resource. Check if multiple resources with same label were configured.\n",
71 irqfd->ticket.label);
72 return false;
73 }
74
75 irqfd->ghrsc = ghrsc;
76 if (irqfd->level) {
77 /* Configure the bell to trigger when bit 0 is asserted (see
78 * irq_wakeup) and for bell to automatically clear bit 0 once
79 * received by the VM (ack_mask). need to make sure bit 0 is cleared right away,
80 * otherwise the line will never be deasserted. Emulating edge
81 * trigger interrupt does not need to set either mask
82 * because irq is listed only once per gunyah_hypercall_bell_send
83 */
84 ret = gunyah_hypercall_bell_set_mask(irqfd->ghrsc->capid, 1, 1);
85 if (ret)
86 pr_warn("irq %d couldn't be set as level triggered. Might cause IRQ storm if asserted\n",
87 irqfd->ticket.label);
88 }
89
90 return true;
91 }
92
gunyah_irqfd_unpopulate(struct gunyah_vm_resource_ticket * ticket,struct gunyah_resource * ghrsc)93 static void gunyah_irqfd_unpopulate(struct gunyah_vm_resource_ticket *ticket,
94 struct gunyah_resource *ghrsc)
95 {
96 }
97
gunyah_irqfd_bind(struct gunyah_vm_function_instance * f)98 static long gunyah_irqfd_bind(struct gunyah_vm_function_instance *f)
99 {
100 struct gunyah_fn_irqfd_arg *args = f->argp;
101 struct gunyah_irqfd *irqfd;
102 __poll_t events;
103 struct fd fd;
104 long r;
105
106 if (f->arg_size != sizeof(*args))
107 return -EINVAL;
108
109 /* All other flag bits are reserved for future use */
110 if (args->flags & ~GUNYAH_IRQFD_FLAGS_LEVEL)
111 return -EINVAL;
112
113 irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
114 if (!irqfd)
115 return -ENOMEM;
116
117 irqfd->f = f;
118 f->data = irqfd;
119
120 fd = fdget(args->fd);
121 if (!fd_file(fd)) {
122 kfree(irqfd);
123 return -EBADF;
124 }
125
126 irqfd->ctx = eventfd_ctx_fileget(fd_file(fd));
127 if (IS_ERR(irqfd->ctx)) {
128 r = PTR_ERR(irqfd->ctx);
129 goto err_fdput;
130 }
131
132 if (args->flags & GUNYAH_IRQFD_FLAGS_LEVEL)
133 irqfd->level = true;
134
135 init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
136 init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);
137
138 irqfd->ticket.resource_type = GUNYAH_RESOURCE_TYPE_BELL_TX;
139 irqfd->ticket.label = args->label;
140 irqfd->ticket.owner = THIS_MODULE;
141 irqfd->ticket.populate = gunyah_irqfd_populate;
142 irqfd->ticket.unpopulate = gunyah_irqfd_unpopulate;
143
144 r = gunyah_vm_add_resource_ticket(f->ghvm, &irqfd->ticket);
145 if (r)
146 goto err_ctx;
147
148 events = vfs_poll(fd_file(fd), &irqfd->pt);
149 if (events & EPOLLIN)
150 pr_warn("Premature injection of interrupt\n");
151 fdput(fd);
152
153 return 0;
154 err_ctx:
155 eventfd_ctx_put(irqfd->ctx);
156 err_fdput:
157 fdput(fd);
158 kfree(irqfd);
159 return r;
160 }
161
gunyah_irqfd_unbind(struct gunyah_vm_function_instance * f)162 static void gunyah_irqfd_unbind(struct gunyah_vm_function_instance *f)
163 {
164 struct gunyah_irqfd *irqfd = f->data;
165 u64 cnt;
166
167 eventfd_ctx_remove_wait_queue(irqfd->ctx, &irqfd->wait, &cnt);
168 gunyah_vm_remove_resource_ticket(irqfd->f->ghvm, &irqfd->ticket);
169 eventfd_ctx_put(irqfd->ctx);
170 kfree(irqfd);
171 }
172
gunyah_irqfd_compare(const struct gunyah_vm_function_instance * f,const void * arg,size_t size)173 static bool gunyah_irqfd_compare(const struct gunyah_vm_function_instance *f,
174 const void *arg, size_t size)
175 {
176 const struct gunyah_fn_irqfd_arg *instance = f->argp, *other = arg;
177
178 if (sizeof(*other) != size)
179 return false;
180
181 return instance->label == other->label;
182 }
183
184 DECLARE_GUNYAH_VM_FUNCTION_INIT(irqfd, GUNYAH_FN_IRQFD, 2, gunyah_irqfd_bind,
185 gunyah_irqfd_unbind, gunyah_irqfd_compare);
186 MODULE_DESCRIPTION("Gunyah irqfd VM Function");
187 MODULE_LICENSE("GPL");
188