qemu-patch-raspberry4/hw/hyperv/vmbus.c
Eduardo Habkost 80cc1a0dd1 vmbus: Don't make QOM property registration conditional
Having properties registered conditionally makes QOM type
introspection difficult.  Instead of skipping registration of the
"instanceid" property, always register the property but validate
its value against the instance id required by the class.

Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
Acked-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
Message-Id: <20201009200701.1830060-1-ehabkost@redhat.com>
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2021-07-06 18:04:38 -04:00

2786 lines
76 KiB
C

/*
* QEMU Hyper-V VMBus
*
* Copyright (c) 2017-2018 Virtuozzo International GmbH.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qemu/main-loop.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/hyperv/hyperv.h"
#include "hw/hyperv/vmbus.h"
#include "hw/hyperv/vmbus-bridge.h"
#include "hw/sysbus.h"
#include "cpu.h"
#include "trace.h"
enum {
VMGPADL_INIT,
VMGPADL_ALIVE,
VMGPADL_TEARINGDOWN,
VMGPADL_TORNDOWN,
};
struct VMBusGpadl {
/* GPADL id */
uint32_t id;
/* associated channel id (rudimentary?) */
uint32_t child_relid;
/* number of pages in the GPADL as declared in GPADL_HEADER message */
uint32_t num_gfns;
/*
* Due to limited message size, GPADL may not fit fully in a single
* GPADL_HEADER message, and is further popluated using GPADL_BODY
* messages. @seen_gfns is the number of pages seen so far; once it
* reaches @num_gfns, the GPADL is ready to use.
*/
uint32_t seen_gfns;
/* array of GFNs (of size @num_gfns once allocated) */
uint64_t *gfns;
uint8_t state;
QTAILQ_ENTRY(VMBusGpadl) link;
VMBus *vmbus;
unsigned refcount;
};
/*
* Wrap sequential read from / write to GPADL.
*/
typedef struct GpadlIter {
VMBusGpadl *gpadl;
AddressSpace *as;
DMADirection dir;
/* offset into GPADL where the next i/o will be performed */
uint32_t off;
/*
* Cached mapping of the currently accessed page, up to page boundary.
* Updated lazily on i/o.
* Note: MemoryRegionCache can not be used here because pages in the GPADL
* are non-contiguous and may belong to different memory regions.
*/
void *map;
/* offset after last i/o (i.e. not affected by seek) */
uint32_t last_off;
/*
* Indicator that the iterator is active and may have a cached mapping.
* Allows to enforce bracketing of all i/o (which may create cached
* mappings) and thus exclude mapping leaks.
*/
bool active;
} GpadlIter;
/*
* Ring buffer. There are two of them, sitting in the same GPADL, for each
* channel.
* Each ring buffer consists of a set of pages, with the first page containing
* the ring buffer header, and the remaining pages being for data packets.
*/
typedef struct VMBusRingBufCommon {
AddressSpace *as;
/* GPA of the ring buffer header */
dma_addr_t rb_addr;
/* start and length of the ring buffer data area within GPADL */
uint32_t base;
uint32_t len;
GpadlIter iter;
} VMBusRingBufCommon;
typedef struct VMBusSendRingBuf {
VMBusRingBufCommon common;
/* current write index, to be committed at the end of send */
uint32_t wr_idx;
/* write index at the start of send */
uint32_t last_wr_idx;
/* space to be requested from the guest */
uint32_t wanted;
/* space reserved for planned sends */
uint32_t reserved;
/* last seen read index */
uint32_t last_seen_rd_idx;
} VMBusSendRingBuf;
typedef struct VMBusRecvRingBuf {
VMBusRingBufCommon common;
/* current read index, to be committed at the end of receive */
uint32_t rd_idx;
/* read index at the start of receive */
uint32_t last_rd_idx;
/* last seen write index */
uint32_t last_seen_wr_idx;
} VMBusRecvRingBuf;
enum {
VMOFFER_INIT,
VMOFFER_SENDING,
VMOFFER_SENT,
};
enum {
VMCHAN_INIT,
VMCHAN_OPENING,
VMCHAN_OPEN,
};
struct VMBusChannel {
VMBusDevice *dev;
/* channel id */
uint32_t id;
/*
* subchannel index within the device; subchannel #0 is "primary" and
* always exists
*/
uint16_t subchan_idx;
uint32_t open_id;
/* VP_INDEX of the vCPU to notify with (synthetic) interrupts */
uint32_t target_vp;
/* GPADL id to use for the ring buffers */
uint32_t ringbuf_gpadl;
/* start (in pages) of the send ring buffer within @ringbuf_gpadl */
uint32_t ringbuf_send_offset;
uint8_t offer_state;
uint8_t state;
bool is_open;
/* main device worker; copied from the device class */
VMBusChannelNotifyCb notify_cb;
/*
* guest->host notifications, either sent directly or dispatched via
* interrupt page (older VMBus)
*/
EventNotifier notifier;
VMBus *vmbus;
/*
* SINT route to signal with host->guest notifications; may be shared with
* the main VMBus SINT route
*/
HvSintRoute *notify_route;
VMBusGpadl *gpadl;
VMBusSendRingBuf send_ringbuf;
VMBusRecvRingBuf recv_ringbuf;
QTAILQ_ENTRY(VMBusChannel) link;
};
/*
* Hyper-V spec mandates that every message port has 16 buffers, which means
* that the guest can post up to this many messages without blocking.
* Therefore a queue for incoming messages has to be provided.
* For outgoing (i.e. host->guest) messages there's no queue; the VMBus just
* doesn't transition to a new state until the message is known to have been
* successfully delivered to the respective SynIC message slot.
*/
#define HV_MSG_QUEUE_LEN 16
/* Hyper-V devices never use channel #0. Must be something special. */
#define VMBUS_FIRST_CHANID 1
/* Each channel occupies one bit within a single event page sint slot. */
#define VMBUS_CHANID_COUNT (HV_EVENT_FLAGS_COUNT - VMBUS_FIRST_CHANID)
/* Leave a few connection numbers for other purposes. */
#define VMBUS_CHAN_CONNECTION_OFFSET 16
/*
* Since the success or failure of sending a message is reported
* asynchronously, the VMBus state machine has effectively two entry points:
* vmbus_run and vmbus_msg_cb (the latter is called when the host->guest
* message delivery status becomes known). Both are run as oneshot BHs on the
* main aio context, ensuring serialization.
*/
enum {
VMBUS_LISTEN,
VMBUS_HANDSHAKE,
VMBUS_OFFER,
VMBUS_CREATE_GPADL,
VMBUS_TEARDOWN_GPADL,
VMBUS_OPEN_CHANNEL,
VMBUS_UNLOAD,
VMBUS_STATE_MAX
};
struct VMBus {
BusState parent;
uint8_t state;
/* protection against recursive aio_poll (see vmbus_run) */
bool in_progress;
/* whether there's a message being delivered to the guest */
bool msg_in_progress;
uint32_t version;
/* VP_INDEX of the vCPU to send messages and interrupts to */
uint32_t target_vp;
HvSintRoute *sint_route;
/*
* interrupt page for older protocol versions; newer ones use SynIC event
* flags directly
*/
hwaddr int_page_gpa;
DECLARE_BITMAP(chanid_bitmap, VMBUS_CHANID_COUNT);
/* incoming message queue */
struct hyperv_post_message_input rx_queue[HV_MSG_QUEUE_LEN];
uint8_t rx_queue_head;
uint8_t rx_queue_size;
QemuMutex rx_queue_lock;
QTAILQ_HEAD(, VMBusGpadl) gpadl_list;
QTAILQ_HEAD(, VMBusChannel) channel_list;
/*
* guest->host notifications for older VMBus, to be dispatched via
* interrupt page
*/
EventNotifier notifier;
};
static bool gpadl_full(VMBusGpadl *gpadl)
{
return gpadl->seen_gfns == gpadl->num_gfns;
}
static VMBusGpadl *create_gpadl(VMBus *vmbus, uint32_t id,
uint32_t child_relid, uint32_t num_gfns)
{
VMBusGpadl *gpadl = g_new0(VMBusGpadl, 1);
gpadl->id = id;
gpadl->child_relid = child_relid;
gpadl->num_gfns = num_gfns;
gpadl->gfns = g_new(uint64_t, num_gfns);
QTAILQ_INSERT_HEAD(&vmbus->gpadl_list, gpadl, link);
gpadl->vmbus = vmbus;
gpadl->refcount = 1;
return gpadl;
}
static void free_gpadl(VMBusGpadl *gpadl)
{
QTAILQ_REMOVE(&gpadl->vmbus->gpadl_list, gpadl, link);
g_free(gpadl->gfns);
g_free(gpadl);
}
static VMBusGpadl *find_gpadl(VMBus *vmbus, uint32_t gpadl_id)
{
VMBusGpadl *gpadl;
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
if (gpadl->id == gpadl_id) {
return gpadl;
}
}
return NULL;
}
VMBusGpadl *vmbus_get_gpadl(VMBusChannel *chan, uint32_t gpadl_id)
{
VMBusGpadl *gpadl = find_gpadl(chan->vmbus, gpadl_id);
if (!gpadl || !gpadl_full(gpadl)) {
return NULL;
}
gpadl->refcount++;
return gpadl;
}
void vmbus_put_gpadl(VMBusGpadl *gpadl)
{
if (!gpadl) {
return;
}
if (--gpadl->refcount) {
return;
}
free_gpadl(gpadl);
}
uint32_t vmbus_gpadl_len(VMBusGpadl *gpadl)
{
return gpadl->num_gfns * TARGET_PAGE_SIZE;
}
static void gpadl_iter_init(GpadlIter *iter, VMBusGpadl *gpadl,
AddressSpace *as, DMADirection dir)
{
iter->gpadl = gpadl;
iter->as = as;
iter->dir = dir;
iter->active = false;
}
static inline void gpadl_iter_cache_unmap(GpadlIter *iter)
{
uint32_t map_start_in_page = (uintptr_t)iter->map & ~TARGET_PAGE_MASK;
uint32_t io_end_in_page = ((iter->last_off - 1) & ~TARGET_PAGE_MASK) + 1;
/* mapping is only done to do non-zero amount of i/o */
assert(iter->last_off > 0);
assert(map_start_in_page < io_end_in_page);
dma_memory_unmap(iter->as, iter->map, TARGET_PAGE_SIZE - map_start_in_page,
iter->dir, io_end_in_page - map_start_in_page);
}
/*
* Copy exactly @len bytes between the GPADL pointed to by @iter and @buf.
* The direction of the copy is determined by @iter->dir.
* The caller must ensure the operation overflows neither @buf nor the GPADL
* (there's an assert for the latter).
* Reuse the currently mapped page in the GPADL if possible.
*/
static ssize_t gpadl_iter_io(GpadlIter *iter, void *buf, uint32_t len)
{
ssize_t ret = len;
assert(iter->active);
while (len) {
uint32_t off_in_page = iter->off & ~TARGET_PAGE_MASK;
uint32_t pgleft = TARGET_PAGE_SIZE - off_in_page;
uint32_t cplen = MIN(pgleft, len);
void *p;
/* try to reuse the cached mapping */
if (iter->map) {
uint32_t map_start_in_page =
(uintptr_t)iter->map & ~TARGET_PAGE_MASK;
uint32_t off_base = iter->off & ~TARGET_PAGE_MASK;
uint32_t mapped_base = (iter->last_off - 1) & ~TARGET_PAGE_MASK;
if (off_base != mapped_base || off_in_page < map_start_in_page) {
gpadl_iter_cache_unmap(iter);
iter->map = NULL;
}
}
if (!iter->map) {
dma_addr_t maddr;
dma_addr_t mlen = pgleft;
uint32_t idx = iter->off >> TARGET_PAGE_BITS;
assert(idx < iter->gpadl->num_gfns);
maddr = (iter->gpadl->gfns[idx] << TARGET_PAGE_BITS) | off_in_page;
iter->map = dma_memory_map(iter->as, maddr, &mlen, iter->dir);
if (mlen != pgleft) {
dma_memory_unmap(iter->as, iter->map, mlen, iter->dir, 0);
iter->map = NULL;
return -EFAULT;
}
}
p = (void *)(uintptr_t)(((uintptr_t)iter->map & TARGET_PAGE_MASK) |
off_in_page);
if (iter->dir == DMA_DIRECTION_FROM_DEVICE) {
memcpy(p, buf, cplen);
} else {
memcpy(buf, p, cplen);
}
buf += cplen;
len -= cplen;
iter->off += cplen;
iter->last_off = iter->off;
}
return ret;
}
/*
* Position the iterator @iter at new offset @new_off.
* If this results in the cached mapping being unusable with the new offset,
* unmap it.
*/
static inline void gpadl_iter_seek(GpadlIter *iter, uint32_t new_off)
{
assert(iter->active);
iter->off = new_off;
}
/*
* Start a series of i/o on the GPADL.
* After this i/o and seek operations on @iter become legal.
*/
static inline void gpadl_iter_start_io(GpadlIter *iter)
{
assert(!iter->active);
/* mapping is cached lazily on i/o */
iter->map = NULL;
iter->active = true;
}
/*
* End the eariler started series of i/o on the GPADL and release the cached
* mapping if any.
*/
static inline void gpadl_iter_end_io(GpadlIter *iter)
{
assert(iter->active);
if (iter->map) {
gpadl_iter_cache_unmap(iter);
}
iter->active = false;
}
static void vmbus_resched(VMBus *vmbus);
static void vmbus_msg_cb(void *data, int status);
ssize_t vmbus_iov_to_gpadl(VMBusChannel *chan, VMBusGpadl *gpadl, uint32_t off,
const struct iovec *iov, size_t iov_cnt)
{
GpadlIter iter;
size_t i;
ssize_t ret = 0;
gpadl_iter_init(&iter, gpadl, chan->dev->dma_as,
DMA_DIRECTION_FROM_DEVICE);
gpadl_iter_start_io(&iter);
gpadl_iter_seek(&iter, off);
for (i = 0; i < iov_cnt; i++) {
ret = gpadl_iter_io(&iter, iov[i].iov_base, iov[i].iov_len);
if (ret < 0) {
goto out;
}
}
out:
gpadl_iter_end_io(&iter);
return ret;
}
int vmbus_map_sgl(VMBusChanReq *req, DMADirection dir, struct iovec *iov,
unsigned iov_cnt, size_t len, size_t off)
{
int ret_cnt = 0, ret;
unsigned i;
QEMUSGList *sgl = &req->sgl;
ScatterGatherEntry *sg = sgl->sg;
for (i = 0; i < sgl->nsg; i++) {
if (sg[i].len > off) {
break;
}
off -= sg[i].len;
}
for (; len && i < sgl->nsg; i++) {
dma_addr_t mlen = MIN(sg[i].len - off, len);
dma_addr_t addr = sg[i].base + off;
len -= mlen;
off = 0;
for (; mlen; ret_cnt++) {
dma_addr_t l = mlen;
dma_addr_t a = addr;
if (ret_cnt == iov_cnt) {
ret = -ENOBUFS;
goto err;
}
iov[ret_cnt].iov_base = dma_memory_map(sgl->as, a, &l, dir);
if (!l) {
ret = -EFAULT;
goto err;
}
iov[ret_cnt].iov_len = l;
addr += l;
mlen -= l;
}
}
return ret_cnt;
err:
vmbus_unmap_sgl(req, dir, iov, ret_cnt, 0);
return ret;
}
void vmbus_unmap_sgl(VMBusChanReq *req, DMADirection dir, struct iovec *iov,
unsigned iov_cnt, size_t accessed)
{
QEMUSGList *sgl = &req->sgl;
unsigned i;
for (i = 0; i < iov_cnt; i++) {
size_t acsd = MIN(accessed, iov[i].iov_len);
dma_memory_unmap(sgl->as, iov[i].iov_base, iov[i].iov_len, dir, acsd);
accessed -= acsd;
}
}
static const VMStateDescription vmstate_gpadl = {
.name = "vmbus/gpadl",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_UINT32(id, VMBusGpadl),
VMSTATE_UINT32(child_relid, VMBusGpadl),
VMSTATE_UINT32(num_gfns, VMBusGpadl),
VMSTATE_UINT32(seen_gfns, VMBusGpadl),
VMSTATE_VARRAY_UINT32_ALLOC(gfns, VMBusGpadl, num_gfns, 0,
vmstate_info_uint64, uint64_t),
VMSTATE_UINT8(state, VMBusGpadl),
VMSTATE_END_OF_LIST()
}
};
/*
* Wrap the index into a ring buffer of @len bytes.
* @idx is assumed not to exceed twice the size of the ringbuffer, so only
* single wraparound is considered.
*/
static inline uint32_t rb_idx_wrap(uint32_t idx, uint32_t len)
{
if (idx >= len) {
idx -= len;
}
return idx;
}
/*
* Circular difference between two indices into a ring buffer of @len bytes.
* @allow_catchup - whether @idx1 may catch up @idx2; e.g. read index may catch
* up write index but not vice versa.
*/
static inline uint32_t rb_idx_delta(uint32_t idx1, uint32_t idx2, uint32_t len,
bool allow_catchup)
{
return rb_idx_wrap(idx2 + len - idx1 - !allow_catchup, len);
}
static vmbus_ring_buffer *ringbuf_map_hdr(VMBusRingBufCommon *ringbuf)
{
vmbus_ring_buffer *rb;
dma_addr_t mlen = sizeof(*rb);
rb = dma_memory_map(ringbuf->as, ringbuf->rb_addr, &mlen,
DMA_DIRECTION_FROM_DEVICE);
if (mlen != sizeof(*rb)) {
dma_memory_unmap(ringbuf->as, rb, mlen,
DMA_DIRECTION_FROM_DEVICE, 0);
return NULL;
}
return rb;
}
static void ringbuf_unmap_hdr(VMBusRingBufCommon *ringbuf,
vmbus_ring_buffer *rb, bool dirty)
{
assert(rb);
dma_memory_unmap(ringbuf->as, rb, sizeof(*rb), DMA_DIRECTION_FROM_DEVICE,
dirty ? sizeof(*rb) : 0);
}
static void ringbuf_init_common(VMBusRingBufCommon *ringbuf, VMBusGpadl *gpadl,
AddressSpace *as, DMADirection dir,
uint32_t begin, uint32_t end)
{
ringbuf->as = as;
ringbuf->rb_addr = gpadl->gfns[begin] << TARGET_PAGE_BITS;
ringbuf->base = (begin + 1) << TARGET_PAGE_BITS;
ringbuf->len = (end - begin - 1) << TARGET_PAGE_BITS;
gpadl_iter_init(&ringbuf->iter, gpadl, as, dir);
}
static int ringbufs_init(VMBusChannel *chan)
{
vmbus_ring_buffer *rb;
VMBusSendRingBuf *send_ringbuf = &chan->send_ringbuf;
VMBusRecvRingBuf *recv_ringbuf = &chan->recv_ringbuf;
if (chan->ringbuf_send_offset <= 1 ||
chan->gpadl->num_gfns <= chan->ringbuf_send_offset + 1) {
return -EINVAL;
}
ringbuf_init_common(&recv_ringbuf->common, chan->gpadl, chan->dev->dma_as,
DMA_DIRECTION_TO_DEVICE, 0, chan->ringbuf_send_offset);
ringbuf_init_common(&send_ringbuf->common, chan->gpadl, chan->dev->dma_as,
DMA_DIRECTION_FROM_DEVICE, chan->ringbuf_send_offset,
chan->gpadl->num_gfns);
send_ringbuf->wanted = 0;
send_ringbuf->reserved = 0;
rb = ringbuf_map_hdr(&recv_ringbuf->common);
if (!rb) {
return -EFAULT;
}
recv_ringbuf->rd_idx = recv_ringbuf->last_rd_idx = rb->read_index;
ringbuf_unmap_hdr(&recv_ringbuf->common, rb, false);
rb = ringbuf_map_hdr(&send_ringbuf->common);
if (!rb) {
return -EFAULT;
}
send_ringbuf->wr_idx = send_ringbuf->last_wr_idx = rb->write_index;
send_ringbuf->last_seen_rd_idx = rb->read_index;
rb->feature_bits |= VMBUS_RING_BUFFER_FEAT_PENDING_SZ;
ringbuf_unmap_hdr(&send_ringbuf->common, rb, true);
if (recv_ringbuf->rd_idx >= recv_ringbuf->common.len ||
send_ringbuf->wr_idx >= send_ringbuf->common.len) {
return -EOVERFLOW;
}
return 0;
}
/*
* Perform io between the GPADL-backed ringbuffer @ringbuf and @buf, wrapping
* around if needed.
* @len is assumed not to exceed the size of the ringbuffer, so only single
* wraparound is considered.
*/
static ssize_t ringbuf_io(VMBusRingBufCommon *ringbuf, void *buf, uint32_t len)
{
ssize_t ret1 = 0, ret2 = 0;
uint32_t remain = ringbuf->len + ringbuf->base - ringbuf->iter.off;
if (len >= remain) {
ret1 = gpadl_iter_io(&ringbuf->iter, buf, remain);
if (ret1 < 0) {
return ret1;
}
gpadl_iter_seek(&ringbuf->iter, ringbuf->base);
buf += remain;
len -= remain;
}
ret2 = gpadl_iter_io(&ringbuf->iter, buf, len);
if (ret2 < 0) {
return ret2;
}
return ret1 + ret2;
}
/*
* Position the circular iterator within @ringbuf to offset @new_off, wrapping
* around if needed.
* @new_off is assumed not to exceed twice the size of the ringbuffer, so only
* single wraparound is considered.
*/
static inline void ringbuf_seek(VMBusRingBufCommon *ringbuf, uint32_t new_off)
{
gpadl_iter_seek(&ringbuf->iter,
ringbuf->base + rb_idx_wrap(new_off, ringbuf->len));
}
static inline uint32_t ringbuf_tell(VMBusRingBufCommon *ringbuf)
{
return ringbuf->iter.off - ringbuf->base;
}
static inline void ringbuf_start_io(VMBusRingBufCommon *ringbuf)
{
gpadl_iter_start_io(&ringbuf->iter);
}
static inline void ringbuf_end_io(VMBusRingBufCommon *ringbuf)
{
gpadl_iter_end_io(&ringbuf->iter);
}
VMBusDevice *vmbus_channel_device(VMBusChannel *chan)
{
return chan->dev;
}
VMBusChannel *vmbus_device_channel(VMBusDevice *dev, uint32_t chan_idx)
{
if (chan_idx >= dev->num_channels) {
return NULL;
}
return &dev->channels[chan_idx];
}
uint32_t vmbus_channel_idx(VMBusChannel *chan)
{
return chan - chan->dev->channels;
}
void vmbus_channel_notify_host(VMBusChannel *chan)
{
event_notifier_set(&chan->notifier);
}
bool vmbus_channel_is_open(VMBusChannel *chan)
{
return chan->is_open;
}
/*
* Notify the guest side about the data to work on in the channel ring buffer.
* The notification is done by signaling a dedicated per-channel SynIC event
* flag (more recent guests) or setting a bit in the interrupt page and firing
* the VMBus SINT (older guests).
*/
static int vmbus_channel_notify_guest(VMBusChannel *chan)
{
int res = 0;
unsigned long *int_map, mask;
unsigned idx;
hwaddr addr = chan->vmbus->int_page_gpa;
hwaddr len = TARGET_PAGE_SIZE / 2, dirty = 0;
trace_vmbus_channel_notify_guest(chan->id);
if (!addr) {
return hyperv_set_event_flag(chan->notify_route, chan->id);
}
int_map = cpu_physical_memory_map(addr, &len, 1);
if (len != TARGET_PAGE_SIZE / 2) {
res = -ENXIO;
goto unmap;
}
idx = BIT_WORD(chan->id);
mask = BIT_MASK(chan->id);
if ((qatomic_fetch_or(&int_map[idx], mask) & mask) != mask) {
res = hyperv_sint_route_set_sint(chan->notify_route);
dirty = len;
}
unmap:
cpu_physical_memory_unmap(int_map, len, 1, dirty);
return res;
}
#define VMBUS_PKT_TRAILER sizeof(uint64_t)
static uint32_t vmbus_pkt_hdr_set_offsets(vmbus_packet_hdr *hdr,
uint32_t desclen, uint32_t msglen)
{
hdr->offset_qwords = sizeof(*hdr) / sizeof(uint64_t) +
DIV_ROUND_UP(desclen, sizeof(uint64_t));
hdr->len_qwords = hdr->offset_qwords +
DIV_ROUND_UP(msglen, sizeof(uint64_t));
return hdr->len_qwords * sizeof(uint64_t) + VMBUS_PKT_TRAILER;
}
/*
* Simplified ring buffer operation with paired barriers annotations in the
* producer and consumer loops:
*
* producer * consumer
* ~~~~~~~~ * ~~~~~~~~
* write pending_send_sz * read write_index
* smp_mb [A] * smp_mb [C]
* read read_index * read packet
* smp_mb [B] * read/write out-of-band data
* read/write out-of-band data * smp_mb [B]
* write packet * write read_index
* smp_mb [C] * smp_mb [A]
* write write_index * read pending_send_sz
* smp_wmb [D] * smp_rmb [D]
* write pending_send_sz * read write_index
* ... * ...
*/
static inline uint32_t ringbuf_send_avail(VMBusSendRingBuf *ringbuf)
{
/* don't trust guest data */
if (ringbuf->last_seen_rd_idx >= ringbuf->common.len) {
return 0;
}
return rb_idx_delta(ringbuf->wr_idx, ringbuf->last_seen_rd_idx,
ringbuf->common.len, false);
}
static ssize_t ringbuf_send_update_idx(VMBusChannel *chan)
{
VMBusSendRingBuf *ringbuf = &chan->send_ringbuf;
vmbus_ring_buffer *rb;
uint32_t written;
written = rb_idx_delta(ringbuf->last_wr_idx, ringbuf->wr_idx,
ringbuf->common.len, true);
if (!written) {
return 0;
}
rb = ringbuf_map_hdr(&ringbuf->common);
if (!rb) {
return -EFAULT;
}
ringbuf->reserved -= written;
/* prevent reorder with the data operation and packet write */
smp_mb(); /* barrier pair [C] */
rb->write_index = ringbuf->wr_idx;
/*
* If the producer earlier indicated that it wants to be notified when the
* consumer frees certain amount of space in the ring buffer, that amount
* is reduced by the size of the completed write.
*/
if (ringbuf->wanted) {
/* otherwise reservation would fail */
assert(ringbuf->wanted < written);
ringbuf->wanted -= written;
/* prevent reorder with write_index write */
smp_wmb(); /* barrier pair [D] */
rb->pending_send_sz = ringbuf->wanted;
}
/* prevent reorder with write_index or pending_send_sz write */
smp_mb(); /* barrier pair [A] */
ringbuf->last_seen_rd_idx = rb->read_index;
/*
* The consumer may have missed the reduction of pending_send_sz and skip
* notification, so re-check the blocking condition, and, if it's no longer
* true, ensure processing another iteration by simulating consumer's
* notification.
*/
if (ringbuf_send_avail(ringbuf) >= ringbuf->wanted) {
vmbus_channel_notify_host(chan);
}
/* skip notification by consumer's request */
if (rb->interrupt_mask) {
goto out;
}
/*
* The consumer hasn't caught up with the producer's previous state so it's
* not blocked.
* (last_seen_rd_idx comes from the guest but it's safe to use w/o
* validation here as it only affects notification.)
*/
if (rb_idx_delta(ringbuf->last_seen_rd_idx, ringbuf->wr_idx,
ringbuf->common.len, true) > written) {
goto out;
}
vmbus_channel_notify_guest(chan);
out:
ringbuf_unmap_hdr(&ringbuf->common, rb, true);
ringbuf->last_wr_idx = ringbuf->wr_idx;
return written;
}
int vmbus_channel_reserve(VMBusChannel *chan,
uint32_t desclen, uint32_t msglen)
{
VMBusSendRingBuf *ringbuf = &chan->send_ringbuf;
vmbus_ring_buffer *rb = NULL;
vmbus_packet_hdr hdr;
uint32_t needed = ringbuf->reserved +
vmbus_pkt_hdr_set_offsets(&hdr, desclen, msglen);
/* avoid touching the guest memory if possible */
if (likely(needed <= ringbuf_send_avail(ringbuf))) {
goto success;
}
rb = ringbuf_map_hdr(&ringbuf->common);
if (!rb) {
return -EFAULT;
}
/* fetch read index from guest memory and try again */
ringbuf->last_seen_rd_idx = rb->read_index;
if (likely(needed <= ringbuf_send_avail(ringbuf))) {
goto success;
}
rb->pending_send_sz = needed;
/*
* The consumer may have made progress and freed up some space before
* seeing updated pending_send_sz, so re-read read_index (preventing
* reorder with the pending_send_sz write) and try again.
*/
smp_mb(); /* barrier pair [A] */
ringbuf->last_seen_rd_idx = rb->read_index;
if (needed > ringbuf_send_avail(ringbuf)) {
goto out;
}
success:
ringbuf->reserved = needed;
needed = 0;
/* clear pending_send_sz if it was set */
if (ringbuf->wanted) {
if (!rb) {
rb = ringbuf_map_hdr(&ringbuf->common);
if (!rb) {
/* failure to clear pending_send_sz is non-fatal */
goto out;
}
}
rb->pending_send_sz = 0;
}
/* prevent reorder of the following data operation with read_index read */
smp_mb(); /* barrier pair [B] */
out:
if (rb) {
ringbuf_unmap_hdr(&ringbuf->common, rb, ringbuf->wanted == needed);
}
ringbuf->wanted = needed;
return needed ? -ENOSPC : 0;
}
ssize_t vmbus_channel_send(VMBusChannel *chan, uint16_t pkt_type,
void *desc, uint32_t desclen,
void *msg, uint32_t msglen,
bool need_comp, uint64_t transaction_id)
{
ssize_t ret = 0;
vmbus_packet_hdr hdr;
uint32_t totlen;
VMBusSendRingBuf *ringbuf = &chan->send_ringbuf;
if (!vmbus_channel_is_open(chan)) {
return -EINVAL;
}
totlen = vmbus_pkt_hdr_set_offsets(&hdr, desclen, msglen);
hdr.type = pkt_type;
hdr.flags = need_comp ? VMBUS_PACKET_FLAG_REQUEST_COMPLETION : 0;
hdr.transaction_id = transaction_id;
assert(totlen <= ringbuf->reserved);
ringbuf_start_io(&ringbuf->common);
ringbuf_seek(&ringbuf->common, ringbuf->wr_idx);
ret = ringbuf_io(&ringbuf->common, &hdr, sizeof(hdr));
if (ret < 0) {
goto out;
}
if (desclen) {
assert(desc);
ret = ringbuf_io(&ringbuf->common, desc, desclen);
if (ret < 0) {
goto out;
}
ringbuf_seek(&ringbuf->common,
ringbuf->wr_idx + hdr.offset_qwords * sizeof(uint64_t));
}
ret = ringbuf_io(&ringbuf->common, msg, msglen);
if (ret < 0) {
goto out;
}
ringbuf_seek(&ringbuf->common, ringbuf->wr_idx + totlen);
ringbuf->wr_idx = ringbuf_tell(&ringbuf->common);
ret = 0;
out:
ringbuf_end_io(&ringbuf->common);
if (ret) {
return ret;
}
return ringbuf_send_update_idx(chan);
}
ssize_t vmbus_channel_send_completion(VMBusChanReq *req,
void *msg, uint32_t msglen)
{
assert(req->need_comp);
return vmbus_channel_send(req->chan, VMBUS_PACKET_COMP, NULL, 0,
msg, msglen, false, req->transaction_id);
}
static int sgl_from_gpa_ranges(QEMUSGList *sgl, VMBusDevice *dev,
VMBusRingBufCommon *ringbuf, uint32_t len)
{
int ret;
vmbus_pkt_gpa_direct hdr;
hwaddr curaddr = 0;
hwaddr curlen = 0;
int num;
if (len < sizeof(hdr)) {
return -EIO;
}
ret = ringbuf_io(ringbuf, &hdr, sizeof(hdr));
if (ret < 0) {
return ret;
}
len -= sizeof(hdr);
num = (len - hdr.rangecount * sizeof(vmbus_gpa_range)) / sizeof(uint64_t);
if (num < 0) {
return -EIO;
}
qemu_sglist_init(sgl, DEVICE(dev), num, ringbuf->as);
for (; hdr.rangecount; hdr.rangecount--) {
vmbus_gpa_range range;
if (len < sizeof(range)) {
goto eio;
}
ret = ringbuf_io(ringbuf, &range, sizeof(range));
if (ret < 0) {
goto err;
}
len -= sizeof(range);
if (range.byte_offset & TARGET_PAGE_MASK) {
goto eio;
}
for (; range.byte_count; range.byte_offset = 0) {
uint64_t paddr;
uint32_t plen = MIN(range.byte_count,
TARGET_PAGE_SIZE - range.byte_offset);
if (len < sizeof(uint64_t)) {
goto eio;
}
ret = ringbuf_io(ringbuf, &paddr, sizeof(paddr));
if (ret < 0) {
goto err;
}
len -= sizeof(uint64_t);
paddr <<= TARGET_PAGE_BITS;
paddr |= range.byte_offset;
range.byte_count -= plen;
if (curaddr + curlen == paddr) {
/* consecutive fragments - join */
curlen += plen;
} else {
if (curlen) {
qemu_sglist_add(sgl, curaddr, curlen);
}
curaddr = paddr;
curlen = plen;
}
}
}
if (curlen) {
qemu_sglist_add(sgl, curaddr, curlen);
}
return 0;
eio:
ret = -EIO;
err:
qemu_sglist_destroy(sgl);
return ret;
}
static VMBusChanReq *vmbus_alloc_req(VMBusChannel *chan,
uint32_t size, uint16_t pkt_type,
uint32_t msglen, uint64_t transaction_id,
bool need_comp)
{
VMBusChanReq *req;
uint32_t msgoff = QEMU_ALIGN_UP(size, __alignof__(*req->msg));
uint32_t totlen = msgoff + msglen;
req = g_malloc0(totlen);
req->chan = chan;
req->pkt_type = pkt_type;
req->msg = (void *)req + msgoff;
req->msglen = msglen;
req->transaction_id = transaction_id;
req->need_comp = need_comp;
return req;
}
int vmbus_channel_recv_start(VMBusChannel *chan)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
vmbus_ring_buffer *rb;
rb = ringbuf_map_hdr(&ringbuf->common);
if (!rb) {
return -EFAULT;
}
ringbuf->last_seen_wr_idx = rb->write_index;
ringbuf_unmap_hdr(&ringbuf->common, rb, false);
if (ringbuf->last_seen_wr_idx >= ringbuf->common.len) {
return -EOVERFLOW;
}
/* prevent reorder of the following data operation with write_index read */
smp_mb(); /* barrier pair [C] */
return 0;
}
void *vmbus_channel_recv_peek(VMBusChannel *chan, uint32_t size)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
vmbus_packet_hdr hdr = {};
VMBusChanReq *req;
uint32_t avail;
uint32_t totlen, pktlen, msglen, msgoff, desclen;
assert(size >= sizeof(*req));
/* safe as last_seen_wr_idx is validated in vmbus_channel_recv_start */
avail = rb_idx_delta(ringbuf->rd_idx, ringbuf->last_seen_wr_idx,
ringbuf->common.len, true);
if (avail < sizeof(hdr)) {
return NULL;
}
ringbuf_seek(&ringbuf->common, ringbuf->rd_idx);
if (ringbuf_io(&ringbuf->common, &hdr, sizeof(hdr)) < 0) {
return NULL;
}
pktlen = hdr.len_qwords * sizeof(uint64_t);
totlen = pktlen + VMBUS_PKT_TRAILER;
if (totlen > avail) {
return NULL;
}
msgoff = hdr.offset_qwords * sizeof(uint64_t);
if (msgoff > pktlen || msgoff < sizeof(hdr)) {
error_report("%s: malformed packet: %u %u", __func__, msgoff, pktlen);
return NULL;
}
msglen = pktlen - msgoff;
req = vmbus_alloc_req(chan, size, hdr.type, msglen, hdr.transaction_id,
hdr.flags & VMBUS_PACKET_FLAG_REQUEST_COMPLETION);
switch (hdr.type) {
case VMBUS_PACKET_DATA_USING_GPA_DIRECT:
desclen = msgoff - sizeof(hdr);
if (sgl_from_gpa_ranges(&req->sgl, chan->dev, &ringbuf->common,
desclen) < 0) {
error_report("%s: failed to convert GPA ranges to SGL", __func__);
goto free_req;
}
break;
case VMBUS_PACKET_DATA_INBAND:
case VMBUS_PACKET_COMP:
break;
default:
error_report("%s: unexpected msg type: %x", __func__, hdr.type);
goto free_req;
}
ringbuf_seek(&ringbuf->common, ringbuf->rd_idx + msgoff);
if (ringbuf_io(&ringbuf->common, req->msg, msglen) < 0) {
goto free_req;
}
ringbuf_seek(&ringbuf->common, ringbuf->rd_idx + totlen);
return req;
free_req:
vmbus_free_req(req);
return NULL;
}
void vmbus_channel_recv_pop(VMBusChannel *chan)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
ringbuf->rd_idx = ringbuf_tell(&ringbuf->common);
}
ssize_t vmbus_channel_recv_done(VMBusChannel *chan)
{
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
vmbus_ring_buffer *rb;
uint32_t read;
read = rb_idx_delta(ringbuf->last_rd_idx, ringbuf->rd_idx,
ringbuf->common.len, true);
if (!read) {
return 0;
}
rb = ringbuf_map_hdr(&ringbuf->common);
if (!rb) {
return -EFAULT;
}
/* prevent reorder with the data operation and packet read */
smp_mb(); /* barrier pair [B] */
rb->read_index = ringbuf->rd_idx;
/* prevent reorder of the following pending_send_sz read */
smp_mb(); /* barrier pair [A] */
if (rb->interrupt_mask) {
goto out;
}
if (rb->feature_bits & VMBUS_RING_BUFFER_FEAT_PENDING_SZ) {
uint32_t wr_idx, wr_avail;
uint32_t wanted = rb->pending_send_sz;
if (!wanted) {
goto out;
}
/* prevent reorder with pending_send_sz read */
smp_rmb(); /* barrier pair [D] */
wr_idx = rb->write_index;
wr_avail = rb_idx_delta(wr_idx, ringbuf->rd_idx, ringbuf->common.len,
true);
/* the producer wasn't blocked on the consumer state */
if (wr_avail >= read + wanted) {
goto out;
}
/* there's not enough space for the producer to make progress */
if (wr_avail < wanted) {
goto out;
}
}
vmbus_channel_notify_guest(chan);
out:
ringbuf_unmap_hdr(&ringbuf->common, rb, true);
ringbuf->last_rd_idx = ringbuf->rd_idx;
return read;
}
void vmbus_free_req(void *req)
{
VMBusChanReq *r = req;
if (!req) {
return;
}
if (r->sgl.dev) {
qemu_sglist_destroy(&r->sgl);
}
g_free(req);
}
static const VMStateDescription vmstate_sgent = {
.name = "vmbus/sgentry",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_UINT64(base, ScatterGatherEntry),
VMSTATE_UINT64(len, ScatterGatherEntry),
VMSTATE_END_OF_LIST()
}
};
typedef struct VMBusChanReqSave {
uint16_t chan_idx;
uint16_t pkt_type;
uint32_t msglen;
void *msg;
uint64_t transaction_id;
bool need_comp;
uint32_t num;
ScatterGatherEntry *sgl;
} VMBusChanReqSave;
static const VMStateDescription vmstate_vmbus_chan_req = {
.name = "vmbus/vmbus_chan_req",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_UINT16(chan_idx, VMBusChanReqSave),
VMSTATE_UINT16(pkt_type, VMBusChanReqSave),
VMSTATE_UINT32(msglen, VMBusChanReqSave),
VMSTATE_VBUFFER_ALLOC_UINT32(msg, VMBusChanReqSave, 0, NULL, msglen),
VMSTATE_UINT64(transaction_id, VMBusChanReqSave),
VMSTATE_BOOL(need_comp, VMBusChanReqSave),
VMSTATE_UINT32(num, VMBusChanReqSave),
VMSTATE_STRUCT_VARRAY_POINTER_UINT32(sgl, VMBusChanReqSave, num,
vmstate_sgent, ScatterGatherEntry),
VMSTATE_END_OF_LIST()
}
};
void vmbus_save_req(QEMUFile *f, VMBusChanReq *req)
{
VMBusChanReqSave req_save;
req_save.chan_idx = req->chan->subchan_idx;
req_save.pkt_type = req->pkt_type;
req_save.msglen = req->msglen;
req_save.msg = req->msg;
req_save.transaction_id = req->transaction_id;
req_save.need_comp = req->need_comp;
req_save.num = req->sgl.nsg;
req_save.sgl = g_memdup(req->sgl.sg,
req_save.num * sizeof(ScatterGatherEntry));
vmstate_save_state(f, &vmstate_vmbus_chan_req, &req_save, NULL);
g_free(req_save.sgl);
}
void *vmbus_load_req(QEMUFile *f, VMBusDevice *dev, uint32_t size)
{
VMBusChanReqSave req_save;
VMBusChanReq *req = NULL;
VMBusChannel *chan = NULL;
uint32_t i;
vmstate_load_state(f, &vmstate_vmbus_chan_req, &req_save, 0);
if (req_save.chan_idx >= dev->num_channels) {
error_report("%s: %u(chan_idx) > %u(num_channels)", __func__,
req_save.chan_idx, dev->num_channels);
goto out;
}
chan = &dev->channels[req_save.chan_idx];
if (vmbus_channel_reserve(chan, 0, req_save.msglen)) {
goto out;
}
req = vmbus_alloc_req(chan, size, req_save.pkt_type, req_save.msglen,
req_save.transaction_id, req_save.need_comp);
if (req_save.msglen) {
memcpy(req->msg, req_save.msg, req_save.msglen);
}
for (i = 0; i < req_save.num; i++) {
qemu_sglist_add(&req->sgl, req_save.sgl[i].base, req_save.sgl[i].len);
}
out:
if (req_save.msglen) {
g_free(req_save.msg);
}
if (req_save.num) {
g_free(req_save.sgl);
}
return req;
}
static void channel_event_cb(EventNotifier *e)
{
VMBusChannel *chan = container_of(e, VMBusChannel, notifier);
if (event_notifier_test_and_clear(e)) {
/*
* All receives are supposed to happen within the device worker, so
* bracket it with ringbuf_start/end_io on the receive ringbuffer, and
* potentially reuse the cached mapping throughout the worker.
* Can't do this for sends as they may happen outside the device
* worker.
*/
VMBusRecvRingBuf *ringbuf = &chan->recv_ringbuf;
ringbuf_start_io(&ringbuf->common);
chan->notify_cb(chan);
ringbuf_end_io(&ringbuf->common);
}
}
static int alloc_chan_id(VMBus *vmbus)
{
int ret;
ret = find_next_zero_bit(vmbus->chanid_bitmap, VMBUS_CHANID_COUNT, 0);
if (ret == VMBUS_CHANID_COUNT) {
return -ENOMEM;
}
return ret + VMBUS_FIRST_CHANID;
}
static int register_chan_id(VMBusChannel *chan)
{
return test_and_set_bit(chan->id - VMBUS_FIRST_CHANID,
chan->vmbus->chanid_bitmap) ? -EEXIST : 0;
}
static void unregister_chan_id(VMBusChannel *chan)
{
clear_bit(chan->id - VMBUS_FIRST_CHANID, chan->vmbus->chanid_bitmap);
}
static uint32_t chan_connection_id(VMBusChannel *chan)
{
return VMBUS_CHAN_CONNECTION_OFFSET + chan->id;
}
static void init_channel(VMBus *vmbus, VMBusDevice *dev, VMBusDeviceClass *vdc,
VMBusChannel *chan, uint16_t idx, Error **errp)
{
int res;
chan->dev = dev;
chan->notify_cb = vdc->chan_notify_cb;
chan->subchan_idx = idx;
chan->vmbus = vmbus;
res = alloc_chan_id(vmbus);
if (res < 0) {
error_setg(errp, "no spare channel id");
return;
}
chan->id = res;
register_chan_id(chan);
/*
* The guest drivers depend on the device subchannels (idx #1+) to be
* offered after the primary channel (idx #0) of that device. To ensure
* that, record the channels on the channel list in the order they appear
* within the device.
*/
QTAILQ_INSERT_TAIL(&vmbus->channel_list, chan, link);
}
static void deinit_channel(VMBusChannel *chan)
{
assert(chan->state == VMCHAN_INIT);
QTAILQ_REMOVE(&chan->vmbus->channel_list, chan, link);
unregister_chan_id(chan);
}
static void create_channels(VMBus *vmbus, VMBusDevice *dev, Error **errp)
{
uint16_t i;
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(dev);
Error *err = NULL;
dev->num_channels = vdc->num_channels ? vdc->num_channels(dev) : 1;
if (dev->num_channels < 1) {
error_setg(errp, "invalid #channels: %u", dev->num_channels);
return;
}
dev->channels = g_new0(VMBusChannel, dev->num_channels);
for (i = 0; i < dev->num_channels; i++) {
init_channel(vmbus, dev, vdc, &dev->channels[i], i, &err);
if (err) {
goto err_init;
}
}
return;
err_init:
while (i--) {
deinit_channel(&dev->channels[i]);
}
error_propagate(errp, err);
}
static void free_channels(VMBusDevice *dev)
{
uint16_t i;
for (i = 0; i < dev->num_channels; i++) {
deinit_channel(&dev->channels[i]);
}
g_free(dev->channels);
}
static HvSintRoute *make_sint_route(VMBus *vmbus, uint32_t vp_index)
{
VMBusChannel *chan;
if (vp_index == vmbus->target_vp) {
hyperv_sint_route_ref(vmbus->sint_route);
return vmbus->sint_route;
}
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->target_vp == vp_index && vmbus_channel_is_open(chan)) {
hyperv_sint_route_ref(chan->notify_route);
return chan->notify_route;
}
}
return hyperv_sint_route_new(vp_index, VMBUS_SINT, NULL, NULL);
}
static void open_channel(VMBusChannel *chan)
{
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(chan->dev);
chan->gpadl = vmbus_get_gpadl(chan, chan->ringbuf_gpadl);
if (!chan->gpadl) {
return;
}
if (ringbufs_init(chan)) {
goto put_gpadl;
}
if (event_notifier_init(&chan->notifier, 0)) {
goto put_gpadl;
}
event_notifier_set_handler(&chan->notifier, channel_event_cb);
if (hyperv_set_event_flag_handler(chan_connection_id(chan),
&chan->notifier)) {
goto cleanup_notifier;
}
chan->notify_route = make_sint_route(chan->vmbus, chan->target_vp);
if (!chan->notify_route) {
goto clear_event_flag_handler;
}
if (vdc->open_channel && vdc->open_channel(chan)) {
goto unref_sint_route;
}
chan->is_open = true;
return;
unref_sint_route:
hyperv_sint_route_unref(chan->notify_route);
clear_event_flag_handler:
hyperv_set_event_flag_handler(chan_connection_id(chan), NULL);
cleanup_notifier:
event_notifier_set_handler(&chan->notifier, NULL);
event_notifier_cleanup(&chan->notifier);
put_gpadl:
vmbus_put_gpadl(chan->gpadl);
}
static void close_channel(VMBusChannel *chan)
{
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(chan->dev);
if (!chan->is_open) {
return;
}
if (vdc->close_channel) {
vdc->close_channel(chan);
}
hyperv_sint_route_unref(chan->notify_route);
hyperv_set_event_flag_handler(chan_connection_id(chan), NULL);
event_notifier_set_handler(&chan->notifier, NULL);
event_notifier_cleanup(&chan->notifier);
vmbus_put_gpadl(chan->gpadl);
chan->is_open = false;
}
static int channel_post_load(void *opaque, int version_id)
{
VMBusChannel *chan = opaque;
return register_chan_id(chan);
}
static const VMStateDescription vmstate_channel = {
.name = "vmbus/channel",
.version_id = 0,
.minimum_version_id = 0,
.post_load = channel_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT32(id, VMBusChannel),
VMSTATE_UINT16(subchan_idx, VMBusChannel),
VMSTATE_UINT32(open_id, VMBusChannel),
VMSTATE_UINT32(target_vp, VMBusChannel),
VMSTATE_UINT32(ringbuf_gpadl, VMBusChannel),
VMSTATE_UINT32(ringbuf_send_offset, VMBusChannel),
VMSTATE_UINT8(offer_state, VMBusChannel),
VMSTATE_UINT8(state, VMBusChannel),
VMSTATE_END_OF_LIST()
}
};
static VMBusChannel *find_channel(VMBus *vmbus, uint32_t id)
{
VMBusChannel *chan;
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->id == id) {
return chan;
}
}
return NULL;
}
static int enqueue_incoming_message(VMBus *vmbus,
const struct hyperv_post_message_input *msg)
{
int ret = 0;
uint8_t idx, prev_size;
qemu_mutex_lock(&vmbus->rx_queue_lock);
if (vmbus->rx_queue_size == HV_MSG_QUEUE_LEN) {
ret = -ENOBUFS;
goto out;
}
prev_size = vmbus->rx_queue_size;
idx = (vmbus->rx_queue_head + vmbus->rx_queue_size) % HV_MSG_QUEUE_LEN;
memcpy(&vmbus->rx_queue[idx], msg, sizeof(*msg));
vmbus->rx_queue_size++;
/* only need to resched if the queue was empty before */
if (!prev_size) {
vmbus_resched(vmbus);
}
out:
qemu_mutex_unlock(&vmbus->rx_queue_lock);
return ret;
}
static uint16_t vmbus_recv_message(const struct hyperv_post_message_input *msg,
void *data)
{
VMBus *vmbus = data;
struct vmbus_message_header *vmbus_msg;
if (msg->message_type != HV_MESSAGE_VMBUS) {
return HV_STATUS_INVALID_HYPERCALL_INPUT;
}
if (msg->payload_size < sizeof(struct vmbus_message_header)) {
return HV_STATUS_INVALID_HYPERCALL_INPUT;
}
vmbus_msg = (struct vmbus_message_header *)msg->payload;
trace_vmbus_recv_message(vmbus_msg->message_type, msg->payload_size);
if (vmbus_msg->message_type == VMBUS_MSG_INVALID ||
vmbus_msg->message_type >= VMBUS_MSG_COUNT) {
error_report("vmbus: unknown message type %#x",
vmbus_msg->message_type);
return HV_STATUS_INVALID_HYPERCALL_INPUT;
}
if (enqueue_incoming_message(vmbus, msg)) {
return HV_STATUS_INSUFFICIENT_BUFFERS;
}
return HV_STATUS_SUCCESS;
}
static bool vmbus_initialized(VMBus *vmbus)
{
return vmbus->version > 0 && vmbus->version <= VMBUS_VERSION_CURRENT;
}
static void vmbus_reset_all(VMBus *vmbus)
{
qbus_reset_all(BUS(vmbus));
}
static void post_msg(VMBus *vmbus, void *msgdata, uint32_t msglen)
{
int ret;
struct hyperv_message msg = {
.header.message_type = HV_MESSAGE_VMBUS,
};
assert(!vmbus->msg_in_progress);
assert(msglen <= sizeof(msg.payload));
assert(msglen >= sizeof(struct vmbus_message_header));
vmbus->msg_in_progress = true;
trace_vmbus_post_msg(((struct vmbus_message_header *)msgdata)->message_type,
msglen);
memcpy(msg.payload, msgdata, msglen);
msg.header.payload_size = ROUND_UP(msglen, VMBUS_MESSAGE_SIZE_ALIGN);
ret = hyperv_post_msg(vmbus->sint_route, &msg);
if (ret == 0 || ret == -EAGAIN) {
return;
}
error_report("message delivery fatal failure: %d; aborting vmbus", ret);
vmbus_reset_all(vmbus);
}
static int vmbus_init(VMBus *vmbus)
{
if (vmbus->target_vp != (uint32_t)-1) {
vmbus->sint_route = hyperv_sint_route_new(vmbus->target_vp, VMBUS_SINT,
vmbus_msg_cb, vmbus);
if (!vmbus->sint_route) {
error_report("failed to set up SINT route");
return -ENOMEM;
}
}
return 0;
}
static void vmbus_deinit(VMBus *vmbus)
{
VMBusGpadl *gpadl, *tmp_gpadl;
VMBusChannel *chan;
QTAILQ_FOREACH_SAFE(gpadl, &vmbus->gpadl_list, link, tmp_gpadl) {
if (gpadl->state == VMGPADL_TORNDOWN) {
continue;
}
vmbus_put_gpadl(gpadl);
}
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
chan->offer_state = VMOFFER_INIT;
}
hyperv_sint_route_unref(vmbus->sint_route);
vmbus->sint_route = NULL;
vmbus->int_page_gpa = 0;
vmbus->target_vp = (uint32_t)-1;
vmbus->version = 0;
vmbus->state = VMBUS_LISTEN;
vmbus->msg_in_progress = false;
}
static void handle_initiate_contact(VMBus *vmbus,
vmbus_message_initiate_contact *msg,
uint32_t msglen)
{
if (msglen < sizeof(*msg)) {
return;
}
trace_vmbus_initiate_contact(msg->version_requested >> 16,
msg->version_requested & 0xffff,
msg->target_vcpu, msg->monitor_page1,
msg->monitor_page2, msg->interrupt_page);
/*
* Reset vmbus on INITIATE_CONTACT regardless of its previous state.
* Useful, in particular, with vmbus-aware BIOS which can't shut vmbus down
* before handing over to OS loader.
*/
vmbus_reset_all(vmbus);
vmbus->target_vp = msg->target_vcpu;
vmbus->version = msg->version_requested;
if (vmbus->version < VMBUS_VERSION_WIN8) {
/* linux passes interrupt page even when it doesn't need it */
vmbus->int_page_gpa = msg->interrupt_page;
}
vmbus->state = VMBUS_HANDSHAKE;
if (vmbus_init(vmbus)) {
error_report("failed to init vmbus; aborting");
vmbus_deinit(vmbus);
return;
}
}
static void send_handshake(VMBus *vmbus)
{
struct vmbus_message_version_response msg = {
.header.message_type = VMBUS_MSG_VERSION_RESPONSE,
.version_supported = vmbus_initialized(vmbus),
};
post_msg(vmbus, &msg, sizeof(msg));
}
static void handle_request_offers(VMBus *vmbus, void *msgdata, uint32_t msglen)
{
VMBusChannel *chan;
if (!vmbus_initialized(vmbus)) {
return;
}
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->offer_state == VMOFFER_INIT) {
chan->offer_state = VMOFFER_SENDING;
break;
}
}
vmbus->state = VMBUS_OFFER;
}
static void send_offer(VMBus *vmbus)
{
VMBusChannel *chan;
struct vmbus_message_header alloffers_msg = {
.message_type = VMBUS_MSG_ALLOFFERS_DELIVERED,
};
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->offer_state == VMOFFER_SENDING) {
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(chan->dev);
/* Hyper-V wants LE GUIDs */
QemuUUID classid = qemu_uuid_bswap(vdc->classid);
QemuUUID instanceid = qemu_uuid_bswap(chan->dev->instanceid);
struct vmbus_message_offer_channel msg = {
.header.message_type = VMBUS_MSG_OFFERCHANNEL,
.child_relid = chan->id,
.connection_id = chan_connection_id(chan),
.channel_flags = vdc->channel_flags,
.mmio_size_mb = vdc->mmio_size_mb,
.sub_channel_index = vmbus_channel_idx(chan),
.interrupt_flags = VMBUS_OFFER_INTERRUPT_DEDICATED,
};
memcpy(msg.type_uuid, &classid, sizeof(classid));
memcpy(msg.instance_uuid, &instanceid, sizeof(instanceid));
trace_vmbus_send_offer(chan->id, chan->dev);
post_msg(vmbus, &msg, sizeof(msg));
return;
}
}
/* no more offers, send terminator message */
trace_vmbus_terminate_offers();
post_msg(vmbus, &alloffers_msg, sizeof(alloffers_msg));
}
static bool complete_offer(VMBus *vmbus)
{
VMBusChannel *chan;
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->offer_state == VMOFFER_SENDING) {
chan->offer_state = VMOFFER_SENT;
goto next_offer;
}
}
/*
* no transitioning channels found so this is completing the terminator
* message, and vmbus can move to the next state
*/
return true;
next_offer:
/* try to mark another channel for offering */
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->offer_state == VMOFFER_INIT) {
chan->offer_state = VMOFFER_SENDING;
break;
}
}
/*
* if an offer has been sent there are more offers or the terminator yet to
* send, so no state transition for vmbus
*/
return false;
}
static void handle_gpadl_header(VMBus *vmbus, vmbus_message_gpadl_header *msg,
uint32_t msglen)
{
VMBusGpadl *gpadl;
uint32_t num_gfns, i;
/* must include at least one gpa range */
if (msglen < sizeof(*msg) + sizeof(msg->range[0]) ||
!vmbus_initialized(vmbus)) {
return;
}
num_gfns = (msg->range_buflen - msg->rangecount * sizeof(msg->range[0])) /
sizeof(msg->range[0].pfn_array[0]);
trace_vmbus_gpadl_header(msg->gpadl_id, num_gfns);
/*
* In theory the GPADL_HEADER message can define a GPADL with multiple GPA
* ranges each with arbitrary size and alignment. However in practice only
* single-range page-aligned GPADLs have been observed so just ignore
* anything else and simplify things greatly.
*/
if (msg->rangecount != 1 || msg->range[0].byte_offset ||
(msg->range[0].byte_count != (num_gfns << TARGET_PAGE_BITS))) {
return;
}
/* ignore requests to create already existing GPADLs */
if (find_gpadl(vmbus, msg->gpadl_id)) {
return;
}
gpadl = create_gpadl(vmbus, msg->gpadl_id, msg->child_relid, num_gfns);
for (i = 0; i < num_gfns &&
(void *)&msg->range[0].pfn_array[i + 1] <= (void *)msg + msglen;
i++) {
gpadl->gfns[gpadl->seen_gfns++] = msg->range[0].pfn_array[i];
}
if (gpadl_full(gpadl)) {
vmbus->state = VMBUS_CREATE_GPADL;
}
}
static void handle_gpadl_body(VMBus *vmbus, vmbus_message_gpadl_body *msg,
uint32_t msglen)
{
VMBusGpadl *gpadl;
uint32_t num_gfns_left, i;
if (msglen < sizeof(*msg) || !vmbus_initialized(vmbus)) {
return;
}
trace_vmbus_gpadl_body(msg->gpadl_id);
gpadl = find_gpadl(vmbus, msg->gpadl_id);
if (!gpadl) {
return;
}
num_gfns_left = gpadl->num_gfns - gpadl->seen_gfns;
assert(num_gfns_left);
for (i = 0; i < num_gfns_left &&
(void *)&msg->pfn_array[i + 1] <= (void *)msg + msglen; i++) {
gpadl->gfns[gpadl->seen_gfns++] = msg->pfn_array[i];
}
if (gpadl_full(gpadl)) {
vmbus->state = VMBUS_CREATE_GPADL;
}
}
static void send_create_gpadl(VMBus *vmbus)
{
VMBusGpadl *gpadl;
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
if (gpadl_full(gpadl) && gpadl->state == VMGPADL_INIT) {
struct vmbus_message_gpadl_created msg = {
.header.message_type = VMBUS_MSG_GPADL_CREATED,
.gpadl_id = gpadl->id,
.child_relid = gpadl->child_relid,
};
trace_vmbus_gpadl_created(gpadl->id);
post_msg(vmbus, &msg, sizeof(msg));
return;
}
}
assert(false);
}
static bool complete_create_gpadl(VMBus *vmbus)
{
VMBusGpadl *gpadl;
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
if (gpadl_full(gpadl) && gpadl->state == VMGPADL_INIT) {
gpadl->state = VMGPADL_ALIVE;
return true;
}
}
assert(false);
return false;
}
static void handle_gpadl_teardown(VMBus *vmbus,
vmbus_message_gpadl_teardown *msg,
uint32_t msglen)
{
VMBusGpadl *gpadl;
if (msglen < sizeof(*msg) || !vmbus_initialized(vmbus)) {
return;
}
trace_vmbus_gpadl_teardown(msg->gpadl_id);
gpadl = find_gpadl(vmbus, msg->gpadl_id);
if (!gpadl || gpadl->state == VMGPADL_TORNDOWN) {
return;
}
gpadl->state = VMGPADL_TEARINGDOWN;
vmbus->state = VMBUS_TEARDOWN_GPADL;
}
static void send_teardown_gpadl(VMBus *vmbus)
{
VMBusGpadl *gpadl;
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
if (gpadl->state == VMGPADL_TEARINGDOWN) {
struct vmbus_message_gpadl_torndown msg = {
.header.message_type = VMBUS_MSG_GPADL_TORNDOWN,
.gpadl_id = gpadl->id,
};
trace_vmbus_gpadl_torndown(gpadl->id);
post_msg(vmbus, &msg, sizeof(msg));
return;
}
}
assert(false);
}
static bool complete_teardown_gpadl(VMBus *vmbus)
{
VMBusGpadl *gpadl;
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
if (gpadl->state == VMGPADL_TEARINGDOWN) {
gpadl->state = VMGPADL_TORNDOWN;
vmbus_put_gpadl(gpadl);
return true;
}
}
assert(false);
return false;
}
static void handle_open_channel(VMBus *vmbus, vmbus_message_open_channel *msg,
uint32_t msglen)
{
VMBusChannel *chan;
if (msglen < sizeof(*msg) || !vmbus_initialized(vmbus)) {
return;
}
trace_vmbus_open_channel(msg->child_relid, msg->ring_buffer_gpadl_id,
msg->target_vp);
chan = find_channel(vmbus, msg->child_relid);
if (!chan || chan->state != VMCHAN_INIT) {
return;
}
chan->ringbuf_gpadl = msg->ring_buffer_gpadl_id;
chan->ringbuf_send_offset = msg->ring_buffer_offset;
chan->target_vp = msg->target_vp;
chan->open_id = msg->open_id;
open_channel(chan);
chan->state = VMCHAN_OPENING;
vmbus->state = VMBUS_OPEN_CHANNEL;
}
static void send_open_channel(VMBus *vmbus)
{
VMBusChannel *chan;
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->state == VMCHAN_OPENING) {
struct vmbus_message_open_result msg = {
.header.message_type = VMBUS_MSG_OPENCHANNEL_RESULT,
.child_relid = chan->id,
.open_id = chan->open_id,
.status = !vmbus_channel_is_open(chan),
};
trace_vmbus_channel_open(chan->id, msg.status);
post_msg(vmbus, &msg, sizeof(msg));
return;
}
}
assert(false);
}
static bool complete_open_channel(VMBus *vmbus)
{
VMBusChannel *chan;
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->state == VMCHAN_OPENING) {
if (vmbus_channel_is_open(chan)) {
chan->state = VMCHAN_OPEN;
/*
* simulate guest notification of ringbuffer space made
* available, for the channel protocols where the host
* initiates the communication
*/
vmbus_channel_notify_host(chan);
} else {
chan->state = VMCHAN_INIT;
}
return true;
}
}
assert(false);
return false;
}
static void vdev_reset_on_close(VMBusDevice *vdev)
{
uint16_t i;
for (i = 0; i < vdev->num_channels; i++) {
if (vmbus_channel_is_open(&vdev->channels[i])) {
return;
}
}
/* all channels closed -- reset device */
qdev_reset_all(DEVICE(vdev));
}
static void handle_close_channel(VMBus *vmbus, vmbus_message_close_channel *msg,
uint32_t msglen)
{
VMBusChannel *chan;
if (msglen < sizeof(*msg) || !vmbus_initialized(vmbus)) {
return;
}
trace_vmbus_close_channel(msg->child_relid);
chan = find_channel(vmbus, msg->child_relid);
if (!chan) {
return;
}
close_channel(chan);
chan->state = VMCHAN_INIT;
vdev_reset_on_close(chan->dev);
}
static void handle_unload(VMBus *vmbus, void *msg, uint32_t msglen)
{
vmbus->state = VMBUS_UNLOAD;
}
static void send_unload(VMBus *vmbus)
{
vmbus_message_header msg = {
.message_type = VMBUS_MSG_UNLOAD_RESPONSE,
};
qemu_mutex_lock(&vmbus->rx_queue_lock);
vmbus->rx_queue_size = 0;
qemu_mutex_unlock(&vmbus->rx_queue_lock);
post_msg(vmbus, &msg, sizeof(msg));
return;
}
static bool complete_unload(VMBus *vmbus)
{
vmbus_reset_all(vmbus);
return true;
}
static void process_message(VMBus *vmbus)
{
struct hyperv_post_message_input *hv_msg;
struct vmbus_message_header *msg;
void *msgdata;
uint32_t msglen;
qemu_mutex_lock(&vmbus->rx_queue_lock);
if (!vmbus->rx_queue_size) {
goto unlock;
}
hv_msg = &vmbus->rx_queue[vmbus->rx_queue_head];
msglen = hv_msg->payload_size;
if (msglen < sizeof(*msg)) {
goto out;
}
msgdata = hv_msg->payload;
msg = (struct vmbus_message_header *)msgdata;
trace_vmbus_process_incoming_message(msg->message_type);
switch (msg->message_type) {
case VMBUS_MSG_INITIATE_CONTACT:
handle_initiate_contact(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_REQUESTOFFERS:
handle_request_offers(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_GPADL_HEADER:
handle_gpadl_header(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_GPADL_BODY:
handle_gpadl_body(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_GPADL_TEARDOWN:
handle_gpadl_teardown(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_OPENCHANNEL:
handle_open_channel(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_CLOSECHANNEL:
handle_close_channel(vmbus, msgdata, msglen);
break;
case VMBUS_MSG_UNLOAD:
handle_unload(vmbus, msgdata, msglen);
break;
default:
error_report("unknown message type %#x", msg->message_type);
break;
}
out:
vmbus->rx_queue_size--;
vmbus->rx_queue_head++;
vmbus->rx_queue_head %= HV_MSG_QUEUE_LEN;
vmbus_resched(vmbus);
unlock:
qemu_mutex_unlock(&vmbus->rx_queue_lock);
}
static const struct {
void (*run)(VMBus *vmbus);
bool (*complete)(VMBus *vmbus);
} state_runner[] = {
[VMBUS_LISTEN] = {process_message, NULL},
[VMBUS_HANDSHAKE] = {send_handshake, NULL},
[VMBUS_OFFER] = {send_offer, complete_offer},
[VMBUS_CREATE_GPADL] = {send_create_gpadl, complete_create_gpadl},
[VMBUS_TEARDOWN_GPADL] = {send_teardown_gpadl, complete_teardown_gpadl},
[VMBUS_OPEN_CHANNEL] = {send_open_channel, complete_open_channel},
[VMBUS_UNLOAD] = {send_unload, complete_unload},
};
static void vmbus_do_run(VMBus *vmbus)
{
if (vmbus->msg_in_progress) {
return;
}
assert(vmbus->state < VMBUS_STATE_MAX);
assert(state_runner[vmbus->state].run);
state_runner[vmbus->state].run(vmbus);
}
static void vmbus_run(void *opaque)
{
VMBus *vmbus = opaque;
/* make sure no recursion happens (e.g. due to recursive aio_poll()) */
if (vmbus->in_progress) {
return;
}
vmbus->in_progress = true;
/*
* FIXME: if vmbus_resched() is called from within vmbus_do_run(), it
* should go *after* the code that can result in aio_poll; otherwise
* reschedules can be missed. No idea how to enforce that.
*/
vmbus_do_run(vmbus);
vmbus->in_progress = false;
}
static void vmbus_msg_cb(void *data, int status)
{
VMBus *vmbus = data;
bool (*complete)(VMBus *vmbus);
assert(vmbus->msg_in_progress);
trace_vmbus_msg_cb(status);
if (status == -EAGAIN) {
goto out;
}
if (status) {
error_report("message delivery fatal failure: %d; aborting vmbus",
status);
vmbus_reset_all(vmbus);
return;
}
assert(vmbus->state < VMBUS_STATE_MAX);
complete = state_runner[vmbus->state].complete;
if (!complete || complete(vmbus)) {
vmbus->state = VMBUS_LISTEN;
}
out:
vmbus->msg_in_progress = false;
vmbus_resched(vmbus);
}
static void vmbus_resched(VMBus *vmbus)
{
aio_bh_schedule_oneshot(qemu_get_aio_context(), vmbus_run, vmbus);
}
static void vmbus_signal_event(EventNotifier *e)
{
VMBusChannel *chan;
VMBus *vmbus = container_of(e, VMBus, notifier);
unsigned long *int_map;
hwaddr addr, len;
bool is_dirty = false;
if (!event_notifier_test_and_clear(e)) {
return;
}
trace_vmbus_signal_event();
if (!vmbus->int_page_gpa) {
return;
}
addr = vmbus->int_page_gpa + TARGET_PAGE_SIZE / 2;
len = TARGET_PAGE_SIZE / 2;
int_map = cpu_physical_memory_map(addr, &len, 1);
if (len != TARGET_PAGE_SIZE / 2) {
goto unmap;
}
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (bitmap_test_and_clear_atomic(int_map, chan->id, 1)) {
if (!vmbus_channel_is_open(chan)) {
continue;
}
vmbus_channel_notify_host(chan);
is_dirty = true;
}
}
unmap:
cpu_physical_memory_unmap(int_map, len, 1, is_dirty);
}
static void vmbus_dev_realize(DeviceState *dev, Error **errp)
{
VMBusDevice *vdev = VMBUS_DEVICE(dev);
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(vdev);
VMBus *vmbus = VMBUS(qdev_get_parent_bus(dev));
BusChild *child;
Error *err = NULL;
char idstr[UUID_FMT_LEN + 1];
assert(!qemu_uuid_is_null(&vdev->instanceid));
if (!qemu_uuid_is_null(&vdc->instanceid)) {
/* Class wants to only have a single instance with a fixed UUID */
if (!qemu_uuid_is_equal(&vdev->instanceid, &vdc->instanceid)) {
error_setg(&err, "instance id can't be changed");
goto error_out;
}
}
/* Check for instance id collision for this class id */
QTAILQ_FOREACH(child, &BUS(vmbus)->children, sibling) {
VMBusDevice *child_dev = VMBUS_DEVICE(child->child);
if (child_dev == vdev) {
continue;
}
if (qemu_uuid_is_equal(&child_dev->instanceid, &vdev->instanceid)) {
qemu_uuid_unparse(&vdev->instanceid, idstr);
error_setg(&err, "duplicate vmbus device instance id %s", idstr);
goto error_out;
}
}
vdev->dma_as = &address_space_memory;
create_channels(vmbus, vdev, &err);
if (err) {
goto error_out;
}
if (vdc->vmdev_realize) {
vdc->vmdev_realize(vdev, &err);
if (err) {
goto err_vdc_realize;
}
}
return;
err_vdc_realize:
free_channels(vdev);
error_out:
error_propagate(errp, err);
}
static void vmbus_dev_reset(DeviceState *dev)
{
uint16_t i;
VMBusDevice *vdev = VMBUS_DEVICE(dev);
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(vdev);
if (vdev->channels) {
for (i = 0; i < vdev->num_channels; i++) {
VMBusChannel *chan = &vdev->channels[i];
close_channel(chan);
chan->state = VMCHAN_INIT;
}
}
if (vdc->vmdev_reset) {
vdc->vmdev_reset(vdev);
}
}
static void vmbus_dev_unrealize(DeviceState *dev)
{
VMBusDevice *vdev = VMBUS_DEVICE(dev);
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(vdev);
if (vdc->vmdev_unrealize) {
vdc->vmdev_unrealize(vdev);
}
free_channels(vdev);
}
static Property vmbus_dev_props[] = {
DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid),
DEFINE_PROP_END_OF_LIST()
};
static void vmbus_dev_class_init(ObjectClass *klass, void *data)
{
DeviceClass *kdev = DEVICE_CLASS(klass);
device_class_set_props(kdev, vmbus_dev_props);
kdev->bus_type = TYPE_VMBUS;
kdev->realize = vmbus_dev_realize;
kdev->unrealize = vmbus_dev_unrealize;
kdev->reset = vmbus_dev_reset;
}
static void vmbus_dev_instance_init(Object *obj)
{
VMBusDevice *vdev = VMBUS_DEVICE(obj);
VMBusDeviceClass *vdc = VMBUS_DEVICE_GET_CLASS(vdev);
if (!qemu_uuid_is_null(&vdc->instanceid)) {
/* Class wants to only have a single instance with a fixed UUID */
vdev->instanceid = vdc->instanceid;
}
}
const VMStateDescription vmstate_vmbus_dev = {
.name = TYPE_VMBUS_DEVICE,
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_UINT8_ARRAY(instanceid.data, VMBusDevice, 16),
VMSTATE_UINT16(num_channels, VMBusDevice),
VMSTATE_STRUCT_VARRAY_POINTER_UINT16(channels, VMBusDevice,
num_channels, vmstate_channel,
VMBusChannel),
VMSTATE_END_OF_LIST()
}
};
/* vmbus generic device base */
static const TypeInfo vmbus_dev_type_info = {
.name = TYPE_VMBUS_DEVICE,
.parent = TYPE_DEVICE,
.abstract = true,
.instance_size = sizeof(VMBusDevice),
.class_size = sizeof(VMBusDeviceClass),
.class_init = vmbus_dev_class_init,
.instance_init = vmbus_dev_instance_init,
};
static void vmbus_realize(BusState *bus, Error **errp)
{
int ret = 0;
Error *local_err = NULL;
VMBus *vmbus = VMBUS(bus);
qemu_mutex_init(&vmbus->rx_queue_lock);
QTAILQ_INIT(&vmbus->gpadl_list);
QTAILQ_INIT(&vmbus->channel_list);
ret = hyperv_set_msg_handler(VMBUS_MESSAGE_CONNECTION_ID,
vmbus_recv_message, vmbus);
if (ret != 0) {
error_setg(&local_err, "hyperv set message handler failed: %d", ret);
goto error_out;
}
ret = event_notifier_init(&vmbus->notifier, 0);
if (ret != 0) {
error_setg(&local_err, "event notifier failed to init with %d", ret);
goto remove_msg_handler;
}
event_notifier_set_handler(&vmbus->notifier, vmbus_signal_event);
ret = hyperv_set_event_flag_handler(VMBUS_EVENT_CONNECTION_ID,
&vmbus->notifier);
if (ret != 0) {
error_setg(&local_err, "hyperv set event handler failed with %d", ret);
goto clear_event_notifier;
}
return;
clear_event_notifier:
event_notifier_cleanup(&vmbus->notifier);
remove_msg_handler:
hyperv_set_msg_handler(VMBUS_MESSAGE_CONNECTION_ID, NULL, NULL);
error_out:
qemu_mutex_destroy(&vmbus->rx_queue_lock);
error_propagate(errp, local_err);
}
static void vmbus_unrealize(BusState *bus)
{
VMBus *vmbus = VMBUS(bus);
hyperv_set_msg_handler(VMBUS_MESSAGE_CONNECTION_ID, NULL, NULL);
hyperv_set_event_flag_handler(VMBUS_EVENT_CONNECTION_ID, NULL);
event_notifier_cleanup(&vmbus->notifier);
qemu_mutex_destroy(&vmbus->rx_queue_lock);
}
static void vmbus_reset(BusState *bus)
{
vmbus_deinit(VMBUS(bus));
}
static char *vmbus_get_dev_path(DeviceState *dev)
{
BusState *bus = qdev_get_parent_bus(dev);
return qdev_get_dev_path(bus->parent);
}
static char *vmbus_get_fw_dev_path(DeviceState *dev)
{
VMBusDevice *vdev = VMBUS_DEVICE(dev);
char uuid[UUID_FMT_LEN + 1];
qemu_uuid_unparse(&vdev->instanceid, uuid);
return g_strdup_printf("%s@%s", qdev_fw_name(dev), uuid);
}
static void vmbus_class_init(ObjectClass *klass, void *data)
{
BusClass *k = BUS_CLASS(klass);
k->get_dev_path = vmbus_get_dev_path;
k->get_fw_dev_path = vmbus_get_fw_dev_path;
k->realize = vmbus_realize;
k->unrealize = vmbus_unrealize;
k->reset = vmbus_reset;
}
static int vmbus_pre_load(void *opaque)
{
VMBusChannel *chan;
VMBus *vmbus = VMBUS(opaque);
/*
* channel IDs allocated by the source will come in the migration stream
* for each channel, so clean up the ones allocated at realize
*/
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
unregister_chan_id(chan);
}
return 0;
}
static int vmbus_post_load(void *opaque, int version_id)
{
int ret;
VMBus *vmbus = VMBUS(opaque);
VMBusGpadl *gpadl;
VMBusChannel *chan;
ret = vmbus_init(vmbus);
if (ret) {
return ret;
}
QTAILQ_FOREACH(gpadl, &vmbus->gpadl_list, link) {
gpadl->vmbus = vmbus;
gpadl->refcount = 1;
}
/*
* reopening channels depends on initialized vmbus so it's done here
* instead of channel_post_load()
*/
QTAILQ_FOREACH(chan, &vmbus->channel_list, link) {
if (chan->state == VMCHAN_OPENING || chan->state == VMCHAN_OPEN) {
open_channel(chan);
}
if (chan->state != VMCHAN_OPEN) {
continue;
}
if (!vmbus_channel_is_open(chan)) {
/* reopen failed, abort loading */
return -1;
}
/* resume processing on the guest side if it missed the notification */
hyperv_sint_route_set_sint(chan->notify_route);
/* ditto on the host side */
vmbus_channel_notify_host(chan);
}
vmbus_resched(vmbus);
return 0;
}
static const VMStateDescription vmstate_post_message_input = {
.name = "vmbus/hyperv_post_message_input",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
/*
* skip connection_id and message_type as they are validated before
* queueing and ignored on dequeueing
*/
VMSTATE_UINT32(payload_size, struct hyperv_post_message_input),
VMSTATE_UINT8_ARRAY(payload, struct hyperv_post_message_input,
HV_MESSAGE_PAYLOAD_SIZE),
VMSTATE_END_OF_LIST()
}
};
static bool vmbus_rx_queue_needed(void *opaque)
{
VMBus *vmbus = VMBUS(opaque);
return vmbus->rx_queue_size;
}
static const VMStateDescription vmstate_rx_queue = {
.name = "vmbus/rx_queue",
.version_id = 0,
.minimum_version_id = 0,
.needed = vmbus_rx_queue_needed,
.fields = (VMStateField[]) {
VMSTATE_UINT8(rx_queue_head, VMBus),
VMSTATE_UINT8(rx_queue_size, VMBus),
VMSTATE_STRUCT_ARRAY(rx_queue, VMBus,
HV_MSG_QUEUE_LEN, 0,
vmstate_post_message_input,
struct hyperv_post_message_input),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_vmbus = {
.name = TYPE_VMBUS,
.version_id = 0,
.minimum_version_id = 0,
.pre_load = vmbus_pre_load,
.post_load = vmbus_post_load,
.fields = (VMStateField[]) {
VMSTATE_UINT8(state, VMBus),
VMSTATE_UINT32(version, VMBus),
VMSTATE_UINT32(target_vp, VMBus),
VMSTATE_UINT64(int_page_gpa, VMBus),
VMSTATE_QTAILQ_V(gpadl_list, VMBus, 0,
vmstate_gpadl, VMBusGpadl, link),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * []) {
&vmstate_rx_queue,
NULL
}
};
static const TypeInfo vmbus_type_info = {
.name = TYPE_VMBUS,
.parent = TYPE_BUS,
.instance_size = sizeof(VMBus),
.class_init = vmbus_class_init,
};
static void vmbus_bridge_realize(DeviceState *dev, Error **errp)
{
VMBusBridge *bridge = VMBUS_BRIDGE(dev);
/*
* here there's at least one vmbus bridge that is being realized, so
* vmbus_bridge_find can only return NULL if it's not unique
*/
if (!vmbus_bridge_find()) {
error_setg(errp, "there can be at most one %s in the system",
TYPE_VMBUS_BRIDGE);
return;
}
if (!hyperv_is_synic_enabled()) {
error_report("VMBus requires usable Hyper-V SynIC and VP_INDEX");
return;
}
bridge->bus = VMBUS(qbus_create(TYPE_VMBUS, dev, "vmbus"));
}
static char *vmbus_bridge_ofw_unit_address(const SysBusDevice *dev)
{
/* there can be only one VMBus */
return g_strdup("0");
}
static const VMStateDescription vmstate_vmbus_bridge = {
.name = TYPE_VMBUS_BRIDGE,
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_STRUCT_POINTER(bus, VMBusBridge, vmstate_vmbus, VMBus),
VMSTATE_END_OF_LIST()
},
};
static Property vmbus_bridge_props[] = {
DEFINE_PROP_UINT8("irq", VMBusBridge, irq, 7),
DEFINE_PROP_END_OF_LIST()
};
static void vmbus_bridge_class_init(ObjectClass *klass, void *data)
{
DeviceClass *k = DEVICE_CLASS(klass);
SysBusDeviceClass *sk = SYS_BUS_DEVICE_CLASS(klass);
k->realize = vmbus_bridge_realize;
k->fw_name = "vmbus";
sk->explicit_ofw_unit_address = vmbus_bridge_ofw_unit_address;
set_bit(DEVICE_CATEGORY_BRIDGE, k->categories);
k->vmsd = &vmstate_vmbus_bridge;
device_class_set_props(k, vmbus_bridge_props);
/* override SysBusDevice's default */
k->user_creatable = true;
}
static const TypeInfo vmbus_bridge_type_info = {
.name = TYPE_VMBUS_BRIDGE,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(VMBusBridge),
.class_init = vmbus_bridge_class_init,
};
static void vmbus_register_types(void)
{
type_register_static(&vmbus_bridge_type_info);
type_register_static(&vmbus_dev_type_info);
type_register_static(&vmbus_type_info);
}
type_init(vmbus_register_types)