c0c4f14729
vhost-user-scsi has a VirtioDeviceClass->reset() function that calls ->vhost_reset_device(). The other vhost devices don't notify the vhost device upon reset. Stateful vhost devices may need to handle device reset in order to free resources or prevent stale device state from interfering after reset. Call ->vhost_device_reset() from virtio_reset() so that that vhost devices are notified of device reset. This patch affects behavior as follows: - vhost-kernel: No change in behavior since ->vhost_reset_device() is not implemented. - vhost-user: back-ends that negotiate VHOST_USER_PROTOCOL_F_RESET_DEVICE now receive a VHOST_USER_DEVICE_RESET message upon device reset. Otherwise there is no change in behavior. DPDK, SPDK, libvhost-user, and the vhost-user-backend crate do not negotiate VHOST_USER_PROTOCOL_F_RESET_DEVICE automatically. - vhost-vdpa: an extra SET_STATUS 0 call is made during device reset. Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com> Message-Id: <20231004014532.1228637-4-stefanha@redhat.com> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Reviewed-by: Raphael Norwitz <raphael.norwitz@nutanix.com> Reviewed-by: Hanna Czenczek <hreitz@redhat.com>
4087 lines
118 KiB
C
4087 lines
118 KiB
C
/*
|
|
* Virtio Support
|
|
*
|
|
* Copyright IBM, Corp. 2007
|
|
*
|
|
* Authors:
|
|
* Anthony Liguori <aliguori@us.ibm.com>
|
|
*
|
|
* This work is licensed under the terms of the GNU GPL, version 2. See
|
|
* the COPYING file in the top-level directory.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-commands-virtio.h"
|
|
#include "trace.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/main-loop.h"
|
|
#include "qemu/module.h"
|
|
#include "qom/object_interfaces.h"
|
|
#include "hw/core/cpu.h"
|
|
#include "hw/virtio/virtio.h"
|
|
#include "hw/virtio/vhost.h"
|
|
#include "migration/qemu-file-types.h"
|
|
#include "qemu/atomic.h"
|
|
#include "hw/virtio/virtio-bus.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/virtio/virtio-access.h"
|
|
#include "sysemu/dma.h"
|
|
#include "sysemu/runstate.h"
|
|
#include "virtio-qmp.h"
|
|
|
|
#include "standard-headers/linux/virtio_ids.h"
|
|
#include "standard-headers/linux/vhost_types.h"
|
|
#include "standard-headers/linux/virtio_blk.h"
|
|
#include "standard-headers/linux/virtio_console.h"
|
|
#include "standard-headers/linux/virtio_gpu.h"
|
|
#include "standard-headers/linux/virtio_net.h"
|
|
#include "standard-headers/linux/virtio_scsi.h"
|
|
#include "standard-headers/linux/virtio_i2c.h"
|
|
#include "standard-headers/linux/virtio_balloon.h"
|
|
#include "standard-headers/linux/virtio_iommu.h"
|
|
#include "standard-headers/linux/virtio_mem.h"
|
|
#include "standard-headers/linux/virtio_vsock.h"
|
|
|
|
/*
|
|
* Maximum size of virtio device config space
|
|
*/
|
|
#define VHOST_USER_MAX_CONFIG_SIZE 256
|
|
|
|
/*
|
|
* The alignment to use between consumer and producer parts of vring.
|
|
* x86 pagesize again. This is the default, used by transports like PCI
|
|
* which don't provide a means for the guest to tell the host the alignment.
|
|
*/
|
|
#define VIRTIO_PCI_VRING_ALIGN 4096
|
|
|
|
typedef struct VRingDesc
|
|
{
|
|
uint64_t addr;
|
|
uint32_t len;
|
|
uint16_t flags;
|
|
uint16_t next;
|
|
} VRingDesc;
|
|
|
|
typedef struct VRingPackedDesc {
|
|
uint64_t addr;
|
|
uint32_t len;
|
|
uint16_t id;
|
|
uint16_t flags;
|
|
} VRingPackedDesc;
|
|
|
|
typedef struct VRingAvail
|
|
{
|
|
uint16_t flags;
|
|
uint16_t idx;
|
|
uint16_t ring[];
|
|
} VRingAvail;
|
|
|
|
typedef struct VRingUsedElem
|
|
{
|
|
uint32_t id;
|
|
uint32_t len;
|
|
} VRingUsedElem;
|
|
|
|
typedef struct VRingUsed
|
|
{
|
|
uint16_t flags;
|
|
uint16_t idx;
|
|
VRingUsedElem ring[];
|
|
} VRingUsed;
|
|
|
|
typedef struct VRingMemoryRegionCaches {
|
|
struct rcu_head rcu;
|
|
MemoryRegionCache desc;
|
|
MemoryRegionCache avail;
|
|
MemoryRegionCache used;
|
|
} VRingMemoryRegionCaches;
|
|
|
|
typedef struct VRing
|
|
{
|
|
unsigned int num;
|
|
unsigned int num_default;
|
|
unsigned int align;
|
|
hwaddr desc;
|
|
hwaddr avail;
|
|
hwaddr used;
|
|
VRingMemoryRegionCaches *caches;
|
|
} VRing;
|
|
|
|
typedef struct VRingPackedDescEvent {
|
|
uint16_t off_wrap;
|
|
uint16_t flags;
|
|
} VRingPackedDescEvent ;
|
|
|
|
struct VirtQueue
|
|
{
|
|
VRing vring;
|
|
VirtQueueElement *used_elems;
|
|
|
|
/* Next head to pop */
|
|
uint16_t last_avail_idx;
|
|
bool last_avail_wrap_counter;
|
|
|
|
/* Last avail_idx read from VQ. */
|
|
uint16_t shadow_avail_idx;
|
|
bool shadow_avail_wrap_counter;
|
|
|
|
uint16_t used_idx;
|
|
bool used_wrap_counter;
|
|
|
|
/* Last used index value we have signalled on */
|
|
uint16_t signalled_used;
|
|
|
|
/* Last used index value we have signalled on */
|
|
bool signalled_used_valid;
|
|
|
|
/* Notification enabled? */
|
|
bool notification;
|
|
|
|
uint16_t queue_index;
|
|
|
|
unsigned int inuse;
|
|
|
|
uint16_t vector;
|
|
VirtIOHandleOutput handle_output;
|
|
VirtIODevice *vdev;
|
|
EventNotifier guest_notifier;
|
|
EventNotifier host_notifier;
|
|
bool host_notifier_enabled;
|
|
QLIST_ENTRY(VirtQueue) node;
|
|
};
|
|
|
|
const char *virtio_device_names[] = {
|
|
[VIRTIO_ID_NET] = "virtio-net",
|
|
[VIRTIO_ID_BLOCK] = "virtio-blk",
|
|
[VIRTIO_ID_CONSOLE] = "virtio-serial",
|
|
[VIRTIO_ID_RNG] = "virtio-rng",
|
|
[VIRTIO_ID_BALLOON] = "virtio-balloon",
|
|
[VIRTIO_ID_IOMEM] = "virtio-iomem",
|
|
[VIRTIO_ID_RPMSG] = "virtio-rpmsg",
|
|
[VIRTIO_ID_SCSI] = "virtio-scsi",
|
|
[VIRTIO_ID_9P] = "virtio-9p",
|
|
[VIRTIO_ID_MAC80211_WLAN] = "virtio-mac-wlan",
|
|
[VIRTIO_ID_RPROC_SERIAL] = "virtio-rproc-serial",
|
|
[VIRTIO_ID_CAIF] = "virtio-caif",
|
|
[VIRTIO_ID_MEMORY_BALLOON] = "virtio-mem-balloon",
|
|
[VIRTIO_ID_GPU] = "virtio-gpu",
|
|
[VIRTIO_ID_CLOCK] = "virtio-clk",
|
|
[VIRTIO_ID_INPUT] = "virtio-input",
|
|
[VIRTIO_ID_VSOCK] = "vhost-vsock",
|
|
[VIRTIO_ID_CRYPTO] = "virtio-crypto",
|
|
[VIRTIO_ID_SIGNAL_DIST] = "virtio-signal",
|
|
[VIRTIO_ID_PSTORE] = "virtio-pstore",
|
|
[VIRTIO_ID_IOMMU] = "virtio-iommu",
|
|
[VIRTIO_ID_MEM] = "virtio-mem",
|
|
[VIRTIO_ID_SOUND] = "virtio-sound",
|
|
[VIRTIO_ID_FS] = "virtio-user-fs",
|
|
[VIRTIO_ID_PMEM] = "virtio-pmem",
|
|
[VIRTIO_ID_RPMB] = "virtio-rpmb",
|
|
[VIRTIO_ID_MAC80211_HWSIM] = "virtio-mac-hwsim",
|
|
[VIRTIO_ID_VIDEO_ENCODER] = "virtio-vid-encoder",
|
|
[VIRTIO_ID_VIDEO_DECODER] = "virtio-vid-decoder",
|
|
[VIRTIO_ID_SCMI] = "virtio-scmi",
|
|
[VIRTIO_ID_NITRO_SEC_MOD] = "virtio-nitro-sec-mod",
|
|
[VIRTIO_ID_I2C_ADAPTER] = "vhost-user-i2c",
|
|
[VIRTIO_ID_WATCHDOG] = "virtio-watchdog",
|
|
[VIRTIO_ID_CAN] = "virtio-can",
|
|
[VIRTIO_ID_DMABUF] = "virtio-dmabuf",
|
|
[VIRTIO_ID_PARAM_SERV] = "virtio-param-serv",
|
|
[VIRTIO_ID_AUDIO_POLICY] = "virtio-audio-pol",
|
|
[VIRTIO_ID_BT] = "virtio-bluetooth",
|
|
[VIRTIO_ID_GPIO] = "virtio-gpio"
|
|
};
|
|
|
|
static const char *virtio_id_to_name(uint16_t device_id)
|
|
{
|
|
assert(device_id < G_N_ELEMENTS(virtio_device_names));
|
|
const char *name = virtio_device_names[device_id];
|
|
assert(name != NULL);
|
|
return name;
|
|
}
|
|
|
|
/* Called within call_rcu(). */
|
|
static void virtio_free_region_cache(VRingMemoryRegionCaches *caches)
|
|
{
|
|
assert(caches != NULL);
|
|
address_space_cache_destroy(&caches->desc);
|
|
address_space_cache_destroy(&caches->avail);
|
|
address_space_cache_destroy(&caches->used);
|
|
g_free(caches);
|
|
}
|
|
|
|
static void virtio_virtqueue_reset_region_cache(struct VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches;
|
|
|
|
caches = qatomic_read(&vq->vring.caches);
|
|
qatomic_rcu_set(&vq->vring.caches, NULL);
|
|
if (caches) {
|
|
call_rcu(caches, virtio_free_region_cache, rcu);
|
|
}
|
|
}
|
|
|
|
void virtio_init_region_cache(VirtIODevice *vdev, int n)
|
|
{
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
VRingMemoryRegionCaches *old = vq->vring.caches;
|
|
VRingMemoryRegionCaches *new = NULL;
|
|
hwaddr addr, size;
|
|
int64_t len;
|
|
bool packed;
|
|
|
|
|
|
addr = vq->vring.desc;
|
|
if (!addr) {
|
|
goto out_no_cache;
|
|
}
|
|
new = g_new0(VRingMemoryRegionCaches, 1);
|
|
size = virtio_queue_get_desc_size(vdev, n);
|
|
packed = virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED) ?
|
|
true : false;
|
|
len = address_space_cache_init(&new->desc, vdev->dma_as,
|
|
addr, size, packed);
|
|
if (len < size) {
|
|
virtio_error(vdev, "Cannot map desc");
|
|
goto err_desc;
|
|
}
|
|
|
|
size = virtio_queue_get_used_size(vdev, n);
|
|
len = address_space_cache_init(&new->used, vdev->dma_as,
|
|
vq->vring.used, size, true);
|
|
if (len < size) {
|
|
virtio_error(vdev, "Cannot map used");
|
|
goto err_used;
|
|
}
|
|
|
|
size = virtio_queue_get_avail_size(vdev, n);
|
|
len = address_space_cache_init(&new->avail, vdev->dma_as,
|
|
vq->vring.avail, size, false);
|
|
if (len < size) {
|
|
virtio_error(vdev, "Cannot map avail");
|
|
goto err_avail;
|
|
}
|
|
|
|
qatomic_rcu_set(&vq->vring.caches, new);
|
|
if (old) {
|
|
call_rcu(old, virtio_free_region_cache, rcu);
|
|
}
|
|
return;
|
|
|
|
err_avail:
|
|
address_space_cache_destroy(&new->avail);
|
|
err_used:
|
|
address_space_cache_destroy(&new->used);
|
|
err_desc:
|
|
address_space_cache_destroy(&new->desc);
|
|
out_no_cache:
|
|
g_free(new);
|
|
virtio_virtqueue_reset_region_cache(vq);
|
|
}
|
|
|
|
/* virt queue functions */
|
|
void virtio_queue_update_rings(VirtIODevice *vdev, int n)
|
|
{
|
|
VRing *vring = &vdev->vq[n].vring;
|
|
|
|
if (!vring->num || !vring->desc || !vring->align) {
|
|
/* not yet setup -> nothing to do */
|
|
return;
|
|
}
|
|
vring->avail = vring->desc + vring->num * sizeof(VRingDesc);
|
|
vring->used = vring_align(vring->avail +
|
|
offsetof(VRingAvail, ring[vring->num]),
|
|
vring->align);
|
|
virtio_init_region_cache(vdev, n);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static void vring_split_desc_read(VirtIODevice *vdev, VRingDesc *desc,
|
|
MemoryRegionCache *cache, int i)
|
|
{
|
|
address_space_read_cached(cache, i * sizeof(VRingDesc),
|
|
desc, sizeof(VRingDesc));
|
|
virtio_tswap64s(vdev, &desc->addr);
|
|
virtio_tswap32s(vdev, &desc->len);
|
|
virtio_tswap16s(vdev, &desc->flags);
|
|
virtio_tswap16s(vdev, &desc->next);
|
|
}
|
|
|
|
static void vring_packed_event_read(VirtIODevice *vdev,
|
|
MemoryRegionCache *cache,
|
|
VRingPackedDescEvent *e)
|
|
{
|
|
hwaddr off_off = offsetof(VRingPackedDescEvent, off_wrap);
|
|
hwaddr off_flags = offsetof(VRingPackedDescEvent, flags);
|
|
|
|
e->flags = virtio_lduw_phys_cached(vdev, cache, off_flags);
|
|
/* Make sure flags is seen before off_wrap */
|
|
smp_rmb();
|
|
e->off_wrap = virtio_lduw_phys_cached(vdev, cache, off_off);
|
|
virtio_tswap16s(vdev, &e->flags);
|
|
}
|
|
|
|
static void vring_packed_off_wrap_write(VirtIODevice *vdev,
|
|
MemoryRegionCache *cache,
|
|
uint16_t off_wrap)
|
|
{
|
|
hwaddr off = offsetof(VRingPackedDescEvent, off_wrap);
|
|
|
|
virtio_stw_phys_cached(vdev, cache, off, off_wrap);
|
|
address_space_cache_invalidate(cache, off, sizeof(off_wrap));
|
|
}
|
|
|
|
static void vring_packed_flags_write(VirtIODevice *vdev,
|
|
MemoryRegionCache *cache, uint16_t flags)
|
|
{
|
|
hwaddr off = offsetof(VRingPackedDescEvent, flags);
|
|
|
|
virtio_stw_phys_cached(vdev, cache, off, flags);
|
|
address_space_cache_invalidate(cache, off, sizeof(flags));
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static VRingMemoryRegionCaches *vring_get_region_caches(struct VirtQueue *vq)
|
|
{
|
|
return qatomic_rcu_read(&vq->vring.caches);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline uint16_t vring_avail_flags(VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingAvail, flags);
|
|
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
return virtio_lduw_phys_cached(vq->vdev, &caches->avail, pa);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline uint16_t vring_avail_idx(VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingAvail, idx);
|
|
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
vq->shadow_avail_idx = virtio_lduw_phys_cached(vq->vdev, &caches->avail, pa);
|
|
return vq->shadow_avail_idx;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline uint16_t vring_avail_ring(VirtQueue *vq, int i)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingAvail, ring[i]);
|
|
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
return virtio_lduw_phys_cached(vq->vdev, &caches->avail, pa);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline uint16_t vring_get_used_event(VirtQueue *vq)
|
|
{
|
|
return vring_avail_ring(vq, vq->vring.num);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline void vring_used_write(VirtQueue *vq, VRingUsedElem *uelem,
|
|
int i)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingUsed, ring[i]);
|
|
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
virtio_tswap32s(vq->vdev, &uelem->id);
|
|
virtio_tswap32s(vq->vdev, &uelem->len);
|
|
address_space_write_cached(&caches->used, pa, uelem, sizeof(VRingUsedElem));
|
|
address_space_cache_invalidate(&caches->used, pa, sizeof(VRingUsedElem));
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline uint16_t vring_used_flags(VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingUsed, flags);
|
|
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
return virtio_lduw_phys_cached(vq->vdev, &caches->used, pa);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static uint16_t vring_used_idx(VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingUsed, idx);
|
|
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
return virtio_lduw_phys_cached(vq->vdev, &caches->used, pa);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline void vring_used_idx_set(VirtQueue *vq, uint16_t val)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
hwaddr pa = offsetof(VRingUsed, idx);
|
|
|
|
if (caches) {
|
|
virtio_stw_phys_cached(vq->vdev, &caches->used, pa, val);
|
|
address_space_cache_invalidate(&caches->used, pa, sizeof(val));
|
|
}
|
|
|
|
vq->used_idx = val;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline void vring_used_flags_set_bit(VirtQueue *vq, int mask)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
VirtIODevice *vdev = vq->vdev;
|
|
hwaddr pa = offsetof(VRingUsed, flags);
|
|
uint16_t flags;
|
|
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
flags = virtio_lduw_phys_cached(vq->vdev, &caches->used, pa);
|
|
virtio_stw_phys_cached(vdev, &caches->used, pa, flags | mask);
|
|
address_space_cache_invalidate(&caches->used, pa, sizeof(flags));
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline void vring_used_flags_unset_bit(VirtQueue *vq, int mask)
|
|
{
|
|
VRingMemoryRegionCaches *caches = vring_get_region_caches(vq);
|
|
VirtIODevice *vdev = vq->vdev;
|
|
hwaddr pa = offsetof(VRingUsed, flags);
|
|
uint16_t flags;
|
|
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
flags = virtio_lduw_phys_cached(vq->vdev, &caches->used, pa);
|
|
virtio_stw_phys_cached(vdev, &caches->used, pa, flags & ~mask);
|
|
address_space_cache_invalidate(&caches->used, pa, sizeof(flags));
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static inline void vring_set_avail_event(VirtQueue *vq, uint16_t val)
|
|
{
|
|
VRingMemoryRegionCaches *caches;
|
|
hwaddr pa;
|
|
if (!vq->notification) {
|
|
return;
|
|
}
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
pa = offsetof(VRingUsed, ring[vq->vring.num]);
|
|
virtio_stw_phys_cached(vq->vdev, &caches->used, pa, val);
|
|
address_space_cache_invalidate(&caches->used, pa, sizeof(val));
|
|
}
|
|
|
|
static void virtio_queue_split_set_notification(VirtQueue *vq, int enable)
|
|
{
|
|
RCU_READ_LOCK_GUARD();
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
|
vring_set_avail_event(vq, vring_avail_idx(vq));
|
|
} else if (enable) {
|
|
vring_used_flags_unset_bit(vq, VRING_USED_F_NO_NOTIFY);
|
|
} else {
|
|
vring_used_flags_set_bit(vq, VRING_USED_F_NO_NOTIFY);
|
|
}
|
|
if (enable) {
|
|
/* Expose avail event/used flags before caller checks the avail idx. */
|
|
smp_mb();
|
|
}
|
|
}
|
|
|
|
static void virtio_queue_packed_set_notification(VirtQueue *vq, int enable)
|
|
{
|
|
uint16_t off_wrap;
|
|
VRingPackedDescEvent e;
|
|
VRingMemoryRegionCaches *caches;
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
vring_packed_event_read(vq->vdev, &caches->used, &e);
|
|
|
|
if (!enable) {
|
|
e.flags = VRING_PACKED_EVENT_FLAG_DISABLE;
|
|
} else if (virtio_vdev_has_feature(vq->vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
|
off_wrap = vq->shadow_avail_idx | vq->shadow_avail_wrap_counter << 15;
|
|
vring_packed_off_wrap_write(vq->vdev, &caches->used, off_wrap);
|
|
/* Make sure off_wrap is wrote before flags */
|
|
smp_wmb();
|
|
e.flags = VRING_PACKED_EVENT_FLAG_DESC;
|
|
} else {
|
|
e.flags = VRING_PACKED_EVENT_FLAG_ENABLE;
|
|
}
|
|
|
|
vring_packed_flags_write(vq->vdev, &caches->used, e.flags);
|
|
if (enable) {
|
|
/* Expose avail event/used flags before caller checks the avail idx. */
|
|
smp_mb();
|
|
}
|
|
}
|
|
|
|
bool virtio_queue_get_notification(VirtQueue *vq)
|
|
{
|
|
return vq->notification;
|
|
}
|
|
|
|
void virtio_queue_set_notification(VirtQueue *vq, int enable)
|
|
{
|
|
vq->notification = enable;
|
|
|
|
if (!vq->vring.desc) {
|
|
return;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtio_queue_packed_set_notification(vq, enable);
|
|
} else {
|
|
virtio_queue_split_set_notification(vq, enable);
|
|
}
|
|
}
|
|
|
|
int virtio_queue_ready(VirtQueue *vq)
|
|
{
|
|
return vq->vring.avail != 0;
|
|
}
|
|
|
|
static void vring_packed_desc_read_flags(VirtIODevice *vdev,
|
|
uint16_t *flags,
|
|
MemoryRegionCache *cache,
|
|
int i)
|
|
{
|
|
hwaddr off = i * sizeof(VRingPackedDesc) + offsetof(VRingPackedDesc, flags);
|
|
|
|
*flags = virtio_lduw_phys_cached(vdev, cache, off);
|
|
}
|
|
|
|
static void vring_packed_desc_read(VirtIODevice *vdev,
|
|
VRingPackedDesc *desc,
|
|
MemoryRegionCache *cache,
|
|
int i, bool strict_order)
|
|
{
|
|
hwaddr off = i * sizeof(VRingPackedDesc);
|
|
|
|
vring_packed_desc_read_flags(vdev, &desc->flags, cache, i);
|
|
|
|
if (strict_order) {
|
|
/* Make sure flags is read before the rest fields. */
|
|
smp_rmb();
|
|
}
|
|
|
|
address_space_read_cached(cache, off + offsetof(VRingPackedDesc, addr),
|
|
&desc->addr, sizeof(desc->addr));
|
|
address_space_read_cached(cache, off + offsetof(VRingPackedDesc, id),
|
|
&desc->id, sizeof(desc->id));
|
|
address_space_read_cached(cache, off + offsetof(VRingPackedDesc, len),
|
|
&desc->len, sizeof(desc->len));
|
|
virtio_tswap64s(vdev, &desc->addr);
|
|
virtio_tswap16s(vdev, &desc->id);
|
|
virtio_tswap32s(vdev, &desc->len);
|
|
}
|
|
|
|
static void vring_packed_desc_write_data(VirtIODevice *vdev,
|
|
VRingPackedDesc *desc,
|
|
MemoryRegionCache *cache,
|
|
int i)
|
|
{
|
|
hwaddr off_id = i * sizeof(VRingPackedDesc) +
|
|
offsetof(VRingPackedDesc, id);
|
|
hwaddr off_len = i * sizeof(VRingPackedDesc) +
|
|
offsetof(VRingPackedDesc, len);
|
|
|
|
virtio_tswap32s(vdev, &desc->len);
|
|
virtio_tswap16s(vdev, &desc->id);
|
|
address_space_write_cached(cache, off_id, &desc->id, sizeof(desc->id));
|
|
address_space_cache_invalidate(cache, off_id, sizeof(desc->id));
|
|
address_space_write_cached(cache, off_len, &desc->len, sizeof(desc->len));
|
|
address_space_cache_invalidate(cache, off_len, sizeof(desc->len));
|
|
}
|
|
|
|
static void vring_packed_desc_write_flags(VirtIODevice *vdev,
|
|
VRingPackedDesc *desc,
|
|
MemoryRegionCache *cache,
|
|
int i)
|
|
{
|
|
hwaddr off = i * sizeof(VRingPackedDesc) + offsetof(VRingPackedDesc, flags);
|
|
|
|
virtio_stw_phys_cached(vdev, cache, off, desc->flags);
|
|
address_space_cache_invalidate(cache, off, sizeof(desc->flags));
|
|
}
|
|
|
|
static void vring_packed_desc_write(VirtIODevice *vdev,
|
|
VRingPackedDesc *desc,
|
|
MemoryRegionCache *cache,
|
|
int i, bool strict_order)
|
|
{
|
|
vring_packed_desc_write_data(vdev, desc, cache, i);
|
|
if (strict_order) {
|
|
/* Make sure data is wrote before flags. */
|
|
smp_wmb();
|
|
}
|
|
vring_packed_desc_write_flags(vdev, desc, cache, i);
|
|
}
|
|
|
|
static inline bool is_desc_avail(uint16_t flags, bool wrap_counter)
|
|
{
|
|
bool avail, used;
|
|
|
|
avail = !!(flags & (1 << VRING_PACKED_DESC_F_AVAIL));
|
|
used = !!(flags & (1 << VRING_PACKED_DESC_F_USED));
|
|
return (avail != used) && (avail == wrap_counter);
|
|
}
|
|
|
|
/* Fetch avail_idx from VQ memory only when we really need to know if
|
|
* guest has added some buffers.
|
|
* Called within rcu_read_lock(). */
|
|
static int virtio_queue_empty_rcu(VirtQueue *vq)
|
|
{
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
return 1;
|
|
}
|
|
|
|
if (unlikely(!vq->vring.avail)) {
|
|
return 1;
|
|
}
|
|
|
|
if (vq->shadow_avail_idx != vq->last_avail_idx) {
|
|
return 0;
|
|
}
|
|
|
|
return vring_avail_idx(vq) == vq->last_avail_idx;
|
|
}
|
|
|
|
static int virtio_queue_split_empty(VirtQueue *vq)
|
|
{
|
|
bool empty;
|
|
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
return 1;
|
|
}
|
|
|
|
if (unlikely(!vq->vring.avail)) {
|
|
return 1;
|
|
}
|
|
|
|
if (vq->shadow_avail_idx != vq->last_avail_idx) {
|
|
return 0;
|
|
}
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
empty = vring_avail_idx(vq) == vq->last_avail_idx;
|
|
return empty;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static int virtio_queue_packed_empty_rcu(VirtQueue *vq)
|
|
{
|
|
struct VRingPackedDesc desc;
|
|
VRingMemoryRegionCaches *cache;
|
|
|
|
if (unlikely(!vq->vring.desc)) {
|
|
return 1;
|
|
}
|
|
|
|
cache = vring_get_region_caches(vq);
|
|
if (!cache) {
|
|
return 1;
|
|
}
|
|
|
|
vring_packed_desc_read_flags(vq->vdev, &desc.flags, &cache->desc,
|
|
vq->last_avail_idx);
|
|
|
|
return !is_desc_avail(desc.flags, vq->last_avail_wrap_counter);
|
|
}
|
|
|
|
static int virtio_queue_packed_empty(VirtQueue *vq)
|
|
{
|
|
RCU_READ_LOCK_GUARD();
|
|
return virtio_queue_packed_empty_rcu(vq);
|
|
}
|
|
|
|
int virtio_queue_empty(VirtQueue *vq)
|
|
{
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtio_queue_packed_empty(vq);
|
|
} else {
|
|
return virtio_queue_split_empty(vq);
|
|
}
|
|
}
|
|
|
|
static void virtqueue_unmap_sg(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len)
|
|
{
|
|
AddressSpace *dma_as = vq->vdev->dma_as;
|
|
unsigned int offset;
|
|
int i;
|
|
|
|
offset = 0;
|
|
for (i = 0; i < elem->in_num; i++) {
|
|
size_t size = MIN(len - offset, elem->in_sg[i].iov_len);
|
|
|
|
dma_memory_unmap(dma_as, elem->in_sg[i].iov_base,
|
|
elem->in_sg[i].iov_len,
|
|
DMA_DIRECTION_FROM_DEVICE, size);
|
|
|
|
offset += size;
|
|
}
|
|
|
|
for (i = 0; i < elem->out_num; i++)
|
|
dma_memory_unmap(dma_as, elem->out_sg[i].iov_base,
|
|
elem->out_sg[i].iov_len,
|
|
DMA_DIRECTION_TO_DEVICE,
|
|
elem->out_sg[i].iov_len);
|
|
}
|
|
|
|
/* virtqueue_detach_element:
|
|
* @vq: The #VirtQueue
|
|
* @elem: The #VirtQueueElement
|
|
* @len: number of bytes written
|
|
*
|
|
* Detach the element from the virtqueue. This function is suitable for device
|
|
* reset or other situations where a #VirtQueueElement is simply freed and will
|
|
* not be pushed or discarded.
|
|
*/
|
|
void virtqueue_detach_element(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len)
|
|
{
|
|
vq->inuse -= elem->ndescs;
|
|
virtqueue_unmap_sg(vq, elem, len);
|
|
}
|
|
|
|
static void virtqueue_split_rewind(VirtQueue *vq, unsigned int num)
|
|
{
|
|
vq->last_avail_idx -= num;
|
|
}
|
|
|
|
static void virtqueue_packed_rewind(VirtQueue *vq, unsigned int num)
|
|
{
|
|
if (vq->last_avail_idx < num) {
|
|
vq->last_avail_idx = vq->vring.num + vq->last_avail_idx - num;
|
|
vq->last_avail_wrap_counter ^= 1;
|
|
} else {
|
|
vq->last_avail_idx -= num;
|
|
}
|
|
}
|
|
|
|
/* virtqueue_unpop:
|
|
* @vq: The #VirtQueue
|
|
* @elem: The #VirtQueueElement
|
|
* @len: number of bytes written
|
|
*
|
|
* Pretend the most recent element wasn't popped from the virtqueue. The next
|
|
* call to virtqueue_pop() will refetch the element.
|
|
*/
|
|
void virtqueue_unpop(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len)
|
|
{
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtqueue_packed_rewind(vq, 1);
|
|
} else {
|
|
virtqueue_split_rewind(vq, 1);
|
|
}
|
|
|
|
virtqueue_detach_element(vq, elem, len);
|
|
}
|
|
|
|
/* virtqueue_rewind:
|
|
* @vq: The #VirtQueue
|
|
* @num: Number of elements to push back
|
|
*
|
|
* Pretend that elements weren't popped from the virtqueue. The next
|
|
* virtqueue_pop() will refetch the oldest element.
|
|
*
|
|
* Use virtqueue_unpop() instead if you have a VirtQueueElement.
|
|
*
|
|
* Returns: true on success, false if @num is greater than the number of in use
|
|
* elements.
|
|
*/
|
|
bool virtqueue_rewind(VirtQueue *vq, unsigned int num)
|
|
{
|
|
if (num > vq->inuse) {
|
|
return false;
|
|
}
|
|
|
|
vq->inuse -= num;
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtqueue_packed_rewind(vq, num);
|
|
} else {
|
|
virtqueue_split_rewind(vq, num);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void virtqueue_split_fill(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len, unsigned int idx)
|
|
{
|
|
VRingUsedElem uelem;
|
|
|
|
if (unlikely(!vq->vring.used)) {
|
|
return;
|
|
}
|
|
|
|
idx = (idx + vq->used_idx) % vq->vring.num;
|
|
|
|
uelem.id = elem->index;
|
|
uelem.len = len;
|
|
vring_used_write(vq, &uelem, idx);
|
|
}
|
|
|
|
static void virtqueue_packed_fill(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len, unsigned int idx)
|
|
{
|
|
vq->used_elems[idx].index = elem->index;
|
|
vq->used_elems[idx].len = len;
|
|
vq->used_elems[idx].ndescs = elem->ndescs;
|
|
}
|
|
|
|
static void virtqueue_packed_fill_desc(VirtQueue *vq,
|
|
const VirtQueueElement *elem,
|
|
unsigned int idx,
|
|
bool strict_order)
|
|
{
|
|
uint16_t head;
|
|
VRingMemoryRegionCaches *caches;
|
|
VRingPackedDesc desc = {
|
|
.id = elem->index,
|
|
.len = elem->len,
|
|
};
|
|
bool wrap_counter = vq->used_wrap_counter;
|
|
|
|
if (unlikely(!vq->vring.desc)) {
|
|
return;
|
|
}
|
|
|
|
head = vq->used_idx + idx;
|
|
if (head >= vq->vring.num) {
|
|
head -= vq->vring.num;
|
|
wrap_counter ^= 1;
|
|
}
|
|
if (wrap_counter) {
|
|
desc.flags |= (1 << VRING_PACKED_DESC_F_AVAIL);
|
|
desc.flags |= (1 << VRING_PACKED_DESC_F_USED);
|
|
} else {
|
|
desc.flags &= ~(1 << VRING_PACKED_DESC_F_AVAIL);
|
|
desc.flags &= ~(1 << VRING_PACKED_DESC_F_USED);
|
|
}
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
return;
|
|
}
|
|
|
|
vring_packed_desc_write(vq->vdev, &desc, &caches->desc, head, strict_order);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
void virtqueue_fill(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len, unsigned int idx)
|
|
{
|
|
trace_virtqueue_fill(vq, elem, len, idx);
|
|
|
|
virtqueue_unmap_sg(vq, elem, len);
|
|
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
return;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtqueue_packed_fill(vq, elem, len, idx);
|
|
} else {
|
|
virtqueue_split_fill(vq, elem, len, idx);
|
|
}
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static void virtqueue_split_flush(VirtQueue *vq, unsigned int count)
|
|
{
|
|
uint16_t old, new;
|
|
|
|
if (unlikely(!vq->vring.used)) {
|
|
return;
|
|
}
|
|
|
|
/* Make sure buffer is written before we update index. */
|
|
smp_wmb();
|
|
trace_virtqueue_flush(vq, count);
|
|
old = vq->used_idx;
|
|
new = old + count;
|
|
vring_used_idx_set(vq, new);
|
|
vq->inuse -= count;
|
|
if (unlikely((int16_t)(new - vq->signalled_used) < (uint16_t)(new - old)))
|
|
vq->signalled_used_valid = false;
|
|
}
|
|
|
|
static void virtqueue_packed_flush(VirtQueue *vq, unsigned int count)
|
|
{
|
|
unsigned int i, ndescs = 0;
|
|
|
|
if (unlikely(!vq->vring.desc)) {
|
|
return;
|
|
}
|
|
|
|
for (i = 1; i < count; i++) {
|
|
virtqueue_packed_fill_desc(vq, &vq->used_elems[i], i, false);
|
|
ndescs += vq->used_elems[i].ndescs;
|
|
}
|
|
virtqueue_packed_fill_desc(vq, &vq->used_elems[0], 0, true);
|
|
ndescs += vq->used_elems[0].ndescs;
|
|
|
|
vq->inuse -= ndescs;
|
|
vq->used_idx += ndescs;
|
|
if (vq->used_idx >= vq->vring.num) {
|
|
vq->used_idx -= vq->vring.num;
|
|
vq->used_wrap_counter ^= 1;
|
|
vq->signalled_used_valid = false;
|
|
}
|
|
}
|
|
|
|
void virtqueue_flush(VirtQueue *vq, unsigned int count)
|
|
{
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
vq->inuse -= count;
|
|
return;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtqueue_packed_flush(vq, count);
|
|
} else {
|
|
virtqueue_split_flush(vq, count);
|
|
}
|
|
}
|
|
|
|
void virtqueue_push(VirtQueue *vq, const VirtQueueElement *elem,
|
|
unsigned int len)
|
|
{
|
|
RCU_READ_LOCK_GUARD();
|
|
virtqueue_fill(vq, elem, len, 0);
|
|
virtqueue_flush(vq, 1);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static int virtqueue_num_heads(VirtQueue *vq, unsigned int idx)
|
|
{
|
|
uint16_t avail_idx, num_heads;
|
|
|
|
/* Use shadow index whenever possible. */
|
|
avail_idx = (vq->shadow_avail_idx != idx) ? vq->shadow_avail_idx
|
|
: vring_avail_idx(vq);
|
|
num_heads = avail_idx - idx;
|
|
|
|
/* Check it isn't doing very strange things with descriptor numbers. */
|
|
if (num_heads > vq->vring.num) {
|
|
virtio_error(vq->vdev, "Guest moved used index from %u to %u",
|
|
idx, vq->shadow_avail_idx);
|
|
return -EINVAL;
|
|
}
|
|
/*
|
|
* On success, callers read a descriptor at vq->last_avail_idx.
|
|
* Make sure descriptor read does not bypass avail index read.
|
|
*
|
|
* This is necessary even if we are using a shadow index, since
|
|
* the shadow index could have been initialized by calling
|
|
* vring_avail_idx() outside of this function, i.e., by a guest
|
|
* memory read not accompanied by a barrier.
|
|
*/
|
|
if (num_heads) {
|
|
smp_rmb();
|
|
}
|
|
|
|
return num_heads;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static bool virtqueue_get_head(VirtQueue *vq, unsigned int idx,
|
|
unsigned int *head)
|
|
{
|
|
/* Grab the next descriptor number they're advertising, and increment
|
|
* the index we've seen. */
|
|
*head = vring_avail_ring(vq, idx % vq->vring.num);
|
|
|
|
/* If their number is silly, that's a fatal mistake. */
|
|
if (*head >= vq->vring.num) {
|
|
virtio_error(vq->vdev, "Guest says index %u is available", *head);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
enum {
|
|
VIRTQUEUE_READ_DESC_ERROR = -1,
|
|
VIRTQUEUE_READ_DESC_DONE = 0, /* end of chain */
|
|
VIRTQUEUE_READ_DESC_MORE = 1, /* more buffers in chain */
|
|
};
|
|
|
|
/* Reads the 'desc->next' descriptor into '*desc'. */
|
|
static int virtqueue_split_read_next_desc(VirtIODevice *vdev, VRingDesc *desc,
|
|
MemoryRegionCache *desc_cache,
|
|
unsigned int max)
|
|
{
|
|
/* If this descriptor says it doesn't chain, we're done. */
|
|
if (!(desc->flags & VRING_DESC_F_NEXT)) {
|
|
return VIRTQUEUE_READ_DESC_DONE;
|
|
}
|
|
|
|
/* Check they're not leading us off end of descriptors. */
|
|
if (desc->next >= max) {
|
|
virtio_error(vdev, "Desc next is %u", desc->next);
|
|
return VIRTQUEUE_READ_DESC_ERROR;
|
|
}
|
|
|
|
vring_split_desc_read(vdev, desc, desc_cache, desc->next);
|
|
return VIRTQUEUE_READ_DESC_MORE;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static void virtqueue_split_get_avail_bytes(VirtQueue *vq,
|
|
unsigned int *in_bytes, unsigned int *out_bytes,
|
|
unsigned max_in_bytes, unsigned max_out_bytes,
|
|
VRingMemoryRegionCaches *caches)
|
|
{
|
|
VirtIODevice *vdev = vq->vdev;
|
|
unsigned int idx;
|
|
unsigned int total_bufs, in_total, out_total;
|
|
MemoryRegionCache indirect_desc_cache;
|
|
int64_t len = 0;
|
|
int rc;
|
|
|
|
address_space_cache_init_empty(&indirect_desc_cache);
|
|
|
|
idx = vq->last_avail_idx;
|
|
total_bufs = in_total = out_total = 0;
|
|
|
|
while ((rc = virtqueue_num_heads(vq, idx)) > 0) {
|
|
MemoryRegionCache *desc_cache = &caches->desc;
|
|
unsigned int num_bufs;
|
|
VRingDesc desc;
|
|
unsigned int i;
|
|
unsigned int max = vq->vring.num;
|
|
|
|
num_bufs = total_bufs;
|
|
|
|
if (!virtqueue_get_head(vq, idx++, &i)) {
|
|
goto err;
|
|
}
|
|
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
|
|
if (desc.flags & VRING_DESC_F_INDIRECT) {
|
|
if (!desc.len || (desc.len % sizeof(VRingDesc))) {
|
|
virtio_error(vdev, "Invalid size for indirect buffer table");
|
|
goto err;
|
|
}
|
|
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (num_bufs >= max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err;
|
|
}
|
|
|
|
/* loop over the indirect descriptor table */
|
|
len = address_space_cache_init(&indirect_desc_cache,
|
|
vdev->dma_as,
|
|
desc.addr, desc.len, false);
|
|
desc_cache = &indirect_desc_cache;
|
|
if (len < desc.len) {
|
|
virtio_error(vdev, "Cannot map indirect buffer");
|
|
goto err;
|
|
}
|
|
|
|
max = desc.len / sizeof(VRingDesc);
|
|
num_bufs = i = 0;
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
}
|
|
|
|
do {
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (++num_bufs > max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err;
|
|
}
|
|
|
|
if (desc.flags & VRING_DESC_F_WRITE) {
|
|
in_total += desc.len;
|
|
} else {
|
|
out_total += desc.len;
|
|
}
|
|
if (in_total >= max_in_bytes && out_total >= max_out_bytes) {
|
|
goto done;
|
|
}
|
|
|
|
rc = virtqueue_split_read_next_desc(vdev, &desc, desc_cache, max);
|
|
} while (rc == VIRTQUEUE_READ_DESC_MORE);
|
|
|
|
if (rc == VIRTQUEUE_READ_DESC_ERROR) {
|
|
goto err;
|
|
}
|
|
|
|
if (desc_cache == &indirect_desc_cache) {
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
total_bufs++;
|
|
} else {
|
|
total_bufs = num_bufs;
|
|
}
|
|
}
|
|
|
|
if (rc < 0) {
|
|
goto err;
|
|
}
|
|
|
|
done:
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
if (in_bytes) {
|
|
*in_bytes = in_total;
|
|
}
|
|
if (out_bytes) {
|
|
*out_bytes = out_total;
|
|
}
|
|
return;
|
|
|
|
err:
|
|
in_total = out_total = 0;
|
|
goto done;
|
|
}
|
|
|
|
static int virtqueue_packed_read_next_desc(VirtQueue *vq,
|
|
VRingPackedDesc *desc,
|
|
MemoryRegionCache
|
|
*desc_cache,
|
|
unsigned int max,
|
|
unsigned int *next,
|
|
bool indirect)
|
|
{
|
|
/* If this descriptor says it doesn't chain, we're done. */
|
|
if (!indirect && !(desc->flags & VRING_DESC_F_NEXT)) {
|
|
return VIRTQUEUE_READ_DESC_DONE;
|
|
}
|
|
|
|
++*next;
|
|
if (*next == max) {
|
|
if (indirect) {
|
|
return VIRTQUEUE_READ_DESC_DONE;
|
|
} else {
|
|
(*next) -= vq->vring.num;
|
|
}
|
|
}
|
|
|
|
vring_packed_desc_read(vq->vdev, desc, desc_cache, *next, false);
|
|
return VIRTQUEUE_READ_DESC_MORE;
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static void virtqueue_packed_get_avail_bytes(VirtQueue *vq,
|
|
unsigned int *in_bytes,
|
|
unsigned int *out_bytes,
|
|
unsigned max_in_bytes,
|
|
unsigned max_out_bytes,
|
|
VRingMemoryRegionCaches *caches)
|
|
{
|
|
VirtIODevice *vdev = vq->vdev;
|
|
unsigned int idx;
|
|
unsigned int total_bufs, in_total, out_total;
|
|
MemoryRegionCache indirect_desc_cache;
|
|
MemoryRegionCache *desc_cache;
|
|
int64_t len = 0;
|
|
VRingPackedDesc desc;
|
|
bool wrap_counter;
|
|
|
|
address_space_cache_init_empty(&indirect_desc_cache);
|
|
|
|
idx = vq->last_avail_idx;
|
|
wrap_counter = vq->last_avail_wrap_counter;
|
|
total_bufs = in_total = out_total = 0;
|
|
|
|
for (;;) {
|
|
unsigned int num_bufs = total_bufs;
|
|
unsigned int i = idx;
|
|
int rc;
|
|
unsigned int max = vq->vring.num;
|
|
|
|
desc_cache = &caches->desc;
|
|
|
|
vring_packed_desc_read(vdev, &desc, desc_cache, idx, true);
|
|
if (!is_desc_avail(desc.flags, wrap_counter)) {
|
|
break;
|
|
}
|
|
|
|
if (desc.flags & VRING_DESC_F_INDIRECT) {
|
|
if (desc.len % sizeof(VRingPackedDesc)) {
|
|
virtio_error(vdev, "Invalid size for indirect buffer table");
|
|
goto err;
|
|
}
|
|
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (num_bufs >= max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err;
|
|
}
|
|
|
|
/* loop over the indirect descriptor table */
|
|
len = address_space_cache_init(&indirect_desc_cache,
|
|
vdev->dma_as,
|
|
desc.addr, desc.len, false);
|
|
desc_cache = &indirect_desc_cache;
|
|
if (len < desc.len) {
|
|
virtio_error(vdev, "Cannot map indirect buffer");
|
|
goto err;
|
|
}
|
|
|
|
max = desc.len / sizeof(VRingPackedDesc);
|
|
num_bufs = i = 0;
|
|
vring_packed_desc_read(vdev, &desc, desc_cache, i, false);
|
|
}
|
|
|
|
do {
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (++num_bufs > max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err;
|
|
}
|
|
|
|
if (desc.flags & VRING_DESC_F_WRITE) {
|
|
in_total += desc.len;
|
|
} else {
|
|
out_total += desc.len;
|
|
}
|
|
if (in_total >= max_in_bytes && out_total >= max_out_bytes) {
|
|
goto done;
|
|
}
|
|
|
|
rc = virtqueue_packed_read_next_desc(vq, &desc, desc_cache, max,
|
|
&i, desc_cache ==
|
|
&indirect_desc_cache);
|
|
} while (rc == VIRTQUEUE_READ_DESC_MORE);
|
|
|
|
if (desc_cache == &indirect_desc_cache) {
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
total_bufs++;
|
|
idx++;
|
|
} else {
|
|
idx += num_bufs - total_bufs;
|
|
total_bufs = num_bufs;
|
|
}
|
|
|
|
if (idx >= vq->vring.num) {
|
|
idx -= vq->vring.num;
|
|
wrap_counter ^= 1;
|
|
}
|
|
}
|
|
|
|
/* Record the index and wrap counter for a kick we want */
|
|
vq->shadow_avail_idx = idx;
|
|
vq->shadow_avail_wrap_counter = wrap_counter;
|
|
done:
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
if (in_bytes) {
|
|
*in_bytes = in_total;
|
|
}
|
|
if (out_bytes) {
|
|
*out_bytes = out_total;
|
|
}
|
|
return;
|
|
|
|
err:
|
|
in_total = out_total = 0;
|
|
goto done;
|
|
}
|
|
|
|
void virtqueue_get_avail_bytes(VirtQueue *vq, unsigned int *in_bytes,
|
|
unsigned int *out_bytes,
|
|
unsigned max_in_bytes, unsigned max_out_bytes)
|
|
{
|
|
uint16_t desc_size;
|
|
VRingMemoryRegionCaches *caches;
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
|
|
if (unlikely(!vq->vring.desc)) {
|
|
goto err;
|
|
}
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
goto err;
|
|
}
|
|
|
|
desc_size = virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED) ?
|
|
sizeof(VRingPackedDesc) : sizeof(VRingDesc);
|
|
if (caches->desc.len < vq->vring.num * desc_size) {
|
|
virtio_error(vq->vdev, "Cannot map descriptor ring");
|
|
goto err;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtqueue_packed_get_avail_bytes(vq, in_bytes, out_bytes,
|
|
max_in_bytes, max_out_bytes,
|
|
caches);
|
|
} else {
|
|
virtqueue_split_get_avail_bytes(vq, in_bytes, out_bytes,
|
|
max_in_bytes, max_out_bytes,
|
|
caches);
|
|
}
|
|
|
|
return;
|
|
err:
|
|
if (in_bytes) {
|
|
*in_bytes = 0;
|
|
}
|
|
if (out_bytes) {
|
|
*out_bytes = 0;
|
|
}
|
|
}
|
|
|
|
int virtqueue_avail_bytes(VirtQueue *vq, unsigned int in_bytes,
|
|
unsigned int out_bytes)
|
|
{
|
|
unsigned int in_total, out_total;
|
|
|
|
virtqueue_get_avail_bytes(vq, &in_total, &out_total, in_bytes, out_bytes);
|
|
return in_bytes <= in_total && out_bytes <= out_total;
|
|
}
|
|
|
|
static bool virtqueue_map_desc(VirtIODevice *vdev, unsigned int *p_num_sg,
|
|
hwaddr *addr, struct iovec *iov,
|
|
unsigned int max_num_sg, bool is_write,
|
|
hwaddr pa, size_t sz)
|
|
{
|
|
bool ok = false;
|
|
unsigned num_sg = *p_num_sg;
|
|
assert(num_sg <= max_num_sg);
|
|
|
|
if (!sz) {
|
|
virtio_error(vdev, "virtio: zero sized buffers are not allowed");
|
|
goto out;
|
|
}
|
|
|
|
while (sz) {
|
|
hwaddr len = sz;
|
|
|
|
if (num_sg == max_num_sg) {
|
|
virtio_error(vdev, "virtio: too many write descriptors in "
|
|
"indirect table");
|
|
goto out;
|
|
}
|
|
|
|
iov[num_sg].iov_base = dma_memory_map(vdev->dma_as, pa, &len,
|
|
is_write ?
|
|
DMA_DIRECTION_FROM_DEVICE :
|
|
DMA_DIRECTION_TO_DEVICE,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
if (!iov[num_sg].iov_base) {
|
|
virtio_error(vdev, "virtio: bogus descriptor or out of resources");
|
|
goto out;
|
|
}
|
|
|
|
iov[num_sg].iov_len = len;
|
|
addr[num_sg] = pa;
|
|
|
|
sz -= len;
|
|
pa += len;
|
|
num_sg++;
|
|
}
|
|
ok = true;
|
|
|
|
out:
|
|
*p_num_sg = num_sg;
|
|
return ok;
|
|
}
|
|
|
|
/* Only used by error code paths before we have a VirtQueueElement (therefore
|
|
* virtqueue_unmap_sg() can't be used). Assumes buffers weren't written to
|
|
* yet.
|
|
*/
|
|
static void virtqueue_undo_map_desc(unsigned int out_num, unsigned int in_num,
|
|
struct iovec *iov)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < out_num + in_num; i++) {
|
|
int is_write = i >= out_num;
|
|
|
|
cpu_physical_memory_unmap(iov->iov_base, iov->iov_len, is_write, 0);
|
|
iov++;
|
|
}
|
|
}
|
|
|
|
static void virtqueue_map_iovec(VirtIODevice *vdev, struct iovec *sg,
|
|
hwaddr *addr, unsigned int num_sg,
|
|
bool is_write)
|
|
{
|
|
unsigned int i;
|
|
hwaddr len;
|
|
|
|
for (i = 0; i < num_sg; i++) {
|
|
len = sg[i].iov_len;
|
|
sg[i].iov_base = dma_memory_map(vdev->dma_as,
|
|
addr[i], &len, is_write ?
|
|
DMA_DIRECTION_FROM_DEVICE :
|
|
DMA_DIRECTION_TO_DEVICE,
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
if (!sg[i].iov_base) {
|
|
error_report("virtio: error trying to map MMIO memory");
|
|
exit(1);
|
|
}
|
|
if (len != sg[i].iov_len) {
|
|
error_report("virtio: unexpected memory split");
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
void virtqueue_map(VirtIODevice *vdev, VirtQueueElement *elem)
|
|
{
|
|
virtqueue_map_iovec(vdev, elem->in_sg, elem->in_addr, elem->in_num, true);
|
|
virtqueue_map_iovec(vdev, elem->out_sg, elem->out_addr, elem->out_num,
|
|
false);
|
|
}
|
|
|
|
static void *virtqueue_alloc_element(size_t sz, unsigned out_num, unsigned in_num)
|
|
{
|
|
VirtQueueElement *elem;
|
|
size_t in_addr_ofs = QEMU_ALIGN_UP(sz, __alignof__(elem->in_addr[0]));
|
|
size_t out_addr_ofs = in_addr_ofs + in_num * sizeof(elem->in_addr[0]);
|
|
size_t out_addr_end = out_addr_ofs + out_num * sizeof(elem->out_addr[0]);
|
|
size_t in_sg_ofs = QEMU_ALIGN_UP(out_addr_end, __alignof__(elem->in_sg[0]));
|
|
size_t out_sg_ofs = in_sg_ofs + in_num * sizeof(elem->in_sg[0]);
|
|
size_t out_sg_end = out_sg_ofs + out_num * sizeof(elem->out_sg[0]);
|
|
|
|
assert(sz >= sizeof(VirtQueueElement));
|
|
elem = g_malloc(out_sg_end);
|
|
trace_virtqueue_alloc_element(elem, sz, in_num, out_num);
|
|
elem->out_num = out_num;
|
|
elem->in_num = in_num;
|
|
elem->in_addr = (void *)elem + in_addr_ofs;
|
|
elem->out_addr = (void *)elem + out_addr_ofs;
|
|
elem->in_sg = (void *)elem + in_sg_ofs;
|
|
elem->out_sg = (void *)elem + out_sg_ofs;
|
|
return elem;
|
|
}
|
|
|
|
static void *virtqueue_split_pop(VirtQueue *vq, size_t sz)
|
|
{
|
|
unsigned int i, head, max;
|
|
VRingMemoryRegionCaches *caches;
|
|
MemoryRegionCache indirect_desc_cache;
|
|
MemoryRegionCache *desc_cache;
|
|
int64_t len;
|
|
VirtIODevice *vdev = vq->vdev;
|
|
VirtQueueElement *elem = NULL;
|
|
unsigned out_num, in_num, elem_entries;
|
|
hwaddr addr[VIRTQUEUE_MAX_SIZE];
|
|
struct iovec iov[VIRTQUEUE_MAX_SIZE];
|
|
VRingDesc desc;
|
|
int rc;
|
|
|
|
address_space_cache_init_empty(&indirect_desc_cache);
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
if (virtio_queue_empty_rcu(vq)) {
|
|
goto done;
|
|
}
|
|
/* Needed after virtio_queue_empty(), see comment in
|
|
* virtqueue_num_heads(). */
|
|
smp_rmb();
|
|
|
|
/* When we start there are none of either input nor output. */
|
|
out_num = in_num = elem_entries = 0;
|
|
|
|
max = vq->vring.num;
|
|
|
|
if (vq->inuse >= vq->vring.num) {
|
|
virtio_error(vdev, "Virtqueue size exceeded");
|
|
goto done;
|
|
}
|
|
|
|
if (!virtqueue_get_head(vq, vq->last_avail_idx++, &head)) {
|
|
goto done;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
|
vring_set_avail_event(vq, vq->last_avail_idx);
|
|
}
|
|
|
|
i = head;
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
virtio_error(vdev, "Region caches not initialized");
|
|
goto done;
|
|
}
|
|
|
|
if (caches->desc.len < max * sizeof(VRingDesc)) {
|
|
virtio_error(vdev, "Cannot map descriptor ring");
|
|
goto done;
|
|
}
|
|
|
|
desc_cache = &caches->desc;
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
if (desc.flags & VRING_DESC_F_INDIRECT) {
|
|
if (!desc.len || (desc.len % sizeof(VRingDesc))) {
|
|
virtio_error(vdev, "Invalid size for indirect buffer table");
|
|
goto done;
|
|
}
|
|
|
|
/* loop over the indirect descriptor table */
|
|
len = address_space_cache_init(&indirect_desc_cache, vdev->dma_as,
|
|
desc.addr, desc.len, false);
|
|
desc_cache = &indirect_desc_cache;
|
|
if (len < desc.len) {
|
|
virtio_error(vdev, "Cannot map indirect buffer");
|
|
goto done;
|
|
}
|
|
|
|
max = desc.len / sizeof(VRingDesc);
|
|
i = 0;
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
}
|
|
|
|
/* Collect all the descriptors */
|
|
do {
|
|
bool map_ok;
|
|
|
|
if (desc.flags & VRING_DESC_F_WRITE) {
|
|
map_ok = virtqueue_map_desc(vdev, &in_num, addr + out_num,
|
|
iov + out_num,
|
|
VIRTQUEUE_MAX_SIZE - out_num, true,
|
|
desc.addr, desc.len);
|
|
} else {
|
|
if (in_num) {
|
|
virtio_error(vdev, "Incorrect order for descriptors");
|
|
goto err_undo_map;
|
|
}
|
|
map_ok = virtqueue_map_desc(vdev, &out_num, addr, iov,
|
|
VIRTQUEUE_MAX_SIZE, false,
|
|
desc.addr, desc.len);
|
|
}
|
|
if (!map_ok) {
|
|
goto err_undo_map;
|
|
}
|
|
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (++elem_entries > max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err_undo_map;
|
|
}
|
|
|
|
rc = virtqueue_split_read_next_desc(vdev, &desc, desc_cache, max);
|
|
} while (rc == VIRTQUEUE_READ_DESC_MORE);
|
|
|
|
if (rc == VIRTQUEUE_READ_DESC_ERROR) {
|
|
goto err_undo_map;
|
|
}
|
|
|
|
/* Now copy what we have collected and mapped */
|
|
elem = virtqueue_alloc_element(sz, out_num, in_num);
|
|
elem->index = head;
|
|
elem->ndescs = 1;
|
|
for (i = 0; i < out_num; i++) {
|
|
elem->out_addr[i] = addr[i];
|
|
elem->out_sg[i] = iov[i];
|
|
}
|
|
for (i = 0; i < in_num; i++) {
|
|
elem->in_addr[i] = addr[out_num + i];
|
|
elem->in_sg[i] = iov[out_num + i];
|
|
}
|
|
|
|
vq->inuse++;
|
|
|
|
trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num);
|
|
done:
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
|
|
return elem;
|
|
|
|
err_undo_map:
|
|
virtqueue_undo_map_desc(out_num, in_num, iov);
|
|
goto done;
|
|
}
|
|
|
|
static void *virtqueue_packed_pop(VirtQueue *vq, size_t sz)
|
|
{
|
|
unsigned int i, max;
|
|
VRingMemoryRegionCaches *caches;
|
|
MemoryRegionCache indirect_desc_cache;
|
|
MemoryRegionCache *desc_cache;
|
|
int64_t len;
|
|
VirtIODevice *vdev = vq->vdev;
|
|
VirtQueueElement *elem = NULL;
|
|
unsigned out_num, in_num, elem_entries;
|
|
hwaddr addr[VIRTQUEUE_MAX_SIZE];
|
|
struct iovec iov[VIRTQUEUE_MAX_SIZE];
|
|
VRingPackedDesc desc;
|
|
uint16_t id;
|
|
int rc;
|
|
|
|
address_space_cache_init_empty(&indirect_desc_cache);
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
if (virtio_queue_packed_empty_rcu(vq)) {
|
|
goto done;
|
|
}
|
|
|
|
/* When we start there are none of either input nor output. */
|
|
out_num = in_num = elem_entries = 0;
|
|
|
|
max = vq->vring.num;
|
|
|
|
if (vq->inuse >= vq->vring.num) {
|
|
virtio_error(vdev, "Virtqueue size exceeded");
|
|
goto done;
|
|
}
|
|
|
|
i = vq->last_avail_idx;
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
virtio_error(vdev, "Region caches not initialized");
|
|
goto done;
|
|
}
|
|
|
|
if (caches->desc.len < max * sizeof(VRingDesc)) {
|
|
virtio_error(vdev, "Cannot map descriptor ring");
|
|
goto done;
|
|
}
|
|
|
|
desc_cache = &caches->desc;
|
|
vring_packed_desc_read(vdev, &desc, desc_cache, i, true);
|
|
id = desc.id;
|
|
if (desc.flags & VRING_DESC_F_INDIRECT) {
|
|
if (desc.len % sizeof(VRingPackedDesc)) {
|
|
virtio_error(vdev, "Invalid size for indirect buffer table");
|
|
goto done;
|
|
}
|
|
|
|
/* loop over the indirect descriptor table */
|
|
len = address_space_cache_init(&indirect_desc_cache, vdev->dma_as,
|
|
desc.addr, desc.len, false);
|
|
desc_cache = &indirect_desc_cache;
|
|
if (len < desc.len) {
|
|
virtio_error(vdev, "Cannot map indirect buffer");
|
|
goto done;
|
|
}
|
|
|
|
max = desc.len / sizeof(VRingPackedDesc);
|
|
i = 0;
|
|
vring_packed_desc_read(vdev, &desc, desc_cache, i, false);
|
|
}
|
|
|
|
/* Collect all the descriptors */
|
|
do {
|
|
bool map_ok;
|
|
|
|
if (desc.flags & VRING_DESC_F_WRITE) {
|
|
map_ok = virtqueue_map_desc(vdev, &in_num, addr + out_num,
|
|
iov + out_num,
|
|
VIRTQUEUE_MAX_SIZE - out_num, true,
|
|
desc.addr, desc.len);
|
|
} else {
|
|
if (in_num) {
|
|
virtio_error(vdev, "Incorrect order for descriptors");
|
|
goto err_undo_map;
|
|
}
|
|
map_ok = virtqueue_map_desc(vdev, &out_num, addr, iov,
|
|
VIRTQUEUE_MAX_SIZE, false,
|
|
desc.addr, desc.len);
|
|
}
|
|
if (!map_ok) {
|
|
goto err_undo_map;
|
|
}
|
|
|
|
/* If we've got too many, that implies a descriptor loop. */
|
|
if (++elem_entries > max) {
|
|
virtio_error(vdev, "Looped descriptor");
|
|
goto err_undo_map;
|
|
}
|
|
|
|
rc = virtqueue_packed_read_next_desc(vq, &desc, desc_cache, max, &i,
|
|
desc_cache ==
|
|
&indirect_desc_cache);
|
|
} while (rc == VIRTQUEUE_READ_DESC_MORE);
|
|
|
|
/* Now copy what we have collected and mapped */
|
|
elem = virtqueue_alloc_element(sz, out_num, in_num);
|
|
for (i = 0; i < out_num; i++) {
|
|
elem->out_addr[i] = addr[i];
|
|
elem->out_sg[i] = iov[i];
|
|
}
|
|
for (i = 0; i < in_num; i++) {
|
|
elem->in_addr[i] = addr[out_num + i];
|
|
elem->in_sg[i] = iov[out_num + i];
|
|
}
|
|
|
|
elem->index = id;
|
|
elem->ndescs = (desc_cache == &indirect_desc_cache) ? 1 : elem_entries;
|
|
vq->last_avail_idx += elem->ndescs;
|
|
vq->inuse += elem->ndescs;
|
|
|
|
if (vq->last_avail_idx >= vq->vring.num) {
|
|
vq->last_avail_idx -= vq->vring.num;
|
|
vq->last_avail_wrap_counter ^= 1;
|
|
}
|
|
|
|
vq->shadow_avail_idx = vq->last_avail_idx;
|
|
vq->shadow_avail_wrap_counter = vq->last_avail_wrap_counter;
|
|
|
|
trace_virtqueue_pop(vq, elem, elem->in_num, elem->out_num);
|
|
done:
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
|
|
return elem;
|
|
|
|
err_undo_map:
|
|
virtqueue_undo_map_desc(out_num, in_num, iov);
|
|
goto done;
|
|
}
|
|
|
|
void *virtqueue_pop(VirtQueue *vq, size_t sz)
|
|
{
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
return NULL;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vq->vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtqueue_packed_pop(vq, sz);
|
|
} else {
|
|
return virtqueue_split_pop(vq, sz);
|
|
}
|
|
}
|
|
|
|
static unsigned int virtqueue_packed_drop_all(VirtQueue *vq)
|
|
{
|
|
VRingMemoryRegionCaches *caches;
|
|
MemoryRegionCache *desc_cache;
|
|
unsigned int dropped = 0;
|
|
VirtQueueElement elem = {};
|
|
VirtIODevice *vdev = vq->vdev;
|
|
VRingPackedDesc desc;
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
return 0;
|
|
}
|
|
|
|
desc_cache = &caches->desc;
|
|
|
|
virtio_queue_set_notification(vq, 0);
|
|
|
|
while (vq->inuse < vq->vring.num) {
|
|
unsigned int idx = vq->last_avail_idx;
|
|
/*
|
|
* works similar to virtqueue_pop but does not map buffers
|
|
* and does not allocate any memory.
|
|
*/
|
|
vring_packed_desc_read(vdev, &desc, desc_cache,
|
|
vq->last_avail_idx , true);
|
|
if (!is_desc_avail(desc.flags, vq->last_avail_wrap_counter)) {
|
|
break;
|
|
}
|
|
elem.index = desc.id;
|
|
elem.ndescs = 1;
|
|
while (virtqueue_packed_read_next_desc(vq, &desc, desc_cache,
|
|
vq->vring.num, &idx, false)) {
|
|
++elem.ndescs;
|
|
}
|
|
/*
|
|
* immediately push the element, nothing to unmap
|
|
* as both in_num and out_num are set to 0.
|
|
*/
|
|
virtqueue_push(vq, &elem, 0);
|
|
dropped++;
|
|
vq->last_avail_idx += elem.ndescs;
|
|
if (vq->last_avail_idx >= vq->vring.num) {
|
|
vq->last_avail_idx -= vq->vring.num;
|
|
vq->last_avail_wrap_counter ^= 1;
|
|
}
|
|
}
|
|
|
|
return dropped;
|
|
}
|
|
|
|
static unsigned int virtqueue_split_drop_all(VirtQueue *vq)
|
|
{
|
|
unsigned int dropped = 0;
|
|
VirtQueueElement elem = {};
|
|
VirtIODevice *vdev = vq->vdev;
|
|
bool fEventIdx = virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX);
|
|
|
|
while (!virtio_queue_empty(vq) && vq->inuse < vq->vring.num) {
|
|
/* works similar to virtqueue_pop but does not map buffers
|
|
* and does not allocate any memory */
|
|
smp_rmb();
|
|
if (!virtqueue_get_head(vq, vq->last_avail_idx, &elem.index)) {
|
|
break;
|
|
}
|
|
vq->inuse++;
|
|
vq->last_avail_idx++;
|
|
if (fEventIdx) {
|
|
vring_set_avail_event(vq, vq->last_avail_idx);
|
|
}
|
|
/* immediately push the element, nothing to unmap
|
|
* as both in_num and out_num are set to 0 */
|
|
virtqueue_push(vq, &elem, 0);
|
|
dropped++;
|
|
}
|
|
|
|
return dropped;
|
|
}
|
|
|
|
/* virtqueue_drop_all:
|
|
* @vq: The #VirtQueue
|
|
* Drops all queued buffers and indicates them to the guest
|
|
* as if they are done. Useful when buffers can not be
|
|
* processed but must be returned to the guest.
|
|
*/
|
|
unsigned int virtqueue_drop_all(VirtQueue *vq)
|
|
{
|
|
struct VirtIODevice *vdev = vq->vdev;
|
|
|
|
if (virtio_device_disabled(vq->vdev)) {
|
|
return 0;
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtqueue_packed_drop_all(vq);
|
|
} else {
|
|
return virtqueue_split_drop_all(vq);
|
|
}
|
|
}
|
|
|
|
/* Reading and writing a structure directly to QEMUFile is *awful*, but
|
|
* it is what QEMU has always done by mistake. We can change it sooner
|
|
* or later by bumping the version number of the affected vm states.
|
|
* In the meanwhile, since the in-memory layout of VirtQueueElement
|
|
* has changed, we need to marshal to and from the layout that was
|
|
* used before the change.
|
|
*/
|
|
typedef struct VirtQueueElementOld {
|
|
unsigned int index;
|
|
unsigned int out_num;
|
|
unsigned int in_num;
|
|
hwaddr in_addr[VIRTQUEUE_MAX_SIZE];
|
|
hwaddr out_addr[VIRTQUEUE_MAX_SIZE];
|
|
struct iovec in_sg[VIRTQUEUE_MAX_SIZE];
|
|
struct iovec out_sg[VIRTQUEUE_MAX_SIZE];
|
|
} VirtQueueElementOld;
|
|
|
|
void *qemu_get_virtqueue_element(VirtIODevice *vdev, QEMUFile *f, size_t sz)
|
|
{
|
|
VirtQueueElement *elem;
|
|
VirtQueueElementOld data;
|
|
int i;
|
|
|
|
qemu_get_buffer(f, (uint8_t *)&data, sizeof(VirtQueueElementOld));
|
|
|
|
/* TODO: teach all callers that this can fail, and return failure instead
|
|
* of asserting here.
|
|
* This is just one thing (there are probably more) that must be
|
|
* fixed before we can allow NDEBUG compilation.
|
|
*/
|
|
assert(ARRAY_SIZE(data.in_addr) >= data.in_num);
|
|
assert(ARRAY_SIZE(data.out_addr) >= data.out_num);
|
|
|
|
elem = virtqueue_alloc_element(sz, data.out_num, data.in_num);
|
|
elem->index = data.index;
|
|
|
|
for (i = 0; i < elem->in_num; i++) {
|
|
elem->in_addr[i] = data.in_addr[i];
|
|
}
|
|
|
|
for (i = 0; i < elem->out_num; i++) {
|
|
elem->out_addr[i] = data.out_addr[i];
|
|
}
|
|
|
|
for (i = 0; i < elem->in_num; i++) {
|
|
/* Base is overwritten by virtqueue_map. */
|
|
elem->in_sg[i].iov_base = 0;
|
|
elem->in_sg[i].iov_len = data.in_sg[i].iov_len;
|
|
}
|
|
|
|
for (i = 0; i < elem->out_num; i++) {
|
|
/* Base is overwritten by virtqueue_map. */
|
|
elem->out_sg[i].iov_base = 0;
|
|
elem->out_sg[i].iov_len = data.out_sg[i].iov_len;
|
|
}
|
|
|
|
if (virtio_host_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
qemu_get_be32s(f, &elem->ndescs);
|
|
}
|
|
|
|
virtqueue_map(vdev, elem);
|
|
return elem;
|
|
}
|
|
|
|
void qemu_put_virtqueue_element(VirtIODevice *vdev, QEMUFile *f,
|
|
VirtQueueElement *elem)
|
|
{
|
|
VirtQueueElementOld data;
|
|
int i;
|
|
|
|
memset(&data, 0, sizeof(data));
|
|
data.index = elem->index;
|
|
data.in_num = elem->in_num;
|
|
data.out_num = elem->out_num;
|
|
|
|
for (i = 0; i < elem->in_num; i++) {
|
|
data.in_addr[i] = elem->in_addr[i];
|
|
}
|
|
|
|
for (i = 0; i < elem->out_num; i++) {
|
|
data.out_addr[i] = elem->out_addr[i];
|
|
}
|
|
|
|
for (i = 0; i < elem->in_num; i++) {
|
|
/* Base is overwritten by virtqueue_map when loading. Do not
|
|
* save it, as it would leak the QEMU address space layout. */
|
|
data.in_sg[i].iov_len = elem->in_sg[i].iov_len;
|
|
}
|
|
|
|
for (i = 0; i < elem->out_num; i++) {
|
|
/* Do not save iov_base as above. */
|
|
data.out_sg[i].iov_len = elem->out_sg[i].iov_len;
|
|
}
|
|
|
|
if (virtio_host_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
qemu_put_be32s(f, &elem->ndescs);
|
|
}
|
|
|
|
qemu_put_buffer(f, (uint8_t *)&data, sizeof(VirtQueueElementOld));
|
|
}
|
|
|
|
/* virtio device */
|
|
static void virtio_notify_vector(VirtIODevice *vdev, uint16_t vector)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
if (virtio_device_disabled(vdev)) {
|
|
return;
|
|
}
|
|
|
|
if (k->notify) {
|
|
k->notify(qbus->parent, vector);
|
|
}
|
|
}
|
|
|
|
void virtio_update_irq(VirtIODevice *vdev)
|
|
{
|
|
virtio_notify_vector(vdev, VIRTIO_NO_VECTOR);
|
|
}
|
|
|
|
static int virtio_validate_features(VirtIODevice *vdev)
|
|
{
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
|
|
if (virtio_host_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM) &&
|
|
!virtio_vdev_has_feature(vdev, VIRTIO_F_IOMMU_PLATFORM)) {
|
|
return -EFAULT;
|
|
}
|
|
|
|
if (k->validate_features) {
|
|
return k->validate_features(vdev);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int virtio_set_status(VirtIODevice *vdev, uint8_t val)
|
|
{
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
trace_virtio_set_status(vdev, val);
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
if (!(vdev->status & VIRTIO_CONFIG_S_FEATURES_OK) &&
|
|
val & VIRTIO_CONFIG_S_FEATURES_OK) {
|
|
int ret = virtio_validate_features(vdev);
|
|
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((vdev->status & VIRTIO_CONFIG_S_DRIVER_OK) !=
|
|
(val & VIRTIO_CONFIG_S_DRIVER_OK)) {
|
|
virtio_set_started(vdev, val & VIRTIO_CONFIG_S_DRIVER_OK);
|
|
}
|
|
|
|
if (k->set_status) {
|
|
k->set_status(vdev, val);
|
|
}
|
|
vdev->status = val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum virtio_device_endian virtio_default_endian(void)
|
|
{
|
|
if (target_words_bigendian()) {
|
|
return VIRTIO_DEVICE_ENDIAN_BIG;
|
|
} else {
|
|
return VIRTIO_DEVICE_ENDIAN_LITTLE;
|
|
}
|
|
}
|
|
|
|
static enum virtio_device_endian virtio_current_cpu_endian(void)
|
|
{
|
|
if (cpu_virtio_is_big_endian(current_cpu)) {
|
|
return VIRTIO_DEVICE_ENDIAN_BIG;
|
|
} else {
|
|
return VIRTIO_DEVICE_ENDIAN_LITTLE;
|
|
}
|
|
}
|
|
|
|
static void __virtio_queue_reset(VirtIODevice *vdev, uint32_t i)
|
|
{
|
|
vdev->vq[i].vring.desc = 0;
|
|
vdev->vq[i].vring.avail = 0;
|
|
vdev->vq[i].vring.used = 0;
|
|
vdev->vq[i].last_avail_idx = 0;
|
|
vdev->vq[i].shadow_avail_idx = 0;
|
|
vdev->vq[i].used_idx = 0;
|
|
vdev->vq[i].last_avail_wrap_counter = true;
|
|
vdev->vq[i].shadow_avail_wrap_counter = true;
|
|
vdev->vq[i].used_wrap_counter = true;
|
|
virtio_queue_set_vector(vdev, i, VIRTIO_NO_VECTOR);
|
|
vdev->vq[i].signalled_used = 0;
|
|
vdev->vq[i].signalled_used_valid = false;
|
|
vdev->vq[i].notification = true;
|
|
vdev->vq[i].vring.num = vdev->vq[i].vring.num_default;
|
|
vdev->vq[i].inuse = 0;
|
|
virtio_virtqueue_reset_region_cache(&vdev->vq[i]);
|
|
}
|
|
|
|
void virtio_queue_reset(VirtIODevice *vdev, uint32_t queue_index)
|
|
{
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
|
|
if (k->queue_reset) {
|
|
k->queue_reset(vdev, queue_index);
|
|
}
|
|
|
|
__virtio_queue_reset(vdev, queue_index);
|
|
}
|
|
|
|
void virtio_queue_enable(VirtIODevice *vdev, uint32_t queue_index)
|
|
{
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
|
|
/*
|
|
* TODO: Seabios is currently out of spec and triggering this error.
|
|
* So this needs to be fixed in Seabios, then this can
|
|
* be re-enabled for new machine types only, and also after
|
|
* being converted to LOG_GUEST_ERROR.
|
|
*
|
|
if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
error_report("queue_enable is only supported in devices of virtio "
|
|
"1.0 or later.");
|
|
}
|
|
*/
|
|
|
|
if (k->queue_enable) {
|
|
k->queue_enable(vdev, queue_index);
|
|
}
|
|
}
|
|
|
|
void virtio_reset(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
int i;
|
|
|
|
virtio_set_status(vdev, 0);
|
|
if (current_cpu) {
|
|
/* Guest initiated reset */
|
|
vdev->device_endian = virtio_current_cpu_endian();
|
|
} else {
|
|
/* System reset */
|
|
vdev->device_endian = virtio_default_endian();
|
|
}
|
|
|
|
if (vdev->vhost_started) {
|
|
vhost_reset_device(k->get_vhost(vdev));
|
|
}
|
|
|
|
if (k->reset) {
|
|
k->reset(vdev);
|
|
}
|
|
|
|
vdev->start_on_kick = false;
|
|
vdev->started = false;
|
|
vdev->broken = false;
|
|
vdev->guest_features = 0;
|
|
vdev->queue_sel = 0;
|
|
vdev->status = 0;
|
|
vdev->disabled = false;
|
|
qatomic_set(&vdev->isr, 0);
|
|
vdev->config_vector = VIRTIO_NO_VECTOR;
|
|
virtio_notify_vector(vdev, vdev->config_vector);
|
|
|
|
for(i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
__virtio_queue_reset(vdev, i);
|
|
}
|
|
}
|
|
|
|
void virtio_queue_set_addr(VirtIODevice *vdev, int n, hwaddr addr)
|
|
{
|
|
if (!vdev->vq[n].vring.num) {
|
|
return;
|
|
}
|
|
vdev->vq[n].vring.desc = addr;
|
|
virtio_queue_update_rings(vdev, n);
|
|
}
|
|
|
|
hwaddr virtio_queue_get_addr(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.desc;
|
|
}
|
|
|
|
void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc,
|
|
hwaddr avail, hwaddr used)
|
|
{
|
|
if (!vdev->vq[n].vring.num) {
|
|
return;
|
|
}
|
|
vdev->vq[n].vring.desc = desc;
|
|
vdev->vq[n].vring.avail = avail;
|
|
vdev->vq[n].vring.used = used;
|
|
virtio_init_region_cache(vdev, n);
|
|
}
|
|
|
|
void virtio_queue_set_num(VirtIODevice *vdev, int n, int num)
|
|
{
|
|
/* Don't allow guest to flip queue between existent and
|
|
* nonexistent states, or to set it to an invalid size.
|
|
*/
|
|
if (!!num != !!vdev->vq[n].vring.num ||
|
|
num > VIRTQUEUE_MAX_SIZE ||
|
|
num < 0) {
|
|
return;
|
|
}
|
|
vdev->vq[n].vring.num = num;
|
|
}
|
|
|
|
VirtQueue *virtio_vector_first_queue(VirtIODevice *vdev, uint16_t vector)
|
|
{
|
|
return QLIST_FIRST(&vdev->vector_queues[vector]);
|
|
}
|
|
|
|
VirtQueue *virtio_vector_next_queue(VirtQueue *vq)
|
|
{
|
|
return QLIST_NEXT(vq, node);
|
|
}
|
|
|
|
int virtio_queue_get_num(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.num;
|
|
}
|
|
|
|
int virtio_queue_get_max_num(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.num_default;
|
|
}
|
|
|
|
int virtio_get_num_queues(VirtIODevice *vdev)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (!virtio_queue_get_num(vdev, i)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
void virtio_queue_set_align(VirtIODevice *vdev, int n, int align)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
/* virtio-1 compliant devices cannot change the alignment */
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
error_report("tried to modify queue alignment for virtio-1 device");
|
|
return;
|
|
}
|
|
/* Check that the transport told us it was going to do this
|
|
* (so a buggy transport will immediately assert rather than
|
|
* silently failing to migrate this state)
|
|
*/
|
|
assert(k->has_variable_vring_alignment);
|
|
|
|
if (align) {
|
|
vdev->vq[n].vring.align = align;
|
|
virtio_queue_update_rings(vdev, n);
|
|
}
|
|
}
|
|
|
|
static void virtio_queue_notify_vq(VirtQueue *vq)
|
|
{
|
|
if (vq->vring.desc && vq->handle_output) {
|
|
VirtIODevice *vdev = vq->vdev;
|
|
|
|
if (unlikely(vdev->broken)) {
|
|
return;
|
|
}
|
|
|
|
trace_virtio_queue_notify(vdev, vq - vdev->vq, vq);
|
|
vq->handle_output(vdev, vq);
|
|
|
|
if (unlikely(vdev->start_on_kick)) {
|
|
virtio_set_started(vdev, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void virtio_queue_notify(VirtIODevice *vdev, int n)
|
|
{
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
|
|
if (unlikely(!vq->vring.desc || vdev->broken)) {
|
|
return;
|
|
}
|
|
|
|
trace_virtio_queue_notify(vdev, vq - vdev->vq, vq);
|
|
if (vq->host_notifier_enabled) {
|
|
event_notifier_set(&vq->host_notifier);
|
|
} else if (vq->handle_output) {
|
|
vq->handle_output(vdev, vq);
|
|
|
|
if (unlikely(vdev->start_on_kick)) {
|
|
virtio_set_started(vdev, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint16_t virtio_queue_vector(VirtIODevice *vdev, int n)
|
|
{
|
|
return n < VIRTIO_QUEUE_MAX ? vdev->vq[n].vector :
|
|
VIRTIO_NO_VECTOR;
|
|
}
|
|
|
|
void virtio_queue_set_vector(VirtIODevice *vdev, int n, uint16_t vector)
|
|
{
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
|
|
if (n < VIRTIO_QUEUE_MAX) {
|
|
if (vdev->vector_queues &&
|
|
vdev->vq[n].vector != VIRTIO_NO_VECTOR) {
|
|
QLIST_REMOVE(vq, node);
|
|
}
|
|
vdev->vq[n].vector = vector;
|
|
if (vdev->vector_queues &&
|
|
vector != VIRTIO_NO_VECTOR) {
|
|
QLIST_INSERT_HEAD(&vdev->vector_queues[vector], vq, node);
|
|
}
|
|
}
|
|
}
|
|
|
|
VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
|
|
VirtIOHandleOutput handle_output)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num == 0)
|
|
break;
|
|
}
|
|
|
|
if (i == VIRTIO_QUEUE_MAX || queue_size > VIRTQUEUE_MAX_SIZE)
|
|
abort();
|
|
|
|
vdev->vq[i].vring.num = queue_size;
|
|
vdev->vq[i].vring.num_default = queue_size;
|
|
vdev->vq[i].vring.align = VIRTIO_PCI_VRING_ALIGN;
|
|
vdev->vq[i].handle_output = handle_output;
|
|
vdev->vq[i].used_elems = g_new0(VirtQueueElement, queue_size);
|
|
|
|
return &vdev->vq[i];
|
|
}
|
|
|
|
void virtio_delete_queue(VirtQueue *vq)
|
|
{
|
|
vq->vring.num = 0;
|
|
vq->vring.num_default = 0;
|
|
vq->handle_output = NULL;
|
|
g_free(vq->used_elems);
|
|
vq->used_elems = NULL;
|
|
virtio_virtqueue_reset_region_cache(vq);
|
|
}
|
|
|
|
void virtio_del_queue(VirtIODevice *vdev, int n)
|
|
{
|
|
if (n < 0 || n >= VIRTIO_QUEUE_MAX) {
|
|
abort();
|
|
}
|
|
|
|
virtio_delete_queue(&vdev->vq[n]);
|
|
}
|
|
|
|
static void virtio_set_isr(VirtIODevice *vdev, int value)
|
|
{
|
|
uint8_t old = qatomic_read(&vdev->isr);
|
|
|
|
/* Do not write ISR if it does not change, so that its cacheline remains
|
|
* shared in the common case where the guest does not read it.
|
|
*/
|
|
if ((old & value) != value) {
|
|
qatomic_or(&vdev->isr, value);
|
|
}
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static bool virtio_split_should_notify(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
uint16_t old, new;
|
|
bool v;
|
|
/* We need to expose used array entries before checking used event. */
|
|
smp_mb();
|
|
/* Always notify when queue is empty (when feature acknowledge) */
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFY_ON_EMPTY) &&
|
|
!vq->inuse && virtio_queue_empty(vq)) {
|
|
return true;
|
|
}
|
|
|
|
if (!virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
|
return !(vring_avail_flags(vq) & VRING_AVAIL_F_NO_INTERRUPT);
|
|
}
|
|
|
|
v = vq->signalled_used_valid;
|
|
vq->signalled_used_valid = true;
|
|
old = vq->signalled_used;
|
|
new = vq->signalled_used = vq->used_idx;
|
|
return !v || vring_need_event(vring_get_used_event(vq), new, old);
|
|
}
|
|
|
|
static bool vring_packed_need_event(VirtQueue *vq, bool wrap,
|
|
uint16_t off_wrap, uint16_t new,
|
|
uint16_t old)
|
|
{
|
|
int off = off_wrap & ~(1 << 15);
|
|
|
|
if (wrap != off_wrap >> 15) {
|
|
off -= vq->vring.num;
|
|
}
|
|
|
|
return vring_need_event(off, new, old);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static bool virtio_packed_should_notify(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
VRingPackedDescEvent e;
|
|
uint16_t old, new;
|
|
bool v;
|
|
VRingMemoryRegionCaches *caches;
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
return false;
|
|
}
|
|
|
|
vring_packed_event_read(vdev, &caches->avail, &e);
|
|
|
|
old = vq->signalled_used;
|
|
new = vq->signalled_used = vq->used_idx;
|
|
v = vq->signalled_used_valid;
|
|
vq->signalled_used_valid = true;
|
|
|
|
if (e.flags == VRING_PACKED_EVENT_FLAG_DISABLE) {
|
|
return false;
|
|
} else if (e.flags == VRING_PACKED_EVENT_FLAG_ENABLE) {
|
|
return true;
|
|
}
|
|
|
|
return !v || vring_packed_need_event(vq, vq->used_wrap_counter,
|
|
e.off_wrap, new, old);
|
|
}
|
|
|
|
/* Called within rcu_read_lock(). */
|
|
static bool virtio_should_notify(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtio_packed_should_notify(vdev, vq);
|
|
} else {
|
|
return virtio_split_should_notify(vdev, vq);
|
|
}
|
|
}
|
|
|
|
void virtio_notify_irqfd(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
WITH_RCU_READ_LOCK_GUARD() {
|
|
if (!virtio_should_notify(vdev, vq)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
trace_virtio_notify_irqfd(vdev, vq);
|
|
|
|
/*
|
|
* virtio spec 1.0 says ISR bit 0 should be ignored with MSI, but
|
|
* windows drivers included in virtio-win 1.8.0 (circa 2015) are
|
|
* incorrectly polling this bit during crashdump and hibernation
|
|
* in MSI mode, causing a hang if this bit is never updated.
|
|
* Recent releases of Windows do not really shut down, but rather
|
|
* log out and hibernate to make the next startup faster. Hence,
|
|
* this manifested as a more serious hang during shutdown with
|
|
*
|
|
* Next driver release from 2016 fixed this problem, so working around it
|
|
* is not a must, but it's easy to do so let's do it here.
|
|
*
|
|
* Note: it's safe to update ISR from any thread as it was switched
|
|
* to an atomic operation.
|
|
*/
|
|
virtio_set_isr(vq->vdev, 0x1);
|
|
event_notifier_set(&vq->guest_notifier);
|
|
}
|
|
|
|
static void virtio_irq(VirtQueue *vq)
|
|
{
|
|
virtio_set_isr(vq->vdev, 0x1);
|
|
virtio_notify_vector(vq->vdev, vq->vector);
|
|
}
|
|
|
|
void virtio_notify(VirtIODevice *vdev, VirtQueue *vq)
|
|
{
|
|
WITH_RCU_READ_LOCK_GUARD() {
|
|
if (!virtio_should_notify(vdev, vq)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
trace_virtio_notify(vdev, vq);
|
|
virtio_irq(vq);
|
|
}
|
|
|
|
void virtio_notify_config(VirtIODevice *vdev)
|
|
{
|
|
if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK))
|
|
return;
|
|
|
|
virtio_set_isr(vdev, 0x3);
|
|
vdev->generation++;
|
|
virtio_notify_vector(vdev, vdev->config_vector);
|
|
}
|
|
|
|
static bool virtio_device_endian_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
assert(vdev->device_endian != VIRTIO_DEVICE_ENDIAN_UNKNOWN);
|
|
if (!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
return vdev->device_endian != virtio_default_endian();
|
|
}
|
|
/* Devices conforming to VIRTIO 1.0 or later are always LE. */
|
|
return vdev->device_endian != VIRTIO_DEVICE_ENDIAN_LITTLE;
|
|
}
|
|
|
|
static bool virtio_64bit_features_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return (vdev->host_features >> 32) != 0;
|
|
}
|
|
|
|
static bool virtio_virtqueue_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return virtio_host_has_feature(vdev, VIRTIO_F_VERSION_1);
|
|
}
|
|
|
|
static bool virtio_packed_virtqueue_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return virtio_host_has_feature(vdev, VIRTIO_F_RING_PACKED);
|
|
}
|
|
|
|
static bool virtio_ringsize_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
int i;
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num != vdev->vq[i].vring.num_default) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool virtio_extra_state_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
return k->has_extra_state &&
|
|
k->has_extra_state(qbus->parent);
|
|
}
|
|
|
|
static bool virtio_broken_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return vdev->broken;
|
|
}
|
|
|
|
static bool virtio_started_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return vdev->started;
|
|
}
|
|
|
|
static bool virtio_disabled_needed(void *opaque)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
|
|
return vdev->disabled;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_virtqueue = {
|
|
.name = "virtqueue_state",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT64(vring.avail, struct VirtQueue),
|
|
VMSTATE_UINT64(vring.used, struct VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_packed_virtqueue = {
|
|
.name = "packed_virtqueue_state",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT16(last_avail_idx, struct VirtQueue),
|
|
VMSTATE_BOOL(last_avail_wrap_counter, struct VirtQueue),
|
|
VMSTATE_UINT16(used_idx, struct VirtQueue),
|
|
VMSTATE_BOOL(used_wrap_counter, struct VirtQueue),
|
|
VMSTATE_UINT32(inuse, struct VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_virtqueues = {
|
|
.name = "virtio/virtqueues",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_virtqueue_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT_VARRAY_POINTER_KNOWN(vq, struct VirtIODevice,
|
|
VIRTIO_QUEUE_MAX, 0, vmstate_virtqueue, VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_packed_virtqueues = {
|
|
.name = "virtio/packed_virtqueues",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_packed_virtqueue_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT_VARRAY_POINTER_KNOWN(vq, struct VirtIODevice,
|
|
VIRTIO_QUEUE_MAX, 0, vmstate_packed_virtqueue, VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_ringsize = {
|
|
.name = "ringsize_state",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32(vring.num_default, struct VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_ringsize = {
|
|
.name = "virtio/ringsize",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_ringsize_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT_VARRAY_POINTER_KNOWN(vq, struct VirtIODevice,
|
|
VIRTIO_QUEUE_MAX, 0, vmstate_ringsize, VirtQueue),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static int get_extra_state(QEMUFile *f, void *pv, size_t size,
|
|
const VMStateField *field)
|
|
{
|
|
VirtIODevice *vdev = pv;
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
if (!k->load_extra_state) {
|
|
return -1;
|
|
} else {
|
|
return k->load_extra_state(qbus->parent, f);
|
|
}
|
|
}
|
|
|
|
static int put_extra_state(QEMUFile *f, void *pv, size_t size,
|
|
const VMStateField *field, JSONWriter *vmdesc)
|
|
{
|
|
VirtIODevice *vdev = pv;
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
k->save_extra_state(qbus->parent, f);
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateInfo vmstate_info_extra_state = {
|
|
.name = "virtqueue_extra_state",
|
|
.get = get_extra_state,
|
|
.put = put_extra_state,
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_extra_state = {
|
|
.name = "virtio/extra_state",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_extra_state_needed,
|
|
.fields = (VMStateField[]) {
|
|
{
|
|
.name = "extra_state",
|
|
.version_id = 0,
|
|
.field_exists = NULL,
|
|
.size = 0,
|
|
.info = &vmstate_info_extra_state,
|
|
.flags = VMS_SINGLE,
|
|
.offset = 0,
|
|
},
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_device_endian = {
|
|
.name = "virtio/device_endian",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_device_endian_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT8(device_endian, VirtIODevice),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_64bit_features = {
|
|
.name = "virtio/64bit_features",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_64bit_features_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT64(guest_features, VirtIODevice),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_broken = {
|
|
.name = "virtio/broken",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_broken_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BOOL(broken, VirtIODevice),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_started = {
|
|
.name = "virtio/started",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_started_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BOOL(started, VirtIODevice),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio_disabled = {
|
|
.name = "virtio/disabled",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.needed = &virtio_disabled_needed,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_BOOL(disabled, VirtIODevice),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
static const VMStateDescription vmstate_virtio = {
|
|
.name = "virtio",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_END_OF_LIST()
|
|
},
|
|
.subsections = (const VMStateDescription*[]) {
|
|
&vmstate_virtio_device_endian,
|
|
&vmstate_virtio_64bit_features,
|
|
&vmstate_virtio_virtqueues,
|
|
&vmstate_virtio_ringsize,
|
|
&vmstate_virtio_broken,
|
|
&vmstate_virtio_extra_state,
|
|
&vmstate_virtio_started,
|
|
&vmstate_virtio_packed_virtqueues,
|
|
&vmstate_virtio_disabled,
|
|
NULL
|
|
}
|
|
};
|
|
|
|
int virtio_save(VirtIODevice *vdev, QEMUFile *f)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
uint32_t guest_features_lo = (vdev->guest_features & 0xffffffff);
|
|
int i;
|
|
|
|
if (k->save_config) {
|
|
k->save_config(qbus->parent, f);
|
|
}
|
|
|
|
qemu_put_8s(f, &vdev->status);
|
|
qemu_put_8s(f, &vdev->isr);
|
|
qemu_put_be16s(f, &vdev->queue_sel);
|
|
qemu_put_be32s(f, &guest_features_lo);
|
|
qemu_put_be32(f, vdev->config_len);
|
|
qemu_put_buffer(f, vdev->config, vdev->config_len);
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num == 0)
|
|
break;
|
|
}
|
|
|
|
qemu_put_be32(f, i);
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num == 0)
|
|
break;
|
|
|
|
qemu_put_be32(f, vdev->vq[i].vring.num);
|
|
if (k->has_variable_vring_alignment) {
|
|
qemu_put_be32(f, vdev->vq[i].vring.align);
|
|
}
|
|
/*
|
|
* Save desc now, the rest of the ring addresses are saved in
|
|
* subsections for VIRTIO-1 devices.
|
|
*/
|
|
qemu_put_be64(f, vdev->vq[i].vring.desc);
|
|
qemu_put_be16s(f, &vdev->vq[i].last_avail_idx);
|
|
if (k->save_queue) {
|
|
k->save_queue(qbus->parent, i, f);
|
|
}
|
|
}
|
|
|
|
if (vdc->save != NULL) {
|
|
vdc->save(vdev, f);
|
|
}
|
|
|
|
if (vdc->vmsd) {
|
|
int ret = vmstate_save_state(f, vdc->vmsd, vdev, NULL);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Subsections */
|
|
return vmstate_save_state(f, &vmstate_virtio, vdev, NULL);
|
|
}
|
|
|
|
/* A wrapper for use as a VMState .put function */
|
|
static int virtio_device_put(QEMUFile *f, void *opaque, size_t size,
|
|
const VMStateField *field, JSONWriter *vmdesc)
|
|
{
|
|
return virtio_save(VIRTIO_DEVICE(opaque), f);
|
|
}
|
|
|
|
/* A wrapper for use as a VMState .get function */
|
|
static int coroutine_mixed_fn
|
|
virtio_device_get(QEMUFile *f, void *opaque, size_t size,
|
|
const VMStateField *field)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(opaque);
|
|
DeviceClass *dc = DEVICE_CLASS(VIRTIO_DEVICE_GET_CLASS(vdev));
|
|
|
|
return virtio_load(vdev, f, dc->vmsd->version_id);
|
|
}
|
|
|
|
const VMStateInfo virtio_vmstate_info = {
|
|
.name = "virtio",
|
|
.get = virtio_device_get,
|
|
.put = virtio_device_put,
|
|
};
|
|
|
|
static int virtio_set_features_nocheck(VirtIODevice *vdev, uint64_t val)
|
|
{
|
|
VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
bool bad = (val & ~(vdev->host_features)) != 0;
|
|
|
|
val &= vdev->host_features;
|
|
if (k->set_features) {
|
|
k->set_features(vdev, val);
|
|
}
|
|
vdev->guest_features = val;
|
|
return bad ? -1 : 0;
|
|
}
|
|
|
|
typedef struct VirtioSetFeaturesNocheckData {
|
|
Coroutine *co;
|
|
VirtIODevice *vdev;
|
|
uint64_t val;
|
|
int ret;
|
|
} VirtioSetFeaturesNocheckData;
|
|
|
|
static void virtio_set_features_nocheck_bh(void *opaque)
|
|
{
|
|
VirtioSetFeaturesNocheckData *data = opaque;
|
|
|
|
data->ret = virtio_set_features_nocheck(data->vdev, data->val);
|
|
aio_co_wake(data->co);
|
|
}
|
|
|
|
static int coroutine_mixed_fn
|
|
virtio_set_features_nocheck_maybe_co(VirtIODevice *vdev, uint64_t val)
|
|
{
|
|
if (qemu_in_coroutine()) {
|
|
VirtioSetFeaturesNocheckData data = {
|
|
.co = qemu_coroutine_self(),
|
|
.vdev = vdev,
|
|
.val = val,
|
|
};
|
|
aio_bh_schedule_oneshot(qemu_get_current_aio_context(),
|
|
virtio_set_features_nocheck_bh, &data);
|
|
qemu_coroutine_yield();
|
|
return data.ret;
|
|
} else {
|
|
return virtio_set_features_nocheck(vdev, val);
|
|
}
|
|
}
|
|
|
|
int virtio_set_features(VirtIODevice *vdev, uint64_t val)
|
|
{
|
|
int ret;
|
|
/*
|
|
* The driver must not attempt to set features after feature negotiation
|
|
* has finished.
|
|
*/
|
|
if (vdev->status & VIRTIO_CONFIG_S_FEATURES_OK) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (val & (1ull << VIRTIO_F_BAD_FEATURE)) {
|
|
qemu_log_mask(LOG_GUEST_ERROR,
|
|
"%s: guest driver for %s has enabled UNUSED(30) feature bit!\n",
|
|
__func__, vdev->name);
|
|
}
|
|
|
|
ret = virtio_set_features_nocheck(vdev, val);
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX)) {
|
|
/* VIRTIO_RING_F_EVENT_IDX changes the size of the caches. */
|
|
int i;
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num != 0) {
|
|
virtio_init_region_cache(vdev, i);
|
|
}
|
|
}
|
|
}
|
|
if (!ret) {
|
|
if (!virtio_device_started(vdev, vdev->status) &&
|
|
!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
vdev->start_on_kick = true;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
size_t virtio_get_config_size(const VirtIOConfigSizeParams *params,
|
|
uint64_t host_features)
|
|
{
|
|
size_t config_size = params->min_size;
|
|
const VirtIOFeature *feature_sizes = params->feature_sizes;
|
|
size_t i;
|
|
|
|
for (i = 0; feature_sizes[i].flags != 0; i++) {
|
|
if (host_features & feature_sizes[i].flags) {
|
|
config_size = MAX(feature_sizes[i].end, config_size);
|
|
}
|
|
}
|
|
|
|
assert(config_size <= params->max_size);
|
|
return config_size;
|
|
}
|
|
|
|
int coroutine_mixed_fn
|
|
virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|
{
|
|
int i, ret;
|
|
int32_t config_len;
|
|
uint32_t num;
|
|
uint32_t features;
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
|
|
/*
|
|
* We poison the endianness to ensure it does not get used before
|
|
* subsections have been loaded.
|
|
*/
|
|
vdev->device_endian = VIRTIO_DEVICE_ENDIAN_UNKNOWN;
|
|
|
|
if (k->load_config) {
|
|
ret = k->load_config(qbus->parent, f);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
qemu_get_8s(f, &vdev->status);
|
|
qemu_get_8s(f, &vdev->isr);
|
|
qemu_get_be16s(f, &vdev->queue_sel);
|
|
if (vdev->queue_sel >= VIRTIO_QUEUE_MAX) {
|
|
return -1;
|
|
}
|
|
qemu_get_be32s(f, &features);
|
|
|
|
/*
|
|
* Temporarily set guest_features low bits - needed by
|
|
* virtio net load code testing for VIRTIO_NET_F_CTRL_GUEST_OFFLOADS
|
|
* VIRTIO_NET_F_GUEST_ANNOUNCE and VIRTIO_NET_F_CTRL_VQ.
|
|
*
|
|
* Note: devices should always test host features in future - don't create
|
|
* new dependencies like this.
|
|
*/
|
|
vdev->guest_features = features;
|
|
|
|
config_len = qemu_get_be32(f);
|
|
|
|
/*
|
|
* There are cases where the incoming config can be bigger or smaller
|
|
* than what we have; so load what we have space for, and skip
|
|
* any excess that's in the stream.
|
|
*/
|
|
qemu_get_buffer(f, vdev->config, MIN(config_len, vdev->config_len));
|
|
|
|
while (config_len > vdev->config_len) {
|
|
qemu_get_byte(f);
|
|
config_len--;
|
|
}
|
|
|
|
num = qemu_get_be32(f);
|
|
|
|
if (num > VIRTIO_QUEUE_MAX) {
|
|
error_report("Invalid number of virtqueues: 0x%x", num);
|
|
return -1;
|
|
}
|
|
|
|
for (i = 0; i < num; i++) {
|
|
vdev->vq[i].vring.num = qemu_get_be32(f);
|
|
if (k->has_variable_vring_alignment) {
|
|
vdev->vq[i].vring.align = qemu_get_be32(f);
|
|
}
|
|
vdev->vq[i].vring.desc = qemu_get_be64(f);
|
|
qemu_get_be16s(f, &vdev->vq[i].last_avail_idx);
|
|
vdev->vq[i].signalled_used_valid = false;
|
|
vdev->vq[i].notification = true;
|
|
|
|
if (!vdev->vq[i].vring.desc && vdev->vq[i].last_avail_idx) {
|
|
error_report("VQ %d address 0x0 "
|
|
"inconsistent with Host index 0x%x",
|
|
i, vdev->vq[i].last_avail_idx);
|
|
return -1;
|
|
}
|
|
if (k->load_queue) {
|
|
ret = k->load_queue(qbus->parent, i, f);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
virtio_notify_vector(vdev, VIRTIO_NO_VECTOR);
|
|
|
|
if (vdc->load != NULL) {
|
|
ret = vdc->load(vdev, f, version_id);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
if (vdc->vmsd) {
|
|
ret = vmstate_load_state(f, vdc->vmsd, vdev, version_id);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
/* Subsections */
|
|
ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
if (vdev->device_endian == VIRTIO_DEVICE_ENDIAN_UNKNOWN) {
|
|
vdev->device_endian = virtio_default_endian();
|
|
}
|
|
|
|
if (virtio_64bit_features_needed(vdev)) {
|
|
/*
|
|
* Subsection load filled vdev->guest_features. Run them
|
|
* through virtio_set_features to sanity-check them against
|
|
* host_features.
|
|
*/
|
|
uint64_t features64 = vdev->guest_features;
|
|
if (virtio_set_features_nocheck_maybe_co(vdev, features64) < 0) {
|
|
error_report("Features 0x%" PRIx64 " unsupported. "
|
|
"Allowed features: 0x%" PRIx64,
|
|
features64, vdev->host_features);
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (virtio_set_features_nocheck_maybe_co(vdev, features) < 0) {
|
|
error_report("Features 0x%x unsupported. "
|
|
"Allowed features: 0x%" PRIx64,
|
|
features, vdev->host_features);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (!virtio_device_started(vdev, vdev->status) &&
|
|
!virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
vdev->start_on_kick = true;
|
|
}
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
for (i = 0; i < num; i++) {
|
|
if (vdev->vq[i].vring.desc) {
|
|
uint16_t nheads;
|
|
|
|
/*
|
|
* VIRTIO-1 devices migrate desc, used, and avail ring addresses so
|
|
* only the region cache needs to be set up. Legacy devices need
|
|
* to calculate used and avail ring addresses based on the desc
|
|
* address.
|
|
*/
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
virtio_init_region_cache(vdev, i);
|
|
} else {
|
|
virtio_queue_update_rings(vdev, i);
|
|
}
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
vdev->vq[i].shadow_avail_idx = vdev->vq[i].last_avail_idx;
|
|
vdev->vq[i].shadow_avail_wrap_counter =
|
|
vdev->vq[i].last_avail_wrap_counter;
|
|
continue;
|
|
}
|
|
|
|
nheads = vring_avail_idx(&vdev->vq[i]) - vdev->vq[i].last_avail_idx;
|
|
/* Check it isn't doing strange things with descriptor numbers. */
|
|
if (nheads > vdev->vq[i].vring.num) {
|
|
virtio_error(vdev, "VQ %d size 0x%x Guest index 0x%x "
|
|
"inconsistent with Host index 0x%x: delta 0x%x",
|
|
i, vdev->vq[i].vring.num,
|
|
vring_avail_idx(&vdev->vq[i]),
|
|
vdev->vq[i].last_avail_idx, nheads);
|
|
vdev->vq[i].used_idx = 0;
|
|
vdev->vq[i].shadow_avail_idx = 0;
|
|
vdev->vq[i].inuse = 0;
|
|
continue;
|
|
}
|
|
vdev->vq[i].used_idx = vring_used_idx(&vdev->vq[i]);
|
|
vdev->vq[i].shadow_avail_idx = vring_avail_idx(&vdev->vq[i]);
|
|
|
|
/*
|
|
* Some devices migrate VirtQueueElements that have been popped
|
|
* from the avail ring but not yet returned to the used ring.
|
|
* Since max ring size < UINT16_MAX it's safe to use modulo
|
|
* UINT16_MAX + 1 subtraction.
|
|
*/
|
|
vdev->vq[i].inuse = (uint16_t)(vdev->vq[i].last_avail_idx -
|
|
vdev->vq[i].used_idx);
|
|
if (vdev->vq[i].inuse > vdev->vq[i].vring.num) {
|
|
error_report("VQ %d size 0x%x < last_avail_idx 0x%x - "
|
|
"used_idx 0x%x",
|
|
i, vdev->vq[i].vring.num,
|
|
vdev->vq[i].last_avail_idx,
|
|
vdev->vq[i].used_idx);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vdc->post_load) {
|
|
ret = vdc->post_load(vdev);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void virtio_cleanup(VirtIODevice *vdev)
|
|
{
|
|
qemu_del_vm_change_state_handler(vdev->vmstate);
|
|
}
|
|
|
|
static void virtio_vmstate_change(void *opaque, bool running, RunState state)
|
|
{
|
|
VirtIODevice *vdev = opaque;
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
bool backend_run = running && virtio_device_started(vdev, vdev->status);
|
|
vdev->vm_running = running;
|
|
|
|
if (backend_run) {
|
|
virtio_set_status(vdev, vdev->status);
|
|
}
|
|
|
|
if (k->vmstate_change) {
|
|
k->vmstate_change(qbus->parent, backend_run);
|
|
}
|
|
|
|
if (!backend_run) {
|
|
virtio_set_status(vdev, vdev->status);
|
|
}
|
|
}
|
|
|
|
void virtio_instance_init_common(Object *proxy_obj, void *data,
|
|
size_t vdev_size, const char *vdev_name)
|
|
{
|
|
DeviceState *vdev = data;
|
|
|
|
object_initialize_child_with_props(proxy_obj, "virtio-backend", vdev,
|
|
vdev_size, vdev_name, &error_abort,
|
|
NULL);
|
|
qdev_alias_all_properties(vdev, proxy_obj);
|
|
}
|
|
|
|
void virtio_init(VirtIODevice *vdev, uint16_t device_id, size_t config_size)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
int i;
|
|
int nvectors = k->query_nvectors ? k->query_nvectors(qbus->parent) : 0;
|
|
|
|
if (nvectors) {
|
|
vdev->vector_queues =
|
|
g_malloc0(sizeof(*vdev->vector_queues) * nvectors);
|
|
}
|
|
|
|
vdev->start_on_kick = false;
|
|
vdev->started = false;
|
|
vdev->vhost_started = false;
|
|
vdev->device_id = device_id;
|
|
vdev->status = 0;
|
|
qatomic_set(&vdev->isr, 0);
|
|
vdev->queue_sel = 0;
|
|
vdev->config_vector = VIRTIO_NO_VECTOR;
|
|
vdev->vq = g_new0(VirtQueue, VIRTIO_QUEUE_MAX);
|
|
vdev->vm_running = runstate_is_running();
|
|
vdev->broken = false;
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
vdev->vq[i].vector = VIRTIO_NO_VECTOR;
|
|
vdev->vq[i].vdev = vdev;
|
|
vdev->vq[i].queue_index = i;
|
|
vdev->vq[i].host_notifier_enabled = false;
|
|
}
|
|
|
|
vdev->name = virtio_id_to_name(device_id);
|
|
vdev->config_len = config_size;
|
|
if (vdev->config_len) {
|
|
vdev->config = g_malloc0(config_size);
|
|
} else {
|
|
vdev->config = NULL;
|
|
}
|
|
vdev->vmstate = qdev_add_vm_change_state_handler(DEVICE(vdev),
|
|
virtio_vmstate_change, vdev);
|
|
vdev->device_endian = virtio_default_endian();
|
|
vdev->use_guest_notifier_mask = true;
|
|
}
|
|
|
|
/*
|
|
* Only devices that have already been around prior to defining the virtio
|
|
* standard support legacy mode; this includes devices not specified in the
|
|
* standard. All newer devices conform to the virtio standard only.
|
|
*/
|
|
bool virtio_legacy_allowed(VirtIODevice *vdev)
|
|
{
|
|
switch (vdev->device_id) {
|
|
case VIRTIO_ID_NET:
|
|
case VIRTIO_ID_BLOCK:
|
|
case VIRTIO_ID_CONSOLE:
|
|
case VIRTIO_ID_RNG:
|
|
case VIRTIO_ID_BALLOON:
|
|
case VIRTIO_ID_RPMSG:
|
|
case VIRTIO_ID_SCSI:
|
|
case VIRTIO_ID_9P:
|
|
case VIRTIO_ID_RPROC_SERIAL:
|
|
case VIRTIO_ID_CAIF:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool virtio_legacy_check_disabled(VirtIODevice *vdev)
|
|
{
|
|
return vdev->disable_legacy_check;
|
|
}
|
|
|
|
hwaddr virtio_queue_get_desc_addr(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.desc;
|
|
}
|
|
|
|
bool virtio_queue_enabled_legacy(VirtIODevice *vdev, int n)
|
|
{
|
|
return virtio_queue_get_desc_addr(vdev, n) != 0;
|
|
}
|
|
|
|
bool virtio_queue_enabled(VirtIODevice *vdev, int n)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
if (k->queue_enabled) {
|
|
return k->queue_enabled(qbus->parent, n);
|
|
}
|
|
return virtio_queue_enabled_legacy(vdev, n);
|
|
}
|
|
|
|
hwaddr virtio_queue_get_avail_addr(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.avail;
|
|
}
|
|
|
|
hwaddr virtio_queue_get_used_addr(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq[n].vring.used;
|
|
}
|
|
|
|
hwaddr virtio_queue_get_desc_size(VirtIODevice *vdev, int n)
|
|
{
|
|
return sizeof(VRingDesc) * vdev->vq[n].vring.num;
|
|
}
|
|
|
|
hwaddr virtio_queue_get_avail_size(VirtIODevice *vdev, int n)
|
|
{
|
|
int s;
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return sizeof(struct VRingPackedDescEvent);
|
|
}
|
|
|
|
s = virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX) ? 2 : 0;
|
|
return offsetof(VRingAvail, ring) +
|
|
sizeof(uint16_t) * vdev->vq[n].vring.num + s;
|
|
}
|
|
|
|
hwaddr virtio_queue_get_used_size(VirtIODevice *vdev, int n)
|
|
{
|
|
int s;
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return sizeof(struct VRingPackedDescEvent);
|
|
}
|
|
|
|
s = virtio_vdev_has_feature(vdev, VIRTIO_RING_F_EVENT_IDX) ? 2 : 0;
|
|
return offsetof(VRingUsed, ring) +
|
|
sizeof(VRingUsedElem) * vdev->vq[n].vring.num + s;
|
|
}
|
|
|
|
static unsigned int virtio_queue_packed_get_last_avail_idx(VirtIODevice *vdev,
|
|
int n)
|
|
{
|
|
unsigned int avail, used;
|
|
|
|
avail = vdev->vq[n].last_avail_idx;
|
|
avail |= ((uint16_t)vdev->vq[n].last_avail_wrap_counter) << 15;
|
|
|
|
used = vdev->vq[n].used_idx;
|
|
used |= ((uint16_t)vdev->vq[n].used_wrap_counter) << 15;
|
|
|
|
return avail | used << 16;
|
|
}
|
|
|
|
static uint16_t virtio_queue_split_get_last_avail_idx(VirtIODevice *vdev,
|
|
int n)
|
|
{
|
|
return vdev->vq[n].last_avail_idx;
|
|
}
|
|
|
|
unsigned int virtio_queue_get_last_avail_idx(VirtIODevice *vdev, int n)
|
|
{
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtio_queue_packed_get_last_avail_idx(vdev, n);
|
|
} else {
|
|
return virtio_queue_split_get_last_avail_idx(vdev, n);
|
|
}
|
|
}
|
|
|
|
static void virtio_queue_packed_set_last_avail_idx(VirtIODevice *vdev,
|
|
int n, unsigned int idx)
|
|
{
|
|
struct VirtQueue *vq = &vdev->vq[n];
|
|
|
|
vq->last_avail_idx = vq->shadow_avail_idx = idx & 0x7fff;
|
|
vq->last_avail_wrap_counter =
|
|
vq->shadow_avail_wrap_counter = !!(idx & 0x8000);
|
|
idx >>= 16;
|
|
vq->used_idx = idx & 0x7fff;
|
|
vq->used_wrap_counter = !!(idx & 0x8000);
|
|
}
|
|
|
|
static void virtio_queue_split_set_last_avail_idx(VirtIODevice *vdev,
|
|
int n, unsigned int idx)
|
|
{
|
|
vdev->vq[n].last_avail_idx = idx;
|
|
vdev->vq[n].shadow_avail_idx = idx;
|
|
}
|
|
|
|
void virtio_queue_set_last_avail_idx(VirtIODevice *vdev, int n,
|
|
unsigned int idx)
|
|
{
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtio_queue_packed_set_last_avail_idx(vdev, n, idx);
|
|
} else {
|
|
virtio_queue_split_set_last_avail_idx(vdev, n, idx);
|
|
}
|
|
}
|
|
|
|
static void virtio_queue_packed_restore_last_avail_idx(VirtIODevice *vdev,
|
|
int n)
|
|
{
|
|
/* We don't have a reference like avail idx in shared memory */
|
|
return;
|
|
}
|
|
|
|
static void virtio_queue_split_restore_last_avail_idx(VirtIODevice *vdev,
|
|
int n)
|
|
{
|
|
RCU_READ_LOCK_GUARD();
|
|
if (vdev->vq[n].vring.desc) {
|
|
vdev->vq[n].last_avail_idx = vring_used_idx(&vdev->vq[n]);
|
|
vdev->vq[n].shadow_avail_idx = vdev->vq[n].last_avail_idx;
|
|
}
|
|
}
|
|
|
|
void virtio_queue_restore_last_avail_idx(VirtIODevice *vdev, int n)
|
|
{
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
virtio_queue_packed_restore_last_avail_idx(vdev, n);
|
|
} else {
|
|
virtio_queue_split_restore_last_avail_idx(vdev, n);
|
|
}
|
|
}
|
|
|
|
static void virtio_queue_packed_update_used_idx(VirtIODevice *vdev, int n)
|
|
{
|
|
/* used idx was updated through set_last_avail_idx() */
|
|
return;
|
|
}
|
|
|
|
static void virtio_split_packed_update_used_idx(VirtIODevice *vdev, int n)
|
|
{
|
|
RCU_READ_LOCK_GUARD();
|
|
if (vdev->vq[n].vring.desc) {
|
|
vdev->vq[n].used_idx = vring_used_idx(&vdev->vq[n]);
|
|
}
|
|
}
|
|
|
|
void virtio_queue_update_used_idx(VirtIODevice *vdev, int n)
|
|
{
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
return virtio_queue_packed_update_used_idx(vdev, n);
|
|
} else {
|
|
return virtio_split_packed_update_used_idx(vdev, n);
|
|
}
|
|
}
|
|
|
|
void virtio_queue_invalidate_signalled_used(VirtIODevice *vdev, int n)
|
|
{
|
|
vdev->vq[n].signalled_used_valid = false;
|
|
}
|
|
|
|
VirtQueue *virtio_get_queue(VirtIODevice *vdev, int n)
|
|
{
|
|
return vdev->vq + n;
|
|
}
|
|
|
|
uint16_t virtio_get_queue_index(VirtQueue *vq)
|
|
{
|
|
return vq->queue_index;
|
|
}
|
|
|
|
static void virtio_queue_guest_notifier_read(EventNotifier *n)
|
|
{
|
|
VirtQueue *vq = container_of(n, VirtQueue, guest_notifier);
|
|
if (event_notifier_test_and_clear(n)) {
|
|
virtio_irq(vq);
|
|
}
|
|
}
|
|
static void virtio_config_guest_notifier_read(EventNotifier *n)
|
|
{
|
|
VirtIODevice *vdev = container_of(n, VirtIODevice, config_notifier);
|
|
|
|
if (event_notifier_test_and_clear(n)) {
|
|
virtio_notify_config(vdev);
|
|
}
|
|
}
|
|
void virtio_queue_set_guest_notifier_fd_handler(VirtQueue *vq, bool assign,
|
|
bool with_irqfd)
|
|
{
|
|
if (assign && !with_irqfd) {
|
|
event_notifier_set_handler(&vq->guest_notifier,
|
|
virtio_queue_guest_notifier_read);
|
|
} else {
|
|
event_notifier_set_handler(&vq->guest_notifier, NULL);
|
|
}
|
|
if (!assign) {
|
|
/* Test and clear notifier before closing it,
|
|
* in case poll callback didn't have time to run. */
|
|
virtio_queue_guest_notifier_read(&vq->guest_notifier);
|
|
}
|
|
}
|
|
|
|
void virtio_config_set_guest_notifier_fd_handler(VirtIODevice *vdev,
|
|
bool assign, bool with_irqfd)
|
|
{
|
|
EventNotifier *n;
|
|
n = &vdev->config_notifier;
|
|
if (assign && !with_irqfd) {
|
|
event_notifier_set_handler(n, virtio_config_guest_notifier_read);
|
|
} else {
|
|
event_notifier_set_handler(n, NULL);
|
|
}
|
|
if (!assign) {
|
|
/* Test and clear notifier before closing it,*/
|
|
/* in case poll callback didn't have time to run. */
|
|
virtio_config_guest_notifier_read(n);
|
|
}
|
|
}
|
|
|
|
EventNotifier *virtio_queue_get_guest_notifier(VirtQueue *vq)
|
|
{
|
|
return &vq->guest_notifier;
|
|
}
|
|
|
|
static void virtio_queue_host_notifier_aio_poll_begin(EventNotifier *n)
|
|
{
|
|
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
|
|
|
virtio_queue_set_notification(vq, 0);
|
|
}
|
|
|
|
static bool virtio_queue_host_notifier_aio_poll(void *opaque)
|
|
{
|
|
EventNotifier *n = opaque;
|
|
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
|
|
|
return vq->vring.desc && !virtio_queue_empty(vq);
|
|
}
|
|
|
|
static void virtio_queue_host_notifier_aio_poll_ready(EventNotifier *n)
|
|
{
|
|
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
|
|
|
virtio_queue_notify_vq(vq);
|
|
}
|
|
|
|
static void virtio_queue_host_notifier_aio_poll_end(EventNotifier *n)
|
|
{
|
|
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
|
|
|
/* Caller polls once more after this to catch requests that race with us */
|
|
virtio_queue_set_notification(vq, 1);
|
|
}
|
|
|
|
void virtio_queue_aio_attach_host_notifier(VirtQueue *vq, AioContext *ctx)
|
|
{
|
|
aio_set_event_notifier(ctx, &vq->host_notifier,
|
|
virtio_queue_host_notifier_read,
|
|
virtio_queue_host_notifier_aio_poll,
|
|
virtio_queue_host_notifier_aio_poll_ready);
|
|
aio_set_event_notifier_poll(ctx, &vq->host_notifier,
|
|
virtio_queue_host_notifier_aio_poll_begin,
|
|
virtio_queue_host_notifier_aio_poll_end);
|
|
}
|
|
|
|
/*
|
|
* Same as virtio_queue_aio_attach_host_notifier() but without polling. Use
|
|
* this for rx virtqueues and similar cases where the virtqueue handler
|
|
* function does not pop all elements. When the virtqueue is left non-empty
|
|
* polling consumes CPU cycles and should not be used.
|
|
*/
|
|
void virtio_queue_aio_attach_host_notifier_no_poll(VirtQueue *vq, AioContext *ctx)
|
|
{
|
|
aio_set_event_notifier(ctx, &vq->host_notifier,
|
|
virtio_queue_host_notifier_read,
|
|
NULL, NULL);
|
|
}
|
|
|
|
void virtio_queue_aio_detach_host_notifier(VirtQueue *vq, AioContext *ctx)
|
|
{
|
|
aio_set_event_notifier(ctx, &vq->host_notifier, NULL, NULL, NULL);
|
|
}
|
|
|
|
void virtio_queue_host_notifier_read(EventNotifier *n)
|
|
{
|
|
VirtQueue *vq = container_of(n, VirtQueue, host_notifier);
|
|
if (event_notifier_test_and_clear(n)) {
|
|
virtio_queue_notify_vq(vq);
|
|
}
|
|
}
|
|
|
|
EventNotifier *virtio_queue_get_host_notifier(VirtQueue *vq)
|
|
{
|
|
return &vq->host_notifier;
|
|
}
|
|
|
|
EventNotifier *virtio_config_get_guest_notifier(VirtIODevice *vdev)
|
|
{
|
|
return &vdev->config_notifier;
|
|
}
|
|
|
|
void virtio_queue_set_host_notifier_enabled(VirtQueue *vq, bool enabled)
|
|
{
|
|
vq->host_notifier_enabled = enabled;
|
|
}
|
|
|
|
int virtio_queue_set_host_notifier_mr(VirtIODevice *vdev, int n,
|
|
MemoryRegion *mr, bool assign)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
|
|
|
if (k->set_host_notifier_mr) {
|
|
return k->set_host_notifier_mr(qbus->parent, n, mr, assign);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void virtio_device_set_child_bus_name(VirtIODevice *vdev, char *bus_name)
|
|
{
|
|
g_free(vdev->bus_name);
|
|
vdev->bus_name = g_strdup(bus_name);
|
|
}
|
|
|
|
void G_GNUC_PRINTF(2, 3) virtio_error(VirtIODevice *vdev, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
error_vreport(fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_VERSION_1)) {
|
|
vdev->status = vdev->status | VIRTIO_CONFIG_S_NEEDS_RESET;
|
|
virtio_notify_config(vdev);
|
|
}
|
|
|
|
vdev->broken = true;
|
|
}
|
|
|
|
static void virtio_memory_listener_commit(MemoryListener *listener)
|
|
{
|
|
VirtIODevice *vdev = container_of(listener, VirtIODevice, listener);
|
|
int i;
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num == 0) {
|
|
break;
|
|
}
|
|
virtio_init_region_cache(vdev, i);
|
|
}
|
|
}
|
|
|
|
static void virtio_device_realize(DeviceState *dev, Error **errp)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
|
|
Error *err = NULL;
|
|
|
|
/* Devices should either use vmsd or the load/save methods */
|
|
assert(!vdc->vmsd || !vdc->load);
|
|
|
|
if (vdc->realize != NULL) {
|
|
vdc->realize(dev, &err);
|
|
if (err != NULL) {
|
|
error_propagate(errp, err);
|
|
return;
|
|
}
|
|
}
|
|
|
|
virtio_bus_device_plugged(vdev, &err);
|
|
if (err != NULL) {
|
|
error_propagate(errp, err);
|
|
vdc->unrealize(dev);
|
|
return;
|
|
}
|
|
|
|
vdev->listener.commit = virtio_memory_listener_commit;
|
|
vdev->listener.name = "virtio";
|
|
memory_listener_register(&vdev->listener, vdev->dma_as);
|
|
}
|
|
|
|
static void virtio_device_unrealize(DeviceState *dev)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(dev);
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(dev);
|
|
|
|
memory_listener_unregister(&vdev->listener);
|
|
virtio_bus_device_unplugged(vdev);
|
|
|
|
if (vdc->unrealize != NULL) {
|
|
vdc->unrealize(dev);
|
|
}
|
|
|
|
g_free(vdev->bus_name);
|
|
vdev->bus_name = NULL;
|
|
}
|
|
|
|
static void virtio_device_free_virtqueues(VirtIODevice *vdev)
|
|
{
|
|
int i;
|
|
if (!vdev->vq) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < VIRTIO_QUEUE_MAX; i++) {
|
|
if (vdev->vq[i].vring.num == 0) {
|
|
break;
|
|
}
|
|
virtio_virtqueue_reset_region_cache(&vdev->vq[i]);
|
|
}
|
|
g_free(vdev->vq);
|
|
}
|
|
|
|
static void virtio_device_instance_finalize(Object *obj)
|
|
{
|
|
VirtIODevice *vdev = VIRTIO_DEVICE(obj);
|
|
|
|
virtio_device_free_virtqueues(vdev);
|
|
|
|
g_free(vdev->config);
|
|
g_free(vdev->vector_queues);
|
|
}
|
|
|
|
static Property virtio_properties[] = {
|
|
DEFINE_VIRTIO_COMMON_FEATURES(VirtIODevice, host_features),
|
|
DEFINE_PROP_BOOL("use-started", VirtIODevice, use_started, true),
|
|
DEFINE_PROP_BOOL("use-disabled-flag", VirtIODevice, use_disabled_flag, true),
|
|
DEFINE_PROP_BOOL("x-disable-legacy-check", VirtIODevice,
|
|
disable_legacy_check, false),
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
};
|
|
|
|
static int virtio_device_start_ioeventfd_impl(VirtIODevice *vdev)
|
|
{
|
|
VirtioBusState *qbus = VIRTIO_BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
|
int i, n, r, err;
|
|
|
|
/*
|
|
* Batch all the host notifiers in a single transaction to avoid
|
|
* quadratic time complexity in address_space_update_ioeventfds().
|
|
*/
|
|
memory_region_transaction_begin();
|
|
for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
if (!virtio_queue_get_num(vdev, n)) {
|
|
continue;
|
|
}
|
|
r = virtio_bus_set_host_notifier(qbus, n, true);
|
|
if (r < 0) {
|
|
err = r;
|
|
goto assign_error;
|
|
}
|
|
event_notifier_set_handler(&vq->host_notifier,
|
|
virtio_queue_host_notifier_read);
|
|
}
|
|
|
|
for (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
|
|
/* Kick right away to begin processing requests already in vring */
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
if (!vq->vring.num) {
|
|
continue;
|
|
}
|
|
event_notifier_set(&vq->host_notifier);
|
|
}
|
|
memory_region_transaction_commit();
|
|
return 0;
|
|
|
|
assign_error:
|
|
i = n; /* save n for a second iteration after transaction is committed. */
|
|
while (--n >= 0) {
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
if (!virtio_queue_get_num(vdev, n)) {
|
|
continue;
|
|
}
|
|
|
|
event_notifier_set_handler(&vq->host_notifier, NULL);
|
|
r = virtio_bus_set_host_notifier(qbus, n, false);
|
|
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();
|
|
|
|
while (--i >= 0) {
|
|
if (!virtio_queue_get_num(vdev, i)) {
|
|
continue;
|
|
}
|
|
virtio_bus_cleanup_host_notifier(qbus, i);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
int virtio_device_start_ioeventfd(VirtIODevice *vdev)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusState *vbus = VIRTIO_BUS(qbus);
|
|
|
|
return virtio_bus_start_ioeventfd(vbus);
|
|
}
|
|
|
|
static void virtio_device_stop_ioeventfd_impl(VirtIODevice *vdev)
|
|
{
|
|
VirtioBusState *qbus = VIRTIO_BUS(qdev_get_parent_bus(DEVICE(vdev)));
|
|
int n, 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 (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
|
|
VirtQueue *vq = &vdev->vq[n];
|
|
|
|
if (!virtio_queue_get_num(vdev, n)) {
|
|
continue;
|
|
}
|
|
event_notifier_set_handler(&vq->host_notifier, NULL);
|
|
r = virtio_bus_set_host_notifier(qbus, n, false);
|
|
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 (n = 0; n < VIRTIO_QUEUE_MAX; n++) {
|
|
if (!virtio_queue_get_num(vdev, n)) {
|
|
continue;
|
|
}
|
|
virtio_bus_cleanup_host_notifier(qbus, n);
|
|
}
|
|
}
|
|
|
|
int virtio_device_grab_ioeventfd(VirtIODevice *vdev)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusState *vbus = VIRTIO_BUS(qbus);
|
|
|
|
return virtio_bus_grab_ioeventfd(vbus);
|
|
}
|
|
|
|
void virtio_device_release_ioeventfd(VirtIODevice *vdev)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusState *vbus = VIRTIO_BUS(qbus);
|
|
|
|
virtio_bus_release_ioeventfd(vbus);
|
|
}
|
|
|
|
static void virtio_device_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
/* Set the default value here. */
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = virtio_device_realize;
|
|
dc->unrealize = virtio_device_unrealize;
|
|
dc->bus_type = TYPE_VIRTIO_BUS;
|
|
device_class_set_props(dc, virtio_properties);
|
|
vdc->start_ioeventfd = virtio_device_start_ioeventfd_impl;
|
|
vdc->stop_ioeventfd = virtio_device_stop_ioeventfd_impl;
|
|
|
|
vdc->legacy_features |= VIRTIO_LEGACY_FEATURES;
|
|
}
|
|
|
|
bool virtio_device_ioeventfd_enabled(VirtIODevice *vdev)
|
|
{
|
|
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
|
VirtioBusState *vbus = VIRTIO_BUS(qbus);
|
|
|
|
return virtio_bus_ioeventfd_enabled(vbus);
|
|
}
|
|
|
|
VirtQueueStatus *qmp_x_query_virtio_queue_status(const char *path,
|
|
uint16_t queue,
|
|
Error **errp)
|
|
{
|
|
VirtIODevice *vdev;
|
|
VirtQueueStatus *status;
|
|
|
|
vdev = qmp_find_virtio_device(path);
|
|
if (vdev == NULL) {
|
|
error_setg(errp, "Path %s is not a VirtIODevice", path);
|
|
return NULL;
|
|
}
|
|
|
|
if (queue >= VIRTIO_QUEUE_MAX || !virtio_queue_get_num(vdev, queue)) {
|
|
error_setg(errp, "Invalid virtqueue number %d", queue);
|
|
return NULL;
|
|
}
|
|
|
|
status = g_new0(VirtQueueStatus, 1);
|
|
status->name = g_strdup(vdev->name);
|
|
status->queue_index = vdev->vq[queue].queue_index;
|
|
status->inuse = vdev->vq[queue].inuse;
|
|
status->vring_num = vdev->vq[queue].vring.num;
|
|
status->vring_num_default = vdev->vq[queue].vring.num_default;
|
|
status->vring_align = vdev->vq[queue].vring.align;
|
|
status->vring_desc = vdev->vq[queue].vring.desc;
|
|
status->vring_avail = vdev->vq[queue].vring.avail;
|
|
status->vring_used = vdev->vq[queue].vring.used;
|
|
status->used_idx = vdev->vq[queue].used_idx;
|
|
status->signalled_used = vdev->vq[queue].signalled_used;
|
|
status->signalled_used_valid = vdev->vq[queue].signalled_used_valid;
|
|
|
|
if (vdev->vhost_started) {
|
|
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
|
struct vhost_dev *hdev = vdc->get_vhost(vdev);
|
|
|
|
/* check if vq index exists for vhost as well */
|
|
if (queue >= hdev->vq_index && queue < hdev->vq_index + hdev->nvqs) {
|
|
status->has_last_avail_idx = true;
|
|
|
|
int vhost_vq_index =
|
|
hdev->vhost_ops->vhost_get_vq_index(hdev, queue);
|
|
struct vhost_vring_state state = {
|
|
.index = vhost_vq_index,
|
|
};
|
|
|
|
status->last_avail_idx =
|
|
hdev->vhost_ops->vhost_get_vring_base(hdev, &state);
|
|
}
|
|
} else {
|
|
status->has_shadow_avail_idx = true;
|
|
status->has_last_avail_idx = true;
|
|
status->last_avail_idx = vdev->vq[queue].last_avail_idx;
|
|
status->shadow_avail_idx = vdev->vq[queue].shadow_avail_idx;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
static strList *qmp_decode_vring_desc_flags(uint16_t flags)
|
|
{
|
|
strList *list = NULL;
|
|
strList *node;
|
|
int i;
|
|
|
|
struct {
|
|
uint16_t flag;
|
|
const char *value;
|
|
} map[] = {
|
|
{ VRING_DESC_F_NEXT, "next" },
|
|
{ VRING_DESC_F_WRITE, "write" },
|
|
{ VRING_DESC_F_INDIRECT, "indirect" },
|
|
{ 1 << VRING_PACKED_DESC_F_AVAIL, "avail" },
|
|
{ 1 << VRING_PACKED_DESC_F_USED, "used" },
|
|
{ 0, "" }
|
|
};
|
|
|
|
for (i = 0; map[i].flag; i++) {
|
|
if ((map[i].flag & flags) == 0) {
|
|
continue;
|
|
}
|
|
node = g_malloc0(sizeof(strList));
|
|
node->value = g_strdup(map[i].value);
|
|
node->next = list;
|
|
list = node;
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
VirtioQueueElement *qmp_x_query_virtio_queue_element(const char *path,
|
|
uint16_t queue,
|
|
bool has_index,
|
|
uint16_t index,
|
|
Error **errp)
|
|
{
|
|
VirtIODevice *vdev;
|
|
VirtQueue *vq;
|
|
VirtioQueueElement *element = NULL;
|
|
|
|
vdev = qmp_find_virtio_device(path);
|
|
if (vdev == NULL) {
|
|
error_setg(errp, "Path %s is not a VirtIO device", path);
|
|
return NULL;
|
|
}
|
|
|
|
if (queue >= VIRTIO_QUEUE_MAX || !virtio_queue_get_num(vdev, queue)) {
|
|
error_setg(errp, "Invalid virtqueue number %d", queue);
|
|
return NULL;
|
|
}
|
|
vq = &vdev->vq[queue];
|
|
|
|
if (virtio_vdev_has_feature(vdev, VIRTIO_F_RING_PACKED)) {
|
|
error_setg(errp, "Packed ring not supported");
|
|
return NULL;
|
|
} else {
|
|
unsigned int head, i, max;
|
|
VRingMemoryRegionCaches *caches;
|
|
MemoryRegionCache indirect_desc_cache;
|
|
MemoryRegionCache *desc_cache;
|
|
VRingDesc desc;
|
|
VirtioRingDescList *list = NULL;
|
|
VirtioRingDescList *node;
|
|
int rc; int ndescs;
|
|
|
|
address_space_cache_init_empty(&indirect_desc_cache);
|
|
|
|
RCU_READ_LOCK_GUARD();
|
|
|
|
max = vq->vring.num;
|
|
|
|
if (!has_index) {
|
|
head = vring_avail_ring(vq, vq->last_avail_idx % vq->vring.num);
|
|
} else {
|
|
head = vring_avail_ring(vq, index % vq->vring.num);
|
|
}
|
|
i = head;
|
|
|
|
caches = vring_get_region_caches(vq);
|
|
if (!caches) {
|
|
error_setg(errp, "Region caches not initialized");
|
|
return NULL;
|
|
}
|
|
if (caches->desc.len < max * sizeof(VRingDesc)) {
|
|
error_setg(errp, "Cannot map descriptor ring");
|
|
return NULL;
|
|
}
|
|
|
|
desc_cache = &caches->desc;
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
if (desc.flags & VRING_DESC_F_INDIRECT) {
|
|
int64_t len;
|
|
len = address_space_cache_init(&indirect_desc_cache, vdev->dma_as,
|
|
desc.addr, desc.len, false);
|
|
desc_cache = &indirect_desc_cache;
|
|
if (len < desc.len) {
|
|
error_setg(errp, "Cannot map indirect buffer");
|
|
goto done;
|
|
}
|
|
|
|
max = desc.len / sizeof(VRingDesc);
|
|
i = 0;
|
|
vring_split_desc_read(vdev, &desc, desc_cache, i);
|
|
}
|
|
|
|
element = g_new0(VirtioQueueElement, 1);
|
|
element->avail = g_new0(VirtioRingAvail, 1);
|
|
element->used = g_new0(VirtioRingUsed, 1);
|
|
element->name = g_strdup(vdev->name);
|
|
element->index = head;
|
|
element->avail->flags = vring_avail_flags(vq);
|
|
element->avail->idx = vring_avail_idx(vq);
|
|
element->avail->ring = head;
|
|
element->used->flags = vring_used_flags(vq);
|
|
element->used->idx = vring_used_idx(vq);
|
|
ndescs = 0;
|
|
|
|
do {
|
|
/* A buggy driver may produce an infinite loop */
|
|
if (ndescs >= max) {
|
|
break;
|
|
}
|
|
node = g_new0(VirtioRingDescList, 1);
|
|
node->value = g_new0(VirtioRingDesc, 1);
|
|
node->value->addr = desc.addr;
|
|
node->value->len = desc.len;
|
|
node->value->flags = qmp_decode_vring_desc_flags(desc.flags);
|
|
node->next = list;
|
|
list = node;
|
|
|
|
ndescs++;
|
|
rc = virtqueue_split_read_next_desc(vdev, &desc, desc_cache, max);
|
|
} while (rc == VIRTQUEUE_READ_DESC_MORE);
|
|
element->descs = list;
|
|
done:
|
|
address_space_cache_destroy(&indirect_desc_cache);
|
|
}
|
|
|
|
return element;
|
|
}
|
|
|
|
static const TypeInfo virtio_device_info = {
|
|
.name = TYPE_VIRTIO_DEVICE,
|
|
.parent = TYPE_DEVICE,
|
|
.instance_size = sizeof(VirtIODevice),
|
|
.class_init = virtio_device_class_init,
|
|
.instance_finalize = virtio_device_instance_finalize,
|
|
.abstract = true,
|
|
.class_size = sizeof(VirtioDeviceClass),
|
|
};
|
|
|
|
static void virtio_register_types(void)
|
|
{
|
|
type_register_static(&virtio_device_info);
|
|
}
|
|
|
|
type_init(virtio_register_types)
|