xref: /openbmc/linux/drivers/virt/acrn/ioeventfd.c (revision a266ef69)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * ACRN HSM eventfd - use eventfd objects to signal expected I/O requests
4  *
5  * Copyright (C) 2020 Intel Corporation. All rights reserved.
6  *
7  * Authors:
8  *	Shuo Liu <shuo.a.liu@intel.com>
9  *	Yakui Zhao <yakui.zhao@intel.com>
10  */
11 
12 #include <linux/eventfd.h>
13 #include <linux/slab.h>
14 
15 #include "acrn_drv.h"
16 
17 /**
18  * struct hsm_ioeventfd - Properties of HSM ioeventfd
19  * @list:	Entry within &acrn_vm.ioeventfds of ioeventfds of a VM
20  * @eventfd:	Eventfd of the HSM ioeventfd
21  * @addr:	Address of I/O range
22  * @data:	Data for matching
23  * @length:	Length of I/O range
24  * @type:	Type of I/O range (ACRN_IOREQ_TYPE_MMIO/ACRN_IOREQ_TYPE_PORTIO)
25  * @wildcard:	Data matching or not
26  */
27 struct hsm_ioeventfd {
28 	struct list_head	list;
29 	struct eventfd_ctx	*eventfd;
30 	u64			addr;
31 	u64			data;
32 	int			length;
33 	int			type;
34 	bool			wildcard;
35 };
36 
37 static inline int ioreq_type_from_flags(int flags)
38 {
39 	return flags & ACRN_IOEVENTFD_FLAG_PIO ?
40 		       ACRN_IOREQ_TYPE_PORTIO : ACRN_IOREQ_TYPE_MMIO;
41 }
42 
43 static void acrn_ioeventfd_shutdown(struct acrn_vm *vm, struct hsm_ioeventfd *p)
44 {
45 	lockdep_assert_held(&vm->ioeventfds_lock);
46 
47 	eventfd_ctx_put(p->eventfd);
48 	list_del(&p->list);
49 	kfree(p);
50 }
51 
52 static bool hsm_ioeventfd_is_conflict(struct acrn_vm *vm,
53 				      struct hsm_ioeventfd *ioeventfd)
54 {
55 	struct hsm_ioeventfd *p;
56 
57 	lockdep_assert_held(&vm->ioeventfds_lock);
58 
59 	/* Either one is wildcard, the data matching will be skipped. */
60 	list_for_each_entry(p, &vm->ioeventfds, list)
61 		if (p->eventfd == ioeventfd->eventfd &&
62 		    p->addr == ioeventfd->addr &&
63 		    p->type == ioeventfd->type &&
64 		    (p->wildcard || ioeventfd->wildcard ||
65 			p->data == ioeventfd->data))
66 			return true;
67 
68 	return false;
69 }
70 
71 /*
72  * Assign an eventfd to a VM and create a HSM ioeventfd associated with the
73  * eventfd. The properties of the HSM ioeventfd are built from a &struct
74  * acrn_ioeventfd.
75  */
76 static int acrn_ioeventfd_assign(struct acrn_vm *vm,
77 				 struct acrn_ioeventfd *args)
78 {
79 	struct eventfd_ctx *eventfd;
80 	struct hsm_ioeventfd *p;
81 	int ret;
82 
83 	/* Check for range overflow */
84 	if (args->addr + args->len < args->addr)
85 		return -EINVAL;
86 
87 	/*
88 	 * Currently, acrn_ioeventfd is used to support vhost. 1,2,4,8 width
89 	 * accesses can cover vhost's requirements.
90 	 */
91 	if (!(args->len == 1 || args->len == 2 ||
92 	      args->len == 4 || args->len == 8))
93 		return -EINVAL;
94 
95 	eventfd = eventfd_ctx_fdget(args->fd);
96 	if (IS_ERR(eventfd))
97 		return PTR_ERR(eventfd);
98 
99 	p = kzalloc(sizeof(*p), GFP_KERNEL);
100 	if (!p) {
101 		ret = -ENOMEM;
102 		goto fail;
103 	}
104 
105 	INIT_LIST_HEAD(&p->list);
106 	p->addr = args->addr;
107 	p->length = args->len;
108 	p->eventfd = eventfd;
109 	p->type = ioreq_type_from_flags(args->flags);
110 
111 	/*
112 	 * ACRN_IOEVENTFD_FLAG_DATAMATCH flag is set in virtio 1.0 support, the
113 	 * writing of notification register of each virtqueue may trigger the
114 	 * notification. There is no data matching requirement.
115 	 */
116 	if (args->flags & ACRN_IOEVENTFD_FLAG_DATAMATCH)
117 		p->data = args->data;
118 	else
119 		p->wildcard = true;
120 
121 	mutex_lock(&vm->ioeventfds_lock);
122 
123 	if (hsm_ioeventfd_is_conflict(vm, p)) {
124 		ret = -EEXIST;
125 		goto unlock_fail;
126 	}
127 
128 	/* register the I/O range into ioreq client */
129 	ret = acrn_ioreq_range_add(vm->ioeventfd_client, p->type,
130 				   p->addr, p->addr + p->length - 1);
131 	if (ret < 0)
132 		goto unlock_fail;
133 
134 	list_add_tail(&p->list, &vm->ioeventfds);
135 	mutex_unlock(&vm->ioeventfds_lock);
136 
137 	return 0;
138 
139 unlock_fail:
140 	mutex_unlock(&vm->ioeventfds_lock);
141 	kfree(p);
142 fail:
143 	eventfd_ctx_put(eventfd);
144 	return ret;
145 }
146 
147 static int acrn_ioeventfd_deassign(struct acrn_vm *vm,
148 				   struct acrn_ioeventfd *args)
149 {
150 	struct hsm_ioeventfd *p;
151 	struct eventfd_ctx *eventfd;
152 
153 	eventfd = eventfd_ctx_fdget(args->fd);
154 	if (IS_ERR(eventfd))
155 		return PTR_ERR(eventfd);
156 
157 	mutex_lock(&vm->ioeventfds_lock);
158 	list_for_each_entry(p, &vm->ioeventfds, list) {
159 		if (p->eventfd != eventfd)
160 			continue;
161 
162 		acrn_ioreq_range_del(vm->ioeventfd_client, p->type,
163 				     p->addr, p->addr + p->length - 1);
164 		acrn_ioeventfd_shutdown(vm, p);
165 		break;
166 	}
167 	mutex_unlock(&vm->ioeventfds_lock);
168 
169 	eventfd_ctx_put(eventfd);
170 	return 0;
171 }
172 
173 static struct hsm_ioeventfd *hsm_ioeventfd_match(struct acrn_vm *vm, u64 addr,
174 						 u64 data, int len, int type)
175 {
176 	struct hsm_ioeventfd *p = NULL;
177 
178 	lockdep_assert_held(&vm->ioeventfds_lock);
179 
180 	list_for_each_entry(p, &vm->ioeventfds, list) {
181 		if (p->type == type && p->addr == addr && p->length >= len &&
182 		    (p->wildcard || p->data == data))
183 			return p;
184 	}
185 
186 	return NULL;
187 }
188 
189 static int acrn_ioeventfd_handler(struct acrn_ioreq_client *client,
190 				  struct acrn_io_request *req)
191 {
192 	struct hsm_ioeventfd *p;
193 	u64 addr, val;
194 	int size;
195 
196 	if (req->type == ACRN_IOREQ_TYPE_MMIO) {
197 		/*
198 		 * I/O requests are dispatched by range check only, so a
199 		 * acrn_ioreq_client need process both READ and WRITE accesses
200 		 * of same range. READ accesses are safe to be ignored here
201 		 * because virtio PCI devices write the notify registers for
202 		 * notification.
203 		 */
204 		if (req->reqs.mmio_request.direction == ACRN_IOREQ_DIR_READ) {
205 			/* reading does nothing and return 0 */
206 			req->reqs.mmio_request.value = 0;
207 			return 0;
208 		}
209 		addr = req->reqs.mmio_request.address;
210 		size = req->reqs.mmio_request.size;
211 		val = req->reqs.mmio_request.value;
212 	} else {
213 		if (req->reqs.pio_request.direction == ACRN_IOREQ_DIR_READ) {
214 			/* reading does nothing and return 0 */
215 			req->reqs.pio_request.value = 0;
216 			return 0;
217 		}
218 		addr = req->reqs.pio_request.address;
219 		size = req->reqs.pio_request.size;
220 		val = req->reqs.pio_request.value;
221 	}
222 
223 	mutex_lock(&client->vm->ioeventfds_lock);
224 	p = hsm_ioeventfd_match(client->vm, addr, val, size, req->type);
225 	if (p)
226 		eventfd_signal(p->eventfd, 1);
227 	mutex_unlock(&client->vm->ioeventfds_lock);
228 
229 	return 0;
230 }
231 
232 int acrn_ioeventfd_config(struct acrn_vm *vm, struct acrn_ioeventfd *args)
233 {
234 	int ret;
235 
236 	if (args->flags & ACRN_IOEVENTFD_FLAG_DEASSIGN)
237 		ret = acrn_ioeventfd_deassign(vm, args);
238 	else
239 		ret = acrn_ioeventfd_assign(vm, args);
240 
241 	return ret;
242 }
243 
244 int acrn_ioeventfd_init(struct acrn_vm *vm)
245 {
246 	char name[ACRN_NAME_LEN];
247 
248 	mutex_init(&vm->ioeventfds_lock);
249 	INIT_LIST_HEAD(&vm->ioeventfds);
250 	snprintf(name, sizeof(name), "ioeventfd-%u", vm->vmid);
251 	vm->ioeventfd_client = acrn_ioreq_client_create(vm,
252 							acrn_ioeventfd_handler,
253 							NULL, false, name);
254 	if (!vm->ioeventfd_client) {
255 		dev_err(acrn_dev.this_device, "Failed to create ioeventfd ioreq client!\n");
256 		return -EINVAL;
257 	}
258 
259 	dev_dbg(acrn_dev.this_device, "VM %u ioeventfd init.\n", vm->vmid);
260 	return 0;
261 }
262 
263 void acrn_ioeventfd_deinit(struct acrn_vm *vm)
264 {
265 	struct hsm_ioeventfd *p, *next;
266 
267 	dev_dbg(acrn_dev.this_device, "VM %u ioeventfd deinit.\n", vm->vmid);
268 	acrn_ioreq_client_destroy(vm->ioeventfd_client);
269 	mutex_lock(&vm->ioeventfds_lock);
270 	list_for_each_entry_safe(p, next, &vm->ioeventfds, list)
271 		acrn_ioeventfd_shutdown(vm, p);
272 	mutex_unlock(&vm->ioeventfds_lock);
273 }
274