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