mirror of
https://github.com/xemu-project/xemu.git
synced 2024-12-25 05:15:28 +00:00
dcfe480544
Replace error_setg(&err, ...); error_propagate(errp, err); by error_setg(errp, ...); Related pattern: if (...) { error_setg(&err, ...); goto out; } ... out: error_propagate(errp, err); return; When all paths to label out are that way, replace by if (...) { error_setg(errp, ...); return; } and delete the label along with the error_propagate(). When we have at most one other path that actually needs to propagate, and maybe one at the end that where propagation is unnecessary, e.g. foo(..., &err); if (err) { goto out; } ... bar(..., &err); out: error_propagate(errp, err); return; move the error_propagate() to where it's needed, like if (...) { foo(..., &err); error_propagate(errp, err); return; } ... bar(..., errp); return; and transform the error_setg() as above. In some places, the transformation results in obviously unnecessary error_propagate(). The next few commits will eliminate them. Bonus: the elimination of gotos will make later patches in this series easier to review. Candidates for conversion tracked down with this Coccinelle script: @@ identifier err, errp; expression list args; @@ - error_setg(&err, args); + error_setg(errp, args); ... when != err error_propagate(errp, err); Signed-off-by: Markus Armbruster <armbru@redhat.com> Reviewed-by: Eric Blake <eblake@redhat.com> Message-Id: <20200707160613.848843-34-armbru@redhat.com>
2777 lines
76 KiB
C
2777 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/hyperv/hyperv.h"
|
|
#include "hw/hyperv/vmbus.h"
|
|
#include "hw/hyperv/vmbus-bridge.h"
|
|
#include "hw/sysbus.h"
|
|
#include "cpu.h"
|
|
#include "trace.h"
|
|
|
|
#define TYPE_VMBUS "vmbus"
|
|
#define VMBUS(obj) OBJECT_CHECK(VMBus, (obj), TYPE_VMBUS)
|
|
|
|
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)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 ((atomic_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));
|
|
|
|
/* 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 void vmbus_dev_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *kdev = DEVICE_CLASS(klass);
|
|
kdev->bus_type = TYPE_VMBUS;
|
|
kdev->realize = vmbus_dev_realize;
|
|
kdev->unrealize = vmbus_dev_unrealize;
|
|
kdev->reset = vmbus_dev_reset;
|
|
}
|
|
|
|
static Property vmbus_dev_instanceid =
|
|
DEFINE_PROP_UUID("instanceid", VMBusDevice, instanceid);
|
|
|
|
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;
|
|
} else {
|
|
qdev_property_add_static(DEVICE(vdev), &vmbus_dev_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)
|