| /* |
| * 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, |
| MEMTXATTRS_UNSPECIFIED); |
| 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, |
| MEMTXATTRS_UNSPECIFIED); |
| 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 = (const 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, MEMTXATTRS_UNSPECIFIED); |
| 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 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 = (const 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) |
| { |
| bus_cold_reset(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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| } |
| |
| 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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| 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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| } |
| |
| 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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| 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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| } |
| |
| 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; |
| } |
| } |
| |
| g_assert_not_reached(); |
| 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 */ |
| device_cold_reset(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 = 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_STR_LEN]; |
| |
| 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; |
| device_class_set_legacy_reset(kdev, 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 = (const 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; |
| 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(errp, "hyperv set message handler failed: %d", ret); |
| goto error_out; |
| } |
| |
| ret = event_notifier_init(&vmbus->notifier, 0); |
| if (ret != 0) { |
| error_setg(errp, "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(errp, "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); |
| } |
| |
| 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_hold(Object *obj, ResetType type) |
| { |
| vmbus_deinit(VMBUS(obj)); |
| } |
| |
| 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_STR_LEN]; |
| |
| 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); |
| ResettableClass *rc = RESETTABLE_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; |
| rc->phases.hold = vmbus_reset_hold; |
| } |
| |
| 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 = (const 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 = (const 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 = (const 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 * const []) { |
| &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; |
| } |
| |
| if (!hyperv_are_vmbus_recommended_features_enabled()) { |
| warn_report("VMBus enabled without the recommended set of Hyper-V features: " |
| "hv-stimer, hv-vapic and hv-runtime. " |
| "Some Windows versions might not boot or enable the VMBus device"); |
| } |
| |
| bridge->bus = VMBUS(qbus_new(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 = (const 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) |