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