• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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