master
c 463 lines 12.8 KB
Raw
1 /*
2 * Inter-VM Shared Memory Flat Device
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 * Copyright (c) 2023 Linaro Ltd.
6 * Authors:
7 * Gustavo Romero
8 *
9 */
10
11 #include "qemu/osdep.h"
12 #include "qemu/units.h"
13 #include "qemu/error-report.h"
14 #include "qemu/module.h"
15 #include "qapi/error.h"
16 #include "hw/core/irq.h"
17 #include "hw/core/qdev-properties-system.h"
18 #include "hw/core/sysbus.h"
19 #include "chardev/char-fe.h"
20 #include "system/address-spaces.h"
21 #include "trace.h"
22
23 #include "hw/misc/ivshmem-flat.h"
24
25 static int64_t ivshmem_flat_recv_msg(IvshmemFTState *s, int *pfd)
26 {
27 int64_t msg;
28 int n, ret;
29
30 n = 0;
31 do {
32 ret = qemu_chr_fe_read_all(&s->server_chr, (uint8_t *)&msg + n,
33 sizeof(msg) - n);
34 if (ret < 0) {
35 if (ret == -EINTR) {
36 continue;
37 }
38 exit(1);
39 }
40 n += ret;
41 } while (n < sizeof(msg));
42
43 if (pfd) {
44 *pfd = qemu_chr_fe_get_msgfd(&s->server_chr);
45 }
46 return le64_to_cpu(msg);
47 }
48
49 static void ivshmem_flat_irq_handler(void *opaque)
50 {
51 VectorInfo *vi = opaque;
52 EventNotifier *e = &vi->event_notifier;
53 uint16_t vector_id;
54 const VectorInfo (*v)[64];
55
56 assert(e->initialized);
57
58 vector_id = vi->id;
59
60 /*
61 * The vector info struct is passed to the handler via the 'opaque' pointer.
62 * This struct pointer allows the retrieval of the vector ID and its
63 * associated event notifier. However, for triggering an interrupt using
64 * qemu_set_irq, it's necessary to also have a pointer to the device state,
65 * i.e., a pointer to the IvshmemFTState struct. Since the vector info
66 * struct is contained within the IvshmemFTState struct, its pointer can be
67 * used to obtain the pointer to IvshmemFTState through simple pointer math.
68 */
69 v = (void *)(vi - vector_id); /* v = &IvshmemPeer->vector[0] */
70 IvshmemPeer *own_peer = container_of(v, IvshmemPeer, vector);
71 IvshmemFTState *s = container_of(own_peer, IvshmemFTState, own);
72
73 /* Clear event */
74 if (!event_notifier_test_and_clear(e)) {
75 return;
76 }
77
78 trace_ivshmem_flat_irq_handler(vector_id);
79
80 /*
81 * Toggle device's output line, which is connected to interrupt controller,
82 * generating an interrupt request to the CPU.
83 */
84 qemu_irq_pulse(s->irq);
85 }
86
87 static IvshmemPeer *ivshmem_flat_find_peer(IvshmemFTState *s, uint16_t peer_id)
88 {
89 IvshmemPeer *peer;
90
91 /* Own ID */
92 if (s->own.id == peer_id) {
93 return &s->own;
94 }
95
96 /* Peer ID */
97 QTAILQ_FOREACH(peer, &s->peer, next) {
98 if (peer->id == peer_id) {
99 return peer;
100 }
101 }
102
103 return NULL;
104 }
105
106 static IvshmemPeer *ivshmem_flat_add_peer(IvshmemFTState *s, uint16_t peer_id)
107 {
108 IvshmemPeer *new_peer;
109
110 new_peer = g_malloc0(sizeof(*new_peer));
111 new_peer->id = peer_id;
112 new_peer->vector_counter = 0;
113
114 QTAILQ_INSERT_TAIL(&s->peer, new_peer, next);
115
116 trace_ivshmem_flat_new_peer(peer_id);
117
118 return new_peer;
119 }
120
121 static void ivshmem_flat_remove_peer(IvshmemFTState *s, uint16_t peer_id)
122 {
123 IvshmemPeer *peer;
124
125 peer = ivshmem_flat_find_peer(s, peer_id);
126 assert(peer);
127
128 QTAILQ_REMOVE(&s->peer, peer, next);
129 for (int n = 0; n < peer->vector_counter; n++) {
130 int efd;
131 efd = event_notifier_get_fd(&(peer->vector[n].event_notifier));
132 close(efd);
133 }
134
135 g_free(peer);
136 }
137
138 static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer,
139 int vector_fd)
140 {
141 Error *err = NULL;
142
143 if (peer->vector_counter >= IVSHMEM_MAX_VECTOR_NUM) {
144 trace_ivshmem_flat_add_vector_failure(peer->vector_counter,
145 vector_fd, peer->id);
146 close(vector_fd);
147
148 return;
149 }
150
151 trace_ivshmem_flat_add_vector_success(peer->vector_counter,
152 vector_fd, peer->id);
153
154 /*
155 * Set vector ID and its associated eventfd notifier and add them to the
156 * peer.
157 */
158 peer->vector[peer->vector_counter].id = peer->vector_counter;
159 if (!qemu_set_blocking(vector_fd, false, &err)) {
160 /* FIXME handle the error */
161 warn_report_err(err);
162 }
163 event_notifier_init_fd(&peer->vector[peer->vector_counter].event_notifier,
164 vector_fd);
165
166 /*
167 * If it's the device's own ID, register also the handler for the eventfd
168 * so the device can be notified by the other peers.
169 */
170 if (peer == &s->own) {
171 qemu_set_fd_handler(vector_fd, ivshmem_flat_irq_handler, NULL,
172 &peer->vector);
173 }
174
175 peer->vector_counter++;
176 }
177
178 static void ivshmem_flat_process_msg(IvshmemFTState *s, uint64_t msg, int fd)
179 {
180 uint16_t peer_id;
181 IvshmemPeer *peer;
182
183 peer_id = msg & 0xFFFF;
184 peer = ivshmem_flat_find_peer(s, peer_id);
185
186 if (!peer) {
187 peer = ivshmem_flat_add_peer(s, peer_id);
188 }
189
190 if (fd >= 0) {
191 ivshmem_flat_add_vector(s, peer, fd);
192 } else { /* fd == -1, which is received when peers disconnect. */
193 ivshmem_flat_remove_peer(s, peer_id);
194 }
195 }
196
197 static int ivshmem_flat_can_receive_data(void *opaque)
198 {
199 IvshmemFTState *s = opaque;
200
201 assert(s->msg_buffered_bytes < sizeof(s->msg_buf));
202 return sizeof(s->msg_buf) - s->msg_buffered_bytes;
203 }
204
205 static void ivshmem_flat_read_msg(void *opaque, const uint8_t *buf, int size)
206 {
207 IvshmemFTState *s = opaque;
208 int fd;
209 int64_t msg;
210
211 assert(size >= 0 && s->msg_buffered_bytes + size <= sizeof(s->msg_buf));
212 memcpy((unsigned char *)&s->msg_buf + s->msg_buffered_bytes, buf, size);
213 s->msg_buffered_bytes += size;
214 if (s->msg_buffered_bytes < sizeof(s->msg_buf)) {
215 return;
216 }
217 msg = le64_to_cpu(s->msg_buf);
218 s->msg_buffered_bytes = 0;
219
220 fd = qemu_chr_fe_get_msgfd(&s->server_chr);
221
222 ivshmem_flat_process_msg(s, msg, fd);
223 }
224
225 static uint64_t ivshmem_flat_iomem_read(void *opaque,
226 hwaddr offset, unsigned size)
227 {
228 IvshmemFTState *s = opaque;
229 uint32_t ret;
230
231 trace_ivshmem_flat_read_mmr(offset);
232
233 switch (offset) {
234 case INTMASK:
235 ret = 0; /* Ignore read since all bits are reserved in rev 1. */
236 break;
237 case INTSTATUS:
238 ret = 0; /* Ignore read since all bits are reserved in rev 1. */
239 break;
240 case IVPOSITION:
241 ret = s->own.id;
242 break;
243 case DOORBELL:
244 trace_ivshmem_flat_read_mmr_doorbell(); /* DOORBELL is write-only */
245 ret = 0;
246 break;
247 default:
248 /* Should never reach out here due to iomem map range being exact */
249 trace_ivshmem_flat_read_write_mmr_invalid(offset);
250 ret = 0;
251 }
252
253 return ret;
254 }
255
256 static int ivshmem_flat_interrupt_peer(IvshmemFTState *s,
257 uint16_t peer_id, uint16_t vector_id)
258 {
259 IvshmemPeer *peer;
260
261 peer = ivshmem_flat_find_peer(s, peer_id);
262 if (!peer) {
263 trace_ivshmem_flat_interrupt_invalid_peer(peer_id);
264 return 1;
265 }
266
267 event_notifier_set(&(peer->vector[vector_id].event_notifier));
268
269 return 0;
270 }
271
272 static void ivshmem_flat_iomem_write(void *opaque, hwaddr offset,
273 uint64_t value, unsigned size)
274 {
275 IvshmemFTState *s = opaque;
276 uint16_t peer_id = (value >> 16) & 0xFFFF;
277 uint16_t vector_id = value & 0xFFFF;
278
279 trace_ivshmem_flat_write_mmr(offset);
280
281 switch (offset) {
282 case INTMASK:
283 break;
284 case INTSTATUS:
285 break;
286 case IVPOSITION:
287 break;
288 case DOORBELL:
289 trace_ivshmem_flat_interrupt_peer(peer_id, vector_id);
290 ivshmem_flat_interrupt_peer(s, peer_id, vector_id);
291 break;
292 default:
293 /* Should never reach out here due to iomem map range being exact. */
294 trace_ivshmem_flat_read_write_mmr_invalid(offset);
295 break;
296 }
297 }
298
299 static const MemoryRegionOps ivshmem_flat_ops = {
300 .read = ivshmem_flat_iomem_read,
301 .write = ivshmem_flat_iomem_write,
302 .endianness = DEVICE_LITTLE_ENDIAN,
303 .impl = { /* Read/write aligned at 32 bits. */
304 .min_access_size = 4,
305 .max_access_size = 4,
306 },
307 };
308
309 static void ivshmem_flat_instance_init(Object *obj)
310 {
311 SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
312 IvshmemFTState *s = IVSHMEM_FLAT(obj);
313
314 /*
315 * Init mem region for 4 MMRs (ivshmem_registers),
316 * 32 bits each => 16 bytes (0x10).
317 */
318 memory_region_init_io(&s->iomem, obj, &ivshmem_flat_ops, s,
319 "ivshmem-mmio", 0x10);
320 sysbus_init_mmio(sbd, &s->iomem);
321
322 /*
323 * Create one output IRQ that will be connect to the
324 * machine's interrupt controller.
325 */
326 sysbus_init_irq(sbd, &s->irq);
327
328 QTAILQ_INIT(&s->peer);
329 }
330
331 static bool ivshmem_flat_connect_server(DeviceState *dev, Error **errp)
332 {
333 IvshmemFTState *s = IVSHMEM_FLAT(dev);
334 SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
335 int64_t protocol_version, msg;
336 int shmem_fd;
337 uint16_t peer_id;
338 struct stat fdstat;
339
340 /* Check ivshmem server connection. */
341 if (!qemu_chr_fe_backend_connected(&s->server_chr)) {
342 error_setg(errp, "ivshmem server socket not specified or incorret."
343 " Can't create device.");
344 return false;
345 }
346
347 /*
348 * Message sequence from server on new connection:
349 * _____________________________________
350 * |STEP| uint64_t msg | int fd |
351 * -------------------------------------
352 *
353 * 0 PROTOCOL -1 \
354 * 1 OWN PEER ID -1 |-- Header/Greeting
355 * 2 -1 shmem fd /
356 *
357 * 3 PEER IDx Other peer's Vector 0 eventfd
358 * 4 PEER IDx Other peer's Vector 1 eventfd
359 * . .
360 * . .
361 * . .
362 * N PEER IDy Other peer's Vector 0 eventfd
363 * N+1 PEER IDy Other peer's Vector 1 eventfd
364 * . .
365 * . .
366 * . .
367 *
368 * ivshmem_flat_recv_msg() calls return 'msg' and 'fd'.
369 *
370 * See docs/specs/ivshmem-spec.rst for details on the protocol.
371 */
372
373 /* Step 0 */
374 protocol_version = ivshmem_flat_recv_msg(s, NULL);
375
376 /* Step 1 */
377 msg = ivshmem_flat_recv_msg(s, NULL);
378 peer_id = 0xFFFF & msg;
379 s->own.id = peer_id;
380 s->own.vector_counter = 0;
381
382 trace_ivshmem_flat_proto_ver_own_id(protocol_version, s->own.id);
383
384 /* Step 2 */
385 msg = ivshmem_flat_recv_msg(s, &shmem_fd);
386 /* Map shmem fd and MMRs into memory regions. */
387 if (msg != -1 || shmem_fd < 0) {
388 error_setg(errp, "Could not receive valid shmem fd."
389 " Can't create device!");
390 return false;
391 }
392
393 if (fstat(shmem_fd, &fdstat) != 0) {
394 error_setg(errp, "Could not determine shmem fd size."
395 " Can't create device!");
396 return false;
397 }
398 trace_ivshmem_flat_shmem_size(shmem_fd, fdstat.st_size);
399
400 /*
401 * Shmem size provided by the ivshmem server must be equal to
402 * device's shmem size.
403 */
404 if (fdstat.st_size != s->shmem_size) {
405 error_setg(errp, "Can't map shmem fd: shmem size different"
406 " from device size!");
407 return false;
408 }
409
410 /*
411 * Beyond step 2 ivshmem_process_msg, called by ivshmem_flat_read_msg
412 * handler -- when data is available on the server socket -- will handle
413 * the additional messages that will be generated by the server as peers
414 * connect or disconnect.
415 */
416 qemu_chr_fe_set_handlers(&s->server_chr, ivshmem_flat_can_receive_data,
417 ivshmem_flat_read_msg, NULL, NULL, s, NULL, true);
418
419 memory_region_init_ram_from_fd(&s->shmem, OBJECT(s),
420 "ivshmem-shmem", s->shmem_size,
421 RAM_SHARED, shmem_fd, 0, NULL);
422 sysbus_init_mmio(sbd, &s->shmem);
423
424 return true;
425 }
426
427 static void ivshmem_flat_realize(DeviceState *dev, Error **errp)
428 {
429 if (!ivshmem_flat_connect_server(dev, errp)) {
430 return;
431 }
432 }
433
434 static const Property ivshmem_flat_props[] = {
435 DEFINE_PROP_CHR("chardev", IvshmemFTState, server_chr),
436 DEFINE_PROP_UINT32("shmem-size", IvshmemFTState, shmem_size, 4 * MiB),
437 };
438
439 static void ivshmem_flat_class_init(ObjectClass *klass, const void *data)
440 {
441 DeviceClass *dc = DEVICE_CLASS(klass);
442
443 dc->hotpluggable = true;
444 dc->realize = ivshmem_flat_realize;
445
446 set_bit(DEVICE_CATEGORY_MISC, dc->categories);
447 device_class_set_props(dc, ivshmem_flat_props);
448
449 /* Reason: Must be wired up in code (sysbus MRs and IRQ) */
450 dc->user_creatable = false;
451 }
452
453 static const TypeInfo ivshmem_flat_types[] = {
454 {
455 .name = TYPE_IVSHMEM_FLAT,
456 .parent = TYPE_SYS_BUS_DEVICE,
457 .instance_size = sizeof(IvshmemFTState),
458 .instance_init = ivshmem_flat_instance_init,
459 .class_init = ivshmem_flat_class_init,
460 },
461 };
462
463 DEFINE_TYPES(ivshmem_flat_types)