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/irq.h"
17 #include "hw/qdev-properties-system.h"
18 #include "hw/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
ivshmem_flat_recv_msg(IvshmemFTState * s,int * pfd)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
ivshmem_flat_irq_handler(void * opaque)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
ivshmem_flat_find_peer(IvshmemFTState * s,uint16_t peer_id)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
ivshmem_flat_add_peer(IvshmemFTState * s,uint16_t peer_id)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
ivshmem_flat_remove_peer(IvshmemFTState * s,uint16_t peer_id)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
ivshmem_flat_add_vector(IvshmemFTState * s,IvshmemPeer * peer,int vector_fd)138 static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer,
139 int vector_fd)
140 {
141 if (peer->vector_counter >= IVSHMEM_MAX_VECTOR_NUM) {
142 trace_ivshmem_flat_add_vector_failure(peer->vector_counter,
143 vector_fd, peer->id);
144 close(vector_fd);
145
146 return;
147 }
148
149 trace_ivshmem_flat_add_vector_success(peer->vector_counter,
150 vector_fd, peer->id);
151
152 /*
153 * Set vector ID and its associated eventfd notifier and add them to the
154 * peer.
155 */
156 peer->vector[peer->vector_counter].id = peer->vector_counter;
157 g_unix_set_fd_nonblocking(vector_fd, true, NULL);
158 event_notifier_init_fd(&peer->vector[peer->vector_counter].event_notifier,
159 vector_fd);
160
161 /*
162 * If it's the device's own ID, register also the handler for the eventfd
163 * so the device can be notified by the other peers.
164 */
165 if (peer == &s->own) {
166 qemu_set_fd_handler(vector_fd, ivshmem_flat_irq_handler, NULL,
167 &peer->vector);
168 }
169
170 peer->vector_counter++;
171 }
172
ivshmem_flat_process_msg(IvshmemFTState * s,uint64_t msg,int fd)173 static void ivshmem_flat_process_msg(IvshmemFTState *s, uint64_t msg, int fd)
174 {
175 uint16_t peer_id;
176 IvshmemPeer *peer;
177
178 peer_id = msg & 0xFFFF;
179 peer = ivshmem_flat_find_peer(s, peer_id);
180
181 if (!peer) {
182 peer = ivshmem_flat_add_peer(s, peer_id);
183 }
184
185 if (fd >= 0) {
186 ivshmem_flat_add_vector(s, peer, fd);
187 } else { /* fd == -1, which is received when peers disconnect. */
188 ivshmem_flat_remove_peer(s, peer_id);
189 }
190 }
191
ivshmem_flat_can_receive_data(void * opaque)192 static int ivshmem_flat_can_receive_data(void *opaque)
193 {
194 IvshmemFTState *s = opaque;
195
196 assert(s->msg_buffered_bytes < sizeof(s->msg_buf));
197 return sizeof(s->msg_buf) - s->msg_buffered_bytes;
198 }
199
ivshmem_flat_read_msg(void * opaque,const uint8_t * buf,int size)200 static void ivshmem_flat_read_msg(void *opaque, const uint8_t *buf, int size)
201 {
202 IvshmemFTState *s = opaque;
203 int fd;
204 int64_t msg;
205
206 assert(size >= 0 && s->msg_buffered_bytes + size <= sizeof(s->msg_buf));
207 memcpy((unsigned char *)&s->msg_buf + s->msg_buffered_bytes, buf, size);
208 s->msg_buffered_bytes += size;
209 if (s->msg_buffered_bytes < sizeof(s->msg_buf)) {
210 return;
211 }
212 msg = le64_to_cpu(s->msg_buf);
213 s->msg_buffered_bytes = 0;
214
215 fd = qemu_chr_fe_get_msgfd(&s->server_chr);
216
217 ivshmem_flat_process_msg(s, msg, fd);
218 }
219
ivshmem_flat_iomem_read(void * opaque,hwaddr offset,unsigned size)220 static uint64_t ivshmem_flat_iomem_read(void *opaque,
221 hwaddr offset, unsigned size)
222 {
223 IvshmemFTState *s = opaque;
224 uint32_t ret;
225
226 trace_ivshmem_flat_read_mmr(offset);
227
228 switch (offset) {
229 case INTMASK:
230 ret = 0; /* Ignore read since all bits are reserved in rev 1. */
231 break;
232 case INTSTATUS:
233 ret = 0; /* Ignore read since all bits are reserved in rev 1. */
234 break;
235 case IVPOSITION:
236 ret = s->own.id;
237 break;
238 case DOORBELL:
239 trace_ivshmem_flat_read_mmr_doorbell(); /* DOORBELL is write-only */
240 ret = 0;
241 break;
242 default:
243 /* Should never reach out here due to iomem map range being exact */
244 trace_ivshmem_flat_read_write_mmr_invalid(offset);
245 ret = 0;
246 }
247
248 return ret;
249 }
250
ivshmem_flat_interrupt_peer(IvshmemFTState * s,uint16_t peer_id,uint16_t vector_id)251 static int ivshmem_flat_interrupt_peer(IvshmemFTState *s,
252 uint16_t peer_id, uint16_t vector_id)
253 {
254 IvshmemPeer *peer;
255
256 peer = ivshmem_flat_find_peer(s, peer_id);
257 if (!peer) {
258 trace_ivshmem_flat_interrupt_invalid_peer(peer_id);
259 return 1;
260 }
261
262 event_notifier_set(&(peer->vector[vector_id].event_notifier));
263
264 return 0;
265 }
266
ivshmem_flat_iomem_write(void * opaque,hwaddr offset,uint64_t value,unsigned size)267 static void ivshmem_flat_iomem_write(void *opaque, hwaddr offset,
268 uint64_t value, unsigned size)
269 {
270 IvshmemFTState *s = opaque;
271 uint16_t peer_id = (value >> 16) & 0xFFFF;
272 uint16_t vector_id = value & 0xFFFF;
273
274 trace_ivshmem_flat_write_mmr(offset);
275
276 switch (offset) {
277 case INTMASK:
278 break;
279 case INTSTATUS:
280 break;
281 case IVPOSITION:
282 break;
283 case DOORBELL:
284 trace_ivshmem_flat_interrupt_peer(peer_id, vector_id);
285 ivshmem_flat_interrupt_peer(s, peer_id, vector_id);
286 break;
287 default:
288 /* Should never reach out here due to iomem map range being exact. */
289 trace_ivshmem_flat_read_write_mmr_invalid(offset);
290 break;
291 }
292 }
293
294 static const MemoryRegionOps ivshmem_flat_ops = {
295 .read = ivshmem_flat_iomem_read,
296 .write = ivshmem_flat_iomem_write,
297 .endianness = DEVICE_LITTLE_ENDIAN,
298 .impl = { /* Read/write aligned at 32 bits. */
299 .min_access_size = 4,
300 .max_access_size = 4,
301 },
302 };
303
ivshmem_flat_instance_init(Object * obj)304 static void ivshmem_flat_instance_init(Object *obj)
305 {
306 SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
307 IvshmemFTState *s = IVSHMEM_FLAT(obj);
308
309 /*
310 * Init mem region for 4 MMRs (ivshmem_registers),
311 * 32 bits each => 16 bytes (0x10).
312 */
313 memory_region_init_io(&s->iomem, obj, &ivshmem_flat_ops, s,
314 "ivshmem-mmio", 0x10);
315 sysbus_init_mmio(sbd, &s->iomem);
316
317 /*
318 * Create one output IRQ that will be connect to the
319 * machine's interrupt controller.
320 */
321 sysbus_init_irq(sbd, &s->irq);
322
323 QTAILQ_INIT(&s->peer);
324 }
325
ivshmem_flat_connect_server(DeviceState * dev,Error ** errp)326 static bool ivshmem_flat_connect_server(DeviceState *dev, Error **errp)
327 {
328 IvshmemFTState *s = IVSHMEM_FLAT(dev);
329 SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
330 int64_t protocol_version, msg;
331 int shmem_fd;
332 uint16_t peer_id;
333 struct stat fdstat;
334
335 /* Check ivshmem server connection. */
336 if (!qemu_chr_fe_backend_connected(&s->server_chr)) {
337 error_setg(errp, "ivshmem server socket not specified or incorret."
338 " Can't create device.");
339 return false;
340 }
341
342 /*
343 * Message sequence from server on new connection:
344 * _____________________________________
345 * |STEP| uint64_t msg | int fd |
346 * -------------------------------------
347 *
348 * 0 PROTOCOL -1 \
349 * 1 OWN PEER ID -1 |-- Header/Greeting
350 * 2 -1 shmem fd /
351 *
352 * 3 PEER IDx Other peer's Vector 0 eventfd
353 * 4 PEER IDx Other peer's Vector 1 eventfd
354 * . .
355 * . .
356 * . .
357 * N PEER IDy Other peer's Vector 0 eventfd
358 * N+1 PEER IDy Other peer's Vector 1 eventfd
359 * . .
360 * . .
361 * . .
362 *
363 * ivshmem_flat_recv_msg() calls return 'msg' and 'fd'.
364 *
365 * See ./docs/specs/ivshmem-spec.txt for details on the protocol.
366 */
367
368 /* Step 0 */
369 protocol_version = ivshmem_flat_recv_msg(s, NULL);
370
371 /* Step 1 */
372 msg = ivshmem_flat_recv_msg(s, NULL);
373 peer_id = 0xFFFF & msg;
374 s->own.id = peer_id;
375 s->own.vector_counter = 0;
376
377 trace_ivshmem_flat_proto_ver_own_id(protocol_version, s->own.id);
378
379 /* Step 2 */
380 msg = ivshmem_flat_recv_msg(s, &shmem_fd);
381 /* Map shmem fd and MMRs into memory regions. */
382 if (msg != -1 || shmem_fd < 0) {
383 error_setg(errp, "Could not receive valid shmem fd."
384 " Can't create device!");
385 return false;
386 }
387
388 if (fstat(shmem_fd, &fdstat) != 0) {
389 error_setg(errp, "Could not determine shmem fd size."
390 " Can't create device!");
391 return false;
392 }
393 trace_ivshmem_flat_shmem_size(shmem_fd, fdstat.st_size);
394
395 /*
396 * Shmem size provided by the ivshmem server must be equal to
397 * device's shmem size.
398 */
399 if (fdstat.st_size != s->shmem_size) {
400 error_setg(errp, "Can't map shmem fd: shmem size different"
401 " from device size!");
402 return false;
403 }
404
405 /*
406 * Beyond step 2 ivshmem_process_msg, called by ivshmem_flat_read_msg
407 * handler -- when data is available on the server socket -- will handle
408 * the additional messages that will be generated by the server as peers
409 * connect or disconnect.
410 */
411 qemu_chr_fe_set_handlers(&s->server_chr, ivshmem_flat_can_receive_data,
412 ivshmem_flat_read_msg, NULL, NULL, s, NULL, true);
413
414 memory_region_init_ram_from_fd(&s->shmem, OBJECT(s),
415 "ivshmem-shmem", s->shmem_size,
416 RAM_SHARED, shmem_fd, 0, NULL);
417 sysbus_init_mmio(sbd, &s->shmem);
418
419 return true;
420 }
421
ivshmem_flat_realize(DeviceState * dev,Error ** errp)422 static void ivshmem_flat_realize(DeviceState *dev, Error **errp)
423 {
424 if (!ivshmem_flat_connect_server(dev, errp)) {
425 return;
426 }
427 }
428
429 static const Property ivshmem_flat_props[] = {
430 DEFINE_PROP_CHR("chardev", IvshmemFTState, server_chr),
431 DEFINE_PROP_UINT32("shmem-size", IvshmemFTState, shmem_size, 4 * MiB),
432 };
433
ivshmem_flat_class_init(ObjectClass * klass,const void * data)434 static void ivshmem_flat_class_init(ObjectClass *klass, const void *data)
435 {
436 DeviceClass *dc = DEVICE_CLASS(klass);
437
438 dc->hotpluggable = true;
439 dc->realize = ivshmem_flat_realize;
440
441 set_bit(DEVICE_CATEGORY_MISC, dc->categories);
442 device_class_set_props(dc, ivshmem_flat_props);
443
444 /* Reason: Must be wired up in code (sysbus MRs and IRQ) */
445 dc->user_creatable = false;
446 }
447
448 static const TypeInfo ivshmem_flat_types[] = {
449 {
450 .name = TYPE_IVSHMEM_FLAT,
451 .parent = TYPE_SYS_BUS_DEVICE,
452 .instance_size = sizeof(IvshmemFTState),
453 .instance_init = ivshmem_flat_instance_init,
454 .class_init = ivshmem_flat_class_init,
455 },
456 };
457
458 DEFINE_TYPES(ivshmem_flat_types)
459