 d6f7642230
			
		
	
	
		d6f7642230
		
	
	
	
	
		
			
			Per [*]: "we're only interested in adopting SPDX for recording the licensing info, [not] any other SPDX metadata." Replace the 'SPDX-FileCopyrightText' and 'SPDX-FileContributor' tags added by Linaro by 'Copyright (c)' and 'Authors' words respectively. [*] https://lore.kernel.org/qemu-devel/20241007154548.1144961-4-berrange@redhat.com/ Inspired-by: Daniel P. Berrangé <berrange@redhat.com> Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> Reviewed-by: Michael Tokarev <mjt@tls.msk.ru> Signed-off-by: Michael Tokarev <mjt@tls.msk.ru>
		
			
				
	
	
		
			461 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Inter-VM Shared Memory Flat Device
 | |
|  *
 | |
|  * SPDX-License-Identifier: GPL-2.0-or-later
 | |
|  * Copyright (c) 2023 Linaro Ltd.
 | |
|  * Authors:
 | |
|  *   Gustavo Romero
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include "qemu/osdep.h"
 | |
| #include "qemu/units.h"
 | |
| #include "qemu/error-report.h"
 | |
| #include "qemu/module.h"
 | |
| #include "qapi/error.h"
 | |
| #include "hw/irq.h"
 | |
| #include "hw/qdev-properties-system.h"
 | |
| #include "hw/sysbus.h"
 | |
| #include "chardev/char-fe.h"
 | |
| #include "exec/address-spaces.h"
 | |
| #include "trace.h"
 | |
| 
 | |
| #include "hw/misc/ivshmem-flat.h"
 | |
| 
 | |
| static int64_t ivshmem_flat_recv_msg(IvshmemFTState *s, int *pfd)
 | |
