|  | /* | 
|  | * Inter-VM Shared Memory Flat Device | 
|  | * | 
|  | * SPDX-FileCopyrightText: 2023 Linaro Ltd. | 
|  | * SPDX-FileContributor: Gustavo Romero <gustavo.romero@linaro.org> | 
|  | * SPDX-License-Identifier: GPL-2.0-or-later | 
|  | * | 
|  | */ | 
|  |  | 
|  | #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) |