xref: /qemu/hw/misc/ivshmem-flat.c (revision 06b40d250ecfa1633209c2e431a7a38acfd03a98)
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