2010-03-17 12:08:17 +01:00
|
|
|
/*
|
|
|
|
* vhost support
|
|
|
|
*
|
|
|
|
* Copyright Red Hat, Inc. 2010
|
|
|
|
*
|
|
|
|
* Authors:
|
|
|
|
* Michael S. Tsirkin <mst@redhat.com>
|
|
|
|
*
|
|
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
|
|
* the COPYING file in the top-level directory.
|
2012-01-13 17:44:23 +01:00
|
|
|
*
|
|
|
|
* Contributions after 2012-01-13 are licensed under the terms of the
|
|
|
|
* GNU GPL, version 2 or (at your option) any later version.
|
2010-03-17 12:08:17 +01:00
|
|
|
*/
|
|
|
|
|
2016-01-26 19:17:07 +01:00
|
|
|
#include "qemu/osdep.h"
|
include/qemu/osdep.h: Don't include qapi/error.h
Commit 57cb38b included qapi/error.h into qemu/osdep.h to get the
Error typedef. Since then, we've moved to include qemu/osdep.h
everywhere. Its file comment explains: "To avoid getting into
possible circular include dependencies, this file should not include
any other QEMU headers, with the exceptions of config-host.h,
compiler.h, os-posix.h and os-win32.h, all of which are doing a
similar job to this file and are under similar constraints."
qapi/error.h doesn't do a similar job, and it doesn't adhere to
similar constraints: it includes qapi-types.h. That's in excess of
100KiB of crap most .c files don't actually need.
Add the typedef to qemu/typedefs.h, and include that instead of
qapi/error.h. Include qapi/error.h in .c files that need it and don't
get it now. Include qapi-types.h in qom/object.h for uint16List.
Update scripts/clean-includes accordingly. Update it further to match
reality: replace config.h by config-target.h, add sysemu/os-posix.h,
sysemu/os-win32.h. Update the list of includes in the qemu/osdep.h
comment quoted above similarly.
This reduces the number of objects depending on qapi/error.h from "all
of them" to less than a third. Unfortunately, the number depending on
qapi-types.h shrinks only a little. More work is needed for that one.
Signed-off-by: Markus Armbruster <armbru@redhat.com>
[Fix compilation without the spice devel packages. - Paolo]
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
2016-03-14 09:01:28 +01:00
|
|
|
#include "qapi/error.h"
|
2013-02-05 17:06:20 +01:00
|
|
|
#include "hw/virtio/vhost.h"
|
2013-05-13 13:29:47 +02:00
|
|
|
#include "qemu/atomic.h"
|
2012-12-17 18:20:00 +01:00
|
|
|
#include "qemu/range.h"
|
2015-06-17 15:23:39 +02:00
|
|
|
#include "qemu/error-report.h"
|
2015-10-09 17:17:25 +02:00
|
|
|
#include "qemu/memfd.h"
|
2022-12-16 04:35:52 +01:00
|
|
|
#include "qemu/log.h"
|
2019-02-14 18:35:50 +01:00
|
|
|
#include "standard-headers/linux/vhost_types.h"
|
2013-04-24 10:21:21 +02:00
|
|
|
#include "hw/virtio/virtio-bus.h"
|
2023-09-26 20:57:29 +02:00
|
|
|
#include "hw/mem/memory-device.h"
|
2017-04-06 12:00:28 +02:00
|
|
|
#include "migration/blocker.h"
|
2019-08-12 07:23:39 +02:00
|
|
|
#include "migration/qemu-file-types.h"
|
2017-01-11 05:32:12 +01:00
|
|
|
#include "sysemu/dma.h"
|
2018-01-19 11:39:24 +01:00
|
|
|
#include "trace.h"
|
2010-03-17 12:08:17 +01:00
|
|
|
|
2016-07-26 23:15:05 +02:00
|
|
|
/* enabled until disconnected backend stabilizes */
|
|
|
|
#define _VHOST_DEBUG 1
|
|
|
|
|
|
|
|
#ifdef _VHOST_DEBUG
|
2021-11-11 16:33:53 +01:00
|
|
|
#define VHOST_OPS_DEBUG(retval, fmt, ...) \
|
|
|
|
do { \
|
|
|
|
error_report(fmt ": %s (%d)", ## __VA_ARGS__, \
|
|
|
|
strerror(-retval), -retval); \
|
|
|
|
} while (0)
|
2016-07-26 23:15:05 +02:00
|
|
|
#else
|
2021-11-11 16:33:53 +01:00
|
|
|
#define VHOST_OPS_DEBUG(retval, fmt, ...) \
|
2016-07-26 23:15:05 +02:00
|
|
|
do { } while (0)
|
|
|
|
#endif
|
|
|
|
|
2015-06-04 11:28:46 +02:00
|
|
|
static struct vhost_log *vhost_log;
|
2015-10-09 17:17:25 +02:00
|
|
|
static struct vhost_log *vhost_log_shm;
|
2015-06-04 11:28:46 +02:00
|
|
|
|
2023-09-26 20:57:21 +02:00
|
|
|
/* Memslots used by backends that support private memslots (without an fd). */
|
2015-10-06 10:37:27 +02:00
|
|
|
static unsigned int used_memslots;
|
2023-09-26 20:57:21 +02:00
|
|
|
|
|
|
|
/* Memslots used by backends that only support shared memslots (with an fd). */
|
|
|
|
static unsigned int used_shared_memslots;
|
|
|
|
|
2015-10-06 10:37:27 +02:00
|
|
|
static QLIST_HEAD(, vhost_dev) vhost_devices =
|
|
|
|
QLIST_HEAD_INITIALIZER(vhost_devices);
|
|
|
|
|
2023-09-26 20:57:31 +02:00
|
|
|
unsigned int vhost_get_max_memslots(void)
|
|
|
|
{
|
|
|
|
unsigned int max = UINT_MAX;
|
|
|
|
struct vhost_dev *hdev;
|
|
|
|
|
|
|
|
QLIST_FOREACH(hdev, &vhost_devices, entry) {
|
|
|
|
max = MIN(max, hdev->vhost_ops->vhost_backend_memslots_limit(hdev));
|
|
|
|
}
|
|
|
|
return max;
|
|
|
|
}
|
|
|
|
|
2023-09-26 20:57:25 +02:00
|
|
|
unsigned int vhost_get_free_memslots(void)
|
2015-10-06 10:37:27 +02:00
|
|
|
{
|
2023-09-26 20:57:21 +02:00
|
|
|
unsigned int free = UINT_MAX;
|
2015-10-06 10:37:27 +02:00
|
|
|
struct vhost_dev *hdev;
|
|
|
|
|
|
|
|
QLIST_FOREACH(hdev, &vhost_devices, entry) {
|
|
|
|
unsigned int r = hdev->vhost_ops->vhost_backend_memslots_limit(hdev);
|
2023-09-26 20:57:21 +02:00
|
|
|
unsigned int cur_free;
|
|
|
|
|
|
|
|
if (hdev->vhost_ops->vhost_backend_no_private_memslots &&
|
|
|
|
hdev->vhost_ops->vhost_backend_no_private_memslots(hdev)) {
|
|
|
|
cur_free = r - used_shared_memslots;
|
|
|
|
} else {
|
|
|
|
cur_free = r - used_memslots;
|
|
|
|
}
|
|
|
|
free = MIN(free, cur_free);
|
2015-10-06 10:37:27 +02:00
|
|
|
}
|
2023-09-26 20:57:25 +02:00
|
|
|
return free;
|
2015-10-06 10:37:27 +02:00
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
static void vhost_dev_sync_region(struct vhost_dev *dev,
|
2011-12-19 12:18:13 +01:00
|
|
|
MemoryRegionSection *section,
|
2010-03-17 12:08:17 +01:00
|
|
|
uint64_t mfirst, uint64_t mlast,
|
|
|
|
uint64_t rfirst, uint64_t rlast)
|
|
|
|
{
|
2023-10-04 13:48:09 +02:00
|
|
|
vhost_log_chunk_t *dev_log = dev->log->log;
|
2015-06-04 11:28:46 +02:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
uint64_t start = MAX(mfirst, rfirst);
|
|
|
|
uint64_t end = MIN(mlast, rlast);
|
2023-10-04 13:48:09 +02:00
|
|
|
vhost_log_chunk_t *from = dev_log + start / VHOST_LOG_CHUNK;
|
|
|
|
vhost_log_chunk_t *to = dev_log + end / VHOST_LOG_CHUNK + 1;
|
2017-06-22 13:04:16 +02:00
|
|
|
uint64_t addr = QEMU_ALIGN_DOWN(start, VHOST_LOG_CHUNK);
|
2010-03-17 12:08:17 +01:00
|
|
|
|
|
|
|
if (end < start) {
|
|
|
|
return;
|
|
|
|
}
|
2010-08-13 15:54:52 +02:00
|
|
|
assert(end / VHOST_LOG_CHUNK < dev->log_size);
|
2012-04-01 10:39:43 +02:00
|
|
|
assert(start / VHOST_LOG_CHUNK < dev->log_size);
|
2010-08-13 15:54:52 +02:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
for (;from < to; ++from) {
|
|
|
|
vhost_log_chunk_t log;
|
|
|
|
/* We first check with non-atomic: much cheaper,
|
|
|
|
* and we expect non-dirty to be the common case. */
|
|
|
|
if (!*from) {
|
2010-11-27 15:05:07 +01:00
|
|
|
addr += VHOST_LOG_CHUNK;
|
2010-03-17 12:08:17 +01:00
|
|
|
continue;
|
|
|
|
}
|
2013-05-13 13:29:47 +02:00
|
|
|
/* Data must be read atomically. We don't really need barrier semantics
|
|
|
|
* but it's easier to use atomic_* than roll our own. */
|
2020-09-23 12:56:46 +02:00
|
|
|
log = qatomic_xchg(from, 0);
|
2014-04-29 16:17:29 +02:00
|
|
|
while (log) {
|
|
|
|
int bit = ctzl(log);
|
2013-02-21 12:16:06 +01:00
|
|
|
hwaddr page_addr;
|
|
|
|
hwaddr section_offset;
|
|
|
|
hwaddr mr_offset;
|
|
|
|
page_addr = addr + bit * VHOST_LOG_PAGE;
|
|
|
|
section_offset = page_addr - section->offset_within_address_space;
|
|
|
|
mr_offset = section_offset + section->offset_within_region;
|
|
|
|
memory_region_set_dirty(section->mr, mr_offset, VHOST_LOG_PAGE);
|
2010-03-17 12:08:17 +01:00
|
|
|
log &= ~(0x1ull << bit);
|
|
|
|
}
|
|
|
|
addr += VHOST_LOG_CHUNK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-10 07:46:28 +02:00
|
|
|
bool vhost_dev_has_iommu(struct vhost_dev *dev)
|
2022-12-16 04:35:52 +01:00
|
|
|
{
|
|
|
|
VirtIODevice *vdev = dev->vdev;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For vhost, VIRTIO_F_IOMMU_PLATFORM means the backend support
|
|
|
|
* incremental memory mapping API via IOTLB API. For platform that
|
|
|
|
* does not have IOMMU, there's no need to enable this feature
|
|
|
|
* which may cause unnecessary IOTLB miss/update transactions.
|
|
|
|
*/
|
|
|
|
if (vdev) {
|
|
|
|
return virtio_bus_device_iommu_enabled(vdev) &&
|
|
|
|
virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM);
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-18 13:06:05 +01:00
|
|
|
static int vhost_sync_dirty_bitmap(struct vhost_dev *dev,
|
2011-12-19 12:18:13 +01:00
|
|
|
MemoryRegionSection *section,
|
2013-02-21 12:16:06 +01:00
|
|
|
hwaddr first,
|
|
|
|
hwaddr last)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
|
|
|
int i;
|
2013-02-21 12:16:06 +01:00
|
|
|
hwaddr start_addr;
|
|
|
|
hwaddr end_addr;
|
2011-12-18 13:06:05 +01:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
if (!dev->log_enabled || !dev->started) {
|
|
|
|
return 0;
|
|
|
|
}
|
2013-02-21 12:16:06 +01:00
|
|
|
start_addr = section->offset_within_address_space;
|
2013-05-27 10:08:27 +02:00
|
|
|
end_addr = range_get_last(start_addr, int128_get64(section->size));
|
2013-02-21 12:16:06 +01:00
|
|
|
start_addr = MAX(first, start_addr);
|
|
|
|
end_addr = MIN(last, end_addr);
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
for (i = 0; i < dev->mem->nregions; ++i) {
|
|
|
|
struct vhost_memory_region *reg = dev->mem->regions + i;
|
2011-12-19 12:18:13 +01:00
|
|
|
vhost_dev_sync_region(dev, section, start_addr, end_addr,
|
2010-03-17 12:08:17 +01:00
|
|
|
reg->guest_phys_addr,
|
|
|
|
range_get_last(reg->guest_phys_addr,
|
|
|
|
reg->memory_size));
|
|
|
|
}
|
|
|
|
for (i = 0; i < dev->nvqs; ++i) {
|
|
|
|
struct vhost_virtqueue *vq = dev->vqs + i;
|
vhost: fix vhost_log size overflow during migration
When a guest which doesn't support multiqueue is migrated with a multi queues
vhost-user-blk deivce, a crash will occur like:
0 qemu_memfd_alloc (name=<value optimized out>, size=562949953421312, seals=<value optimized out>, fd=0x7f87171fe8b4, errp=0x7f87171fe8a8) at util/memfd.c:153
1 0x00007f883559d7cf in vhost_log_alloc (size=70368744177664, share=true) at hw/virtio/vhost.c:186
2 0x00007f88355a0758 in vhost_log_get (listener=0x7f8838bd7940, enable=1) at qemu-2-12/hw/virtio/vhost.c:211
3 vhost_dev_log_resize (listener=0x7f8838bd7940, enable=1) at hw/virtio/vhost.c:263
4 vhost_migration_log (listener=0x7f8838bd7940, enable=1) at hw/virtio/vhost.c:787
5 0x00007f88355463d6 in memory_global_dirty_log_start () at memory.c:2503
6 0x00007f8835550577 in ram_init_bitmaps (f=0x7f88384ce600, opaque=0x7f8836024098) at migration/ram.c:2173
7 ram_init_all (f=0x7f88384ce600, opaque=0x7f8836024098) at migration/ram.c:2192
8 ram_save_setup (f=0x7f88384ce600, opaque=0x7f8836024098) at migration/ram.c:2219
9 0x00007f88357a419d in qemu_savevm_state_setup (f=0x7f88384ce600) at migration/savevm.c:1002
10 0x00007f883579fc3e in migration_thread (opaque=0x7f8837530400) at migration/migration.c:2382
11 0x00007f8832447893 in start_thread () from /lib64/libpthread.so.0
12 0x00007f8832178bfd in clone () from /lib64/libc.so.6
This is because vhost_get_log_size() returns a overflowed vhost-log size.
In this function, it uses the uninitialized variable vqs->used_phys and
vqs->used_size to get the vhost-log size.
Signed-off-by: Li Hangjing <lihangjing@baidu.com>
Reviewed-by: Xie Yongji <xieyongji@baidu.com>
Reviewed-by: Chai Wen <chaiwen@baidu.com>
Message-Id: <20190603061524.24076-1-lihangjing@baidu.com>
Cc: qemu-stable@nongnu.org
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2019-06-03 08:15:24 +02:00
|
|
|
|
|
|
|
if (!vq->used_phys && !vq->used_size) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-12-16 04:35:52 +01:00
|
|
|
if (vhost_dev_has_iommu(dev)) {
|
|
|
|
IOMMUTLBEntry iotlb;
|
|
|
|
hwaddr used_phys = vq->used_phys, used_size = vq->used_size;
|
|
|
|
hwaddr phys, s, offset;
|
|
|
|
|
|
|
|
while (used_size) {
|
|
|
|
rcu_read_lock();
|
|
|
|
iotlb = address_space_get_iotlb_entry(dev->vdev->dma_as,
|
|
|
|
used_phys,
|
|
|
|
true,
|
|
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
|
|
|
if (!iotlb.target_as) {
|
|
|
|
qemu_log_mask(LOG_GUEST_ERROR, "translation "
|
|
|
|
"failure for used_iova %"PRIx64"\n",
|
|
|
|
used_phys);
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
offset = used_phys & iotlb.addr_mask;
|
|
|
|
phys = iotlb.translated_addr + offset;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Distance from start of used ring until last byte of
|
|
|
|
* IOMMU page.
|
|
|
|
*/
|
|
|
|
s = iotlb.addr_mask - offset;
|
|
|
|
/*
|
|
|
|
* Size of used ring, or of the part of it until end
|
|
|
|
* of IOMMU page. To avoid zero result, do the adding
|
|
|
|
* outside of MIN().
|
|
|
|
*/
|
|
|
|
s = MIN(s, used_size - 1) + 1;
|
|
|
|
|
|
|
|
vhost_dev_sync_region(dev, section, start_addr, end_addr, phys,
|
|
|
|
range_get_last(phys, s));
|
|
|
|
used_size -= s;
|
|
|
|
used_phys += s;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
vhost_dev_sync_region(dev, section, start_addr,
|
|
|
|
end_addr, vq->used_phys,
|
|
|
|
range_get_last(vq->used_phys, vq->used_size));
|
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-12-18 13:06:05 +01:00
|
|
|
static void vhost_log_sync(MemoryListener *listener,
|
|
|
|
MemoryRegionSection *section)
|
|
|
|
{
|
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
memory_listener);
|
2013-02-21 12:16:06 +01:00
|
|
|
vhost_sync_dirty_bitmap(dev, section, 0x0, ~0x0ULL);
|
|
|
|
}
|
2011-12-18 13:06:05 +01:00
|
|
|
|
2013-02-21 12:16:06 +01:00
|
|
|
static void vhost_log_sync_range(struct vhost_dev *dev,
|
|
|
|
hwaddr first, hwaddr last)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
/* FIXME: this is N^2 in number of sections */
|
|
|
|
for (i = 0; i < dev->n_mem_sections; ++i) {
|
|
|
|
MemoryRegionSection *section = &dev->mem_sections[i];
|
|
|
|
vhost_sync_dirty_bitmap(dev, section, first, last);
|
|
|
|
}
|
2011-12-18 13:06:05 +01:00
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
static uint64_t vhost_get_log_size(struct vhost_dev *dev)
|
|
|
|
{
|
|
|
|
uint64_t log_size = 0;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < dev->mem->nregions; ++i) {
|
|
|
|
struct vhost_memory_region *reg = dev->mem->regions + i;
|
|
|
|
uint64_t last = range_get_last(reg->guest_phys_addr,
|
|
|
|
reg->memory_size);
|
|
|
|
log_size = MAX(log_size, last / VHOST_LOG_CHUNK + 1);
|
|
|
|
}
|
|
|
|
return log_size;
|
|
|
|
}
|
2015-10-09 17:17:25 +02:00
|
|
|
|
2021-08-09 15:40:15 +02:00
|
|
|
static int vhost_set_backend_type(struct vhost_dev *dev,
|
|
|
|
VhostBackendType backend_type)
|
|
|
|
{
|
|
|
|
int r = 0;
|
|
|
|
|
|
|
|
switch (backend_type) {
|
|
|
|
#ifdef CONFIG_VHOST_KERNEL
|
|
|
|
case VHOST_BACKEND_TYPE_KERNEL:
|
|
|
|
dev->vhost_ops = &kernel_ops;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_VHOST_USER
|
|
|
|
case VHOST_BACKEND_TYPE_USER:
|
|
|
|
dev->vhost_ops = &user_ops;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_VHOST_VDPA
|
|
|
|
case VHOST_BACKEND_TYPE_VDPA:
|
|
|
|
dev->vhost_ops = &vdpa_ops;
|
|
|
|
break;
|
|
|
|
#endif
|
|
|
|
default:
|
|
|
|
error_report("Unknown vhost backend type");
|
|
|
|
r = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:25 +02:00
|
|
|
static struct vhost_log *vhost_log_alloc(uint64_t size, bool share)
|
2015-06-04 11:28:46 +02:00
|
|
|
{
|
2018-02-01 14:27:51 +01:00
|
|
|
Error *err = NULL;
|
2015-10-09 17:17:25 +02:00
|
|
|
struct vhost_log *log;
|
|
|
|
uint64_t logsize = size * sizeof(*(log->log));
|
|
|
|
int fd = -1;
|
|
|
|
|
|
|
|
log = g_new0(struct vhost_log, 1);
|
|
|
|
if (share) {
|
|
|
|
log->log = qemu_memfd_alloc("vhost-log", logsize,
|
|
|
|
F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL,
|
2018-02-01 14:27:51 +01:00
|
|
|
&fd, &err);
|
|
|
|
if (err) {
|
|
|
|
error_report_err(err);
|
|
|
|
g_free(log);
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-10-09 17:17:25 +02:00
|
|
|
memset(log->log, 0, logsize);
|
|
|
|
} else {
|
|
|
|
log->log = g_malloc0(logsize);
|
|
|
|
}
|
2015-06-04 11:28:46 +02:00
|
|
|
|
|
|
|
log->size = size;
|
|
|
|
log->refcnt = 1;
|
2015-10-09 17:17:25 +02:00
|
|
|
log->fd = fd;
|
2015-06-04 11:28:46 +02:00
|
|
|
|
|
|
|
return log;
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:25 +02:00
|
|
|
static struct vhost_log *vhost_log_get(uint64_t size, bool share)
|
2015-06-04 11:28:46 +02:00
|
|
|
{
|
2015-10-09 17:17:25 +02:00
|
|
|
struct vhost_log *log = share ? vhost_log_shm : vhost_log;
|
|
|
|
|
|
|
|
if (!log || log->size != size) {
|
|
|
|
log = vhost_log_alloc(size, share);
|
|
|
|
if (share) {
|
|
|
|
vhost_log_shm = log;
|
|
|
|
} else {
|
|
|
|
vhost_log = log;
|
|
|
|
}
|
2015-06-04 11:28:46 +02:00
|
|
|
} else {
|
2015-10-09 17:17:25 +02:00
|
|
|
++log->refcnt;
|
2015-06-04 11:28:46 +02:00
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:25 +02:00
|
|
|
return log;
|
2015-06-04 11:28:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_log_put(struct vhost_dev *dev, bool sync)
|
|
|
|
{
|
|
|
|
struct vhost_log *log = dev->log;
|
|
|
|
|
|
|
|
if (!log) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
--log->refcnt;
|
|
|
|
if (log->refcnt == 0) {
|
|
|
|
/* Sync only the range covered by the old log */
|
|
|
|
if (dev->log_size && sync) {
|
|
|
|
vhost_log_sync_range(dev, 0, dev->log_size * VHOST_LOG_CHUNK - 1);
|
|
|
|
}
|
2015-10-09 17:17:25 +02:00
|
|
|
|
2015-06-04 11:28:46 +02:00
|
|
|
if (vhost_log == log) {
|
2015-10-09 17:17:25 +02:00
|
|
|
g_free(log->log);
|
2015-06-04 11:28:46 +02:00
|
|
|
vhost_log = NULL;
|
2015-10-09 17:17:25 +02:00
|
|
|
} else if (vhost_log_shm == log) {
|
|
|
|
qemu_memfd_free(log->log, log->size * sizeof(*(log->log)),
|
|
|
|
log->fd);
|
|
|
|
vhost_log_shm = NULL;
|
2015-06-04 11:28:46 +02:00
|
|
|
}
|
2015-10-09 17:17:25 +02:00
|
|
|
|
2015-06-04 11:28:46 +02:00
|
|
|
g_free(log);
|
|
|
|
}
|
2017-09-20 20:53:06 +02:00
|
|
|
|
|
|
|
dev->log = NULL;
|
|
|
|
dev->log_size = 0;
|
2015-06-04 11:28:46 +02:00
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
|
2015-10-09 17:17:25 +02:00
|
|
|
static bool vhost_dev_log_is_shared(struct vhost_dev *dev)
|
|
|
|
{
|
|
|
|
return dev->vhost_ops->vhost_requires_shm_log &&
|
|
|
|
dev->vhost_ops->vhost_requires_shm_log(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void vhost_dev_log_resize(struct vhost_dev *dev, uint64_t size)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2015-10-09 17:17:25 +02:00
|
|
|
struct vhost_log *log = vhost_log_get(size, vhost_dev_log_is_shared(dev));
|
2015-06-04 11:28:46 +02:00
|
|
|
uint64_t log_base = (uintptr_t)log->log;
|
2013-02-21 12:16:06 +01:00
|
|
|
int r;
|
2013-01-22 11:07:56 +01:00
|
|
|
|
2015-10-09 17:17:22 +02:00
|
|
|
/* inform backend of log switching, this must be done before
|
|
|
|
releasing the current log, to ensure no logging is lost */
|
2015-10-09 17:17:26 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_log_base(dev, log_base, log);
|
2016-07-26 23:15:05 +02:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_log_base failed");
|
2016-07-26 23:15:05 +02:00
|
|
|
}
|
|
|
|
|
2015-06-04 11:28:46 +02:00
|
|
|
vhost_log_put(dev, true);
|
2010-03-17 12:08:17 +01:00
|
|
|
dev->log = log;
|
|
|
|
dev->log_size = size;
|
|
|
|
}
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
static void *vhost_memory_map(struct vhost_dev *dev, hwaddr addr,
|
2020-02-19 19:18:45 +01:00
|
|
|
hwaddr *plen, bool is_write)
|
2017-01-11 05:32:12 +01:00
|
|
|
{
|
|
|
|
if (!vhost_dev_has_iommu(dev)) {
|
|
|
|
return cpu_physical_memory_map(addr, plen, is_write);
|
|
|
|
} else {
|
|
|
|
return (void *)(uintptr_t)addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_memory_unmap(struct vhost_dev *dev, void *buffer,
|
|
|
|
hwaddr len, int is_write,
|
|
|
|
hwaddr access_len)
|
|
|
|
{
|
|
|
|
if (!vhost_dev_has_iommu(dev)) {
|
|
|
|
cpu_physical_memory_unmap(buffer, len, is_write, access_len);
|
|
|
|
}
|
|
|
|
}
|
2016-11-04 09:39:15 +01:00
|
|
|
|
2018-01-19 11:39:19 +01:00
|
|
|
static int vhost_verify_ring_part_mapping(void *ring_hva,
|
|
|
|
uint64_t ring_gpa,
|
|
|
|
uint64_t ring_size,
|
|
|
|
void *reg_hva,
|
|
|
|
uint64_t reg_gpa,
|
|
|
|
uint64_t reg_size)
|
2016-11-04 09:39:15 +01:00
|
|
|
{
|
2018-01-19 11:39:19 +01:00
|
|
|
uint64_t hva_ring_offset;
|
|
|
|
uint64_t ring_last = range_get_last(ring_gpa, ring_size);
|
|
|
|
uint64_t reg_last = range_get_last(reg_gpa, reg_size);
|
2016-11-04 09:39:15 +01:00
|
|
|
|
2018-01-19 11:39:19 +01:00
|
|
|
if (ring_last < reg_gpa || ring_gpa > reg_last) {
|
2016-11-04 09:39:15 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2018-01-19 11:39:19 +01:00
|
|
|
/* check that whole ring's is mapped */
|
|
|
|
if (ring_last > reg_last) {
|
|
|
|
return -ENOMEM;
|
2016-11-04 09:39:15 +01:00
|
|
|
}
|
2018-01-19 11:39:19 +01:00
|
|
|
/* check that ring's MemoryRegion wasn't replaced */
|
|
|
|
hva_ring_offset = ring_gpa - reg_gpa;
|
|
|
|
if (ring_hva != reg_hva + hva_ring_offset) {
|
|
|
|
return -EBUSY;
|
2016-11-04 09:39:15 +01:00
|
|
|
}
|
2018-01-19 11:39:19 +01:00
|
|
|
|
|
|
|
return 0;
|
2016-11-04 09:39:15 +01:00
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
static int vhost_verify_ring_mappings(struct vhost_dev *dev,
|
2018-01-19 11:39:19 +01:00
|
|
|
void *reg_hva,
|
|
|
|
uint64_t reg_gpa,
|
|
|
|
uint64_t reg_size)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2016-11-04 09:39:15 +01:00
|
|
|
int i, j;
|
2014-06-18 17:55:22 +02:00
|
|
|
int r = 0;
|
2016-11-04 09:39:15 +01:00
|
|
|
const char *part_name[] = {
|
|
|
|
"descriptor table",
|
|
|
|
"available ring",
|
|
|
|
"used ring"
|
|
|
|
};
|
2014-06-18 17:55:22 +02:00
|
|
|
|
2018-04-13 05:01:49 +02:00
|
|
|
if (vhost_dev_has_iommu(dev)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-04 09:39:15 +01:00
|
|
|
for (i = 0; i < dev->nvqs; ++i) {
|
2010-03-17 12:08:17 +01:00
|
|
|
struct vhost_virtqueue *vq = dev->vqs + i;
|
|
|
|
|
2018-02-28 10:35:28 +01:00
|
|
|
if (vq->desc_phys == 0) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-04 09:39:15 +01:00
|
|
|
j = 0;
|
2018-01-19 11:39:19 +01:00
|
|
|
r = vhost_verify_ring_part_mapping(
|
|
|
|
vq->desc, vq->desc_phys, vq->desc_size,
|
|
|
|
reg_hva, reg_gpa, reg_size);
|
2017-11-30 22:39:59 +01:00
|
|
|
if (r) {
|
2016-11-04 09:39:15 +01:00
|
|
|
break;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2016-11-04 09:39:15 +01:00
|
|
|
|
|
|
|
j++;
|
2018-01-19 11:39:19 +01:00
|
|
|
r = vhost_verify_ring_part_mapping(
|
2018-02-28 10:35:29 +01:00
|
|
|
vq->avail, vq->avail_phys, vq->avail_size,
|
2018-01-19 11:39:19 +01:00
|
|
|
reg_hva, reg_gpa, reg_size);
|
2017-11-30 22:39:59 +01:00
|
|
|
if (r) {
|
2016-11-04 09:39:15 +01:00
|
|
|
break;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2016-11-04 09:39:15 +01:00
|
|
|
|
|
|
|
j++;
|
2018-01-19 11:39:19 +01:00
|
|
|
r = vhost_verify_ring_part_mapping(
|
2018-02-28 10:35:29 +01:00
|
|
|
vq->used, vq->used_phys, vq->used_size,
|
2018-01-19 11:39:19 +01:00
|
|
|
reg_hva, reg_gpa, reg_size);
|
2017-11-30 22:39:59 +01:00
|
|
|
if (r) {
|
2016-11-04 09:39:15 +01:00
|
|
|
break;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2016-11-04 09:39:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (r == -ENOMEM) {
|
|
|
|
error_report("Unable to map %s for ring %d", part_name[j], i);
|
|
|
|
} else if (r == -EBUSY) {
|
|
|
|
error_report("%s relocated for ring %d", part_name[j], i);
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2014-06-18 17:55:22 +02:00
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2020-06-05 17:49:25 +02:00
|
|
|
/*
|
|
|
|
* vhost_section: identify sections needed for vhost access
|
|
|
|
*
|
|
|
|
* We only care about RAM sections here (where virtqueue and guest
|
2023-09-26 20:57:21 +02:00
|
|
|
* internals accessed by virtio might live).
|
2020-06-05 17:49:25 +02:00
|
|
|
*/
|
2018-05-24 12:33:31 +02:00
|
|
|
static bool vhost_section(struct vhost_dev *dev, MemoryRegionSection *section)
|
2013-04-03 11:15:11 +02:00
|
|
|
{
|
2020-06-05 17:49:25 +02:00
|
|
|
MemoryRegion *mr = section->mr;
|
|
|
|
|
|
|
|
if (memory_region_is_ram(mr) && !memory_region_is_rom(mr)) {
|
|
|
|
uint8_t dirty_mask = memory_region_get_dirty_log_mask(mr);
|
|
|
|
uint8_t handled_dirty;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Kernel based vhost doesn't handle any block which is doing
|
|
|
|
* dirty-tracking other than migration for which it has
|
|
|
|
* specific logging support. However for TCG the kernel never
|
|
|
|
* gets involved anyway so we can also ignore it's
|
|
|
|
* self-modiying code detection flags. However a vhost-user
|
|
|
|
* client could still confuse a TCG guest if it re-writes
|
|
|
|
* executable memory that has already been translated.
|
|
|
|
*/
|
|
|
|
handled_dirty = (1 << DIRTY_MEMORY_MIGRATION) |
|
|
|
|
(1 << DIRTY_MEMORY_CODE);
|
|
|
|
|
|
|
|
if (dirty_mask & ~handled_dirty) {
|
|
|
|
trace_vhost_reject_section(mr->name, 1);
|
|
|
|
return false;
|
|
|
|
}
|
2018-01-19 11:39:24 +01:00
|
|
|
|
2023-09-26 20:57:21 +02:00
|
|
|
/*
|
|
|
|
* Some backends (like vhost-user) can only handle memory regions
|
|
|
|
* that have an fd (can be mapped into a different process). Filter
|
|
|
|
* the ones without an fd out, if requested.
|
|
|
|
*
|
|
|
|
* TODO: we might have to limit to MAP_SHARED as well.
|
|
|
|
*/
|
|
|
|
if (memory_region_get_fd(section->mr) < 0 &&
|
|
|
|
dev->vhost_ops->vhost_backend_no_private_memslots &&
|
|
|
|
dev->vhost_ops->vhost_backend_no_private_memslots(dev)) {
|
2020-06-05 17:49:25 +02:00
|
|
|
trace_vhost_reject_section(mr->name, 2);
|
|
|
|
return false;
|
|
|
|
}
|
2018-05-24 12:33:31 +02:00
|
|
|
|
2020-06-05 17:49:25 +02:00
|
|
|
trace_vhost_section(mr->name);
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
trace_vhost_reject_section(mr->name, 3);
|
|
|
|
return false;
|
|
|
|
}
|
2013-04-03 11:15:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_begin(MemoryListener *listener)
|
|
|
|
{
|
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
memory_listener);
|
2018-01-19 11:39:18 +01:00
|
|
|
dev->tmp_sections = NULL;
|
|
|
|
dev->n_tmp_sections = 0;
|
2013-04-03 11:15:11 +02:00
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
|
2013-04-03 11:15:11 +02:00
|
|
|
static void vhost_commit(MemoryListener *listener)
|
|
|
|
{
|
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
memory_listener);
|
2018-01-19 11:39:18 +01:00
|
|
|
MemoryRegionSection *old_sections;
|
|
|
|
int n_old_sections;
|
2013-04-03 11:15:11 +02:00
|
|
|
uint64_t log_size;
|
2018-01-19 11:39:21 +01:00
|
|
|
size_t regions_size;
|
2013-04-03 11:15:11 +02:00
|
|
|
int r;
|
2018-01-19 11:39:19 +01:00
|
|
|
int i;
|
2018-01-19 11:39:21 +01:00
|
|
|
bool changed = false;
|
2013-04-03 11:15:11 +02:00
|
|
|
|
2018-01-19 11:39:21 +01:00
|
|
|
/* Note we can be called before the device is started, but then
|
|
|
|
* starting the device calls set_mem_table, so we need to have
|
|
|
|
* built the data structures.
|
|
|
|
*/
|
2018-01-19 11:39:18 +01:00
|
|
|
old_sections = dev->mem_sections;
|
|
|
|
n_old_sections = dev->n_mem_sections;
|
|
|
|
dev->mem_sections = dev->tmp_sections;
|
|
|
|
dev->n_mem_sections = dev->n_tmp_sections;
|
|
|
|
|
2018-01-19 11:39:21 +01:00
|
|
|
if (dev->n_mem_sections != n_old_sections) {
|
|
|
|
changed = true;
|
|
|
|
} else {
|
|
|
|
/* Same size, lets check the contents */
|
2023-10-04 13:48:09 +02:00
|
|
|
for (i = 0; i < n_old_sections; i++) {
|
2019-08-14 19:55:35 +02:00
|
|
|
if (!MemoryRegionSection_eq(&old_sections[i],
|
|
|
|
&dev->mem_sections[i])) {
|
|
|
|
changed = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2013-04-03 11:15:11 +02:00
|
|
|
}
|
2018-01-19 11:39:21 +01:00
|
|
|
|
|
|
|
trace_vhost_commit(dev->started, changed);
|
|
|
|
if (!changed) {
|
2018-01-19 11:39:18 +01:00
|
|
|
goto out;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2018-01-19 11:39:21 +01:00
|
|
|
|
|
|
|
/* Rebuild the regions list from the new sections list */
|
|
|
|
regions_size = offsetof(struct vhost_memory, regions) +
|
|
|
|
dev->n_mem_sections * sizeof dev->mem->regions[0];
|
|
|
|
dev->mem = g_realloc(dev->mem, regions_size);
|
|
|
|
dev->mem->nregions = dev->n_mem_sections;
|
2023-09-26 20:57:21 +02:00
|
|
|
|
|
|
|
if (dev->vhost_ops->vhost_backend_no_private_memslots &&
|
|
|
|
dev->vhost_ops->vhost_backend_no_private_memslots(dev)) {
|
|
|
|
used_shared_memslots = dev->mem->nregions;
|
|
|
|
} else {
|
|
|
|
used_memslots = dev->mem->nregions;
|
|
|
|
}
|
|
|
|
|
2018-01-19 11:39:21 +01:00
|
|
|
for (i = 0; i < dev->n_mem_sections; i++) {
|
|
|
|
struct vhost_memory_region *cur_vmr = dev->mem->regions + i;
|
|
|
|
struct MemoryRegionSection *mrs = dev->mem_sections + i;
|
|
|
|
|
|
|
|
cur_vmr->guest_phys_addr = mrs->offset_within_address_space;
|
|
|
|
cur_vmr->memory_size = int128_get64(mrs->size);
|
|
|
|
cur_vmr->userspace_addr =
|
|
|
|
(uintptr_t)memory_region_get_ram_ptr(mrs->mr) +
|
|
|
|
mrs->offset_within_region;
|
|
|
|
cur_vmr->flags_padding = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dev->started) {
|
2018-01-19 11:39:18 +01:00
|
|
|
goto out;
|
2013-04-03 11:15:11 +02:00
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
|
2018-01-19 11:39:19 +01:00
|
|
|
for (i = 0; i < dev->mem->nregions; i++) {
|
|
|
|
if (vhost_verify_ring_mappings(dev,
|
|
|
|
(void *)(uintptr_t)dev->mem->regions[i].userspace_addr,
|
|
|
|
dev->mem->regions[i].guest_phys_addr,
|
|
|
|
dev->mem->regions[i].memory_size)) {
|
|
|
|
error_report("Verify ring failure on region %d", i);
|
|
|
|
abort();
|
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!dev->log_enabled) {
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_mem_table(dev, dev->mem);
|
2016-07-26 23:15:05 +02:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
|
2016-07-26 23:15:05 +02:00
|
|
|
}
|
2018-01-19 11:39:18 +01:00
|
|
|
goto out;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
log_size = vhost_get_log_size(dev);
|
|
|
|
/* We allocate an extra 4K bytes to log,
|
|
|
|
* to reduce the * number of reallocations. */
|
|
|
|
#define VHOST_LOG_BUFFER (0x1000 / sizeof *dev->log)
|
|
|
|
/* To log more, must increase log size before table update. */
|
|
|
|
if (dev->log_size < log_size) {
|
|
|
|
vhost_dev_log_resize(dev, log_size + VHOST_LOG_BUFFER);
|
|
|
|
}
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_mem_table(dev, dev->mem);
|
2016-07-26 23:15:05 +02:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
|
2016-07-26 23:15:05 +02:00
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
/* To log less, can only decrease log size after table update. */
|
|
|
|
if (dev->log_size > log_size + VHOST_LOG_BUFFER) {
|
|
|
|
vhost_dev_log_resize(dev, log_size);
|
|
|
|
}
|
2018-01-19 11:39:18 +01:00
|
|
|
|
|
|
|
out:
|
|
|
|
/* Deref the old list of sections, this must happen _after_ the
|
|
|
|
* vhost_set_mem_table to ensure the client isn't still using the
|
|
|
|
* section we're about to unref.
|
|
|
|
*/
|
|
|
|
while (n_old_sections--) {
|
|
|
|
memory_region_unref(old_sections[n_old_sections].mr);
|
|
|
|
}
|
|
|
|
g_free(old_sections);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-01-19 11:39:20 +01:00
|
|
|
/* Adds the section data to the tmp_section structure.
|
|
|
|
* It relies on the listener calling us in memory address order
|
|
|
|
* and for each region (via the _add and _nop methods) to
|
|
|
|
* join neighbours.
|
|
|
|
*/
|
|
|
|
static void vhost_region_add_section(struct vhost_dev *dev,
|
|
|
|
MemoryRegionSection *section)
|
2018-01-19 11:39:18 +01:00
|
|
|
{
|
2018-01-19 11:39:20 +01:00
|
|
|
bool need_add = true;
|
|
|
|
uint64_t mrs_size = int128_get64(section->size);
|
|
|
|
uint64_t mrs_gpa = section->offset_within_address_space;
|
|
|
|
uintptr_t mrs_host = (uintptr_t)memory_region_get_ram_ptr(section->mr) +
|
|
|
|
section->offset_within_region;
|
2018-03-12 18:21:21 +01:00
|
|
|
RAMBlock *mrs_rb = section->mr->ram_block;
|
2018-01-19 11:39:20 +01:00
|
|
|
|
|
|
|
trace_vhost_region_add_section(section->mr->name, mrs_gpa, mrs_size,
|
|
|
|
mrs_host);
|
|
|
|
|
2020-01-22 09:06:47 +01:00
|
|
|
if (dev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_USER) {
|
2020-01-16 21:24:14 +01:00
|
|
|
/* Round the section to it's page size */
|
|
|
|
/* First align the start down to a page boundary */
|
|
|
|
size_t mrs_page = qemu_ram_pagesize(mrs_rb);
|
|
|
|
uint64_t alignage = mrs_host & (mrs_page - 1);
|
|
|
|
if (alignage) {
|
|
|
|
mrs_host -= alignage;
|
|
|
|
mrs_size += alignage;
|
|
|
|
mrs_gpa -= alignage;
|
|
|
|
}
|
|
|
|
/* Now align the size up to a page boundary */
|
|
|
|
alignage = mrs_size & (mrs_page - 1);
|
|
|
|
if (alignage) {
|
|
|
|
mrs_size += mrs_page - alignage;
|
|
|
|
}
|
2020-01-22 09:06:47 +01:00
|
|
|
trace_vhost_region_add_section_aligned(section->mr->name, mrs_gpa,
|
|
|
|
mrs_size, mrs_host);
|
2020-01-16 21:24:14 +01:00
|
|
|
}
|
2018-03-12 18:21:21 +01:00
|
|
|
|
memory,vhost: Allow for marking memory device memory regions unmergeable
Let's allow for marking memory regions unmergeable, to teach
flatview code and vhost to not merge adjacent aliases to the same memory
region into a larger memory section; instead, we want separate aliases to
stay separate such that we can atomically map/unmap aliases without
affecting other aliases.
This is desired for virtio-mem mapping device memory located on a RAM
memory region via multiple aliases into a memory region container,
resulting in separate memslots that can get (un)mapped atomically.
As an example with virtio-mem, the layout would look something like this:
[...]
0000000240000000-00000020bfffffff (prio 0, i/o): device-memory
0000000240000000-000000043fffffff (prio 0, i/o): virtio-mem
0000000240000000-000000027fffffff (prio 0, ram): alias memslot-0 @mem2 0000000000000000-000000003fffffff
0000000280000000-00000002bfffffff (prio 0, ram): alias memslot-1 @mem2 0000000040000000-000000007fffffff
00000002c0000000-00000002ffffffff (prio 0, ram): alias memslot-2 @mem2 0000000080000000-00000000bfffffff
[...]
Without unmergable memory regions, all three memslots would get merged into
a single memory section. For example, when mapping another alias (e.g.,
virtio-mem-memslot-3) or when unmapping any of the mapped aliases,
memory listeners will first get notified about the removal of the big
memory section to then get notified about re-adding of the new
(differently merged) memory section(s).
In an ideal world, memory listeners would be able to deal with that
atomically, like KVM nowadays does. However, (a) supporting this for other
memory listeners (vhost-user, vfio) is fairly hard: temporary removal
can result in all kinds of issues on concurrent access to guest memory;
and (b) this handling is undesired, because temporarily removing+readding
can consume quite some time on bigger memslots and is not efficient
(e.g., vfio unpinning and repinning pages ...).
Let's allow for marking a memory region unmergeable, such that we
can atomically (un)map aliases to the same memory region, similar to
(un)mapping individual DIMMs.
Similarly, teach vhost code to not redo what flatview core stopped doing:
don't merge such sections. Merging in vhost code is really only relevant
for handling random holes in boot memory where; without this merging,
the vhost-user backend wouldn't be able to mmap() some boot memory
backed on hugetlb.
We'll use this for virtio-mem next.
Message-ID: <20230926185738.277351-18-david@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: David Hildenbrand <david@redhat.com>
2023-09-26 20:57:37 +02:00
|
|
|
if (dev->n_tmp_sections && !section->unmergeable) {
|
2018-01-19 11:39:20 +01:00
|
|
|
/* Since we already have at least one section, lets see if
|
|
|
|
* this extends it; since we're scanning in order, we only
|
|
|
|
* have to look at the last one, and the FlatView that calls
|
|
|
|
* us shouldn't have overlaps.
|
|
|
|
*/
|
|
|
|
MemoryRegionSection *prev_sec = dev->tmp_sections +
|
|
|
|
(dev->n_tmp_sections - 1);
|
|
|
|
uint64_t prev_gpa_start = prev_sec->offset_within_address_space;
|
|
|
|
uint64_t prev_size = int128_get64(prev_sec->size);
|
|
|
|
uint64_t prev_gpa_end = range_get_last(prev_gpa_start, prev_size);
|
|
|
|
uint64_t prev_host_start =
|
|
|
|
(uintptr_t)memory_region_get_ram_ptr(prev_sec->mr) +
|
|
|
|
prev_sec->offset_within_region;
|
|
|
|
uint64_t prev_host_end = range_get_last(prev_host_start, prev_size);
|
|
|
|
|
2018-03-12 18:21:21 +01:00
|
|
|
if (mrs_gpa <= (prev_gpa_end + 1)) {
|
|
|
|
/* OK, looks like overlapping/intersecting - it's possible that
|
|
|
|
* the rounding to page sizes has made them overlap, but they should
|
|
|
|
* match up in the same RAMBlock if they do.
|
|
|
|
*/
|
|
|
|
if (mrs_gpa < prev_gpa_start) {
|
2020-01-16 21:24:13 +01:00
|
|
|
error_report("%s:Section '%s' rounded to %"PRIx64
|
|
|
|
" prior to previous '%s' %"PRIx64,
|
|
|
|
__func__, section->mr->name, mrs_gpa,
|
|
|
|
prev_sec->mr->name, prev_gpa_start);
|
2018-03-12 18:21:21 +01:00
|
|
|
/* A way to cleanly fail here would be better */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
/* Offset from the start of the previous GPA to this GPA */
|
|
|
|
size_t offset = mrs_gpa - prev_gpa_start;
|
|
|
|
|
|
|
|
if (prev_host_start + offset == mrs_host &&
|
memory,vhost: Allow for marking memory device memory regions unmergeable
Let's allow for marking memory regions unmergeable, to teach
flatview code and vhost to not merge adjacent aliases to the same memory
region into a larger memory section; instead, we want separate aliases to
stay separate such that we can atomically map/unmap aliases without
affecting other aliases.
This is desired for virtio-mem mapping device memory located on a RAM
memory region via multiple aliases into a memory region container,
resulting in separate memslots that can get (un)mapped atomically.
As an example with virtio-mem, the layout would look something like this:
[...]
0000000240000000-00000020bfffffff (prio 0, i/o): device-memory
0000000240000000-000000043fffffff (prio 0, i/o): virtio-mem
0000000240000000-000000027fffffff (prio 0, ram): alias memslot-0 @mem2 0000000000000000-000000003fffffff
0000000280000000-00000002bfffffff (prio 0, ram): alias memslot-1 @mem2 0000000040000000-000000007fffffff
00000002c0000000-00000002ffffffff (prio 0, ram): alias memslot-2 @mem2 0000000080000000-00000000bfffffff
[...]
Without unmergable memory regions, all three memslots would get merged into
a single memory section. For example, when mapping another alias (e.g.,
virtio-mem-memslot-3) or when unmapping any of the mapped aliases,
memory listeners will first get notified about the removal of the big
memory section to then get notified about re-adding of the new
(differently merged) memory section(s).
In an ideal world, memory listeners would be able to deal with that
atomically, like KVM nowadays does. However, (a) supporting this for other
memory listeners (vhost-user, vfio) is fairly hard: temporary removal
can result in all kinds of issues on concurrent access to guest memory;
and (b) this handling is undesired, because temporarily removing+readding
can consume quite some time on bigger memslots and is not efficient
(e.g., vfio unpinning and repinning pages ...).
Let's allow for marking a memory region unmergeable, such that we
can atomically (un)map aliases to the same memory region, similar to
(un)mapping individual DIMMs.
Similarly, teach vhost code to not redo what flatview core stopped doing:
don't merge such sections. Merging in vhost code is really only relevant
for handling random holes in boot memory where; without this merging,
the vhost-user backend wouldn't be able to mmap() some boot memory
backed on hugetlb.
We'll use this for virtio-mem next.
Message-ID: <20230926185738.277351-18-david@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: David Hildenbrand <david@redhat.com>
2023-09-26 20:57:37 +02:00
|
|
|
section->mr == prev_sec->mr && !prev_sec->unmergeable) {
|
2018-03-12 18:21:21 +01:00
|
|
|
uint64_t max_end = MAX(prev_host_end, mrs_host + mrs_size);
|
|
|
|
need_add = false;
|
|
|
|
prev_sec->offset_within_address_space =
|
|
|
|
MIN(prev_gpa_start, mrs_gpa);
|
|
|
|
prev_sec->offset_within_region =
|
|
|
|
MIN(prev_host_start, mrs_host) -
|
|
|
|
(uintptr_t)memory_region_get_ram_ptr(prev_sec->mr);
|
|
|
|
prev_sec->size = int128_make64(max_end - MIN(prev_host_start,
|
|
|
|
mrs_host));
|
|
|
|
trace_vhost_region_add_section_merge(section->mr->name,
|
|
|
|
int128_get64(prev_sec->size),
|
|
|
|
prev_sec->offset_within_address_space,
|
|
|
|
prev_sec->offset_within_region);
|
|
|
|
} else {
|
2018-03-23 16:39:39 +01:00
|
|
|
/* adjoining regions are fine, but overlapping ones with
|
|
|
|
* different blocks/offsets shouldn't happen
|
|
|
|
*/
|
|
|
|
if (mrs_gpa != prev_gpa_end + 1) {
|
|
|
|
error_report("%s: Overlapping but not coherent sections "
|
|
|
|
"at %"PRIx64,
|
|
|
|
__func__, mrs_gpa);
|
|
|
|
return;
|
|
|
|
}
|
2018-03-12 18:21:21 +01:00
|
|
|
}
|
2018-01-19 11:39:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (need_add) {
|
|
|
|
++dev->n_tmp_sections;
|
|
|
|
dev->tmp_sections = g_renew(MemoryRegionSection, dev->tmp_sections,
|
|
|
|
dev->n_tmp_sections);
|
|
|
|
dev->tmp_sections[dev->n_tmp_sections - 1] = *section;
|
|
|
|
/* The flatview isn't stable and we don't use it, making it NULL
|
|
|
|
* means we can memcmp the list.
|
|
|
|
*/
|
|
|
|
dev->tmp_sections[dev->n_tmp_sections - 1].fv = NULL;
|
|
|
|
memory_region_ref(section->mr);
|
|
|
|
}
|
2012-02-08 20:36:02 +01:00
|
|
|
}
|
|
|
|
|
2018-01-19 11:39:23 +01:00
|
|
|
/* Used for both add and nop callbacks */
|
|
|
|
static void vhost_region_addnop(MemoryListener *listener,
|
|
|
|
MemoryRegionSection *section)
|
2011-12-18 13:06:05 +01:00
|
|
|
{
|
2011-12-19 12:18:13 +01:00
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
memory_listener);
|
|
|
|
|
2018-05-24 12:33:31 +02:00
|
|
|
if (!vhost_section(dev, section)) {
|
2012-01-09 13:01:39 +01:00
|
|
|
return;
|
|
|
|
}
|
2018-01-19 11:39:20 +01:00
|
|
|
vhost_region_add_section(dev, section);
|
2011-12-18 13:06:05 +01:00
|
|
|
}
|
|
|
|
|
2017-03-29 06:10:04 +02:00
|
|
|
static void vhost_iommu_unmap_notify(IOMMUNotifier *n, IOMMUTLBEntry *iotlb)
|
|
|
|
{
|
|
|
|
struct vhost_iommu *iommu = container_of(n, struct vhost_iommu, n);
|
|
|
|
struct vhost_dev *hdev = iommu->hdev;
|
|
|
|
hwaddr iova = iotlb->iova + iommu->iommu_offset;
|
|
|
|
|
2017-06-02 12:18:28 +02:00
|
|
|
if (vhost_backend_invalidate_device_iotlb(hdev, iova,
|
|
|
|
iotlb->addr_mask + 1)) {
|
2017-03-29 06:10:04 +02:00
|
|
|
error_report("Fail to invalidate device iotlb");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_iommu_region_add(MemoryListener *listener,
|
|
|
|
MemoryRegionSection *section)
|
|
|
|
{
|
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
iommu_listener);
|
|
|
|
struct vhost_iommu *iommu;
|
memory: add section range info for IOMMU notifier
In this patch, IOMMUNotifier.{start|end} are introduced to store section
information for a specific notifier. When notification occurs, we not
only check the notification type (MAP|UNMAP), but also check whether the
notified iova range overlaps with the range of specific IOMMU notifier,
and skip those notifiers if not in the listened range.
When removing an region, we need to make sure we removed the correct
VFIOGuestIOMMU by checking the IOMMUNotifier.start address as well.
This patch is solving the problem that vfio-pci devices receive
duplicated UNMAP notification on x86 platform when vIOMMU is there. The
issue is that x86 IOMMU has a (0, 2^64-1) IOMMU region, which is
splitted by the (0xfee00000, 0xfeefffff) IRQ region. AFAIK
this (splitted IOMMU region) is only happening on x86.
This patch also helps vhost to leverage the new interface as well, so
that vhost won't get duplicated cache flushes. In that sense, it's an
slight performance improvement.
Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <1491562755-23867-2-git-send-email-peterx@redhat.com>
[ehabkost: included extra vhost_iommu_region_del() change from Peter Xu]
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-04-07 12:59:07 +02:00
|
|
|
Int128 end;
|
2020-07-22 10:40:48 +02:00
|
|
|
int iommu_idx;
|
2018-07-20 10:36:44 +02:00
|
|
|
IOMMUMemoryRegion *iommu_mr;
|
2017-03-29 06:10:04 +02:00
|
|
|
|
|
|
|
if (!memory_region_is_iommu(section->mr)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-07-20 10:36:44 +02:00
|
|
|
iommu_mr = IOMMU_MEMORY_REGION(section->mr);
|
|
|
|
|
2017-03-29 06:10:04 +02:00
|
|
|
iommu = g_malloc0(sizeof(*iommu));
|
memory: add section range info for IOMMU notifier
In this patch, IOMMUNotifier.{start|end} are introduced to store section
information for a specific notifier. When notification occurs, we not
only check the notification type (MAP|UNMAP), but also check whether the
notified iova range overlaps with the range of specific IOMMU notifier,
and skip those notifiers if not in the listened range.
When removing an region, we need to make sure we removed the correct
VFIOGuestIOMMU by checking the IOMMUNotifier.start address as well.
This patch is solving the problem that vfio-pci devices receive
duplicated UNMAP notification on x86 platform when vIOMMU is there. The
issue is that x86 IOMMU has a (0, 2^64-1) IOMMU region, which is
splitted by the (0xfee00000, 0xfeefffff) IRQ region. AFAIK
this (splitted IOMMU region) is only happening on x86.
This patch also helps vhost to leverage the new interface as well, so
that vhost won't get duplicated cache flushes. In that sense, it's an
slight performance improvement.
Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <1491562755-23867-2-git-send-email-peterx@redhat.com>
[ehabkost: included extra vhost_iommu_region_del() change from Peter Xu]
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-04-07 12:59:07 +02:00
|
|
|
end = int128_add(int128_make64(section->offset_within_region),
|
|
|
|
section->size);
|
|
|
|
end = int128_sub(end, int128_one());
|
2018-06-15 15:57:16 +02:00
|
|
|
iommu_idx = memory_region_iommu_attrs_to_index(iommu_mr,
|
|
|
|
MEMTXATTRS_UNSPECIFIED);
|
memory: add section range info for IOMMU notifier
In this patch, IOMMUNotifier.{start|end} are introduced to store section
information for a specific notifier. When notification occurs, we not
only check the notification type (MAP|UNMAP), but also check whether the
notified iova range overlaps with the range of specific IOMMU notifier,
and skip those notifiers if not in the listened range.
When removing an region, we need to make sure we removed the correct
VFIOGuestIOMMU by checking the IOMMUNotifier.start address as well.
This patch is solving the problem that vfio-pci devices receive
duplicated UNMAP notification on x86 platform when vIOMMU is there. The
issue is that x86 IOMMU has a (0, 2^64-1) IOMMU region, which is
splitted by the (0xfee00000, 0xfeefffff) IRQ region. AFAIK
this (splitted IOMMU region) is only happening on x86.
This patch also helps vhost to leverage the new interface as well, so
that vhost won't get duplicated cache flushes. In that sense, it's an
slight performance improvement.
Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <1491562755-23867-2-git-send-email-peterx@redhat.com>
[ehabkost: included extra vhost_iommu_region_del() change from Peter Xu]
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-04-07 12:59:07 +02:00
|
|
|
iommu_notifier_init(&iommu->n, vhost_iommu_unmap_notify,
|
2023-06-26 11:12:57 +02:00
|
|
|
dev->vdev->device_iotlb_enabled ?
|
|
|
|
IOMMU_NOTIFIER_DEVIOTLB_UNMAP :
|
|
|
|
IOMMU_NOTIFIER_UNMAP,
|
memory: add section range info for IOMMU notifier
In this patch, IOMMUNotifier.{start|end} are introduced to store section
information for a specific notifier. When notification occurs, we not
only check the notification type (MAP|UNMAP), but also check whether the
notified iova range overlaps with the range of specific IOMMU notifier,
and skip those notifiers if not in the listened range.
When removing an region, we need to make sure we removed the correct
VFIOGuestIOMMU by checking the IOMMUNotifier.start address as well.
This patch is solving the problem that vfio-pci devices receive
duplicated UNMAP notification on x86 platform when vIOMMU is there. The
issue is that x86 IOMMU has a (0, 2^64-1) IOMMU region, which is
splitted by the (0xfee00000, 0xfeefffff) IRQ region. AFAIK
this (splitted IOMMU region) is only happening on x86.
This patch also helps vhost to leverage the new interface as well, so
that vhost won't get duplicated cache flushes. In that sense, it's an
slight performance improvement.
Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <1491562755-23867-2-git-send-email-peterx@redhat.com>
[ehabkost: included extra vhost_iommu_region_del() change from Peter Xu]
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-04-07 12:59:07 +02:00
|
|
|
section->offset_within_region,
|
2018-06-15 15:57:16 +02:00
|
|
|
int128_get64(end),
|
|
|
|
iommu_idx);
|
2017-03-29 06:10:04 +02:00
|
|
|
iommu->mr = section->mr;
|
|
|
|
iommu->iommu_offset = section->offset_within_address_space -
|
|
|
|
section->offset_within_region;
|
|
|
|
iommu->hdev = dev;
|
2023-06-26 11:12:57 +02:00
|
|
|
memory_region_register_iommu_notifier(section->mr, &iommu->n,
|
|
|
|
&error_fatal);
|
2017-03-29 06:10:04 +02:00
|
|
|
QLIST_INSERT_HEAD(&dev->iommu_list, iommu, iommu_next);
|
|
|
|
/* TODO: can replay help performance here? */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_iommu_region_del(MemoryListener *listener,
|
|
|
|
MemoryRegionSection *section)
|
|
|
|
{
|
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
iommu_listener);
|
|
|
|
struct vhost_iommu *iommu;
|
|
|
|
|
|
|
|
if (!memory_region_is_iommu(section->mr)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QLIST_FOREACH(iommu, &dev->iommu_list, iommu_next) {
|
memory: add section range info for IOMMU notifier
In this patch, IOMMUNotifier.{start|end} are introduced to store section
information for a specific notifier. When notification occurs, we not
only check the notification type (MAP|UNMAP), but also check whether the
notified iova range overlaps with the range of specific IOMMU notifier,
and skip those notifiers if not in the listened range.
When removing an region, we need to make sure we removed the correct
VFIOGuestIOMMU by checking the IOMMUNotifier.start address as well.
This patch is solving the problem that vfio-pci devices receive
duplicated UNMAP notification on x86 platform when vIOMMU is there. The
issue is that x86 IOMMU has a (0, 2^64-1) IOMMU region, which is
splitted by the (0xfee00000, 0xfeefffff) IRQ region. AFAIK
this (splitted IOMMU region) is only happening on x86.
This patch also helps vhost to leverage the new interface as well, so
that vhost won't get duplicated cache flushes. In that sense, it's an
slight performance improvement.
Suggested-by: David Gibson <david@gibson.dropbear.id.au>
Reviewed-by: Eric Auger <eric.auger@redhat.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Acked-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Peter Xu <peterx@redhat.com>
Message-Id: <1491562755-23867-2-git-send-email-peterx@redhat.com>
[ehabkost: included extra vhost_iommu_region_del() change from Peter Xu]
Signed-off-by: Eduardo Habkost <ehabkost@redhat.com>
2017-04-07 12:59:07 +02:00
|
|
|
if (iommu->mr == section->mr &&
|
|
|
|
iommu->n.start == section->offset_within_region) {
|
2017-03-29 06:10:04 +02:00
|
|
|
memory_region_unregister_iommu_notifier(iommu->mr,
|
|
|
|
&iommu->n);
|
|
|
|
QLIST_REMOVE(iommu, iommu_next);
|
|
|
|
g_free(iommu);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-26 11:12:57 +02:00
|
|
|
void vhost_toggle_device_iotlb(VirtIODevice *vdev)
|
|
|
|
{
|
|
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
|
|
struct vhost_dev *dev;
|
|
|
|
struct vhost_iommu *iommu;
|
|
|
|
|
|
|
|
if (vdev->vhost_started) {
|
|
|
|
dev = vdc->get_vhost(vdev);
|
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QLIST_FOREACH(iommu, &dev->iommu_list, iommu_next) {
|
|
|
|
memory_region_unregister_iommu_notifier(iommu->mr, &iommu->n);
|
|
|
|
iommu->n.notifier_flags = vdev->device_iotlb_enabled ?
|
|
|
|
IOMMU_NOTIFIER_DEVIOTLB_UNMAP : IOMMU_NOTIFIER_UNMAP;
|
|
|
|
memory_region_register_iommu_notifier(iommu->mr, &iommu->n,
|
|
|
|
&error_fatal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
static int vhost_virtqueue_set_addr(struct vhost_dev *dev,
|
|
|
|
struct vhost_virtqueue *vq,
|
|
|
|
unsigned idx, bool enable_log)
|
|
|
|
{
|
2020-07-01 16:55:33 +02:00
|
|
|
struct vhost_vring_addr addr;
|
|
|
|
int r;
|
|
|
|
memset(&addr, 0, sizeof(struct vhost_vring_addr));
|
|
|
|
|
|
|
|
if (dev->vhost_ops->vhost_vq_get_addr) {
|
|
|
|
r = dev->vhost_ops->vhost_vq_get_addr(dev, &addr, vq);
|
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_vq_get_addr failed");
|
|
|
|
return r;
|
2020-07-01 16:55:33 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
addr.desc_user_addr = (uint64_t)(unsigned long)vq->desc;
|
|
|
|
addr.avail_user_addr = (uint64_t)(unsigned long)vq->avail;
|
|
|
|
addr.used_user_addr = (uint64_t)(unsigned long)vq->used;
|
|
|
|
}
|
|
|
|
addr.index = idx;
|
|
|
|
addr.log_guest_addr = vq->used_phys;
|
|
|
|
addr.flags = enable_log ? (1 << VHOST_VRING_F_LOG) : 0;
|
|
|
|
r = dev->vhost_ops->vhost_set_vring_addr(dev, &addr);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_addr failed");
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2021-11-11 16:33:53 +01:00
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
static int vhost_dev_set_features(struct vhost_dev *dev,
|
|
|
|
bool enable_log)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
|
|
|
uint64_t features = dev->acked_features;
|
|
|
|
int r;
|
|
|
|
if (enable_log) {
|
2015-06-04 12:34:20 +02:00
|
|
|
features |= 0x1ULL << VHOST_F_LOG_ALL;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2020-03-02 05:24:54 +01:00
|
|
|
if (!vhost_dev_has_iommu(dev)) {
|
|
|
|
features &= ~(0x1ULL << VIRTIO_F_IOMMU_PLATFORM);
|
|
|
|
}
|
2020-07-01 16:55:35 +02:00
|
|
|
if (dev->vhost_ops->vhost_force_iommu) {
|
|
|
|
if (dev->vhost_ops->vhost_force_iommu(dev) == true) {
|
|
|
|
features |= 0x1ULL << VIRTIO_F_IOMMU_PLATFORM;
|
|
|
|
}
|
|
|
|
}
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_features(dev, features);
|
2016-07-26 23:15:06 +02:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_features failed");
|
2020-09-07 12:49:02 +02:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
if (dev->vhost_ops->vhost_set_backend_cap) {
|
|
|
|
r = dev->vhost_ops->vhost_set_backend_cap(dev);
|
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_backend_cap failed");
|
2020-09-07 12:49:02 +02:00
|
|
|
goto out;
|
|
|
|
}
|
2016-07-26 23:15:06 +02:00
|
|
|
}
|
2020-09-07 12:49:02 +02:00
|
|
|
|
|
|
|
out:
|
2021-11-11 16:33:53 +01:00
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int vhost_dev_set_log(struct vhost_dev *dev, bool enable_log)
|
|
|
|
{
|
2016-07-26 23:15:05 +02:00
|
|
|
int r, i, idx;
|
2020-09-11 10:39:44 +02:00
|
|
|
hwaddr addr;
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
r = vhost_dev_set_features(dev, enable_log);
|
|
|
|
if (r < 0) {
|
|
|
|
goto err_features;
|
|
|
|
}
|
|
|
|
for (i = 0; i < dev->nvqs; ++i) {
|
2015-10-19 14:59:27 +02:00
|
|
|
idx = dev->vhost_ops->vhost_get_vq_index(dev, dev->vq_index + i);
|
2020-09-11 10:39:44 +02:00
|
|
|
addr = virtio_queue_get_desc_addr(dev->vdev, idx);
|
|
|
|
if (!addr) {
|
|
|
|
/*
|
|
|
|
* The queue might not be ready for start. If this
|
|
|
|
* is the case there is no reason to continue the process.
|
|
|
|
* The similar logic is used by the vhost_virtqueue_start()
|
|
|
|
* routine.
|
|
|
|
*/
|
|
|
|
continue;
|
|
|
|
}
|
2015-10-19 14:59:27 +02:00
|
|
|
r = vhost_virtqueue_set_addr(dev, dev->vqs + i, idx,
|
2010-03-17 12:08:17 +01:00
|
|
|
enable_log);
|
|
|
|
if (r < 0) {
|
|
|
|
goto err_vq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_vq:
|
|
|
|
for (; i >= 0; --i) {
|
2015-10-19 14:59:27 +02:00
|
|
|
idx = dev->vhost_ops->vhost_get_vq_index(dev, dev->vq_index + i);
|
2022-06-09 15:10:12 +02:00
|
|
|
addr = virtio_queue_get_desc_addr(dev->vdev, idx);
|
|
|
|
if (!addr) {
|
|
|
|
continue;
|
|
|
|
}
|
2016-07-26 23:15:05 +02:00
|
|
|
vhost_virtqueue_set_addr(dev, dev->vqs + i, idx,
|
|
|
|
dev->log_enabled);
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2016-07-26 23:15:05 +02:00
|
|
|
vhost_dev_set_features(dev, dev->log_enabled);
|
2010-03-17 12:08:17 +01:00
|
|
|
err_features:
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2020-05-07 23:37:58 +02:00
|
|
|
static int vhost_migration_log(MemoryListener *listener, bool enable)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2011-12-18 13:06:05 +01:00
|
|
|
struct vhost_dev *dev = container_of(listener, struct vhost_dev,
|
|
|
|
memory_listener);
|
2010-03-17 12:08:17 +01:00
|
|
|
int r;
|
2020-05-07 23:37:58 +02:00
|
|
|
if (enable == dev->log_enabled) {
|
2010-03-17 12:08:17 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (!dev->started) {
|
|
|
|
dev->log_enabled = enable;
|
|
|
|
return 0;
|
|
|
|
}
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
|
|
|
|
r = 0;
|
2010-03-17 12:08:17 +01:00
|
|
|
if (!enable) {
|
|
|
|
r = vhost_dev_set_log(dev, false);
|
|
|
|
if (r < 0) {
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
goto check_dev_state;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2015-06-04 11:28:46 +02:00
|
|
|
vhost_log_put(dev, false);
|
2010-03-17 12:08:17 +01:00
|
|
|
} else {
|
|
|
|
vhost_dev_log_resize(dev, vhost_get_log_size(dev));
|
|
|
|
r = vhost_dev_set_log(dev, true);
|
|
|
|
if (r < 0) {
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
goto check_dev_state;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
}
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
|
|
|
|
check_dev_state:
|
2010-03-17 12:08:17 +01:00
|
|
|
dev->log_enabled = enable;
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
/*
|
|
|
|
* vhost-user-* devices could change their state during log
|
|
|
|
* initialization due to disconnect. So check dev state after
|
|
|
|
* vhost communication.
|
|
|
|
*/
|
|
|
|
if (!dev->started) {
|
|
|
|
/*
|
|
|
|
* Since device is in the stopped state, it is okay for
|
|
|
|
* migration. Return success.
|
|
|
|
*/
|
|
|
|
r = 0;
|
|
|
|
}
|
|
|
|
if (r) {
|
2021-03-09 12:15:10 +01:00
|
|
|
/* An error occurred. */
|
vhost: recheck dev state in the vhost_migration_log routine
vhost-user devices can get a disconnect in the middle of the VHOST-USER
handshake on the migration start. If disconnect event happened right
before sending next VHOST-USER command, then the vhost_dev_set_log()
call in the vhost_migration_log() function will return error. This error
will lead to the assert() and close the QEMU migration source process.
For the vhost-user devices the disconnect event should not break the
migration process, because:
- the device will be in the stopped state, so it will not be changed
during migration
- if reconnect will be made the migration log will be reinitialized as
part of reconnect/init process:
#0 vhost_log_global_start (listener=0x563989cf7be0)
at hw/virtio/vhost.c:920
#1 0x000056398603d8bc in listener_add_address_space (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2664
#2 0x000056398603dd30 in memory_listener_register (listener=0x563989cf7be0,
as=0x563986ea4340 <address_space_memory>)
at softmmu/memory.c:2740
#3 0x0000563985fd6956 in vhost_dev_init (hdev=0x563989cf7bd8,
opaque=0x563989cf7e30, backend_type=VHOST_BACKEND_TYPE_USER,
busyloop_timeout=0)
at hw/virtio/vhost.c:1385
#4 0x0000563985f7d0b8 in vhost_user_blk_connect (dev=0x563989cf7990)
at hw/block/vhost-user-blk.c:315
#5 0x0000563985f7d3f6 in vhost_user_blk_event (opaque=0x563989cf7990,
event=CHR_EVENT_OPENED)
at hw/block/vhost-user-blk.c:379
Update the vhost-user-blk device with the internal started_vu field which
will be used for initialization (vhost_user_blk_start) and clean up
(vhost_user_blk_stop). This additional flag in the VhostUserBlk structure
will be used to track whether the device really needs to be stopped and
cleaned up on a vhost-user level.
The disconnect event will set the overall VHOST device (not vhost-user) to
the stopped state, so it can be used by the general vhost_migration_log
routine.
Such approach could be propogated to the other vhost-user devices, but
better idea is just to make the same connect/disconnect code for all the
vhost-user devices.
This migration issue was slightly discussed earlier:
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg01509.html
- https://lists.gnu.org/archive/html/qemu-devel/2020-05/msg05241.html
Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com>
Message-Id: <9fbfba06791a87813fcee3e2315f0b904cc6789a.1599813294.git.dimastep@yandex-team.ru>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
2020-09-11 10:39:43 +02:00
|
|
|
dev->log_enabled = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2011-12-18 13:06:05 +01:00
|
|
|
static void vhost_log_global_start(MemoryListener *listener)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = vhost_migration_log(listener, true);
|
|
|
|
if (r < 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_log_global_stop(MemoryListener *listener)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
r = vhost_migration_log(listener, false);
|
|
|
|
if (r < 0) {
|
|
|
|
abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_log_start(MemoryListener *listener,
|
2015-04-25 14:38:30 +02:00
|
|
|
MemoryRegionSection *section,
|
|
|
|
int old, int new)
|
2011-12-18 13:06:05 +01:00
|
|
|
{
|
|
|
|
/* FIXME: implement */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_log_stop(MemoryListener *listener,
|
2015-04-25 14:38:30 +02:00
|
|
|
MemoryRegionSection *section,
|
|
|
|
int old, int new)
|
2011-12-18 13:06:05 +01:00
|
|
|
{
|
|
|
|
/* FIXME: implement */
|
|
|
|
}
|
|
|
|
|
2016-02-05 11:46:04 +01:00
|
|
|
/* The vhost driver natively knows how to handle the vrings of non
|
|
|
|
* cross-endian legacy devices and modern devices. Only legacy devices
|
|
|
|
* exposed to a bi-endian guest may require the vhost driver to use a
|
|
|
|
* specific endianness.
|
|
|
|
*/
|
2016-02-05 11:45:40 +01:00
|
|
|
static inline bool vhost_needs_vring_endian(VirtIODevice *vdev)
|
|
|
|
{
|
2016-02-05 11:45:49 +01:00
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
|
|
return false;
|
|
|
|
}
|
2022-03-23 16:57:17 +01:00
|
|
|
#if HOST_BIG_ENDIAN
|
2016-02-05 11:46:04 +01:00
|
|
|
return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_LITTLE;
|
2016-02-05 11:45:40 +01:00
|
|
|
#else
|
2016-02-05 11:46:04 +01:00
|
|
|
return vdev->device_endian == VIRTIO_DEVICE_ENDIAN_BIG;
|
2016-02-05 11:45:40 +01:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2015-06-17 15:23:39 +02:00
|
|
|
static int vhost_virtqueue_set_vring_endian_legacy(struct vhost_dev *dev,
|
|
|
|
bool is_big_endian,
|
|
|
|
int vhost_vq_index)
|
|
|
|
{
|
2021-11-11 16:33:53 +01:00
|
|
|
int r;
|
2015-06-17 15:23:39 +02:00
|
|
|
struct vhost_vring_state s = {
|
|
|
|
.index = vhost_vq_index,
|
|
|
|
.num = is_big_endian
|
|
|
|
};
|
|
|
|
|
2021-11-11 16:33:53 +01:00
|
|
|
r = dev->vhost_ops->vhost_set_vring_endian(dev, &s);
|
|
|
|
if (r < 0) {
|
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_endian failed");
|
2015-06-17 15:23:39 +02:00
|
|
|
}
|
2021-11-11 16:33:53 +01:00
|
|
|
return r;
|
2015-06-17 15:23:39 +02:00
|
|
|
}
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
static int vhost_memory_region_lookup(struct vhost_dev *hdev,
|
|
|
|
uint64_t gpa, uint64_t *uaddr,
|
|
|
|
uint64_t *len)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < hdev->mem->nregions; i++) {
|
|
|
|
struct vhost_memory_region *reg = hdev->mem->regions + i;
|
|
|
|
|
|
|
|
if (gpa >= reg->guest_phys_addr &&
|
|
|
|
reg->guest_phys_addr + reg->memory_size > gpa) {
|
|
|
|
*uaddr = reg->userspace_addr + gpa - reg->guest_phys_addr;
|
|
|
|
*len = reg->guest_phys_addr + reg->memory_size - gpa;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -EFAULT;
|
|
|
|
}
|
|
|
|
|
2017-06-02 12:18:27 +02:00
|
|
|
int vhost_device_iotlb_miss(struct vhost_dev *dev, uint64_t iova, int write)
|
2017-01-11 05:32:12 +01:00
|
|
|
{
|
|
|
|
IOMMUTLBEntry iotlb;
|
|
|
|
uint64_t uaddr, len;
|
2017-06-02 12:18:27 +02:00
|
|
|
int ret = -EFAULT;
|
2017-01-11 05:32:12 +01:00
|
|
|
|
2019-10-25 12:34:02 +02:00
|
|
|
RCU_READ_LOCK_GUARD();
|
2017-01-11 05:32:12 +01:00
|
|
|
|
2018-04-27 11:07:24 +02:00
|
|
|
trace_vhost_iotlb_miss(dev, 1);
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
iotlb = address_space_get_iotlb_entry(dev->vdev->dma_as,
|
2018-05-31 15:50:53 +02:00
|
|
|
iova, write,
|
|
|
|
MEMTXATTRS_UNSPECIFIED);
|
2017-01-11 05:32:12 +01:00
|
|
|
if (iotlb.target_as != NULL) {
|
2017-06-02 12:18:27 +02:00
|
|
|
ret = vhost_memory_region_lookup(dev, iotlb.translated_addr,
|
|
|
|
&uaddr, &len);
|
|
|
|
if (ret) {
|
2018-04-27 11:07:24 +02:00
|
|
|
trace_vhost_iotlb_miss(dev, 3);
|
2017-01-11 05:32:12 +01:00
|
|
|
error_report("Fail to lookup the translated address "
|
|
|
|
"%"PRIx64, iotlb.translated_addr);
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = MIN(iotlb.addr_mask + 1, len);
|
|
|
|
iova = iova & ~iotlb.addr_mask;
|
|
|
|
|
2017-06-02 12:18:28 +02:00
|
|
|
ret = vhost_backend_update_device_iotlb(dev, iova, uaddr,
|
|
|
|
len, iotlb.perm);
|
2017-06-02 12:18:27 +02:00
|
|
|
if (ret) {
|
2018-04-27 11:07:24 +02:00
|
|
|
trace_vhost_iotlb_miss(dev, 4);
|
2017-01-11 05:32:12 +01:00
|
|
|
error_report("Fail to update device iotlb");
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
2018-04-27 11:07:24 +02:00
|
|
|
|
|
|
|
trace_vhost_iotlb_miss(dev, 2);
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
out:
|
2017-06-02 12:18:27 +02:00
|
|
|
return ret;
|
2017-01-11 05:32:12 +01:00
|
|
|
}
|
|
|
|
|
2022-10-17 11:25:50 +02:00
|
|
|
int vhost_virtqueue_start(struct vhost_dev *dev,
|
|
|
|
struct VirtIODevice *vdev,
|
|
|
|
struct vhost_virtqueue *vq,
|
|
|
|
unsigned idx)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2016-08-01 10:07:58 +02:00
|
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
|
|
|
VirtioBusState *vbus = VIRTIO_BUS(qbus);
|
|
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(vbus);
|
2012-10-23 12:30:10 +02:00
|
|
|
hwaddr s, l, a;
|
2010-03-17 12:08:17 +01:00
|
|
|
int r;
|
2015-10-09 17:17:28 +02:00
|
|
|
int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, idx);
|
2010-03-17 12:08:17 +01:00
|
|
|
struct vhost_vring_file file = {
|
2013-01-30 12:12:35 +01:00
|
|
|
.index = vhost_vq_index
|
2010-03-17 12:08:17 +01:00
|
|
|
};
|
|
|
|
struct vhost_vring_state state = {
|
2013-01-30 12:12:35 +01:00
|
|
|
.index = vhost_vq_index
|
2010-03-17 12:08:17 +01:00
|
|
|
};
|
|
|
|
struct VirtQueue *vvq = virtio_get_queue(vdev, idx);
|
|
|
|
|
2018-02-28 10:35:28 +01:00
|
|
|
a = virtio_queue_get_desc_addr(vdev, idx);
|
|
|
|
if (a == 0) {
|
|
|
|
/* Queue might not be ready for start */
|
|
|
|
return 0;
|
|
|
|
}
|
2013-01-30 12:12:35 +01:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
vq->num = state.num = virtio_queue_get_num(vdev, idx);
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_vring_num(dev, &state);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_num failed");
|
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
state.num = virtio_queue_get_last_avail_idx(vdev, idx);
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_vring_base(dev, &state);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_base failed");
|
|
|
|
return r;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2016-02-05 11:45:49 +01:00
|
|
|
if (vhost_needs_vring_endian(vdev)) {
|
2015-06-17 15:23:39 +02:00
|
|
|
r = vhost_virtqueue_set_vring_endian_legacy(dev,
|
|
|
|
virtio_is_big_endian(vdev),
|
|
|
|
vhost_vq_index);
|
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
return r;
|
2015-06-17 15:23:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-04 09:39:15 +01:00
|
|
|
vq->desc_size = s = l = virtio_queue_get_desc_size(vdev, idx);
|
2018-02-28 10:35:28 +01:00
|
|
|
vq->desc_phys = a;
|
2020-02-19 19:18:45 +01:00
|
|
|
vq->desc = vhost_memory_map(dev, a, &l, false);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (!vq->desc || l != s) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto fail_alloc_desc;
|
|
|
|
}
|
2016-11-04 09:39:15 +01:00
|
|
|
vq->avail_size = s = l = virtio_queue_get_avail_size(vdev, idx);
|
|
|
|
vq->avail_phys = a = virtio_queue_get_avail_addr(vdev, idx);
|
2020-02-19 19:18:45 +01:00
|
|
|
vq->avail = vhost_memory_map(dev, a, &l, false);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (!vq->avail || l != s) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto fail_alloc_avail;
|
|
|
|
}
|
|
|
|
vq->used_size = s = l = virtio_queue_get_used_size(vdev, idx);
|
|
|
|
vq->used_phys = a = virtio_queue_get_used_addr(vdev, idx);
|
2020-02-19 19:18:45 +01:00
|
|
|
vq->used = vhost_memory_map(dev, a, &l, true);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (!vq->used || l != s) {
|
|
|
|
r = -ENOMEM;
|
|
|
|
goto fail_alloc_used;
|
|
|
|
}
|
|
|
|
|
2013-01-30 12:12:35 +01:00
|
|
|
r = vhost_virtqueue_set_addr(dev, vq, vhost_vq_index, dev->log_enabled);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
|
|
|
goto fail_alloc;
|
|
|
|
}
|
2013-01-30 12:12:35 +01:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
file.fd = event_notifier_get_fd(virtio_queue_get_host_notifier(vvq));
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_vring_kick(dev, &file);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_kick failed");
|
2010-03-17 12:08:17 +01:00
|
|
|
goto fail_kick;
|
|
|
|
}
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
/* Clear and discard previous events if any. */
|
|
|
|
event_notifier_test_and_clear(&vq->masked_notifier);
|
2010-03-17 12:08:17 +01:00
|
|
|
|
2016-02-18 15:12:23 +01:00
|
|
|
/* Init vring in unmasked state, unless guest_notifier_mask
|
|
|
|
* will do it later.
|
|
|
|
*/
|
|
|
|
if (!vdev->use_guest_notifier_mask) {
|
|
|
|
/* TODO: check and handle errors. */
|
|
|
|
vhost_virtqueue_mask(dev, vdev, idx, false);
|
|
|
|
}
|
|
|
|
|
2016-08-01 10:07:58 +02:00
|
|
|
if (k->query_guest_notifiers &&
|
|
|
|
k->query_guest_notifiers(qbus->parent) &&
|
|
|
|
virtio_queue_vector(vdev, idx) == VIRTIO_NO_VECTOR) {
|
|
|
|
file.fd = -1;
|
|
|
|
r = dev->vhost_ops->vhost_set_vring_call(dev, &file);
|
|
|
|
if (r) {
|
|
|
|
goto fail_vector;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
return 0;
|
|
|
|
|
2016-08-01 10:07:58 +02:00
|
|
|
fail_vector:
|
2010-03-17 12:08:17 +01:00
|
|
|
fail_kick:
|
|
|
|
fail_alloc:
|
2017-01-11 05:32:12 +01:00
|
|
|
vhost_memory_unmap(dev, vq->used, virtio_queue_get_used_size(vdev, idx),
|
|
|
|
0, 0);
|
2010-03-17 12:08:17 +01:00
|
|
|
fail_alloc_used:
|
2017-01-11 05:32:12 +01:00
|
|
|
vhost_memory_unmap(dev, vq->avail, virtio_queue_get_avail_size(vdev, idx),
|
|
|
|
0, 0);
|
2010-03-17 12:08:17 +01:00
|
|
|
fail_alloc_avail:
|
2017-01-11 05:32:12 +01:00
|
|
|
vhost_memory_unmap(dev, vq->desc, virtio_queue_get_desc_size(vdev, idx),
|
|
|
|
0, 0);
|
2010-03-17 12:08:17 +01:00
|
|
|
fail_alloc_desc:
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2022-10-17 11:25:51 +02:00
|
|
|
void vhost_virtqueue_stop(struct vhost_dev *dev,
|
|
|
|
struct VirtIODevice *vdev,
|
|
|
|
struct vhost_virtqueue *vq,
|
|
|
|
unsigned idx)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2015-10-09 17:17:28 +02:00
|
|
|
int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, idx);
|
2010-03-17 12:08:17 +01:00
|
|
|
struct vhost_vring_state state = {
|
2015-06-17 15:23:39 +02:00
|
|
|
.index = vhost_vq_index,
|
2010-03-17 12:08:17 +01:00
|
|
|
};
|
|
|
|
int r;
|
2018-02-28 10:35:28 +01:00
|
|
|
|
2018-07-13 16:04:05 +02:00
|
|
|
if (virtio_queue_get_desc_addr(vdev, idx) == 0) {
|
2018-02-28 10:35:28 +01:00
|
|
|
/* Don't stop the virtqueue which might have not been started */
|
|
|
|
return;
|
|
|
|
}
|
2015-09-23 06:19:59 +02:00
|
|
|
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_get_vring_base(dev, &state);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost VQ %u ring restore failed: %d", idx, r);
|
2017-11-16 19:48:35 +01:00
|
|
|
/* Connection to the backend is broken, so let's sync internal
|
|
|
|
* last avail idx to the device used idx.
|
|
|
|
*/
|
|
|
|
virtio_queue_restore_last_avail_idx(vdev, idx);
|
2016-07-26 23:15:27 +02:00
|
|
|
} else {
|
|
|
|
virtio_queue_set_last_avail_idx(vdev, idx, state.num);
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2013-08-12 11:21:36 +02:00
|
|
|
virtio_queue_invalidate_signalled_used(vdev, idx);
|
2016-12-13 09:12:06 +01:00
|
|
|
virtio_queue_update_used_idx(vdev, idx);
|
2015-06-17 15:23:39 +02:00
|
|
|
|
|
|
|
/* In the cross-endian case, we need to reset the vring endianness to
|
|
|
|
* native as legacy devices expect so by default.
|
|
|
|
*/
|
2016-02-05 11:45:49 +01:00
|
|
|
if (vhost_needs_vring_endian(vdev)) {
|
2016-07-26 23:15:05 +02:00
|
|
|
vhost_virtqueue_set_vring_endian_legacy(dev,
|
|
|
|
!virtio_is_big_endian(vdev),
|
|
|
|
vhost_vq_index);
|
2015-06-17 15:23:39 +02:00
|
|
|
}
|
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
vhost_memory_unmap(dev, vq->used, virtio_queue_get_used_size(vdev, idx),
|
|
|
|
1, virtio_queue_get_used_size(vdev, idx));
|
|
|
|
vhost_memory_unmap(dev, vq->avail, virtio_queue_get_avail_size(vdev, idx),
|
|
|
|
0, virtio_queue_get_avail_size(vdev, idx));
|
|
|
|
vhost_memory_unmap(dev, vq->desc, virtio_queue_get_desc_size(vdev, idx),
|
|
|
|
0, virtio_queue_get_desc_size(vdev, idx));
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2016-07-06 03:57:55 +02:00
|
|
|
static int vhost_virtqueue_set_busyloop_timeout(struct vhost_dev *dev,
|
|
|
|
int n, uint32_t timeout)
|
|
|
|
{
|
|
|
|
int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, n);
|
|
|
|
struct vhost_vring_state state = {
|
|
|
|
.index = vhost_vq_index,
|
|
|
|
.num = timeout,
|
|
|
|
};
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (!dev->vhost_ops->vhost_set_vring_busyloop_timeout) {
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = dev->vhost_ops->vhost_set_vring_busyloop_timeout(dev, &state);
|
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_busyloop_timeout failed");
|
2016-07-06 03:57:55 +02:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-23 18:13:25 +02:00
|
|
|
static void vhost_virtqueue_error_notifier(EventNotifier *n)
|
|
|
|
{
|
|
|
|
struct vhost_virtqueue *vq = container_of(n, struct vhost_virtqueue,
|
|
|
|
error_notifier);
|
|
|
|
struct vhost_dev *dev = vq->dev;
|
|
|
|
int index = vq - dev->vqs;
|
|
|
|
|
|
|
|
if (event_notifier_test_and_clear(n) && dev->vdev) {
|
|
|
|
VHOST_OPS_DEBUG(-EINVAL, "vhost vring error in virtqueue %d",
|
|
|
|
dev->vq_index + index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
static int vhost_virtqueue_init(struct vhost_dev *dev,
|
|
|
|
struct vhost_virtqueue *vq, int n)
|
|
|
|
{
|
2015-10-09 17:17:28 +02:00
|
|
|
int vhost_vq_index = dev->vhost_ops->vhost_get_vq_index(dev, n);
|
2012-12-24 16:37:01 +01:00
|
|
|
struct vhost_vring_file file = {
|
vhost-user: add multiple queue support
This patch is initially based a patch from Nikolay Nikolaev.
This patch adds vhost-user multiple queue support, by creating a nc
and vhost_net pair for each queue.
Qemu exits if find that the backend can't support the number of requested
queues (by providing queues=# option). The max number is queried by a
new message, VHOST_USER_GET_QUEUE_NUM, and is sent only when protocol
feature VHOST_USER_PROTOCOL_F_MQ is present first.
The max queue check is done at vhost-user initiation stage. We initiate
one queue first, which, in the meantime, also gets the max_queues the
backend supports.
In older version, it was reported that some messages are sent more times
than necessary. Here we came an agreement with Michael that we could
categorize vhost user messages to 2 types: non-vring specific messages,
which should be sent only once, and vring specific messages, which should
be sent per queue.
Here I introduced a helper function vhost_user_one_time_request(), which
lists following messages as non-vring specific messages:
VHOST_USER_SET_OWNER
VHOST_USER_RESET_DEVICE
VHOST_USER_SET_MEM_TABLE
VHOST_USER_GET_QUEUE_NUM
For above messages, we simply ignore them when they are not sent the first
time.
Signed-off-by: Nikolay Nikolaev <n.nikolaev@virtualopensystems.com>
Signed-off-by: Changchun Ouyang <changchun.ouyang@intel.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Jason Wang <jasowang@redhat.com>
Tested-by: Marcel Apfelbaum <marcel@redhat.com>
2015-09-23 06:20:00 +02:00
|
|
|
.index = vhost_vq_index,
|
2012-12-24 16:37:01 +01:00
|
|
|
};
|
|
|
|
int r = event_notifier_init(&vq->masked_notifier, 0);
|
|
|
|
if (r < 0) {
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2022-03-04 11:08:52 +01:00
|
|
|
file.fd = event_notifier_get_wfd(&vq->masked_notifier);
|
2015-10-09 17:17:28 +02:00
|
|
|
r = dev->vhost_ops->vhost_set_vring_call(dev, &file);
|
2012-12-24 16:37:01 +01:00
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_call failed");
|
2012-12-24 16:37:01 +01:00
|
|
|
goto fail_call;
|
|
|
|
}
|
2017-01-11 05:32:12 +01:00
|
|
|
|
|
|
|
vq->dev = dev;
|
|
|
|
|
2022-06-23 18:13:25 +02:00
|
|
|
if (dev->vhost_ops->vhost_set_vring_err) {
|
|
|
|
r = event_notifier_init(&vq->error_notifier, 0);
|
|
|
|
if (r < 0) {
|
|
|
|
goto fail_call;
|
|
|
|
}
|
|
|
|
|
|
|
|
file.fd = event_notifier_get_fd(&vq->error_notifier);
|
|
|
|
r = dev->vhost_ops->vhost_set_vring_err(dev, &file);
|
|
|
|
if (r) {
|
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_vring_err failed");
|
|
|
|
goto fail_err;
|
|
|
|
}
|
|
|
|
|
|
|
|
event_notifier_set_handler(&vq->error_notifier,
|
|
|
|
vhost_virtqueue_error_notifier);
|
|
|
|
}
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
return 0;
|
2022-06-23 18:13:25 +02:00
|
|
|
|
|
|
|
fail_err:
|
|
|
|
event_notifier_cleanup(&vq->error_notifier);
|
2012-12-24 16:37:01 +01:00
|
|
|
fail_call:
|
|
|
|
event_notifier_cleanup(&vq->masked_notifier);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_virtqueue_cleanup(struct vhost_virtqueue *vq)
|
|
|
|
{
|
|
|
|
event_notifier_cleanup(&vq->masked_notifier);
|
2022-06-23 18:13:25 +02:00
|
|
|
if (vq->dev->vhost_ops->vhost_set_vring_err) {
|
|
|
|
event_notifier_set_handler(&vq->error_notifier, NULL);
|
|
|
|
event_notifier_cleanup(&vq->error_notifier);
|
|
|
|
}
|
2012-12-24 16:37:01 +01:00
|
|
|
}
|
|
|
|
|
2014-05-27 14:05:22 +02:00
|
|
|
int vhost_dev_init(struct vhost_dev *hdev, void *opaque,
|
2021-06-09 17:46:52 +02:00
|
|
|
VhostBackendType backend_type, uint32_t busyloop_timeout,
|
|
|
|
Error **errp)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2023-09-26 20:57:29 +02:00
|
|
|
unsigned int used, reserved, limit;
|
2010-03-17 12:08:17 +01:00
|
|
|
uint64_t features;
|
2016-07-26 23:15:04 +02:00
|
|
|
int i, r, n_initialized_vqs = 0;
|
2014-05-27 14:05:22 +02:00
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
hdev->vdev = NULL;
|
2015-10-09 17:17:27 +02:00
|
|
|
hdev->migration_blocker = NULL;
|
|
|
|
|
2016-07-26 23:14:58 +02:00
|
|
|
r = vhost_set_backend_type(hdev, backend_type);
|
|
|
|
assert(r >= 0);
|
2014-05-27 14:05:49 +02:00
|
|
|
|
2021-06-09 17:46:53 +02:00
|
|
|
r = hdev->vhost_ops->vhost_backend_init(hdev, opaque, errp);
|
2016-07-26 23:14:58 +02:00
|
|
|
if (r < 0) {
|
|
|
|
goto fail;
|
2014-05-27 14:05:35 +02:00
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:28 +02:00
|
|
|
r = hdev->vhost_ops->vhost_set_owner(hdev);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-06-09 17:46:54 +02:00
|
|
|
error_setg_errno(errp, -r, "vhost_set_owner failed");
|
2010-03-17 12:08:17 +01:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:28 +02:00
|
|
|
r = hdev->vhost_ops->vhost_get_features(hdev, &features);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-06-09 17:46:54 +02:00
|
|
|
error_setg_errno(errp, -r, "vhost_get_features failed");
|
2010-03-17 12:08:17 +01:00
|
|
|
goto fail;
|
|
|
|
}
|
2012-12-24 16:37:01 +01:00
|
|
|
|
memory-device,vhost: Support automatic decision on the number of memslots
We want to support memory devices that can automatically decide how many
memslots they will use. In the worst case, they have to use a single
memslot.
The target use cases are virtio-mem and the hyper-v balloon.
Let's calculate a reasonable limit such a memory device may use, and
instruct the device to make a decision based on that limit. Use a simple
heuristic that considers:
* A memslot soft-limit for all memory devices of 256; also, to not
consume too many memslots -- which could harm performance.
* Actually still free and unreserved memslots
* The percentage of the remaining device memory region that memory device
will occupy.
Further, while we properly check before plugging a memory device whether
there still is are free memslots, we have other memslot consumers (such as
boot memory, PCI BARs) that don't perform any checks and might dynamically
consume memslots without any prior reservation. So we might succeed in
plugging a memory device, but once we dynamically map a PCI BAR we would
be in trouble. Doing accounting / reservation / checks for all such
users is problematic (e.g., sometimes we might temporarily split boot
memory into two memslots, triggered by the BIOS).
We use the historic magic memslot number of 509 as orientation to when
supporting 256 memory devices -> memslots (leaving 253 for boot memory and
other devices) has been proven to work reliable. We'll fallback to
suggesting a single memslot if we don't have at least 509 total memslots.
Plugging vhost devices with less than 509 memslots available while we
have memory devices plugged that consume multiple memslots due to
automatic decisions can be problematic. Most configurations might just fail
due to "limit < used + reserved", however, it can also happen that these
memory devices would suddenly consume memslots that would actually be
required by other memslot consumers (boot, PCI BARs) later. Note that this
has always been sketchy with vhost devices that support only a small number
of memslots; but we don't want to make it any worse.So let's keep it simple
and simply reject plugging such vhost devices in such a configuration.
Eventually, all vhost devices that want to be fully compatible with such
memory devices should support a decent number of memslots (>= 509).
Message-ID: <20230926185738.277351-13-david@redhat.com>
Reviewed-by: Maciej S. Szmigiero <maciej.szmigiero@oracle.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: David Hildenbrand <david@redhat.com>
2023-09-26 20:57:32 +02:00
|
|
|
limit = hdev->vhost_ops->vhost_backend_memslots_limit(hdev);
|
|
|
|
if (limit < MEMORY_DEVICES_SAFE_MAX_MEMSLOTS &&
|
|
|
|
memory_devices_memslot_auto_decision_active()) {
|
|
|
|
error_setg(errp, "some memory device (like virtio-mem)"
|
|
|
|
" decided how many memory slots to use based on the overall"
|
|
|
|
" number of memory slots; this vhost backend would further"
|
|
|
|
" restricts the overall number of memory slots");
|
|
|
|
error_append_hint(errp, "Try plugging this vhost backend before"
|
|
|
|
" plugging such memory devices.\n");
|
|
|
|
r = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2016-07-26 23:15:04 +02:00
|
|
|
for (i = 0; i < hdev->nvqs; ++i, ++n_initialized_vqs) {
|
vhost-user: add multiple queue support
This patch is initially based a patch from Nikolay Nikolaev.
This patch adds vhost-user multiple queue support, by creating a nc
and vhost_net pair for each queue.
Qemu exits if find that the backend can't support the number of requested
queues (by providing queues=# option). The max number is queried by a
new message, VHOST_USER_GET_QUEUE_NUM, and is sent only when protocol
feature VHOST_USER_PROTOCOL_F_MQ is present first.
The max queue check is done at vhost-user initiation stage. We initiate
one queue first, which, in the meantime, also gets the max_queues the
backend supports.
In older version, it was reported that some messages are sent more times
than necessary. Here we came an agreement with Michael that we could
categorize vhost user messages to 2 types: non-vring specific messages,
which should be sent only once, and vring specific messages, which should
be sent per queue.
Here I introduced a helper function vhost_user_one_time_request(), which
lists following messages as non-vring specific messages:
VHOST_USER_SET_OWNER
VHOST_USER_RESET_DEVICE
VHOST_USER_SET_MEM_TABLE
VHOST_USER_GET_QUEUE_NUM
For above messages, we simply ignore them when they are not sent the first
time.
Signed-off-by: Nikolay Nikolaev <n.nikolaev@virtualopensystems.com>
Signed-off-by: Changchun Ouyang <changchun.ouyang@intel.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Signed-off-by: Yuanhan Liu <yuanhan.liu@linux.intel.com>
Reviewed-by: Jason Wang <jasowang@redhat.com>
Tested-by: Marcel Apfelbaum <marcel@redhat.com>
2015-09-23 06:20:00 +02:00
|
|
|
r = vhost_virtqueue_init(hdev, hdev->vqs + i, hdev->vq_index + i);
|
2012-12-24 16:37:01 +01:00
|
|
|
if (r < 0) {
|
2021-06-09 17:46:52 +02:00
|
|
|
error_setg_errno(errp, -r, "Failed to initialize virtqueue %d", i);
|
2016-07-26 23:15:04 +02:00
|
|
|
goto fail;
|
2012-12-24 16:37:01 +01:00
|
|
|
}
|
|
|
|
}
|
2016-07-06 03:57:55 +02:00
|
|
|
|
|
|
|
if (busyloop_timeout) {
|
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
|
|
|
r = vhost_virtqueue_set_busyloop_timeout(hdev, hdev->vq_index + i,
|
|
|
|
busyloop_timeout);
|
|
|
|
if (r < 0) {
|
2021-06-09 17:46:54 +02:00
|
|
|
error_setg_errno(errp, -r, "Failed to set busyloop timeout");
|
2016-07-06 03:57:55 +02:00
|
|
|
goto fail_busyloop;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
hdev->features = features;
|
|
|
|
|
2011-12-18 13:06:05 +01:00
|
|
|
hdev->memory_listener = (MemoryListener) {
|
2021-08-17 03:35:52 +02:00
|
|
|
.name = "vhost",
|
2012-02-08 20:36:02 +01:00
|
|
|
.begin = vhost_begin,
|
|
|
|
.commit = vhost_commit,
|
2018-01-19 11:39:23 +01:00
|
|
|
.region_add = vhost_region_addnop,
|
|
|
|
.region_nop = vhost_region_addnop,
|
2011-12-18 13:06:05 +01:00
|
|
|
.log_start = vhost_log_start,
|
|
|
|
.log_stop = vhost_log_stop,
|
|
|
|
.log_sync = vhost_log_sync,
|
|
|
|
.log_global_start = vhost_log_global_start,
|
|
|
|
.log_global_stop = vhost_log_global_stop,
|
2023-06-20 18:50:48 +02:00
|
|
|
.priority = MEMORY_LISTENER_PRIORITY_DEV_BACKEND
|
2011-12-18 13:06:05 +01:00
|
|
|
};
|
2015-10-09 17:17:27 +02:00
|
|
|
|
2017-03-29 06:10:04 +02:00
|
|
|
hdev->iommu_listener = (MemoryListener) {
|
2021-08-17 03:35:52 +02:00
|
|
|
.name = "vhost-iommu",
|
2017-03-29 06:10:04 +02:00
|
|
|
.region_add = vhost_iommu_region_add,
|
|
|
|
.region_del = vhost_iommu_region_del,
|
|
|
|
};
|
2017-01-11 05:32:12 +01:00
|
|
|
|
2015-10-09 17:17:27 +02:00
|
|
|
if (hdev->migration_blocker == NULL) {
|
|
|
|
if (!(hdev->features & (0x1ULL << VHOST_F_LOG_ALL))) {
|
|
|
|
error_setg(&hdev->migration_blocker,
|
|
|
|
"Migration disabled: vhost lacks VHOST_F_LOG_ALL feature.");
|
2018-03-28 14:18:04 +02:00
|
|
|
} else if (vhost_dev_log_is_shared(hdev) && !qemu_memfd_alloc_check()) {
|
2015-10-09 17:17:34 +02:00
|
|
|
error_setg(&hdev->migration_blocker,
|
|
|
|
"Migration disabled: failed to allocate shared memory");
|
2015-10-09 17:17:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hdev->migration_blocker != NULL) {
|
2023-10-25 21:44:27 +02:00
|
|
|
r = migrate_add_blocker_normal(&hdev->migration_blocker, errp);
|
2021-07-20 14:54:01 +02:00
|
|
|
if (r < 0) {
|
2017-01-16 12:31:53 +01:00
|
|
|
goto fail_busyloop;
|
|
|
|
}
|
2014-06-18 16:20:42 +02:00
|
|
|
}
|
2015-10-09 17:17:27 +02:00
|
|
|
|
2011-08-21 05:09:37 +02:00
|
|
|
hdev->mem = g_malloc0(offsetof(struct vhost_memory, regions));
|
2011-12-19 12:18:13 +01:00
|
|
|
hdev->n_mem_sections = 0;
|
|
|
|
hdev->mem_sections = NULL;
|
2010-03-17 12:08:17 +01:00
|
|
|
hdev->log = NULL;
|
|
|
|
hdev->log_size = 0;
|
|
|
|
hdev->log_enabled = false;
|
|
|
|
hdev->started = false;
|
2012-10-02 20:13:51 +02:00
|
|
|
memory_listener_register(&hdev->memory_listener, &address_space_memory);
|
2016-07-26 23:15:01 +02:00
|
|
|
QLIST_INSERT_HEAD(&vhost_devices, hdev, entry);
|
2018-02-27 08:10:04 +01:00
|
|
|
|
2023-09-26 20:57:21 +02:00
|
|
|
/*
|
|
|
|
* The listener we registered properly updated the corresponding counter.
|
|
|
|
* So we can trust that these values are accurate.
|
|
|
|
*/
|
|
|
|
if (hdev->vhost_ops->vhost_backend_no_private_memslots &&
|
|
|
|
hdev->vhost_ops->vhost_backend_no_private_memslots(hdev)) {
|
|
|
|
used = used_shared_memslots;
|
|
|
|
} else {
|
|
|
|
used = used_memslots;
|
|
|
|
}
|
2023-09-26 20:57:29 +02:00
|
|
|
/*
|
|
|
|
* We assume that all reserved memslots actually require a real memslot
|
|
|
|
* in our vhost backend. This might not be true, for example, if the
|
|
|
|
* memslot would be ROM. If ever relevant, we can optimize for that --
|
|
|
|
* but we'll need additional information about the reservations.
|
|
|
|
*/
|
|
|
|
reserved = memory_devices_get_reserved_memslots();
|
|
|
|
if (used + reserved > limit) {
|
|
|
|
error_setg(errp, "vhost backend memory slots limit (%d) is less"
|
|
|
|
" than current number of used (%d) and reserved (%d)"
|
|
|
|
" memory slots for memory devices.", limit, used, reserved);
|
2021-06-09 17:46:54 +02:00
|
|
|
r = -EINVAL;
|
2021-02-22 12:49:31 +01:00
|
|
|
goto fail_busyloop;
|
2018-02-27 08:10:04 +01:00
|
|
|
}
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
return 0;
|
2016-07-26 23:15:04 +02:00
|
|
|
|
2016-07-06 03:57:55 +02:00
|
|
|
fail_busyloop:
|
2021-02-22 12:49:31 +01:00
|
|
|
if (busyloop_timeout) {
|
|
|
|
while (--i >= 0) {
|
|
|
|
vhost_virtqueue_set_busyloop_timeout(hdev, hdev->vq_index + i, 0);
|
|
|
|
}
|
2016-07-06 03:57:55 +02:00
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
fail:
|
2016-07-26 23:15:04 +02:00
|
|
|
hdev->nvqs = n_initialized_vqs;
|
|
|
|
vhost_dev_cleanup(hdev);
|
2010-03-17 12:08:17 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vhost_dev_cleanup(struct vhost_dev *hdev)
|
|
|
|
{
|
2012-12-24 16:37:01 +01:00
|
|
|
int i;
|
2016-07-26 23:15:02 +02:00
|
|
|
|
2022-08-02 11:49:57 +02:00
|
|
|
trace_vhost_dev_cleanup(hdev);
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
|
|
|
vhost_virtqueue_cleanup(hdev->vqs + i);
|
|
|
|
}
|
2016-07-26 23:15:01 +02:00
|
|
|
if (hdev->mem) {
|
|
|
|
/* those are only safe after successful init */
|
|
|
|
memory_listener_unregister(&hdev->memory_listener);
|
|
|
|
QLIST_REMOVE(hdev, entry);
|
|
|
|
}
|
2023-10-18 15:03:36 +02:00
|
|
|
migrate_del_blocker(&hdev->migration_blocker);
|
2011-08-21 05:09:37 +02:00
|
|
|
g_free(hdev->mem);
|
2011-12-19 12:18:13 +01:00
|
|
|
g_free(hdev->mem_sections);
|
2016-07-26 23:15:02 +02:00
|
|
|
if (hdev->vhost_ops) {
|
|
|
|
hdev->vhost_ops->vhost_backend_cleanup(hdev);
|
|
|
|
}
|
2016-07-26 23:15:00 +02:00
|
|
|
assert(!hdev->log);
|
2016-07-26 23:15:02 +02:00
|
|
|
|
|
|
|
memset(hdev, 0, sizeof(struct vhost_dev));
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
|
2023-06-02 18:27:35 +02:00
|
|
|
static void vhost_dev_disable_notifiers_nvqs(struct vhost_dev *hdev,
|
|
|
|
VirtIODevice *vdev,
|
|
|
|
unsigned int nvqs)
|
|
|
|
{
|
|
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
|
|
|
int i, r;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Batch all the host notifiers in a single transaction to avoid
|
|
|
|
* quadratic time complexity in address_space_update_ioeventfds().
|
|
|
|
*/
|
|
|
|
memory_region_transaction_begin();
|
|
|
|
|
|
|
|
for (i = 0; i < nvqs; ++i) {
|
|
|
|
r = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i,
|
|
|
|
false);
|
|
|
|
if (r < 0) {
|
|
|
|
error_report("vhost VQ %d notifier cleanup failed: %d", i, -r);
|
|
|
|
}
|
|
|
|
assert(r >= 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* The transaction expects the ioeventfds to be open when it
|
|
|
|
* commits. Do it now, before the cleanup loop.
|
|
|
|
*/
|
|
|
|
memory_region_transaction_commit();
|
|
|
|
|
|
|
|
for (i = 0; i < nvqs; ++i) {
|
|
|
|
virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i);
|
|
|
|
}
|
|
|
|
virtio_device_release_ioeventfd(vdev);
|
|
|
|
}
|
|
|
|
|
2011-08-11 09:21:18 +02:00
|
|
|
/* Stop processing guest IO notifications in qemu.
|
|
|
|
* Start processing them in vhost in kernel.
|
|
|
|
*/
|
|
|
|
int vhost_dev_enable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
|
|
|
|
{
|
2013-04-24 10:21:21 +02:00
|
|
|
BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
2022-12-27 08:20:13 +01:00
|
|
|
int i, r;
|
2016-07-26 23:15:07 +02:00
|
|
|
|
2016-11-18 16:07:00 +01:00
|
|
|
/* We will pass the notifiers to the kernel, make sure that QEMU
|
|
|
|
* doesn't interfere.
|
|
|
|
*/
|
|
|
|
r = virtio_device_grab_ioeventfd(vdev);
|
|
|
|
if (r < 0) {
|
2016-07-26 23:15:07 +02:00
|
|
|
error_report("binding does not support host notifiers");
|
2022-12-27 08:20:13 +01:00
|
|
|
return r;
|
2011-08-11 09:21:18 +02:00
|
|
|
}
|
|
|
|
|
2022-12-27 08:20:14 +01:00
|
|
|
/*
|
|
|
|
* Batch all the host notifiers in a single transaction to avoid
|
|
|
|
* quadratic time complexity in address_space_update_ioeventfds().
|
|
|
|
*/
|
|
|
|
memory_region_transaction_begin();
|
|
|
|
|
2011-08-11 09:21:18 +02:00
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
2016-06-10 11:04:10 +02:00
|
|
|
r = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), hdev->vq_index + i,
|
|
|
|
true);
|
2011-08-11 09:21:18 +02:00
|
|
|
if (r < 0) {
|
2016-07-26 23:15:07 +02:00
|
|
|
error_report("vhost VQ %d notifier binding failed: %d", i, -r);
|
2022-12-27 08:20:14 +01:00
|
|
|
memory_region_transaction_commit();
|
2023-06-02 18:27:35 +02:00
|
|
|
vhost_dev_disable_notifiers_nvqs(hdev, vdev, i);
|
2022-12-27 08:20:13 +01:00
|
|
|
return r;
|
2011-08-11 09:21:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-27 08:20:14 +01:00
|
|
|
memory_region_transaction_commit();
|
|
|
|
|
2011-08-11 09:21:18 +02:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Stop processing guest IO notifications in vhost.
|
|
|
|
* Start processing them in qemu.
|
|
|
|
* This might actually run the qemu handlers right away,
|
|
|
|
* so virtio in qemu must be completely setup when this is called.
|
|
|
|
*/
|
|
|
|
void vhost_dev_disable_notifiers(struct vhost_dev *hdev, VirtIODevice *vdev)
|
|
|
|
{
|
2023-06-02 18:27:35 +02:00
|
|
|
vhost_dev_disable_notifiers_nvqs(hdev, vdev, hdev->nvqs);
|
2011-08-11 09:21:18 +02:00
|
|
|
}
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
/* Test and clear event pending status.
|
|
|
|
* Should be called after unmask to avoid losing events.
|
|
|
|
*/
|
|
|
|
bool vhost_virtqueue_pending(struct vhost_dev *hdev, int n)
|
|
|
|
{
|
2013-01-30 12:12:35 +01:00
|
|
|
struct vhost_virtqueue *vq = hdev->vqs + n - hdev->vq_index;
|
|
|
|
assert(n >= hdev->vq_index && n < hdev->vq_index + hdev->nvqs);
|
2012-12-24 16:37:01 +01:00
|
|
|
return event_notifier_test_and_clear(&vq->masked_notifier);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Mask/unmask events from this vq. */
|
|
|
|
void vhost_virtqueue_mask(struct vhost_dev *hdev, VirtIODevice *vdev, int n,
|
|
|
|
bool mask)
|
|
|
|
{
|
|
|
|
struct VirtQueue *vvq = virtio_get_queue(vdev, n);
|
2013-01-30 12:12:35 +01:00
|
|
|
int r, index = n - hdev->vq_index;
|
2015-09-23 06:19:59 +02:00
|
|
|
struct vhost_vring_file file;
|
2012-12-24 16:37:01 +01:00
|
|
|
|
2016-07-26 23:15:16 +02:00
|
|
|
/* should only be called after backend is connected */
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
|
2012-12-24 16:37:01 +01:00
|
|
|
if (mask) {
|
2016-02-18 15:12:23 +01:00
|
|
|
assert(vdev->use_guest_notifier_mask);
|
2022-03-04 11:08:52 +01:00
|
|
|
file.fd = event_notifier_get_wfd(&hdev->vqs[index].masked_notifier);
|
2012-12-24 16:37:01 +01:00
|
|
|
} else {
|
2022-03-04 11:08:52 +01:00
|
|
|
file.fd = event_notifier_get_wfd(virtio_queue_get_guest_notifier(vvq));
|
2012-12-24 16:37:01 +01:00
|
|
|
}
|
2015-09-23 06:19:59 +02:00
|
|
|
|
2015-10-09 17:17:28 +02:00
|
|
|
file.index = hdev->vhost_ops->vhost_get_vq_index(hdev, n);
|
|
|
|
r = hdev->vhost_ops->vhost_set_vring_call(hdev, &file);
|
2016-07-26 23:15:05 +02:00
|
|
|
if (r < 0) {
|
2022-12-22 08:04:48 +01:00
|
|
|
error_report("vhost_set_vring_call failed %d", -r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool vhost_config_pending(struct vhost_dev *hdev)
|
|
|
|
{
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
if ((hdev->started == false) ||
|
|
|
|
(hdev->vhost_ops->vhost_set_config_call == NULL)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
EventNotifier *notifier =
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier;
|
|
|
|
return event_notifier_test_and_clear(notifier);
|
|
|
|
}
|
|
|
|
|
|
|
|
void vhost_config_mask(struct vhost_dev *hdev, VirtIODevice *vdev, bool mask)
|
|
|
|
{
|
|
|
|
int fd;
|
|
|
|
int r;
|
|
|
|
EventNotifier *notifier =
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier;
|
|
|
|
EventNotifier *config_notifier = &vdev->config_notifier;
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
|
|
|
|
if ((hdev->started == false) ||
|
|
|
|
(hdev->vhost_ops->vhost_set_config_call == NULL)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (mask) {
|
|
|
|
assert(vdev->use_guest_notifier_mask);
|
|
|
|
fd = event_notifier_get_fd(notifier);
|
|
|
|
} else {
|
|
|
|
fd = event_notifier_get_fd(config_notifier);
|
|
|
|
}
|
|
|
|
r = hdev->vhost_ops->vhost_set_config_call(hdev, fd);
|
|
|
|
if (r < 0) {
|
|
|
|
error_report("vhost_set_config_call failed %d", -r);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_stop_config_intr(struct vhost_dev *dev)
|
|
|
|
{
|
|
|
|
int fd = -1;
|
|
|
|
assert(dev->vhost_ops);
|
|
|
|
if (dev->vhost_ops->vhost_set_config_call) {
|
|
|
|
dev->vhost_ops->vhost_set_config_call(dev, fd);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void vhost_start_config_intr(struct vhost_dev *dev)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(dev->vhost_ops);
|
|
|
|
int fd = event_notifier_get_fd(&dev->vdev->config_notifier);
|
|
|
|
if (dev->vhost_ops->vhost_set_config_call) {
|
|
|
|
r = dev->vhost_ops->vhost_set_config_call(dev, fd);
|
|
|
|
if (!r) {
|
|
|
|
event_notifier_set(&dev->vdev->config_notifier);
|
|
|
|
}
|
2016-07-26 23:15:05 +02:00
|
|
|
}
|
2012-12-24 16:37:01 +01:00
|
|
|
}
|
|
|
|
|
2015-06-04 12:34:20 +02:00
|
|
|
uint64_t vhost_get_features(struct vhost_dev *hdev, const int *feature_bits,
|
|
|
|
uint64_t features)
|
2014-05-27 14:04:42 +02:00
|
|
|
{
|
|
|
|
const int *bit = feature_bits;
|
|
|
|
while (*bit != VHOST_INVALID_FEATURE_BIT) {
|
2015-06-04 12:34:20 +02:00
|
|
|
uint64_t bit_mask = (1ULL << *bit);
|
2014-05-27 14:04:42 +02:00
|
|
|
if (!(hdev->features & bit_mask)) {
|
|
|
|
features &= ~bit_mask;
|
|
|
|
}
|
|
|
|
bit++;
|
|
|
|
}
|
|
|
|
return features;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vhost_ack_features(struct vhost_dev *hdev, const int *feature_bits,
|
2015-06-04 12:34:20 +02:00
|
|
|
uint64_t features)
|
2014-05-27 14:04:42 +02:00
|
|
|
{
|
|
|
|
const int *bit = feature_bits;
|
|
|
|
while (*bit != VHOST_INVALID_FEATURE_BIT) {
|
2015-06-04 12:34:20 +02:00
|
|
|
uint64_t bit_mask = (1ULL << *bit);
|
2014-05-27 14:04:42 +02:00
|
|
|
if (features & bit_mask) {
|
|
|
|
hdev->acked_features |= bit_mask;
|
|
|
|
}
|
|
|
|
bit++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-04 02:53:31 +01:00
|
|
|
int vhost_dev_get_config(struct vhost_dev *hdev, uint8_t *config,
|
2021-06-09 17:46:56 +02:00
|
|
|
uint32_t config_len, Error **errp)
|
2018-01-04 02:53:31 +01:00
|
|
|
{
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
|
|
|
|
if (hdev->vhost_ops->vhost_get_config) {
|
2021-07-20 14:54:04 +02:00
|
|
|
return hdev->vhost_ops->vhost_get_config(hdev, config, config_len,
|
|
|
|
errp);
|
2018-01-04 02:53:31 +01:00
|
|
|
}
|
|
|
|
|
2021-06-09 17:46:56 +02:00
|
|
|
error_setg(errp, "vhost_get_config not implemented");
|
2021-11-11 16:33:53 +01:00
|
|
|
return -ENOSYS;
|
2018-01-04 02:53:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_dev_set_config(struct vhost_dev *hdev, const uint8_t *data,
|
|
|
|
uint32_t offset, uint32_t size, uint32_t flags)
|
|
|
|
{
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
|
|
|
|
if (hdev->vhost_ops->vhost_set_config) {
|
|
|
|
return hdev->vhost_ops->vhost_set_config(hdev, data, offset,
|
|
|
|
size, flags);
|
|
|
|
}
|
|
|
|
|
2021-11-11 16:33:53 +01:00
|
|
|
return -ENOSYS;
|
2018-01-04 02:53:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void vhost_dev_set_config_notifier(struct vhost_dev *hdev,
|
|
|
|
const VhostDevConfigOps *ops)
|
|
|
|
{
|
|
|
|
hdev->config_ops = ops;
|
|
|
|
}
|
|
|
|
|
2019-02-28 09:53:49 +01:00
|
|
|
void vhost_dev_free_inflight(struct vhost_inflight *inflight)
|
|
|
|
{
|
2020-04-17 12:17:07 +02:00
|
|
|
if (inflight && inflight->addr) {
|
2019-02-28 09:53:49 +01:00
|
|
|
qemu_memfd_free(inflight->addr, inflight->size, inflight->fd);
|
|
|
|
inflight->addr = NULL;
|
|
|
|
inflight->fd = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int vhost_dev_resize_inflight(struct vhost_inflight *inflight,
|
|
|
|
uint64_t new_size)
|
|
|
|
{
|
|
|
|
Error *err = NULL;
|
|
|
|
int fd = -1;
|
|
|
|
void *addr = qemu_memfd_alloc("vhost-inflight", new_size,
|
|
|
|
F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL,
|
|
|
|
&fd, &err);
|
|
|
|
|
|
|
|
if (err) {
|
|
|
|
error_report_err(err);
|
2021-11-11 16:33:53 +01:00
|
|
|
return -ENOMEM;
|
2019-02-28 09:53:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
vhost_dev_free_inflight(inflight);
|
|
|
|
inflight->offset = 0;
|
|
|
|
inflight->addr = addr;
|
|
|
|
inflight->fd = fd;
|
|
|
|
inflight->size = new_size;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void vhost_dev_save_inflight(struct vhost_inflight *inflight, QEMUFile *f)
|
|
|
|
{
|
|
|
|
if (inflight->addr) {
|
|
|
|
qemu_put_be64(f, inflight->size);
|
|
|
|
qemu_put_be16(f, inflight->queue_size);
|
|
|
|
qemu_put_buffer(f, inflight->addr, inflight->size);
|
|
|
|
} else {
|
|
|
|
qemu_put_be64(f, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_dev_load_inflight(struct vhost_inflight *inflight, QEMUFile *f)
|
|
|
|
{
|
|
|
|
uint64_t size;
|
|
|
|
|
|
|
|
size = qemu_get_be64(f);
|
|
|
|
if (!size) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (inflight->size != size) {
|
2021-11-11 16:33:53 +01:00
|
|
|
int ret = vhost_dev_resize_inflight(inflight, size);
|
|
|
|
if (ret < 0) {
|
|
|
|
return ret;
|
2019-02-28 09:53:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
inflight->queue_size = qemu_get_be16(f);
|
|
|
|
|
|
|
|
qemu_get_buffer(f, inflight->addr, size);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-11-03 13:36:17 +01:00
|
|
|
int vhost_dev_prepare_inflight(struct vhost_dev *hdev, VirtIODevice *vdev)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (hdev->vhost_ops->vhost_get_inflight_fd == NULL ||
|
|
|
|
hdev->vhost_ops->vhost_set_inflight_fd == NULL) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
hdev->vdev = vdev;
|
|
|
|
|
|
|
|
r = vhost_dev_set_features(hdev, hdev->log_enabled);
|
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_dev_prepare_inflight failed");
|
2020-11-03 13:36:17 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-02-28 09:53:49 +01:00
|
|
|
int vhost_dev_set_inflight(struct vhost_dev *dev,
|
|
|
|
struct vhost_inflight *inflight)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (dev->vhost_ops->vhost_set_inflight_fd && inflight->addr) {
|
|
|
|
r = dev->vhost_ops->vhost_set_inflight_fd(dev, inflight);
|
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_inflight_fd failed");
|
|
|
|
return r;
|
2019-02-28 09:53:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_dev_get_inflight(struct vhost_dev *dev, uint16_t queue_size,
|
|
|
|
struct vhost_inflight *inflight)
|
|
|
|
{
|
|
|
|
int r;
|
|
|
|
|
|
|
|
if (dev->vhost_ops->vhost_get_inflight_fd) {
|
|
|
|
r = dev->vhost_ops->vhost_get_inflight_fd(dev, queue_size, inflight);
|
|
|
|
if (r) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_get_inflight_fd failed");
|
|
|
|
return r;
|
2019-02-28 09:53:49 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-11-30 12:24:36 +01:00
|
|
|
static int vhost_dev_set_vring_enable(struct vhost_dev *hdev, int enable)
|
|
|
|
{
|
|
|
|
if (!hdev->vhost_ops->vhost_set_vring_enable) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* For vhost-user devices, if VHOST_USER_F_PROTOCOL_FEATURES has not
|
|
|
|
* been negotiated, the rings start directly in the enabled state, and
|
|
|
|
* .vhost_set_vring_enable callback will fail since
|
|
|
|
* VHOST_USER_SET_VRING_ENABLE is not supported.
|
|
|
|
*/
|
|
|
|
if (hdev->vhost_ops->backend_type == VHOST_BACKEND_TYPE_USER &&
|
|
|
|
!virtio_has_feature(hdev->backend_features,
|
|
|
|
VHOST_USER_F_PROTOCOL_FEATURES)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hdev->vhost_ops->vhost_set_vring_enable(hdev, enable);
|
|
|
|
}
|
|
|
|
|
2011-08-11 09:21:18 +02:00
|
|
|
/* Host notifiers must be enabled at this point. */
|
2022-11-30 12:24:36 +01:00
|
|
|
int vhost_dev_start(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
|
|
|
int i, r;
|
2012-12-25 16:41:07 +01:00
|
|
|
|
2016-07-26 23:15:16 +02:00
|
|
|
/* should only be called after backend is connected */
|
|
|
|
assert(hdev->vhost_ops);
|
|
|
|
|
2022-11-30 12:24:36 +01:00
|
|
|
trace_vhost_dev_start(hdev, vdev->name, vrings);
|
2022-08-02 11:49:57 +02:00
|
|
|
|
2022-04-01 15:23:19 +02:00
|
|
|
vdev->vhost_started = true;
|
2012-12-25 16:41:07 +01:00
|
|
|
hdev->started = true;
|
2017-01-11 05:32:12 +01:00
|
|
|
hdev->vdev = vdev;
|
2012-12-25 16:41:07 +01:00
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
r = vhost_dev_set_features(hdev, hdev->log_enabled);
|
|
|
|
if (r < 0) {
|
2010-10-06 15:20:17 +02:00
|
|
|
goto fail_features;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2017-01-11 05:32:12 +01:00
|
|
|
|
|
|
|
if (vhost_dev_has_iommu(hdev)) {
|
2017-03-29 06:10:04 +02:00
|
|
|
memory_listener_register(&hdev->iommu_listener, vdev->dma_as);
|
2017-01-11 05:32:12 +01:00
|
|
|
}
|
|
|
|
|
2015-10-09 17:17:28 +02:00
|
|
|
r = hdev->vhost_ops->vhost_set_mem_table(hdev, hdev->mem);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_mem_table failed");
|
2010-10-06 15:20:17 +02:00
|
|
|
goto fail_mem;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2010-07-16 16:11:46 +02:00
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
2012-12-24 16:37:01 +01:00
|
|
|
r = vhost_virtqueue_start(hdev,
|
2013-01-30 12:12:35 +01:00
|
|
|
vdev,
|
|
|
|
hdev->vqs + i,
|
|
|
|
hdev->vq_index + i);
|
2010-07-16 16:11:46 +02:00
|
|
|
if (r < 0) {
|
|
|
|
goto fail_vq;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-22 08:04:48 +01:00
|
|
|
r = event_notifier_init(
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier, 0);
|
|
|
|
if (r < 0) {
|
2023-05-29 13:43:33 +02:00
|
|
|
VHOST_OPS_DEBUG(r, "event_notifier_init failed");
|
|
|
|
goto fail_vq;
|
2022-12-22 08:04:48 +01:00
|
|
|
}
|
|
|
|
event_notifier_test_and_clear(
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier);
|
|
|
|
if (!vdev->use_guest_notifier_mask) {
|
|
|
|
vhost_config_mask(hdev, vdev, true);
|
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
if (hdev->log_enabled) {
|
2015-04-17 17:13:24 +02:00
|
|
|
uint64_t log_base;
|
|
|
|
|
2010-03-17 12:08:17 +01:00
|
|
|
hdev->log_size = vhost_get_log_size(hdev);
|
2015-10-09 17:17:25 +02:00
|
|
|
hdev->log = vhost_log_get(hdev->log_size,
|
|
|
|
vhost_dev_log_is_shared(hdev));
|
2015-06-04 11:28:46 +02:00
|
|
|
log_base = (uintptr_t)hdev->log->log;
|
2015-10-09 17:17:23 +02:00
|
|
|
r = hdev->vhost_ops->vhost_set_log_base(hdev,
|
2015-10-09 17:17:26 +02:00
|
|
|
hdev->log_size ? log_base : 0,
|
|
|
|
hdev->log);
|
2010-03-17 12:08:17 +01:00
|
|
|
if (r < 0) {
|
2021-11-11 16:33:53 +01:00
|
|
|
VHOST_OPS_DEBUG(r, "vhost_set_log_base failed");
|
2010-10-06 15:20:17 +02:00
|
|
|
goto fail_log;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
|
|
|
}
|
2022-11-30 12:24:36 +01:00
|
|
|
if (vrings) {
|
|
|
|
r = vhost_dev_set_vring_enable(hdev, true);
|
|
|
|
if (r) {
|
|
|
|
goto fail_log;
|
|
|
|
}
|
|
|
|
}
|
2020-07-01 16:55:31 +02:00
|
|
|
if (hdev->vhost_ops->vhost_dev_start) {
|
|
|
|
r = hdev->vhost_ops->vhost_dev_start(hdev, true);
|
|
|
|
if (r) {
|
2022-11-30 12:24:36 +01:00
|
|
|
goto fail_start;
|
2020-07-01 16:55:31 +02:00
|
|
|
}
|
|
|
|
}
|
2020-07-01 16:55:29 +02:00
|
|
|
if (vhost_dev_has_iommu(hdev) &&
|
|
|
|
hdev->vhost_ops->vhost_set_iotlb_callback) {
|
|
|
|
hdev->vhost_ops->vhost_set_iotlb_callback(hdev, true);
|
2017-01-11 05:32:12 +01:00
|
|
|
|
|
|
|
/* Update used ring information for IOTLB to work correctly,
|
|
|
|
* vhost-kernel code requires for this.*/
|
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
|
|
|
struct vhost_virtqueue *vq = hdev->vqs + i;
|
|
|
|
vhost_device_iotlb_miss(hdev, vq->used_phys, true);
|
|
|
|
}
|
|
|
|
}
|
2022-12-22 08:04:48 +01:00
|
|
|
vhost_start_config_intr(hdev);
|
2010-03-17 12:08:17 +01:00
|
|
|
return 0;
|
2022-11-30 12:24:36 +01:00
|
|
|
fail_start:
|
|
|
|
if (vrings) {
|
|
|
|
vhost_dev_set_vring_enable(hdev, false);
|
|
|
|
}
|
2010-10-06 15:20:17 +02:00
|
|
|
fail_log:
|
2015-06-05 05:05:58 +02:00
|
|
|
vhost_log_put(hdev, false);
|
2010-03-17 12:08:17 +01:00
|
|
|
fail_vq:
|
|
|
|
while (--i >= 0) {
|
2012-12-24 16:37:01 +01:00
|
|
|
vhost_virtqueue_stop(hdev,
|
2013-01-30 12:12:35 +01:00
|
|
|
vdev,
|
|
|
|
hdev->vqs + i,
|
|
|
|
hdev->vq_index + i);
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2017-01-11 05:32:12 +01:00
|
|
|
|
2010-10-06 15:20:17 +02:00
|
|
|
fail_mem:
|
2023-05-29 13:43:32 +02:00
|
|
|
if (vhost_dev_has_iommu(hdev)) {
|
|
|
|
memory_listener_unregister(&hdev->iommu_listener);
|
|
|
|
}
|
2010-10-06 15:20:17 +02:00
|
|
|
fail_features:
|
2022-04-01 15:23:19 +02:00
|
|
|
vdev->vhost_started = false;
|
2012-12-25 16:41:07 +01:00
|
|
|
hdev->started = false;
|
2010-03-17 12:08:17 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2011-08-11 09:21:18 +02:00
|
|
|
/* Host notifiers must be enabled at this point. */
|
2022-11-30 12:24:36 +01:00
|
|
|
void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings)
|
2010-03-17 12:08:17 +01:00
|
|
|
{
|
2013-01-30 12:12:35 +01:00
|
|
|
int i;
|
2010-10-06 15:20:17 +02:00
|
|
|
|
2016-07-26 23:15:16 +02:00
|
|
|
/* should only be called after backend is connected */
|
|
|
|
assert(hdev->vhost_ops);
|
2022-12-22 08:04:48 +01:00
|
|
|
event_notifier_test_and_clear(
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier);
|
|
|
|
event_notifier_test_and_clear(&vdev->config_notifier);
|
2023-07-31 14:10:06 +02:00
|
|
|
event_notifier_cleanup(
|
|
|
|
&hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier);
|
2016-07-26 23:15:16 +02:00
|
|
|
|
2022-11-30 12:24:36 +01:00
|
|
|
trace_vhost_dev_stop(hdev, vdev->name, vrings);
|
2022-08-02 11:49:57 +02:00
|
|
|
|
2020-07-01 16:55:31 +02:00
|
|
|
if (hdev->vhost_ops->vhost_dev_start) {
|
|
|
|
hdev->vhost_ops->vhost_dev_start(hdev, false);
|
|
|
|
}
|
2022-11-30 12:24:36 +01:00
|
|
|
if (vrings) {
|
|
|
|
vhost_dev_set_vring_enable(hdev, false);
|
|
|
|
}
|
2010-03-17 12:08:17 +01:00
|
|
|
for (i = 0; i < hdev->nvqs; ++i) {
|
2012-12-24 16:37:01 +01:00
|
|
|
vhost_virtqueue_stop(hdev,
|
2013-01-30 12:12:35 +01:00
|
|
|
vdev,
|
|
|
|
hdev->vqs + i,
|
|
|
|
hdev->vq_index + i);
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2023-03-03 18:24:38 +01:00
|
|
|
if (hdev->vhost_ops->vhost_reset_status) {
|
|
|
|
hdev->vhost_ops->vhost_reset_status(hdev);
|
|
|
|
}
|
2010-10-06 15:20:17 +02:00
|
|
|
|
2017-01-11 05:32:12 +01:00
|
|
|
if (vhost_dev_has_iommu(hdev)) {
|
2020-07-01 16:55:29 +02:00
|
|
|
if (hdev->vhost_ops->vhost_set_iotlb_callback) {
|
|
|
|
hdev->vhost_ops->vhost_set_iotlb_callback(hdev, false);
|
|
|
|
}
|
2017-03-29 06:10:04 +02:00
|
|
|
memory_listener_unregister(&hdev->iommu_listener);
|
2017-01-11 05:32:12 +01:00
|
|
|
}
|
2022-12-22 08:04:48 +01:00
|
|
|
vhost_stop_config_intr(hdev);
|
2015-06-04 11:28:46 +02:00
|
|
|
vhost_log_put(hdev, true);
|
2010-03-17 12:08:17 +01:00
|
|
|
hdev->started = false;
|
2022-04-01 15:23:19 +02:00
|
|
|
vdev->vhost_started = false;
|
2017-01-11 05:32:12 +01:00
|
|
|
hdev->vdev = NULL;
|
2010-03-17 12:08:17 +01:00
|
|
|
}
|
2016-07-26 23:15:25 +02:00
|
|
|
|
|
|
|
int vhost_net_set_backend(struct vhost_dev *hdev,
|
|
|
|
struct vhost_vring_file *file)
|
|
|
|
{
|
|
|
|
if (hdev->vhost_ops->vhost_net_set_backend) {
|
|
|
|
return hdev->vhost_ops->vhost_net_set_backend(hdev, file);
|
|
|
|
}
|
|
|
|
|
2021-11-11 16:33:53 +01:00
|
|
|
return -ENOSYS;
|
2016-07-26 23:15:25 +02:00
|
|
|
}
|
2023-10-04 03:45:32 +02:00
|
|
|
|
|
|
|
int vhost_reset_device(struct vhost_dev *hdev)
|
|
|
|
{
|
|
|
|
if (hdev->vhost_ops->vhost_reset_device) {
|
|
|
|
return hdev->vhost_ops->vhost_reset_device(hdev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2023-10-16 15:42:41 +02:00
|
|
|
|
|
|
|
bool vhost_supports_device_state(struct vhost_dev *dev)
|
|
|
|
{
|
|
|
|
if (dev->vhost_ops->vhost_supports_device_state) {
|
|
|
|
return dev->vhost_ops->vhost_supports_device_state(dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_set_device_state_fd(struct vhost_dev *dev,
|
|
|
|
VhostDeviceStateDirection direction,
|
|
|
|
VhostDeviceStatePhase phase,
|
|
|
|
int fd,
|
|
|
|
int *reply_fd,
|
|
|
|
Error **errp)
|
|
|
|
{
|
|
|
|
if (dev->vhost_ops->vhost_set_device_state_fd) {
|
|
|
|
return dev->vhost_ops->vhost_set_device_state_fd(dev, direction, phase,
|
|
|
|
fd, reply_fd, errp);
|
|
|
|
}
|
|
|
|
|
|
|
|
error_setg(errp,
|
|
|
|
"vhost transport does not support migration state transfer");
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_check_device_state(struct vhost_dev *dev, Error **errp)
|
|
|
|
{
|
|
|
|
if (dev->vhost_ops->vhost_check_device_state) {
|
|
|
|
return dev->vhost_ops->vhost_check_device_state(dev, errp);
|
|
|
|
}
|
|
|
|
|
|
|
|
error_setg(errp,
|
|
|
|
"vhost transport does not support migration state transfer");
|
|
|
|
return -ENOSYS;
|
|
|
|
}
|
2023-10-16 15:42:42 +02:00
|
|
|
|
|
|
|
int vhost_save_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp)
|
|
|
|
{
|
|
|
|
/* Maximum chunk size in which to transfer the state */
|
|
|
|
const size_t chunk_size = 1 * 1024 * 1024;
|
|
|
|
g_autofree void *transfer_buf = NULL;
|
|
|
|
g_autoptr(GError) g_err = NULL;
|
|
|
|
int pipe_fds[2], read_fd = -1, write_fd = -1, reply_fd = -1;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* [0] for reading (our end), [1] for writing (back-end's end) */
|
|
|
|
if (!g_unix_open_pipe(pipe_fds, FD_CLOEXEC, &g_err)) {
|
|
|
|
error_setg(errp, "Failed to set up state transfer pipe: %s",
|
|
|
|
g_err->message);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
read_fd = pipe_fds[0];
|
|
|
|
write_fd = pipe_fds[1];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* VHOST_TRANSFER_STATE_PHASE_STOPPED means the device must be stopped.
|
|
|
|
* Ideally, it is suspended, but SUSPEND/RESUME currently do not exist for
|
|
|
|
* vhost-user, so just check that it is stopped at all.
|
|
|
|
*/
|
|
|
|
assert(!dev->started);
|
|
|
|
|
|
|
|
/* Transfer ownership of write_fd to the back-end */
|
|
|
|
ret = vhost_set_device_state_fd(dev,
|
|
|
|
VHOST_TRANSFER_STATE_DIRECTION_SAVE,
|
|
|
|
VHOST_TRANSFER_STATE_PHASE_STOPPED,
|
|
|
|
write_fd,
|
|
|
|
&reply_fd,
|
|
|
|
errp);
|
|
|
|
if (ret < 0) {
|
|
|
|
error_prepend(errp, "Failed to initiate state transfer: ");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the back-end wishes to use a different pipe, switch over */
|
|
|
|
if (reply_fd >= 0) {
|
|
|
|
close(read_fd);
|
|
|
|
read_fd = reply_fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
transfer_buf = g_malloc(chunk_size);
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
ssize_t read_ret;
|
|
|
|
|
|
|
|
read_ret = RETRY_ON_EINTR(read(read_fd, transfer_buf, chunk_size));
|
|
|
|
if (read_ret < 0) {
|
|
|
|
ret = -errno;
|
|
|
|
error_setg_errno(errp, -ret, "Failed to receive state");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(read_ret <= chunk_size);
|
|
|
|
qemu_put_be32(f, read_ret);
|
|
|
|
|
|
|
|
if (read_ret == 0) {
|
|
|
|
/* EOF */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
qemu_put_buffer(f, transfer_buf, read_ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Back-end will not really care, but be clean and close our end of the pipe
|
|
|
|
* before inquiring the back-end about whether transfer was successful
|
|
|
|
*/
|
|
|
|
close(read_fd);
|
|
|
|
read_fd = -1;
|
|
|
|
|
|
|
|
/* Also, verify that the device is still stopped */
|
|
|
|
assert(!dev->started);
|
|
|
|
|
|
|
|
ret = vhost_check_device_state(dev, errp);
|
|
|
|
if (ret < 0) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
|
|
|
if (read_fd >= 0) {
|
|
|
|
close(read_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int vhost_load_backend_state(struct vhost_dev *dev, QEMUFile *f, Error **errp)
|
|
|
|
{
|
|
|
|
size_t transfer_buf_size = 0;
|
|
|
|
g_autofree void *transfer_buf = NULL;
|
|
|
|
g_autoptr(GError) g_err = NULL;
|
|
|
|
int pipe_fds[2], read_fd = -1, write_fd = -1, reply_fd = -1;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* [0] for reading (back-end's end), [1] for writing (our end) */
|
|
|
|
if (!g_unix_open_pipe(pipe_fds, FD_CLOEXEC, &g_err)) {
|
|
|
|
error_setg(errp, "Failed to set up state transfer pipe: %s",
|
|
|
|
g_err->message);
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
read_fd = pipe_fds[0];
|
|
|
|
write_fd = pipe_fds[1];
|
|
|
|
|
|
|
|
/*
|
|
|
|
* VHOST_TRANSFER_STATE_PHASE_STOPPED means the device must be stopped.
|
|
|
|
* Ideally, it is suspended, but SUSPEND/RESUME currently do not exist for
|
|
|
|
* vhost-user, so just check that it is stopped at all.
|
|
|
|
*/
|
|
|
|
assert(!dev->started);
|
|
|
|
|
|
|
|
/* Transfer ownership of read_fd to the back-end */
|
|
|
|
ret = vhost_set_device_state_fd(dev,
|
|
|
|
VHOST_TRANSFER_STATE_DIRECTION_LOAD,
|
|
|
|
VHOST_TRANSFER_STATE_PHASE_STOPPED,
|
|
|
|
read_fd,
|
|
|
|
&reply_fd,
|
|
|
|
errp);
|
|
|
|
if (ret < 0) {
|
|
|
|
error_prepend(errp, "Failed to initiate state transfer: ");
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If the back-end wishes to use a different pipe, switch over */
|
|
|
|
if (reply_fd >= 0) {
|
|
|
|
close(write_fd);
|
|
|
|
write_fd = reply_fd;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
size_t this_chunk_size = qemu_get_be32(f);
|
|
|
|
ssize_t write_ret;
|
|
|
|
const uint8_t *transfer_pointer;
|
|
|
|
|
|
|
|
if (this_chunk_size == 0) {
|
|
|
|
/* End of state */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (transfer_buf_size < this_chunk_size) {
|
|
|
|
transfer_buf = g_realloc(transfer_buf, this_chunk_size);
|
|
|
|
transfer_buf_size = this_chunk_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemu_get_buffer(f, transfer_buf, this_chunk_size) <
|
|
|
|
this_chunk_size)
|
|
|
|
{
|
|
|
|
error_setg(errp, "Failed to read state");
|
|
|
|
ret = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
transfer_pointer = transfer_buf;
|
|
|
|
while (this_chunk_size > 0) {
|
|
|
|
write_ret = RETRY_ON_EINTR(
|
|
|
|
write(write_fd, transfer_pointer, this_chunk_size)
|
|
|
|
);
|
|
|
|
if (write_ret < 0) {
|
|
|
|
ret = -errno;
|
|
|
|
error_setg_errno(errp, -ret, "Failed to send state");
|
|
|
|
goto fail;
|
|
|
|
} else if (write_ret == 0) {
|
|
|
|
error_setg(errp, "Failed to send state: Connection is closed");
|
|
|
|
ret = -ECONNRESET;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(write_ret <= this_chunk_size);
|
|
|
|
this_chunk_size -= write_ret;
|
|
|
|
transfer_pointer += write_ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Close our end, thus ending transfer, before inquiring the back-end about
|
|
|
|
* whether transfer was successful
|
|
|
|
*/
|
|
|
|
close(write_fd);
|
|
|
|
write_fd = -1;
|
|
|
|
|
|
|
|
/* Also, verify that the device is still stopped */
|
|
|
|
assert(!dev->started);
|
|
|
|
|
|
|
|
ret = vhost_check_device_state(dev, errp);
|
|
|
|
if (ret < 0) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
|
|
|
if (write_fd >= 0) {
|
|
|
|
close(write_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|