| {
 | |
|     int64_t msg;
 | |
|     int n, ret;
 | |
| 
 | |
|     n = 0;
 | |
|     do {
 | |
|         ret = qemu_chr_fe_read_all(&s->server_chr, (uint8_t *)&msg + n,
 | |
|                                    sizeof(msg) - n);
 | |
|         if (ret < 0) {
 | |
|             if (ret == -EINTR) {
 | |
|                 continue;
 | |
|             }
 | |
|             exit(1);
 | |
|         }
 | |
|         n += ret;
 | |
|     } while (n < sizeof(msg));
 | |
| 
 | |
|     if (pfd) {
 | |
|         *pfd = qemu_chr_fe_get_msgfd(&s->server_chr);
 | |
|     }
 | |
|     return le64_to_cpu(msg);
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_irq_handler(void *opaque)
 | |
| {
 | |
|     VectorInfo *vi = opaque;
 | |
|     EventNotifier *e = &vi->event_notifier;
 | |
|     uint16_t vector_id;
 | |
|     const VectorInfo (*v)[64];
 | |
| 
 | |
|     assert(e->initialized);
 | |
| 
 | |
|     vector_id = vi->id;
 | |
| 
 | |
|     /*
 | |
|      * The vector info struct is passed to the handler via the 'opaque' pointer.
 | |
|      * This struct pointer allows the retrieval of the vector ID and its
 | |
|      * associated event notifier. However, for triggering an interrupt using
 | |
|      * qemu_set_irq, it's necessary to also have a pointer to the device state,
 | |
|      * i.e., a pointer to the IvshmemFTState struct. Since the vector info
 | |
|      * struct is contained within the IvshmemFTState struct, its pointer can be
 | |
|      * used to obtain the pointer to IvshmemFTState through simple pointer math.
 | |
|      */
 | |
|     v = (void *)(vi - vector_id); /* v =  &IvshmemPeer->vector[0] */
 | |
|     IvshmemPeer *own_peer = container_of(v, IvshmemPeer, vector);
 | |
|     IvshmemFTState *s = container_of(own_peer, IvshmemFTState, own);
 | |
| 
 | |
|     /* Clear event  */
 | |
|     if (!event_notifier_test_and_clear(e)) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     trace_ivshmem_flat_irq_handler(vector_id);
 | |
| 
 | |
|     /*
 | |
|      * Toggle device's output line, which is connected to interrupt controller,
 | |
|      * generating an interrupt request to the CPU.
 | |
|      */
 | |
|     qemu_irq_pulse(s->irq);
 | |
| }
 | |
| 
 | |
| static IvshmemPeer *ivshmem_flat_find_peer(IvshmemFTState *s, uint16_t peer_id)
 | |
| {
 | |
|     IvshmemPeer *peer;
 | |
| 
 | |
|     /* Own ID */
 | |
|     if (s->own.id == peer_id) {
 | |
|         return &s->own;
 | |
|     }
 | |
| 
 | |
|     /* Peer ID */
 | |
|     QTAILQ_FOREACH(peer, &s->peer, next) {
 | |
|         if (peer->id == peer_id) {
 | |
|             return peer;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| static IvshmemPeer *ivshmem_flat_add_peer(IvshmemFTState *s, uint16_t peer_id)
 | |
| {
 | |
|     IvshmemPeer *new_peer;
 | |
| 
 | |
|     new_peer = g_malloc0(sizeof(*new_peer));
 | |
|     new_peer->id = peer_id;
 | |
|     new_peer->vector_counter = 0;
 | |
| 
 | |
|     QTAILQ_INSERT_TAIL(&s->peer, new_peer, next);
 | |
| 
 | |
|     trace_ivshmem_flat_new_peer(peer_id);
 | |
| 
 | |
|     return new_peer;
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_remove_peer(IvshmemFTState *s, uint16_t peer_id)
 | |
| {
 | |
|     IvshmemPeer *peer;
 | |
| 
 | |
|     peer = ivshmem_flat_find_peer(s, peer_id);
 | |
|     assert(peer);
 | |
| 
 | |
|     QTAILQ_REMOVE(&s->peer, peer, next);
 | |
|     for (int n = 0; n < peer->vector_counter; n++) {
 | |
|         int efd;
 | |
|         efd = event_notifier_get_fd(&(peer->vector[n].event_notifier));
 | |
|         close(efd);
 | |
|     }
 | |
| 
 | |
|     g_free(peer);
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_add_vector(IvshmemFTState *s, IvshmemPeer *peer,
 | |
|                                     int vector_fd)
 | |
| {
 | |
|     if (peer->vector_counter >= IVSHMEM_MAX_VECTOR_NUM) {
 | |
|         trace_ivshmem_flat_add_vector_failure(peer->vector_counter,
 | |
|                                               vector_fd, peer->id);
 | |
|         close(vector_fd);
 | |
| 
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     trace_ivshmem_flat_add_vector_success(peer->vector_counter,
 | |
|                                           vector_fd, peer->id);
 | |
| 
 | |
|     /*
 | |
|      * Set vector ID and its associated eventfd notifier and add them to the
 | |
|      * peer.
 | |
|      */
 | |
|     peer->vector[peer->vector_counter].id = peer->vector_counter;
 | |
|     g_unix_set_fd_nonblocking(vector_fd, true, NULL);
 | |
|     event_notifier_init_fd(&peer->vector[peer->vector_counter].event_notifier,
 | |
|                            vector_fd);
 | |
| 
 | |
|     /*
 | |
|      * If it's the device's own ID, register also the handler for the eventfd
 | |
|      * so the device can be notified by the other peers.
 | |
|      */
 | |
|     if (peer == &s->own) {
 | |
|         qemu_set_fd_handler(vector_fd, ivshmem_flat_irq_handler, NULL,
 | |
|                             &peer->vector);
 | |
|     }
 | |
| 
 | |
|     peer->vector_counter++;
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_process_msg(IvshmemFTState *s, uint64_t msg, int fd)
 | |
| {
 | |
|     uint16_t peer_id;
 | |
|     IvshmemPeer *peer;
 | |
| 
 | |
|     peer_id = msg & 0xFFFF;
 | |
|     peer = ivshmem_flat_find_peer(s, peer_id);
 | |
| 
 | |
|     if (!peer) {
 | |
|         peer = ivshmem_flat_add_peer(s, peer_id);
 | |
|     }
 | |
| 
 | |
|     if (fd >= 0) {
 | |
|         ivshmem_flat_add_vector(s, peer, fd);
 | |
|     } else { /* fd == -1, which is received when peers disconnect. */
 | |
|         ivshmem_flat_remove_peer(s, peer_id);
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int ivshmem_flat_can_receive_data(void *opaque)
 | |
| {
 | |
|     IvshmemFTState *s = opaque;
 | |
| 
 | |
|     assert(s->msg_buffered_bytes < sizeof(s->msg_buf));
 | |
|     return sizeof(s->msg_buf) - s->msg_buffered_bytes;
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_read_msg(void *opaque, const uint8_t *buf, int size)
 | |
| {
 | |
|     IvshmemFTState *s = opaque;
 | |
|     int fd;
 | |
|     int64_t msg;
 | |
| 
 | |
|     assert(size >= 0 && s->msg_buffered_bytes + size <= sizeof(s->msg_buf));
 | |
|     memcpy((unsigned char *)&s->msg_buf + s->msg_buffered_bytes, buf, size);
 | |
|     s->msg_buffered_bytes += size;
 | |
|     if (s->msg_buffered_bytes < sizeof(s->msg_buf)) {
 | |
|         return;
 | |
|     }
 | |
|     msg = le64_to_cpu(s->msg_buf);
 | |
|     s->msg_buffered_bytes = 0;
 | |
| 
 | |
|     fd = qemu_chr_fe_get_msgfd(&s->server_chr);
 | |
| 
 | |
|     ivshmem_flat_process_msg(s, msg, fd);
 | |
| }
 | |
| 
 | |
| static uint64_t ivshmem_flat_iomem_read(void *opaque,
 | |
|                                         hwaddr offset, unsigned size)
 | |
| {
 | |
|     IvshmemFTState *s = opaque;
 | |
|     uint32_t ret;
 | |
| 
 | |
|     trace_ivshmem_flat_read_mmr(offset);
 | |
| 
 | |
|     switch (offset) {
 | |
|     case INTMASK:
 | |
|         ret = 0; /* Ignore read since all bits are reserved in rev 1. */
 | |
|         break;
 | |
|     case INTSTATUS:
 | |
|         ret = 0; /* Ignore read since all bits are reserved in rev 1. */
 | |
|         break;
 | |
|     case IVPOSITION:
 | |
|         ret = s->own.id;
 | |
|         break;
 | |
|     case DOORBELL:
 | |
|         trace_ivshmem_flat_read_mmr_doorbell(); /* DOORBELL is write-only */
 | |
|         ret = 0;
 | |
|         break;
 | |
|     default:
 | |
|         /* Should never reach out here due to iomem map range being exact */
 | |
|         trace_ivshmem_flat_read_write_mmr_invalid(offset);
 | |
|         ret = 0;
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static int ivshmem_flat_interrupt_peer(IvshmemFTState *s,
 | |
|                                        uint16_t peer_id, uint16_t vector_id)
 | |
| {
 | |
|     IvshmemPeer *peer;
 | |
| 
 | |
|     peer = ivshmem_flat_find_peer(s, peer_id);
 | |
|     if (!peer) {
 | |
|         trace_ivshmem_flat_interrupt_invalid_peer(peer_id);
 | |
|         return 1;
 | |
|     }
 | |
| 
 | |
|     event_notifier_set(&(peer->vector[vector_id].event_notifier));
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_iomem_write(void *opaque, hwaddr offset,
 | |
|                                      uint64_t value, unsigned size)
 | |
| {
 | |
|     IvshmemFTState *s = opaque;
 | |
|     uint16_t peer_id = (value >> 16) & 0xFFFF;
 | |
|     uint16_t vector_id = value & 0xFFFF;
 | |
| 
 | |
|     trace_ivshmem_flat_write_mmr(offset);
 | |
| 
 | |
|     switch (offset) {
 | |
|     case INTMASK:
 | |
|         break;
 | |
|     case INTSTATUS:
 | |
|         break;
 | |
|     case IVPOSITION:
 | |
|         break;
 | |
|     case DOORBELL:
 | |
|         trace_ivshmem_flat_interrupt_peer(peer_id, vector_id);
 | |
|         ivshmem_flat_interrupt_peer(s, peer_id, vector_id);
 | |
|         break;
 | |
|     default:
 | |
|         /* Should never reach out here due to iomem map range being exact. */
 | |
|         trace_ivshmem_flat_read_write_mmr_invalid(offset);
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     return;
 | |
| }
 | |
| 
 | |
| static const MemoryRegionOps ivshmem_flat_ops = {
 | |
|     .read = ivshmem_flat_iomem_read,
 | |
|     .write = ivshmem_flat_iomem_write,
 | |
|     .endianness = DEVICE_LITTLE_ENDIAN,
 | |
|     .impl = { /* Read/write aligned at 32 bits. */
 | |
|         .min_access_size = 4,
 | |
|         .max_access_size = 4,
 | |
|     },
 | |
| };
 | |
| 
 | |
| static void ivshmem_flat_instance_init(Object *obj)
 | |
| {
 | |
|     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
 | |
|     IvshmemFTState *s = IVSHMEM_FLAT(obj);
 | |
| 
 | |
|     /*
 | |
|      * Init mem region for 4 MMRs (ivshmem_registers),
 | |
|      * 32 bits each => 16 bytes (0x10).
 | |
|      */
 | |
|     memory_region_init_io(&s->iomem, obj, &ivshmem_flat_ops, s,
 | |
|                           "ivshmem-mmio", 0x10);
 | |
|     sysbus_init_mmio(sbd, &s->iomem);
 | |
| 
 | |
|     /*
 | |
|      * Create one output IRQ that will be connect to the
 | |
|      * machine's interrupt controller.
 | |
|      */
 | |
|     sysbus_init_irq(sbd, &s->irq);
 | |
| 
 | |
|     QTAILQ_INIT(&s->peer);
 | |
| }
 | |
| 
 | |
| static bool ivshmem_flat_connect_server(DeviceState *dev, Error **errp)
 | |
| {
 | |
|     IvshmemFTState *s = IVSHMEM_FLAT(dev);
 | |
|     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
 | |
|     int64_t protocol_version, msg;
 | |
|     int shmem_fd;
 | |
|     uint16_t peer_id;
 | |
|     struct stat fdstat;
 | |
| 
 | |
|     /* Check ivshmem server connection. */
 | |
|     if (!qemu_chr_fe_backend_connected(&s->server_chr)) {
 | |
|         error_setg(errp, "ivshmem server socket not specified or incorret."
 | |
|                          " Can't create device.");
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Message sequence from server on new connection:
 | |
|      *  _____________________________________
 | |
|      * |STEP| uint64_t msg  | int fd         |
 | |
|      *  -------------------------------------
 | |
|      *
 | |
|      *  0    PROTOCOL        -1              \
 | |
|      *  1    OWN PEER ID     -1               |-- Header/Greeting
 | |
|      *  2    -1              shmem fd        /
 | |
|      *
 | |
|      *  3    PEER IDx        Other peer's Vector 0 eventfd
 | |
|      *  4    PEER IDx        Other peer's Vector 1 eventfd
 | |
|      *  .                    .
 | |
|      *  .                    .
 | |
|      *  .                    .
 | |
|      *  N    PEER IDy        Other peer's Vector 0 eventfd
 | |
|      *  N+1  PEER IDy        Other peer's Vector 1 eventfd
 | |
|      *  .                    .
 | |
|      *  .                    .
 | |
|      *  .                    .
 | |
|      *
 | |
|      *  ivshmem_flat_recv_msg() calls return 'msg' and 'fd'.
 | |
|      *
 | |
|      *  See ./docs/specs/ivshmem-spec.txt for details on the protocol.
 | |
|      */
 | |
| 
 | |
|     /* Step 0 */
 | |
|     protocol_version = ivshmem_flat_recv_msg(s, NULL);
 | |
| 
 | |
|     /* Step 1 */
 | |
|     msg = ivshmem_flat_recv_msg(s, NULL);
 | |
|     peer_id = 0xFFFF & msg;
 | |
|     s->own.id = peer_id;
 | |
|     s->own.vector_counter = 0;
 | |
| 
 | |
|     trace_ivshmem_flat_proto_ver_own_id(protocol_version, s->own.id);
 | |
| 
 | |
|     /* Step 2 */
 | |
|     msg = ivshmem_flat_recv_msg(s, &shmem_fd);
 | |
|     /* Map shmem fd and MMRs into memory regions. */
 | |
|     if (msg != -1 || shmem_fd < 0) {
 | |
|         error_setg(errp, "Could not receive valid shmem fd."
 | |
|                          " Can't create device!");
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     if (fstat(shmem_fd, &fdstat) != 0) {
 | |
|         error_setg(errp, "Could not determine shmem fd size."
 | |
|                          " Can't create device!");
 | |
|         return false;
 | |
|     }
 | |
|     trace_ivshmem_flat_shmem_size(shmem_fd, fdstat.st_size);
 | |
| 
 | |
|     /*
 | |
|      * Shmem size provided by the ivshmem server must be equal to
 | |
|      * device's shmem size.
 | |
|      */
 | |
|     if (fdstat.st_size != s->shmem_size) {
 | |
|         error_setg(errp, "Can't map shmem fd: shmem size different"
 | |
|                          " from device size!");
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     /*
 | |
|      * Beyond step 2 ivshmem_process_msg, called by ivshmem_flat_read_msg
 | |
|      * handler -- when data is available on the server socket -- will handle
 | |
|      * the additional messages that will be generated by the server as peers
 | |
|      * connect or disconnect.
 | |
|      */
 | |
|     qemu_chr_fe_set_handlers(&s->server_chr, ivshmem_flat_can_receive_data,
 | |
|                              ivshmem_flat_read_msg, NULL, NULL, s, NULL, true);
 | |
| 
 | |
|     memory_region_init_ram_from_fd(&s->shmem, OBJECT(s),
 | |
|                                    "ivshmem-shmem", s->shmem_size,
 | |
|                                    RAM_SHARED, shmem_fd, 0, NULL);
 | |
|     sysbus_init_mmio(sbd, &s->shmem);
 | |
| 
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| static void ivshmem_flat_realize(DeviceState *dev, Error **errp)
 | |
| {
 | |
|     if (!ivshmem_flat_connect_server(dev, errp)) {
 | |
|         return;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static const Property ivshmem_flat_props[] = {
 | |
|     DEFINE_PROP_CHR("chardev", IvshmemFTState, server_chr),
 | |
|     DEFINE_PROP_UINT32("shmem-size", IvshmemFTState, shmem_size, 4 * MiB),
 | |
| };
 | |
| 
 | |
| static void ivshmem_flat_class_init(ObjectClass *klass, void *data)
 | |
| {
 | |
|     DeviceClass *dc = DEVICE_CLASS(klass);
 | |
| 
 | |
|     dc->hotpluggable = true;
 | |
|     dc->realize = ivshmem_flat_realize;
 | |
| 
 | |
|     set_bit(DEVICE_CATEGORY_MISC, dc->categories);
 | |
|     device_class_set_props(dc, ivshmem_flat_props);
 | |
| 
 | |
|     /* Reason: Must be wired up in code (sysbus MRs and IRQ) */
 | |
|     dc->user_creatable = false;
 | |
| }
 | |
| 
 | |
| static const TypeInfo ivshmem_flat_types[] = {
 | |
|     {
 | |
|         .name           = TYPE_IVSHMEM_FLAT,
 | |
|         .parent         = TYPE_SYS_BUS_DEVICE,
 | |
|         .instance_size  = sizeof(IvshmemFTState),
 | |
|         .instance_init  = ivshmem_flat_instance_init,
 | |
|         .class_init     = ivshmem_flat_class_init,
 | |
|     },
 | |
| };
 | |
| 
 | |
| DEFINE_TYPES(ivshmem_flat_types)
 |