| /* |
| * 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) |