mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
13 KiB
C
460 lines
13 KiB
C
/*
|
|
* 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)
|